Revert previous change.
[bpt/emacs.git] / lib-src / update-game-score.c
CommitLineData
2f1de3dd
CW
1/* update-game-score.c --- Update a score file
2 Copyright (C) 2002 Free Software Foundation, Inc.
3
4This file is part of GNU Emacs.
5
6GNU Emacs is free software; you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation; either version 2, or (at your option)
9any later version.
10
11GNU Emacs is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with GNU Emacs; see the file COPYING. If not, write to
18the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19Boston, MA 02111-1307, USA. */
20
21/* This program is allows a game to securely and atomically update a
8eec804f
CW
22 score file. It should be installed setuid, owned by an appropriate
23 user like `games'.
24
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).
2f1de3dd
CW
28
29 Created 2002/03/22, by Colin Walters <walters@debian.org>
30*/
31
32#define _GNU_SOURCE
33
34#include <unistd.h>
35#include <errno.h>
36#include <string.h>
37#include <stdlib.h>
38#include <stdio.h>
39#include <time.h>
cd553ffb 40#include <pwd.h>
2f1de3dd
CW
41#include <ctype.h>
42#include <fcntl.h>
8eec804f 43#include <stdarg.h>
2f1de3dd 44#include <sys/stat.h>
cd553ffb 45#include <config.h>
2f1de3dd
CW
46
47#define MAX_ATTEMPTS 5
8eec804f
CW
48#define MAX_SCORES 200
49#define MAX_DATA_LEN 1024
5795b420
CW
50
51#ifdef HAVE_SHARED_GAME_DIR
52#define SCORE_FILE_PREFIX HAVE_SHARED_GAME_DIR
53#else
8eec804f
CW
54#define SCORE_FILE_PREFIX "~/.emacs.d/games"
55#endif
56
57#if !defined (__GNUC__) || __GNUC__ < 2
58#define __attribute__(x)
5795b420 59#endif
2f1de3dd
CW
60
61int
62usage(int err)
63{
64 fprintf(stdout, "Usage: update-game-score [-m MAX ] [ -r ] game/scorefile SCORE DATA\n");
65 fprintf(stdout, " update-game-score -h\n");
66 fprintf(stdout, " -h\t\tDisplay this help.\n");
67 fprintf(stdout, " -m MAX\t\tLimit the maximum number of scores to MAX.\n");
68 fprintf(stdout, " -r\t\tSort the scores in increasing order.\n");
69 exit(err);
70}
71
72int
73lock_file(const char *filename, void **state);
74int
75unlock_file(const char *filename, void *state);
76
77struct score_entry
78{
79 long score;
cd553ffb 80 char *username;
2f1de3dd
CW
81 char *data;
82};
83
84int
85read_scores(const char *filename, struct score_entry **scores,
86 int *count);
87int
88push_score(struct score_entry **scores, int *count,
cd553ffb 89 int newscore, char *username, char *newdata);
2f1de3dd
CW
90void
91sort_scores(struct score_entry *scores, int count, int reverse);
92int
93write_scores(const char *filename, const struct score_entry *scores,
94 int count);
95
cd553ffb 96char *
5795b420 97get_user_id(struct passwd *buf)
cd553ffb
CW
98{
99 char *name;
cd553ffb
CW
100 if (!buf)
101 {
102 int count = 1;
103 int uid = (int) getuid();
8eec804f
CW
104 int tuid = uid;
105 while (tuid /= 10)
cd553ffb
CW
106 count++;
107 name = malloc(count+1);
108 sprintf(name, "%d", uid);
109 return name;
110 }
111 return buf->pw_name;
112}
113
5795b420
CW
114char *
115get_home_dir(struct passwd *buf)
116{
117 if (!buf)
118 return NULL;
119 return buf->pw_dir;
120}
121
8eec804f
CW
122void lose(const char *msg, ...)
123 __attribute__ ((format (printf,1,0), noreturn));
124
125void lose(const char *msg, ...)
126{
127 va_list ap;
128 va_start(ap, msg);
129 vfprintf(stderr, msg, ap);
130 va_end(ap);
131 exit(1);
132}
133
2f1de3dd
CW
134int
135main(int argc, char **argv)
136{
137 int c;
138 void *lockstate;
5795b420 139 char *scorefile, *prefix;
2f1de3dd
CW
140 struct stat buf;
141 struct score_entry *scores;
8eec804f 142 int newscore, scorecount, reverse = 0, max = MAX_SCORES;
2f1de3dd 143 char *newdata;
5795b420 144 struct passwd *passwdbuf;
2f1de3dd
CW
145
146 srand(time(0));
147
148 while ((c = getopt(argc, argv, "hrm:")) != -1)
149 switch (c)
150 {
151 case 'h':
152 usage(0);
153 break;
154 case 'r':
155 reverse = 1;
156 break;
157 case 'm':
158 max = atoi(optarg);
8eec804f
CW
159 if (max > MAX_SCORES)
160 max = MAX_SCORES;
2f1de3dd
CW
161 break;
162 default:
163 usage(1);
164 }
165
166 if (optind+3 != argc)
167 usage(1);
5795b420
CW
168
169 passwdbuf = getpwuid(getuid());
170
8eec804f 171 if (!strncmp(SCORE_FILE_PREFIX, "~", 1))
5795b420 172 {
8eec804f
CW
173 char *homedir = get_home_dir(passwdbuf);
174 if (!homedir)
175 lose("Unable to determine home directory\n");
176 prefix = malloc(strlen(homedir) + strlen(SCORE_FILE_PREFIX) + 1);
177 strcpy(prefix, homedir);
178 /* Skip over the '~'. */
179 strcat(prefix, SCORE_FILE_PREFIX+1);
5795b420
CW
180 }
181 else
8eec804f
CW
182 prefix = strdup(SCORE_FILE_PREFIX);
183
184 if (!prefix)
185 lose("Couldn't create score file name: %s\n", strerror(errno));
186
187 scorefile = malloc(strlen(prefix) + strlen(argv[optind]) + 2);
2f1de3dd 188 if (!scorefile)
8eec804f
CW
189 lose("Couldn't create score file name: %s\n", strerror(errno));
190
5795b420 191 strcpy(scorefile, prefix);
8eec804f
CW
192 free(prefix);
193 strcat(scorefile, "/");
2f1de3dd
CW
194 strcat(scorefile, argv[optind]);
195 newscore = atoi(argv[optind+1]);
196 newdata = argv[optind+2];
8eec804f
CW
197 if (strlen(newdata) > MAX_DATA_LEN)
198 newdata[MAX_DATA_LEN] = '\0';
2f1de3dd
CW
199
200 if (stat(scorefile, &buf) < 0)
8eec804f
CW
201 lose("Failed to access scores file \"%s\": %s\n", scorefile,
202 strerror(errno));
2f1de3dd 203 if (lock_file(scorefile, &lockstate) < 0)
8eec804f
CW
204 lose("Failed to lock scores file \"%s\": %s\n",
205 scorefile, strerror(errno));
2f1de3dd
CW
206 if (read_scores(scorefile, &scores, &scorecount) < 0)
207 {
8eec804f
CW
208 unlock_file(scorefile, lockstate);
209 lose("Failed to read scores file \"%s\": %s\n", scorefile,
210 strerror(errno));
2f1de3dd 211 }
5795b420 212 push_score(&scores, &scorecount, newscore, get_user_id(passwdbuf), newdata);
8eec804f
CW
213 /* Limit the number of scores. If we're using reverse sorting, then
214 we should increment the beginning of the array, to skip over the
215 *smallest* scores. Otherwise, we just decrement the number of
216 scores, since the smallest will be at the end. */
217 if (scorecount > MAX_SCORES)
218 scorecount -= (scorecount - MAX_SCORES);
219 if (reverse)
220 scores += (scorecount - MAX_SCORES);
2f1de3dd
CW
221 sort_scores(scores, scorecount, reverse);
222 if (write_scores(scorefile, scores, scorecount) < 0)
223 {
8eec804f
CW
224 unlock_file(scorefile, lockstate);
225 lose("Failed to write scores file \"%s\": %s\n", scorefile,
226 strerror(errno));
2f1de3dd
CW
227 }
228 unlock_file(scorefile, lockstate);
229 exit(0);
2f1de3dd
CW
230}
231
232int
233read_score(FILE *f, struct score_entry *score)
234{
235 int c;
236 if (feof(f))
237 return 1;
238 while ((c = getc(f)) != EOF
cd553ffb
CW
239 && isdigit(c))
240 {
241 score->score *= 10;
242 score->score += (c-48);
243 }
244 while ((c = getc(f)) != EOF
245 && isspace(c))
246 ;
247 if (c == EOF)
248 return -1;
f5bceaf8 249 ungetc(c, f);
cd553ffb
CW
250#ifdef HAVE_GETDELIM
251 {
7605f1bd 252 size_t count = 0;
cd553ffb
CW
253 if (getdelim(&score->username, &count, ' ', f) < 1
254 || score->username == NULL)
255 return -1;
2f1de3dd 256 }
cd553ffb
CW
257#else
258 {
259 int unameread = 0;
260 int unamelen = 30;
f5bceaf8
CW
261 char *username = malloc(unamelen);
262 if (!username)
263 return -1;
cd553ffb
CW
264
265 while ((c = getc(f)) != EOF
266 && !isspace(c))
267 {
8eec804f
CW
268 if (unameread >= unamelen-1)
269 if (!(username = realloc(username, unamelen *= 2)))
270 return -1;
cd553ffb
CW
271 username[unameread] = c;
272 unameread++;
273 }
f5bceaf8
CW
274 if (c == EOF)
275 return -1;
276 username[unameread] = '\0';
cd553ffb
CW
277 score->username = username;
278 }
279#endif
2f1de3dd
CW
280#ifdef HAVE_GETLINE
281 score->data = NULL;
7605f1bd 282 errno = 0;
2f1de3dd 283 {
7605f1bd 284 size_t len;
2f1de3dd
CW
285 if (getline(&score->data, &len, f) < 0)
286 return -1;
5795b420 287 score->data[strlen(score->data)-1] = '\0';
2f1de3dd
CW
288 }
289#else
cd553ffb
CW
290 {
291 int cur = 0;
292 int len = 16;
293 char *buf = malloc(len);
294 if (!buf)
295 return -1;
f5bceaf8
CW
296 while ((c = getc(f)) != EOF
297 && c != '\n')
cd553ffb
CW
298 {
299 if (cur >= len-1)
300 {
301 if (!(buf = realloc(buf, len *= 2)))
302 return -1;
303 }
304 buf[cur] = c;
305 cur++;
306 }
307 score->data = buf;
5795b420 308 score->data[cur] = '\0';
cd553ffb 309 }
2f1de3dd 310#endif
2f1de3dd
CW
311 return 0;
312}
313
314int
315read_scores(const char *filename, struct score_entry **scores,
316 int *count)
317{
318 int readval, scorecount, cursize;
319 struct score_entry *ret;
320 FILE *f = fopen(filename, "r");
321 if (!f)
322 return -1;
323 scorecount = 0;
324 cursize = 16;
325 ret = malloc(sizeof(struct score_entry) * cursize);
326 if (!ret)
327 return -1;
328 while ((readval = read_score(f, &ret[scorecount])) == 0)
329 {
330 /* We encoutered an error */
331 if (readval < 0)
332 return -1;
333 scorecount++;
334 if (scorecount >= cursize)
335 {
336 ret = realloc(ret, cursize *= 2);
337 if (!ret)
338 return -1;
339 }
340 }
341 *count = scorecount;
342 *scores = ret;
343 return 0;
344}
345
346int
347score_compare(const void *a, const void *b)
348{
349 const struct score_entry *sa = (const struct score_entry *) a;
350 const struct score_entry *sb = (const struct score_entry *) b;
351 return (sb->score > sa->score) - (sb->score < sa->score);
352}
353
354int
355score_compare_reverse(const void *a, const void *b)
356{
357 const struct score_entry *sa = (const struct score_entry *) a;
358 const struct score_entry *sb = (const struct score_entry *) b;
359 return (sa->score > sb->score) - (sa->score < sb->score);
360}
361
362int
363push_score(struct score_entry **scores, int *count,
cd553ffb 364 int newscore, char *username, char *newdata)
2f1de3dd
CW
365{
366 struct score_entry *newscores = realloc(*scores,
367 sizeof(struct score_entry) * ((*count) + 1));
368 if (!newscores)
369 return -1;
370 newscores[*count].score = newscore;
cd553ffb 371 newscores[*count].username = username;
2f1de3dd
CW
372 newscores[*count].data = newdata;
373 (*count) += 1;
374 *scores = newscores;
375 return 0;
376}
377
378void
379sort_scores(struct score_entry *scores, int count, int reverse)
380{
381 qsort(scores, count, sizeof(struct score_entry),
382 reverse ? score_compare_reverse : score_compare);
383}
384
385int
386write_scores(const char *filename, const struct score_entry *scores,
387 int count)
388{
389 FILE *f;
390 int i;
391 char *tempfile = malloc(strlen(filename) + strlen(".tempXXXXXX") + 1);
392 if (!tempfile)
393 return -1;
394 strcpy(tempfile, filename);
395 strcat(tempfile, ".tempXXXXXX");
8eec804f 396#ifdef HAVE_MKSTEMP
2f1de3dd 397 if (mkstemp(tempfile) < 0
8eec804f
CW
398#else
399 if (mktemp(tempfile) != tempfile
400#endif
2f1de3dd
CW
401 || !(f = fopen(tempfile, "w")))
402 return -1;
403 for (i = 0; i < count; i++)
cd553ffb
CW
404 if (fprintf(f, "%ld %s %s\n", scores[i].score, scores[i].username,
405 scores[i].data) < 0)
2f1de3dd
CW
406 return -1;
407 fclose(f);
8eec804f 408 if (rename(tempfile, filename) < 0)
2f1de3dd 409 return -1;
8eec804f 410 if (chmod(filename, 0644) < 0)
2f1de3dd 411 return -1;
8eec804f 412 return 0;
2f1de3dd
CW
413}
414
2f1de3dd
CW
415int
416lock_file(const char *filename, void **state)
417{
418 int fd;
419 int attempts = 0;
420 char *lockext = ".lockfile";
421 char *lockpath = malloc(strlen(filename) + strlen(lockext) + 60);
422 if (!lockpath)
423 return -1;
424 strcpy(lockpath, filename);
425 strcat(lockpath, lockext);
426 *state = lockpath;
427 trylock:
428 attempts++;
429 if ((fd = open(lockpath, O_CREAT | O_EXCL, 0600)) < 0)
430 {
431 if (errno == EEXIST)
432 {
433 /* Break the lock; we won't corrupt the file, but we might
434 lose some scores. */
435 if (attempts > MAX_ATTEMPTS)
436 unlink(lockpath);
8eec804f 437 sleep((rand() % 2)+1);
2f1de3dd
CW
438 goto trylock;
439 }
440 else
441 return -1;
442 }
443 close(fd);
444 return 0;
445}
446
447int
448unlock_file(const char *filename, void *state)
449{
450 char *lockpath = (char *) state;
451 int ret = unlink(lockpath);
452 int saved_errno = errno;
453 free(lockpath);
454 errno = saved_errno;
455 return ret;
456}