1 /* update-game-score.c --- Update a score file
3 Copyright (C) 2002-2011 Free Software Foundation, Inc.
5 Author: Colin Walters <walters@debian.org>
7 This file is part of GNU Emacs.
9 GNU Emacs is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 GNU Emacs is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
23 /* This program allows a game to securely and atomically update a
24 score file. It should be installed setuid, owned by an appropriate
27 Alternatively, it can be compiled without HAVE_SHARED_GAME_DIR
28 defined, and in that case it will store scores in the user's home
29 directory (it should NOT be setuid).
50 /* Needed for SunOS4, for instance. */
52 extern int optind
, opterr
;
54 static int usage (int err
) NO_RETURN
;
56 #define MAX_ATTEMPTS 5
57 #define MAX_SCORES 200
58 #define MAX_DATA_LEN 1024
61 /* OK on POSIX (time_t is arithmetic type) modulo overflow in subtraction. */
62 #define difftime(t1, t0) (double)((t1) - (t0))
68 fprintf (stdout
, "Usage: update-game-score [-m MAX] [-r] [-d DIR] game/scorefile SCORE DATA\n");
69 fprintf (stdout
, " update-game-score -h\n");
70 fprintf (stdout
, " -h\t\tDisplay this help.\n");
71 fprintf (stdout
, " -m MAX\t\tLimit the maximum number of scores to MAX.\n");
72 fprintf (stdout
, " -r\t\tSort the scores in increasing order.\n");
73 fprintf (stdout
, " -d DIR\t\tStore scores in DIR (only if not setuid).\n");
77 static int lock_file (const char *filename
, void **state
);
78 static int unlock_file (const char *filename
, void *state
);
87 static int read_scores (const char *filename
, struct score_entry
**scores
,
89 static int push_score (struct score_entry
**scores
, int *count
,
90 int newscore
, char *username
, char *newdata
);
91 static void sort_scores (struct score_entry
*scores
, int count
, int reverse
);
92 static int write_scores (const char *filename
,
93 const struct score_entry
*scores
, int count
);
95 static void lose (const char *msg
) NO_RETURN
;
98 lose (const char *msg
)
100 fprintf (stderr
, "%s\n", msg
);
104 static void lose_syserr (const char *msg
) NO_RETURN
;
106 /* Taken from sysdep.c. */
107 #ifndef HAVE_STRERROR
110 strerror (int errnum
)
112 extern char *sys_errlist
[];
115 if (errnum
>= 0 && errnum
< sys_nerr
)
116 return sys_errlist
[errnum
];
117 return (char *) "Unknown error";
119 #endif /* not WINDOWSNT */
120 #endif /* ! HAVE_STRERROR */
123 lose_syserr (const char *msg
)
125 fprintf (stderr
, "%s: %s\n", msg
, strerror (errno
));
132 struct passwd
*buf
= getpwuid (getuid ());
135 long uid
= getuid ();
136 char *name
= malloc (sizeof uid
* CHAR_BIT
/ 3 + 1);
138 sprintf (name
, "%ld", uid
);
145 get_prefix (int running_suid
, const char *user_prefix
)
147 if (!running_suid
&& user_prefix
== NULL
)
148 lose ("Not using a shared game directory, and no prefix given.");
151 #ifdef HAVE_SHARED_GAME_DIR
152 return HAVE_SHARED_GAME_DIR
;
154 lose ("This program was compiled without HAVE_SHARED_GAME_DIR,\n and should not be suid.");
161 main (int argc
, char **argv
)
165 char *user_id
, *scorefile
;
166 const char *prefix
, *user_prefix
= NULL
;
168 struct score_entry
*scores
;
169 int newscore
, scorecount
, reverse
= 0, max
= MAX_SCORES
;
174 while ((c
= getopt (argc
, argv
, "hrm:d:")) != -1)
178 usage (EXIT_SUCCESS
);
181 user_prefix
= optarg
;
188 if (max
> MAX_SCORES
)
192 usage (EXIT_FAILURE
);
195 if (optind
+3 != argc
)
196 usage (EXIT_FAILURE
);
198 running_suid
= (getuid () != geteuid ());
200 prefix
= get_prefix (running_suid
, user_prefix
);
202 scorefile
= malloc (strlen (prefix
) + strlen (argv
[optind
]) + 2);
204 lose_syserr ("Couldn't allocate score file");
206 strcpy (scorefile
, prefix
);
207 strcat (scorefile
, "/");
208 strcat (scorefile
, argv
[optind
]);
209 newscore
= atoi (argv
[optind
+1]);
210 newdata
= argv
[optind
+2];
211 if (strlen (newdata
) > MAX_DATA_LEN
)
212 newdata
[MAX_DATA_LEN
] = '\0';
214 user_id
= get_user_id ();
216 lose_syserr ("Couldn't determine user id");
218 if (stat (scorefile
, &buf
) < 0)
219 lose_syserr ("Failed to access scores file");
221 if (lock_file (scorefile
, &lockstate
) < 0)
222 lose_syserr ("Failed to lock scores file");
224 if (read_scores (scorefile
, &scores
, &scorecount
) < 0)
226 unlock_file (scorefile
, lockstate
);
227 lose_syserr ("Failed to read scores file");
229 push_score (&scores
, &scorecount
, newscore
, user_id
, newdata
);
230 sort_scores (scores
, scorecount
, reverse
);
231 /* Limit the number of scores. If we're using reverse sorting, then
232 also increment the beginning of the array, to skip over the
233 *smallest* scores. Otherwise, just decrementing the number of
234 scores suffices, since the smallest is at the end. */
235 if (scorecount
> MAX_SCORES
)
238 scores
+= (scorecount
- MAX_SCORES
);
239 scorecount
= MAX_SCORES
;
241 if (write_scores (scorefile
, scores
, scorecount
) < 0)
243 unlock_file (scorefile
, lockstate
);
244 lose_syserr ("Failed to write scores file");
246 unlock_file (scorefile
, lockstate
);
251 read_score (FILE *f
, struct score_entry
*score
)
256 while ((c
= getc (f
)) != EOF
260 score
->score
+= (c
-48);
262 while ((c
= getc (f
)) != EOF
271 if (getdelim (&score
->username
, &count
, ' ', f
) < 1
272 || score
->username
== NULL
)
275 score
->username
[strlen (score
->username
)-1] = '\0';
281 char *username
= malloc (unamelen
);
285 while ((c
= getc (f
)) != EOF
288 if (unameread
>= unamelen
-1)
289 if (!(username
= realloc (username
, unamelen
*= 2)))
291 username
[unameread
] = c
;
296 username
[unameread
] = '\0';
297 score
->username
= username
;
305 if (getline (&score
->data
, &len
, f
) < 0)
307 score
->data
[strlen (score
->data
)-1] = '\0';
313 char *buf
= malloc (len
);
316 while ((c
= getc (f
)) != EOF
321 if (!(buf
= realloc (buf
, len
*= 2)))
328 score
->data
[cur
] = '\0';
335 read_scores (const char *filename
, struct score_entry
**scores
, int *count
)
337 int readval
, scorecount
, cursize
;
338 struct score_entry
*ret
;
339 FILE *f
= fopen (filename
, "r");
344 ret
= (struct score_entry
*) malloc (sizeof (struct score_entry
) * cursize
);
347 while ((readval
= read_score (f
, &ret
[scorecount
])) == 0)
349 /* We encountered an error. */
353 if (scorecount
>= cursize
)
356 ret
= (struct score_entry
*)
357 realloc (ret
, (sizeof (struct score_entry
) * cursize
));
368 score_compare (const void *a
, const void *b
)
370 const struct score_entry
*sa
= (const struct score_entry
*) a
;
371 const struct score_entry
*sb
= (const struct score_entry
*) b
;
372 return (sb
->score
> sa
->score
) - (sb
->score
< sa
->score
);
376 score_compare_reverse (const void *a
, const void *b
)
378 const struct score_entry
*sa
= (const struct score_entry
*) a
;
379 const struct score_entry
*sb
= (const struct score_entry
*) b
;
380 return (sa
->score
> sb
->score
) - (sa
->score
< sb
->score
);
384 push_score (struct score_entry
**scores
, int *count
, int newscore
, char *username
, char *newdata
)
386 struct score_entry
*newscores
387 = (struct score_entry
*) realloc (*scores
,
388 sizeof (struct score_entry
) * ((*count
) + 1));
391 newscores
[*count
].score
= newscore
;
392 newscores
[*count
].username
= username
;
393 newscores
[*count
].data
= newdata
;
400 sort_scores (struct score_entry
*scores
, int count
, int reverse
)
402 qsort (scores
, count
, sizeof (struct score_entry
),
403 reverse
? score_compare_reverse
: score_compare
);
407 write_scores (const char *filename
, const struct score_entry
*scores
, int count
)
411 char *tempfile
= malloc (strlen (filename
) + strlen (".tempXXXXXX") + 1);
414 strcpy (tempfile
, filename
);
415 strcat (tempfile
, ".tempXXXXXX");
417 if (mkstemp (tempfile
) < 0
419 if (mktemp (tempfile
) != tempfile
421 || !(f
= fopen (tempfile
, "w")))
423 for (i
= 0; i
< count
; i
++)
424 if (fprintf (f
, "%ld %s %s\n", scores
[i
].score
, scores
[i
].username
,
428 if (rename (tempfile
, filename
) < 0)
430 if (chmod (filename
, 0644) < 0)
436 lock_file (const char *filename
, void **state
)
441 const char *lockext
= ".lockfile";
442 char *lockpath
= malloc (strlen (filename
) + strlen (lockext
) + 60);
445 strcpy (lockpath
, filename
);
446 strcat (lockpath
, lockext
);
450 /* If the lock is over an hour old, delete it. */
451 if (stat (lockpath
, &buf
) == 0
452 && (difftime (buf
.st_ctime
, time (NULL
) > 60*60)))
454 fd
= open (lockpath
, O_CREAT
| O_EXCL
, 0600);
459 /* Break the lock; we won't corrupt the file, but we might
461 if (attempts
> MAX_ATTEMPTS
)
466 sleep ((rand () % 2)+1);
477 unlock_file (const char *filename
, void *state
)
479 char *lockpath
= (char *) state
;
480 int ret
= unlink (lockpath
);
481 int saved_errno
= errno
;
488 /* update-game-score.c ends here */