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>
56 /* Needed for SunOS4, for instance. */
58 extern int optind
, opterr
;
60 #define MAX_ATTEMPTS 5
61 #define MAX_SCORES 200
62 #define MAX_DATA_LEN 1024
64 /* Declare the prototype for a general external function. */
65 #if defined (PROTOTYPES) || defined (WINDOWSNT)
66 #define P_(proto) proto
75 fprintf(stdout
, "Usage: update-game-score [-m MAX ] [ -r ] game/scorefile SCORE DATA\n");
76 fprintf(stdout
, " update-game-score -h\n");
77 fprintf(stdout
, " -h\t\tDisplay this help.\n");
78 fprintf(stdout
, " -m MAX\t\tLimit the maximum number of scores to MAX.\n");
79 fprintf(stdout
, " -r\t\tSort the scores in increasing order.\n");
80 fprintf(stdout
, " -d DIR\t\tStore scores in DIR (only if not setuid).\n");
85 lock_file
P_((const char *filename
, void **state
));
87 unlock_file
P_((const char *filename
, void *state
));
97 read_scores
P_((const char *filename
, struct score_entry
**scores
,
100 push_score
P_((struct score_entry
**scores
, int *count
,
101 int newscore
, char *username
, char *newdata
));
103 sort_scores
P_((struct score_entry
*scores
, int count
, int reverse
));
105 write_scores
P_((const char *filename
, const struct score_entry
*scores
,
108 void lose
P_((const char *msg
)) NO_RETURN
;
113 fprintf(stderr
, "%s\n", msg
);
117 void lose_syserr
P_((const char *msg
)) NO_RETURN
;
119 void lose_syserr(msg
)
122 fprintf(stderr
, "%s: %s\n", msg
, strerror(errno
));
127 get_user_id
P_ ((void))
130 struct passwd
*buf
= getpwuid(getuid());
134 int uid
= (int) getuid();
138 name
= malloc(count
+1);
141 sprintf(name
, "%d", uid
);
148 get_prefix(running_suid
, user_prefix
)
152 if (!running_suid
&& user_prefix
== NULL
)
153 lose("Not using a shared game directory, and no prefix given.");
156 #ifdef HAVE_SHARED_GAME_DIR
157 return HAVE_SHARED_GAME_DIR
;
159 lose("This program was compiled without HAVE_SHARED_GAME_DIR,\n and should not be suid.");
172 char *user_id
, *scorefile
, *prefix
, *user_prefix
= NULL
;
174 struct score_entry
*scores
;
175 int newscore
, scorecount
, reverse
= 0, max
= MAX_SCORES
;
180 while ((c
= getopt(argc
, argv
, "hrm:d:")) != -1)
187 user_prefix
= optarg
;
194 if (max
> MAX_SCORES
)
201 if (optind
+3 != argc
)
204 running_suid
= (getuid() != geteuid());
206 prefix
= get_prefix(running_suid
, user_prefix
);
208 scorefile
= malloc(strlen(prefix
) + strlen(argv
[optind
]) + 2);
210 lose_syserr("Couldn't allocate score file");
212 strcpy(scorefile
, prefix
);
213 strcat(scorefile
, "/");
214 strcat(scorefile
, argv
[optind
]);
215 newscore
= atoi(argv
[optind
+1]);
216 newdata
= argv
[optind
+2];
217 if (strlen(newdata
) > MAX_DATA_LEN
)
218 newdata
[MAX_DATA_LEN
] = '\0';
220 if ((user_id
= get_user_id()) == NULL
)
221 lose_syserr("Couldn't determine user id");
223 if (stat(scorefile
, &buf
) < 0)
224 lose_syserr("Failed to access scores file");
226 if (lock_file(scorefile
, &lockstate
) < 0)
227 lose_syserr("Failed to lock scores file");
229 if (read_scores(scorefile
, &scores
, &scorecount
) < 0)
231 unlock_file(scorefile
, lockstate
);
232 lose_syserr("Failed to read scores file");
234 push_score(&scores
, &scorecount
, newscore
, user_id
, newdata
);
235 /* Limit the number of scores. If we're using reverse sorting, then
236 we should increment the beginning of the array, to skip over the
237 *smallest* scores. Otherwise, we just decrement the number of
238 scores, since the smallest will be at the end. */
239 if (scorecount
> MAX_SCORES
)
240 scorecount
-= (scorecount
- MAX_SCORES
);
242 scores
+= (scorecount
- MAX_SCORES
);
243 sort_scores(scores
, scorecount
, reverse
);
244 if (write_scores(scorefile
, scores
, scorecount
) < 0)
246 unlock_file(scorefile
, lockstate
);
247 lose_syserr("Failed to write scores file");
249 unlock_file(scorefile
, lockstate
);
256 struct score_entry
*score
;
261 while ((c
= getc(f
)) != EOF
265 score
->score
+= (c
-48);
267 while ((c
= getc(f
)) != EOF
276 if (getdelim(&score
->username
, &count
, ' ', f
) < 1
277 || score
->username
== NULL
)
280 score
->username
[strlen(score
->username
)-1] = '\0';
286 char *username
= malloc(unamelen
);
290 while ((c
= getc(f
)) != EOF
293 if (unameread
>= unamelen
-1)
294 if (!(username
= realloc(username
, unamelen
*= 2)))
296 username
[unameread
] = c
;
301 username
[unameread
] = '\0';
302 score
->username
= username
;
310 if (getline(&score
->data
, &len
, f
) < 0)
312 score
->data
[strlen(score
->data
)-1] = '\0';
318 char *buf
= malloc(len
);
321 while ((c
= getc(f
)) != EOF
326 if (!(buf
= realloc(buf
, len
*= 2)))
333 score
->data
[cur
] = '\0';
340 read_scores(filename
, scores
, count
)
341 const char *filename
;
342 struct score_entry
**scores
;
345 int readval
, scorecount
, cursize
;
346 struct score_entry
*ret
;
347 FILE *f
= fopen(filename
, "r");
352 ret
= malloc(sizeof(struct score_entry
) * cursize
);
355 while ((readval
= read_score(f
, &ret
[scorecount
])) == 0)
357 /* We encoutered an error */
361 if (scorecount
>= cursize
)
363 ret
= realloc(ret
, cursize
*= 2);
378 const struct score_entry
*sa
= (const struct score_entry
*) a
;
379 const struct score_entry
*sb
= (const struct score_entry
*) b
;
380 return (sb
->score
> sa
->score
) - (sb
->score
< sa
->score
);
384 score_compare_reverse(a
, b
)
388 const struct score_entry
*sa
= (const struct score_entry
*) a
;
389 const struct score_entry
*sb
= (const struct score_entry
*) b
;
390 return (sa
->score
> sb
->score
) - (sa
->score
< sb
->score
);
394 push_score(scores
, count
, newscore
, username
, newdata
)
395 struct score_entry
**scores
;
396 int *count
; int newscore
;
400 struct score_entry
*newscores
= realloc(*scores
,
401 sizeof(struct score_entry
) * ((*count
) + 1));
404 newscores
[*count
].score
= newscore
;
405 newscores
[*count
].username
= username
;
406 newscores
[*count
].data
= newdata
;
413 sort_scores(scores
, count
, reverse
)
414 struct score_entry
*scores
;
418 qsort(scores
, count
, sizeof(struct score_entry
),
419 reverse
? score_compare_reverse
: score_compare
);
423 write_scores(filename
, scores
, count
)
424 const char *filename
;
425 const struct score_entry
* scores
;
430 char *tempfile
= malloc(strlen(filename
) + strlen(".tempXXXXXX") + 1);
433 strcpy(tempfile
, filename
);
434 strcat(tempfile
, ".tempXXXXXX");
436 if (mkstemp(tempfile
) < 0
438 if (mktemp(tempfile
) != tempfile
440 || !(f
= fopen(tempfile
, "w")))
442 for (i
= 0; i
< count
; i
++)
443 if (fprintf(f
, "%ld %s %s\n", scores
[i
].score
, scores
[i
].username
,
447 if (rename(tempfile
, filename
) < 0)
449 if (chmod(filename
, 0644) < 0)
455 lock_file(filename
, state
)
456 const char *filename
;
462 char *lockext
= ".lockfile";
463 char *lockpath
= malloc(strlen(filename
) + strlen(lockext
) + 60);
466 strcpy(lockpath
, filename
);
467 strcat(lockpath
, lockext
);
471 /* If the lock is over an hour old, delete it. */
472 if (stat(lockpath
, &buf
) == 0
473 && (difftime(buf
.st_ctime
, time(NULL
) > 60*60)))
475 if ((fd
= open(lockpath
, O_CREAT
| O_EXCL
, 0600)) < 0)
479 /* Break the lock; we won't corrupt the file, but we might
481 if (attempts
> MAX_ATTEMPTS
)
486 sleep((rand() % 2)+1);
497 unlock_file(filename
, state
)
498 const char *filename
;
501 char *lockpath
= (char *) state
;
502 int ret
= unlink(lockpath
);
503 int saved_errno
= errno
;