update-game-score.c (SCORE_FILE_PREFIX): Don't hardcode.
[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
22 score file. It should be installed setgid, owned by an appropriate
23 group like `games'.
24
25 Created 2002/03/22, by Colin Walters <walters@debian.org>
26*/
27
28#define _GNU_SOURCE
29
30#include <unistd.h>
31#include <errno.h>
32#include <string.h>
33#include <stdlib.h>
34#include <stdio.h>
35#include <time.h>
cd553ffb 36#include <pwd.h>
2f1de3dd
CW
37#include <ctype.h>
38#include <fcntl.h>
39#include <sys/stat.h>
cd553ffb 40#include <config.h>
2f1de3dd
CW
41
42#define MAX_ATTEMPTS 5
5795b420
CW
43
44#ifdef HAVE_SHARED_GAME_DIR
45#define SCORE_FILE_PREFIX HAVE_SHARED_GAME_DIR
46#else
47#define SCORE_FILE_PREFIX "$HOME"
48#endif
2f1de3dd
CW
49
50int
51usage(int err)
52{
53 fprintf(stdout, "Usage: update-game-score [-m MAX ] [ -r ] game/scorefile SCORE DATA\n");
54 fprintf(stdout, " update-game-score -h\n");
55 fprintf(stdout, " -h\t\tDisplay this help.\n");
56 fprintf(stdout, " -m MAX\t\tLimit the maximum number of scores to MAX.\n");
57 fprintf(stdout, " -r\t\tSort the scores in increasing order.\n");
58 exit(err);
59}
60
61int
62lock_file(const char *filename, void **state);
63int
64unlock_file(const char *filename, void *state);
65
66struct score_entry
67{
68 long score;
cd553ffb 69 char *username;
2f1de3dd
CW
70 char *data;
71};
72
73int
74read_scores(const char *filename, struct score_entry **scores,
75 int *count);
76int
77push_score(struct score_entry **scores, int *count,
cd553ffb 78 int newscore, char *username, char *newdata);
2f1de3dd
CW
79void
80sort_scores(struct score_entry *scores, int count, int reverse);
81int
82write_scores(const char *filename, const struct score_entry *scores,
83 int count);
84
cd553ffb 85char *
5795b420 86get_user_id(struct passwd *buf)
cd553ffb
CW
87{
88 char *name;
cd553ffb
CW
89 if (!buf)
90 {
91 int count = 1;
92 int uid = (int) getuid();
93 while (uid /= 10)
94 count++;
95 name = malloc(count+1);
96 sprintf(name, "%d", uid);
97 return name;
98 }
99 return buf->pw_name;
100}
101
5795b420
CW
102char *
103get_home_dir(struct passwd *buf)
104{
105 if (!buf)
106 return NULL;
107 return buf->pw_dir;
108}
109
2f1de3dd
CW
110int
111main(int argc, char **argv)
112{
113 int c;
114 void *lockstate;
5795b420 115 char *scorefile, *prefix;
2f1de3dd
CW
116 struct stat buf;
117 struct score_entry *scores;
118 int newscore, scorecount, reverse = 0, max = -1;
119 char *newdata;
5795b420 120 struct passwd *passwdbuf;
2f1de3dd
CW
121
122 srand(time(0));
123
124 while ((c = getopt(argc, argv, "hrm:")) != -1)
125 switch (c)
126 {
127 case 'h':
128 usage(0);
129 break;
130 case 'r':
131 reverse = 1;
132 break;
133 case 'm':
134 max = atoi(optarg);
135 break;
136 default:
137 usage(1);
138 }
139
140 if (optind+3 != argc)
141 usage(1);
5795b420
CW
142
143 passwdbuf = getpwuid(getuid());
144
145 if (!strcmp(SCORE_FILE_PREFIX, "$HOME"))
146 {
147 prefix = get_home_dir(passwdbuf);
148 if (!prefix)
149 {
150 fprintf(stderr, "Unable to determine home directory\n");
151 exit(1);
152 }
153 }
154 else
155 prefix = SCORE_FILE_PREFIX;
156
157 scorefile = malloc(strlen(prefix) + strlen(argv[optind]) + 1);
2f1de3dd
CW
158 if (!scorefile)
159 {
160 fprintf(stderr, "Couldn't create score file name: %s\n",
161 strerror(errno));
162 goto fail;
163 }
5795b420 164 strcpy(scorefile, prefix);
2f1de3dd
CW
165 strcat(scorefile, argv[optind]);
166 newscore = atoi(argv[optind+1]);
167 newdata = argv[optind+2];
168
169 if (stat(scorefile, &buf) < 0)
170 {
171 fprintf(stderr, "Failed to access scores file \"%s\": %s\n",
172 scorefile, strerror(errno));
173 goto fail;
174 }
175 if (lock_file(scorefile, &lockstate) < 0)
176 {
177 fprintf(stderr, "Failed to lock scores file \"%s\": %s\n",
178 scorefile, strerror(errno));
179 goto fail;
180 }
181 if (read_scores(scorefile, &scores, &scorecount) < 0)
182 {
183 fprintf(stderr, "Failed to read scores file \"%s\": %s\n",
184 scorefile, strerror(errno));
185 goto fail_unlock;
186 }
5795b420 187 push_score(&scores, &scorecount, newscore, get_user_id(passwdbuf), newdata);
2f1de3dd
CW
188 sort_scores(scores, scorecount, reverse);
189 if (write_scores(scorefile, scores, scorecount) < 0)
190 {
191 fprintf(stderr, "Failed to write scores file \"%s\": %s\n",
192 scorefile, strerror(errno));
193 goto fail_unlock;
194 }
195 unlock_file(scorefile, lockstate);
196 exit(0);
197 fail_unlock:
198 unlock_file(scorefile, lockstate);
199 fail:
200 exit(1);
201}
202
203int
204read_score(FILE *f, struct score_entry *score)
205{
206 int c;
207 if (feof(f))
208 return 1;
209 while ((c = getc(f)) != EOF
cd553ffb
CW
210 && isdigit(c))
211 {
212 score->score *= 10;
213 score->score += (c-48);
214 }
215 while ((c = getc(f)) != EOF
216 && isspace(c))
217 ;
218 if (c == EOF)
219 return -1;
f5bceaf8 220 ungetc(c, f);
cd553ffb
CW
221#ifdef HAVE_GETDELIM
222 {
223 int count = 0;
224 if (getdelim(&score->username, &count, ' ', f) < 1
225 || score->username == NULL)
226 return -1;
2f1de3dd 227 }
cd553ffb
CW
228#else
229 {
230 int unameread = 0;
231 int unamelen = 30;
f5bceaf8
CW
232 char *username = malloc(unamelen);
233 if (!username)
234 return -1;
cd553ffb
CW
235
236 while ((c = getc(f)) != EOF
237 && !isspace(c))
238 {
239 if (unameread == unamelen)
240 {
241 if (!(username = realloc(username, unamelen *= 2)))
242 return -1;
243 }
244 username[unameread] = c;
245 unameread++;
246 }
f5bceaf8
CW
247 if (c == EOF)
248 return -1;
249 username[unameread] = '\0';
cd553ffb
CW
250 score->username = username;
251 }
252#endif
2f1de3dd
CW
253#ifdef HAVE_GETLINE
254 score->data = NULL;
255 errno = ESUCCES;
256 {
257 int len;
258 if (getline(&score->data, &len, f) < 0)
259 return -1;
5795b420 260 score->data[strlen(score->data)-1] = '\0';
2f1de3dd
CW
261 }
262#else
cd553ffb
CW
263 {
264 int cur = 0;
265 int len = 16;
266 char *buf = malloc(len);
267 if (!buf)
268 return -1;
f5bceaf8
CW
269 while ((c = getc(f)) != EOF
270 && c != '\n')
cd553ffb
CW
271 {
272 if (cur >= len-1)
273 {
274 if (!(buf = realloc(buf, len *= 2)))
275 return -1;
276 }
277 buf[cur] = c;
278 cur++;
279 }
280 score->data = buf;
5795b420 281 score->data[cur] = '\0';
cd553ffb 282 }
2f1de3dd 283#endif
2f1de3dd
CW
284 return 0;
285}
286
287int
288read_scores(const char *filename, struct score_entry **scores,
289 int *count)
290{
291 int readval, scorecount, cursize;
292 struct score_entry *ret;
293 FILE *f = fopen(filename, "r");
294 if (!f)
295 return -1;
296 scorecount = 0;
297 cursize = 16;
298 ret = malloc(sizeof(struct score_entry) * cursize);
299 if (!ret)
300 return -1;
301 while ((readval = read_score(f, &ret[scorecount])) == 0)
302 {
303 /* We encoutered an error */
304 if (readval < 0)
305 return -1;
306 scorecount++;
307 if (scorecount >= cursize)
308 {
309 ret = realloc(ret, cursize *= 2);
310 if (!ret)
311 return -1;
312 }
313 }
314 *count = scorecount;
315 *scores = ret;
316 return 0;
317}
318
319int
320score_compare(const void *a, const void *b)
321{
322 const struct score_entry *sa = (const struct score_entry *) a;
323 const struct score_entry *sb = (const struct score_entry *) b;
324 return (sb->score > sa->score) - (sb->score < sa->score);
325}
326
327int
328score_compare_reverse(const void *a, const void *b)
329{
330 const struct score_entry *sa = (const struct score_entry *) a;
331 const struct score_entry *sb = (const struct score_entry *) b;
332 return (sa->score > sb->score) - (sa->score < sb->score);
333}
334
335int
336push_score(struct score_entry **scores, int *count,
cd553ffb 337 int newscore, char *username, char *newdata)
2f1de3dd
CW
338{
339 struct score_entry *newscores = realloc(*scores,
340 sizeof(struct score_entry) * ((*count) + 1));
341 if (!newscores)
342 return -1;
343 newscores[*count].score = newscore;
cd553ffb 344 newscores[*count].username = username;
2f1de3dd
CW
345 newscores[*count].data = newdata;
346 (*count) += 1;
347 *scores = newscores;
348 return 0;
349}
350
351void
352sort_scores(struct score_entry *scores, int count, int reverse)
353{
354 qsort(scores, count, sizeof(struct score_entry),
355 reverse ? score_compare_reverse : score_compare);
356}
357
358int
359write_scores(const char *filename, const struct score_entry *scores,
360 int count)
361{
362 FILE *f;
363 int i;
364 char *tempfile = malloc(strlen(filename) + strlen(".tempXXXXXX") + 1);
365 if (!tempfile)
366 return -1;
367 strcpy(tempfile, filename);
368 strcat(tempfile, ".tempXXXXXX");
369 if (mkstemp(tempfile) < 0
370 || !(f = fopen(tempfile, "w")))
371 return -1;
372 for (i = 0; i < count; i++)
cd553ffb
CW
373 if (fprintf(f, "%ld %s %s\n", scores[i].score, scores[i].username,
374 scores[i].data) < 0)
2f1de3dd
CW
375 return -1;
376 fclose(f);
377 rename(tempfile, filename);
378 return 0;
379}
380
381#if 0
382int
383lock_file(const char *filename, void **state)
384{
385 struct flock lock;
386 int *istate;
387 int fd, attempts = 0, ret = 0;
388 lock.l_type = F_WRLCK;
389 lock.l_whence = SEEK_SET;
390 lock.l_start = 0;
391 lock.l_len = 0;
392 istate = malloc(sizeof(int));
393 if (!istate)
394 return -1;
395 if ((fd = open(filename, O_RDWR, 0600)) < 0)
396 return -1;
397 *istate = fd;
398 trylock:
399 attempts++;
400 if ((ret = fcntl(fd, F_GETLK, &lock)) == -1)
401 {
402 if (ret == EACCES || ret == EAGAIN)
403 if (attempts > MAX_ATTEMPTS)
404 exit(1);
405 else
406 {
407 sleep((rand() % 3)+1);
408 goto trylock;
409 }
410 else
411 ret = 0 ;
412 }
413 else
414 ret = 0;
415 *state = istate;
416 return ret;
417}
418
419int
420unlock_file(const char *filename, void *state)
421{
422 int fd, ret;
423 fd = *((int *) state);
424 free(state);
425 ret = close(fd);
426 return ret;
427}
428
429#else
430
431int
432lock_file(const char *filename, void **state)
433{
434 int fd;
435 int attempts = 0;
436 char *lockext = ".lockfile";
437 char *lockpath = malloc(strlen(filename) + strlen(lockext) + 60);
438 if (!lockpath)
439 return -1;
440 strcpy(lockpath, filename);
441 strcat(lockpath, lockext);
442 *state = lockpath;
443 trylock:
444 attempts++;
445 if ((fd = open(lockpath, O_CREAT | O_EXCL, 0600)) < 0)
446 {
447 if (errno == EEXIST)
448 {
449 /* Break the lock; we won't corrupt the file, but we might
450 lose some scores. */
451 if (attempts > MAX_ATTEMPTS)
452 unlink(lockpath);
453 else
454 sleep((rand() % 2)+1);
455 goto trylock;
456 }
457 else
458 return -1;
459 }
460 close(fd);
461 return 0;
462}
463
464int
465unlock_file(const char *filename, void *state)
466{
467 char *lockpath = (char *) state;
468 int ret = unlink(lockpath);
469 int saved_errno = errno;
470 free(lockpath);
471 errno = saved_errno;
472 return ret;
473}
474
475#endif