1 /* update-game-score.c --- Update a score file
2 Copyright (C) 2002, 2003 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
72 /* OK on POSIX (time_t is arithmetic type) modulo overflow in subtraction. */
73 #define difftime(t1, t0) (double)((t1) - (t0))
80 fprintf (stdout
, "Usage: update-game-score [-m MAX ] [ -r ] game/scorefile SCORE DATA\n");
81 fprintf (stdout
, " update-game-score -h\n");
82 fprintf (stdout
, " -h\t\tDisplay this help.\n");
83 fprintf (stdout
, " -m MAX\t\tLimit the maximum number of scores to MAX.\n");
84 fprintf (stdout
, " -r\t\tSort the scores in increasing order.\n");
85 fprintf (stdout
, " -d DIR\t\tStore scores in DIR (only if not setuid).\n");
89 int lock_file
P_ ((const char *filename
, void **state
));
90 int unlock_file
P_ ((const char *filename
, void *state
));
99 int read_scores
P_ ((const char *filename
, struct score_entry
**scores
,
101 int push_score
P_ ((struct score_entry
**scores
, int *count
,
102 int newscore
, char *username
, char *newdata
));
103 void sort_scores
P_ ((struct score_entry
*scores
, int count
, int reverse
));
104 int write_scores
P_ ((const char *filename
, const struct score_entry
*scores
,
107 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 /* Taken from sysdep.c. */
120 #ifndef HAVE_STRERROR
126 extern char *sys_errlist
[];
129 if (errnum
>= 0 && errnum
< sys_nerr
)
130 return sys_errlist
[errnum
];
131 return (char *) "Unknown error";
133 #endif /* not WINDOWSNT */
134 #endif /* ! HAVE_STRERROR */
140 fprintf (stderr
, "%s: %s\n", msg
, strerror (errno
));
145 get_user_id
P_ ((void))
148 struct passwd
*buf
= getpwuid (getuid ());
152 int uid
= (int) getuid ();
156 name
= malloc (count
+1);
159 sprintf (name
, "%d", uid
);
166 get_prefix (running_suid
, user_prefix
)
170 if (!running_suid
&& user_prefix
== NULL
)
171 lose ("Not using a shared game directory, and no prefix given.");
174 #ifdef HAVE_SHARED_GAME_DIR
175 return HAVE_SHARED_GAME_DIR
;
177 lose ("This program was compiled without HAVE_SHARED_GAME_DIR,\n and should not be suid.");
190 char *user_id
, *scorefile
, *prefix
, *user_prefix
= NULL
;
192 struct score_entry
*scores
;
193 int newscore
, scorecount
, reverse
= 0, max
= MAX_SCORES
;
198 while ((c
= getopt (argc
, argv
, "hrm:d:")) != -1)
205 user_prefix
= optarg
;
212 if (max
> MAX_SCORES
)
219 if (optind
+3 != argc
)
222 running_suid
= (getuid () != geteuid ());
224 prefix
= get_prefix (running_suid
, user_prefix
);
226 scorefile
= malloc (strlen (prefix
) + strlen (argv
[optind
]) + 2);
228 lose_syserr ("Couldn't allocate score file");
230 strcpy (scorefile
, prefix
);
231 strcat (scorefile
, "/");
232 strcat (scorefile
, argv
[optind
]);
233 newscore
= atoi (argv
[optind
+1]);
234 newdata
= argv
[optind
+2];
235 if (strlen (newdata
) > MAX_DATA_LEN
)
236 newdata
[MAX_DATA_LEN
] = '\0';
238 user_id
= get_user_id ();
240 lose_syserr ("Couldn't determine user id");
242 if (stat (scorefile
, &buf
) < 0)
243 lose_syserr ("Failed to access scores file");
245 if (lock_file (scorefile
, &lockstate
) < 0)
246 lose_syserr ("Failed to lock scores file");
248 if (read_scores (scorefile
, &scores
, &scorecount
) < 0)
250 unlock_file (scorefile
, lockstate
);
251 lose_syserr ("Failed to read scores file");
253 push_score (&scores
, &scorecount
, newscore
, user_id
, newdata
);
254 /* Limit the number of scores. If we're using reverse sorting, then
255 we should increment the beginning of the array, to skip over the
256 *smallest* scores. Otherwise, we just decrement the number of
257 scores, since the smallest will be at the end. */
258 if (scorecount
> MAX_SCORES
)
259 scorecount
-= (scorecount
- MAX_SCORES
);
261 scores
+= (scorecount
- MAX_SCORES
);
262 sort_scores (scores
, scorecount
, reverse
);
263 if (write_scores (scorefile
, scores
, scorecount
) < 0)
265 unlock_file (scorefile
, lockstate
);
266 lose_syserr ("Failed to write scores file");
268 unlock_file (scorefile
, lockstate
);
273 read_score (f
, score
)
275 struct score_entry
*score
;
280 while ((c
= getc (f
)) != EOF
284 score
->score
+= (c
-48);
286 while ((c
= getc (f
)) != EOF
295 if (getdelim (&score
->username
, &count
, ' ', f
) < 1
296 || score
->username
== NULL
)
299 score
->username
[strlen (score
->username
)-1] = '\0';
305 char *username
= malloc (unamelen
);
309 while ((c
= getc (f
)) != EOF
312 if (unameread
>= unamelen
-1)
313 if (!(username
= realloc (username
, unamelen
*= 2)))
315 username
[unameread
] = c
;
320 username
[unameread
] = '\0';
321 score
->username
= username
;
329 if (getline (&score
->data
, &len
, f
) < 0)
331 score
->data
[strlen (score
->data
)-1] = '\0';
337 char *buf
= malloc (len
);
340 while ((c
= getc (f
)) != EOF
345 if (!(buf
= realloc (buf
, len
*= 2)))
352 score
->data
[cur
] = '\0';
359 read_scores (filename
, scores
, count
)
360 const char *filename
;
361 struct score_entry
**scores
;
364 int readval
, scorecount
, cursize
;
365 struct score_entry
*ret
;
366 FILE *f
= fopen (filename
, "r");
371 ret
= (struct score_entry
*) malloc (sizeof (struct score_entry
) * cursize
);
374 while ((readval
= read_score (f
, &ret
[scorecount
])) == 0)
376 /* We encoutered an error */
380 if (scorecount
>= cursize
)
383 ret
= (struct score_entry
*)
384 realloc (ret
, (sizeof (struct score_entry
) * cursize
));
399 const struct score_entry
*sa
= (const struct score_entry
*) a
;
400 const struct score_entry
*sb
= (const struct score_entry
*) b
;
401 return (sb
->score
> sa
->score
) - (sb
->score
< sa
->score
);
405 score_compare_reverse (a
, b
)
409 const struct score_entry
*sa
= (const struct score_entry
*) a
;
410 const struct score_entry
*sb
= (const struct score_entry
*) b
;
411 return (sa
->score
> sb
->score
) - (sa
->score
< sb
->score
);
415 push_score (scores
, count
, newscore
, username
, newdata
)
416 struct score_entry
**scores
;
417 int *count
; int newscore
;
421 struct score_entry
*newscores
422 = (struct score_entry
*) realloc (*scores
,
423 sizeof (struct score_entry
) * ((*count
) + 1));
426 newscores
[*count
].score
= newscore
;
427 newscores
[*count
].username
= username
;
428 newscores
[*count
].data
= newdata
;
435 sort_scores (scores
, count
, reverse
)
436 struct score_entry
*scores
;
440 qsort (scores
, count
, sizeof (struct score_entry
),
441 reverse
? score_compare_reverse
: score_compare
);
445 write_scores (filename
, scores
, count
)
446 const char *filename
;
447 const struct score_entry
* scores
;
452 char *tempfile
= malloc (strlen (filename
) + strlen (".tempXXXXXX") + 1);
455 strcpy (tempfile
, filename
);
456 strcat (tempfile
, ".tempXXXXXX");
458 if (mkstemp (tempfile
) < 0
460 if (mktemp (tempfile
) != tempfile
462 || !(f
= fopen (tempfile
, "w")))
464 for (i
= 0; i
< count
; i
++)
465 if (fprintf (f
, "%ld %s %s\n", scores
[i
].score
, scores
[i
].username
,
469 if (rename (tempfile
, filename
) < 0)
471 if (chmod (filename
, 0644) < 0)
477 lock_file (filename
, state
)
478 const char *filename
;
484 char *lockext
= ".lockfile";
485 char *lockpath
= malloc (strlen (filename
) + strlen (lockext
) + 60);
488 strcpy (lockpath
, filename
);
489 strcat (lockpath
, lockext
);
493 /* If the lock is over an hour old, delete it. */
494 if (stat (lockpath
, &buf
) == 0
495 && (difftime (buf
.st_ctime
, time (NULL
) > 60*60)))
497 fd
= open (lockpath
, O_CREAT
| O_EXCL
, 0600);
502 /* Break the lock; we won't corrupt the file, but we might
504 if (attempts
> MAX_ATTEMPTS
)
509 sleep ((rand () % 2)+1);
520 unlock_file (filename
, state
)
521 const char *filename
;
524 char *lockpath
= (char *) state
;
525 int ret
= unlink (lockpath
);
526 int saved_errno
= errno
;