1 /* update-game-score.c --- Update a score file
2 Copyright (C) 2002 Free Software Foundation, Inc.
4 This file is part of GNU Emacs.
6 GNU Emacs is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Emacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Emacs; see the file COPYING. If not, write to
18 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 Boston, MA 02111-1307, USA. */
21 /* This program is allows a game to securely and atomically update a
22 score file. It should be installed setuid, owned by an appropriate
25 Alternatively, it can be compiled without HAVE_SHARED_GAME_DIR
26 defined, and in that case it will store scores in the user's home
27 directory (it should NOT be setuid).
29 Created 2002/03/22, by Colin Walters <walters@debian.org>
48 #define MAX_ATTEMPTS 5
49 #define MAX_SCORES 200
50 #define MAX_DATA_LEN 1024
52 #ifdef HAVE_SHARED_GAME_DIR
53 #define SCORE_FILE_PREFIX HAVE_SHARED_GAME_DIR
55 #define SCORE_FILE_PREFIX "~/.emacs.d/games"
58 #if !defined (__GNUC__) || __GNUC__ < 2
59 #define __attribute__(x)
65 fprintf(stdout
, "Usage: update-game-score [-m MAX ] [ -r ] game/scorefile SCORE DATA\n");
66 fprintf(stdout
, " update-game-score -h\n");
67 fprintf(stdout
, " -h\t\tDisplay this help.\n");
68 fprintf(stdout
, " -m MAX\t\tLimit the maximum number of scores to MAX.\n");
69 fprintf(stdout
, " -r\t\tSort the scores in increasing order.\n");
74 lock_file(const char *filename
, void **state
);
76 unlock_file(const char *filename
, void *state
);
86 read_scores(const char *filename
, struct score_entry
**scores
,
89 push_score(struct score_entry
**scores
, int *count
,
90 int newscore
, char *username
, char *newdata
);
92 sort_scores(struct score_entry
*scores
, int count
, int reverse
);
94 write_scores(const char *filename
, const struct score_entry
*scores
,
98 get_user_id(struct passwd
*buf
)
104 int uid
= (int) getuid();
108 name
= malloc(count
+1);
109 sprintf(name
, "%d", uid
);
116 get_home_dir(struct passwd
*buf
)
123 void lose(const char *msg
, ...)
124 __attribute__ ((format (printf
,1,0), noreturn
));
126 void lose(const char *msg
, ...)
130 vfprintf(stderr
, msg
, ap
);
136 main(int argc
, char **argv
)
140 char *scorefile
, *prefix
;
142 struct score_entry
*scores
;
143 int newscore
, scorecount
, reverse
= 0, max
= MAX_SCORES
;
145 struct passwd
*passwdbuf
;
149 while ((c
= getopt(argc
, argv
, "hrm:")) != -1)
160 if (max
> MAX_SCORES
)
167 if (optind
+3 != argc
)
170 passwdbuf
= getpwuid(getuid());
172 if (!strncmp(SCORE_FILE_PREFIX
, "~", 1))
174 char *homedir
= get_home_dir(passwdbuf
);
176 lose("Unable to determine home directory\n");
177 prefix
= malloc(strlen(homedir
) + strlen(SCORE_FILE_PREFIX
) + 1);
178 strcpy(prefix
, homedir
);
179 /* Skip over the '~'. */
180 strcat(prefix
, SCORE_FILE_PREFIX
+1);
183 prefix
= strdup(SCORE_FILE_PREFIX
);
186 lose("Couldn't create score file name: %s\n", strerror(errno
));
188 scorefile
= malloc(strlen(prefix
) + strlen(argv
[optind
]) + 2);
190 lose("Couldn't create score file name: %s\n", strerror(errno
));
192 strcpy(scorefile
, prefix
);
194 strcat(scorefile
, "/");
195 strcat(scorefile
, argv
[optind
]);
196 newscore
= atoi(argv
[optind
+1]);
197 newdata
= argv
[optind
+2];
198 if (strlen(newdata
) > MAX_DATA_LEN
)
199 newdata
[MAX_DATA_LEN
] = '\0';
201 if (stat(scorefile
, &buf
) < 0)
202 lose("Failed to access scores file \"%s\": %s\n", scorefile
,
204 if (lock_file(scorefile
, &lockstate
) < 0)
205 lose("Failed to lock scores file \"%s\": %s\n",
206 scorefile
, strerror(errno
));
207 if (read_scores(scorefile
, &scores
, &scorecount
) < 0)
209 unlock_file(scorefile
, lockstate
);
210 lose("Failed to read scores file \"%s\": %s\n", scorefile
,
213 push_score(&scores
, &scorecount
, newscore
, get_user_id(passwdbuf
), newdata
);
214 /* Limit the number of scores. If we're using reverse sorting, then
215 we should increment the beginning of the array, to skip over the
216 *smallest* scores. Otherwise, we just decrement the number of
217 scores, since the smallest will be at the end. */
218 if (scorecount
> MAX_SCORES
)
219 scorecount
-= (scorecount
- MAX_SCORES
);
221 scores
+= (scorecount
- MAX_SCORES
);
222 sort_scores(scores
, scorecount
, reverse
);
223 if (write_scores(scorefile
, scores
, scorecount
) < 0)
225 unlock_file(scorefile
, lockstate
);
226 lose("Failed to write scores file \"%s\": %s\n", scorefile
,
229 unlock_file(scorefile
, lockstate
);
234 read_score(FILE *f
, struct score_entry
*score
)
239 while ((c
= getc(f
)) != EOF
243 score
->score
+= (c
-48);
245 while ((c
= getc(f
)) != EOF
254 if (getdelim(&score
->username
, &count
, ' ', f
) < 1
255 || score
->username
== NULL
)
258 score
->username
[strlen(score
->username
)-1] = '\0';
264 char *username
= malloc(unamelen
);
268 while ((c
= getc(f
)) != EOF
271 if (unameread
>= unamelen
-1)
272 if (!(username
= realloc(username
, unamelen
*= 2)))
274 username
[unameread
] = c
;
279 username
[unameread
] = '\0';
280 score
->username
= username
;
288 if (getline(&score
->data
, &len
, f
) < 0)
290 score
->data
[strlen(score
->data
)-1] = '\0';
296 char *buf
= malloc(len
);
299 while ((c
= getc(f
)) != EOF
304 if (!(buf
= realloc(buf
, len
*= 2)))
311 score
->data
[cur
] = '\0';
318 read_scores(const char *filename
, struct score_entry
**scores
,
321 int readval
, scorecount
, cursize
;
322 struct score_entry
*ret
;
323 FILE *f
= fopen(filename
, "r");
328 ret
= malloc(sizeof(struct score_entry
) * cursize
);
331 while ((readval
= read_score(f
, &ret
[scorecount
])) == 0)
333 /* We encoutered an error */
337 if (scorecount
>= cursize
)
339 ret
= realloc(ret
, cursize
*= 2);
350 score_compare(const void *a
, const void *b
)
352 const struct score_entry
*sa
= (const struct score_entry
*) a
;
353 const struct score_entry
*sb
= (const struct score_entry
*) b
;
354 return (sb
->score
> sa
->score
) - (sb
->score
< sa
->score
);
358 score_compare_reverse(const void *a
, const void *b
)
360 const struct score_entry
*sa
= (const struct score_entry
*) a
;
361 const struct score_entry
*sb
= (const struct score_entry
*) b
;
362 return (sa
->score
> sb
->score
) - (sa
->score
< sb
->score
);
366 push_score(struct score_entry
**scores
, int *count
,
367 int newscore
, char *username
, char *newdata
)
369 struct score_entry
*newscores
= realloc(*scores
,
370 sizeof(struct score_entry
) * ((*count
) + 1));
373 newscores
[*count
].score
= newscore
;
374 newscores
[*count
].username
= username
;
375 newscores
[*count
].data
= newdata
;
382 sort_scores(struct score_entry
*scores
, int count
, int reverse
)
384 qsort(scores
, count
, sizeof(struct score_entry
),
385 reverse
? score_compare_reverse
: score_compare
);
389 write_scores(const char *filename
, const struct score_entry
*scores
,
394 char *tempfile
= malloc(strlen(filename
) + strlen(".tempXXXXXX") + 1);
397 strcpy(tempfile
, filename
);
398 strcat(tempfile
, ".tempXXXXXX");
400 if (mkstemp(tempfile
) < 0
402 if (mktemp(tempfile
) != tempfile
404 || !(f
= fopen(tempfile
, "w")))
406 for (i
= 0; i
< count
; i
++)
407 if (fprintf(f
, "%ld %s %s\n", scores
[i
].score
, scores
[i
].username
,
411 if (rename(tempfile
, filename
) < 0)
413 if (chmod(filename
, 0644) < 0)
419 lock_file(const char *filename
, void **state
)
424 char *lockext
= ".lockfile";
425 char *lockpath
= malloc(strlen(filename
) + strlen(lockext
) + 60);
428 strcpy(lockpath
, filename
);
429 strcat(lockpath
, lockext
);
433 /* If the lock is over an hour old, delete it. */
434 if (stat(lockpath
, &buf
) == 0
435 && (difftime(buf
.st_ctime
, time(NULL
) > 60*60)))
437 if ((fd
= open(lockpath
, O_CREAT
| O_EXCL
, 0600)) < 0)
441 /* Break the lock; we won't corrupt the file, but we might
443 if (attempts
> MAX_ATTEMPTS
)
448 sleep((rand() % 2)+1);
459 unlock_file(const char *filename
, void *state
)
461 char *lockpath
= (char *) state
;
462 int ret
= unlink(lockpath
);
463 int saved_errno
= errno
;