*** empty log message ***
[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>
36#include <ctype.h>
37#include <fcntl.h>
38#include <sys/stat.h>
39// #include "config.h"
40
41#define MAX_ATTEMPTS 5
42#define SCORE_FILE_PREFIX "/var/games/emacs/"
43
44int
45usage(int err)
46{
47 fprintf(stdout, "Usage: update-game-score [-m MAX ] [ -r ] game/scorefile SCORE DATA\n");
48 fprintf(stdout, " update-game-score -h\n");
49 fprintf(stdout, " -h\t\tDisplay this help.\n");
50 fprintf(stdout, " -m MAX\t\tLimit the maximum number of scores to MAX.\n");
51 fprintf(stdout, " -r\t\tSort the scores in increasing order.\n");
52 exit(err);
53}
54
55int
56lock_file(const char *filename, void **state);
57int
58unlock_file(const char *filename, void *state);
59
60struct score_entry
61{
62 long score;
63 char *data;
64};
65
66int
67read_scores(const char *filename, struct score_entry **scores,
68 int *count);
69int
70push_score(struct score_entry **scores, int *count,
71 int newscore, char *newdata);
72void
73sort_scores(struct score_entry *scores, int count, int reverse);
74int
75write_scores(const char *filename, const struct score_entry *scores,
76 int count);
77
78int
79main(int argc, char **argv)
80{
81 int c;
82 void *lockstate;
83 char *scorefile;
84 struct stat buf;
85 struct score_entry *scores;
86 int newscore, scorecount, reverse = 0, max = -1;
87 char *newdata;
88
89 srand(time(0));
90
91 while ((c = getopt(argc, argv, "hrm:")) != -1)
92 switch (c)
93 {
94 case 'h':
95 usage(0);
96 break;
97 case 'r':
98 reverse = 1;
99 break;
100 case 'm':
101 max = atoi(optarg);
102 break;
103 default:
104 usage(1);
105 }
106
107 if (optind+3 != argc)
108 usage(1);
109 scorefile = malloc(strlen(SCORE_FILE_PREFIX) + strlen(argv[optind]) + 20);
110 if (!scorefile)
111 {
112 fprintf(stderr, "Couldn't create score file name: %s\n",
113 strerror(errno));
114 goto fail;
115 }
116 strcpy(scorefile, SCORE_FILE_PREFIX);
117 strcat(scorefile, argv[optind]);
118 newscore = atoi(argv[optind+1]);
119 newdata = argv[optind+2];
120
121 if (stat(scorefile, &buf) < 0)
122 {
123 fprintf(stderr, "Failed to access scores file \"%s\": %s\n",
124 scorefile, strerror(errno));
125 goto fail;
126 }
127 if (lock_file(scorefile, &lockstate) < 0)
128 {
129 fprintf(stderr, "Failed to lock scores file \"%s\": %s\n",
130 scorefile, strerror(errno));
131 goto fail;
132 }
133 if (read_scores(scorefile, &scores, &scorecount) < 0)
134 {
135 fprintf(stderr, "Failed to read scores file \"%s\": %s\n",
136 scorefile, strerror(errno));
137 goto fail_unlock;
138 }
139 push_score(&scores, &scorecount, newscore, newdata);
140 sort_scores(scores, scorecount, reverse);
141 if (write_scores(scorefile, scores, scorecount) < 0)
142 {
143 fprintf(stderr, "Failed to write scores file \"%s\": %s\n",
144 scorefile, strerror(errno));
145 goto fail_unlock;
146 }
147 unlock_file(scorefile, lockstate);
148 exit(0);
149 fail_unlock:
150 unlock_file(scorefile, lockstate);
151 fail:
152 exit(1);
153}
154
155int
156read_score(FILE *f, struct score_entry *score)
157{
158 int c;
159 if (feof(f))
160 return 1;
161 while ((c = getc(f)) != EOF
162 && isdigit(c)) {
163 score->score *= 10;
164 score->score += (c-48);
165 }
166#ifdef HAVE_GETLINE
167 score->data = NULL;
168 errno = ESUCCES;
169 {
170 int len;
171 if (getline(&score->data, &len, f) < 0)
172 return -1;
173 }
174#else
175 /* We should probably just copy the getline code into here from
176 glibc, instead of this halfassed solution. */
177 score->data = malloc(122);
178 score->data[0] = '\0';
179 if (!fgets(score->data, 120, f))
180 return -1;
181#endif
182 /* Trim the newline */
183 score->data[strlen(score->data)-1] = '\0';
184 return 0;
185}
186
187int
188read_scores(const char *filename, struct score_entry **scores,
189 int *count)
190{
191 int readval, scorecount, cursize;
192 struct score_entry *ret;
193 FILE *f = fopen(filename, "r");
194 if (!f)
195 return -1;
196 scorecount = 0;
197 cursize = 16;
198 ret = malloc(sizeof(struct score_entry) * cursize);
199 if (!ret)
200 return -1;
201 while ((readval = read_score(f, &ret[scorecount])) == 0)
202 {
203 /* We encoutered an error */
204 if (readval < 0)
205 return -1;
206 scorecount++;
207 if (scorecount >= cursize)
208 {
209 ret = realloc(ret, cursize *= 2);
210 if (!ret)
211 return -1;
212 }
213 }
214 *count = scorecount;
215 *scores = ret;
216 return 0;
217}
218
219int
220score_compare(const void *a, const void *b)
221{
222 const struct score_entry *sa = (const struct score_entry *) a;
223 const struct score_entry *sb = (const struct score_entry *) b;
224 return (sb->score > sa->score) - (sb->score < sa->score);
225}
226
227int
228score_compare_reverse(const void *a, const void *b)
229{
230 const struct score_entry *sa = (const struct score_entry *) a;
231 const struct score_entry *sb = (const struct score_entry *) b;
232 return (sa->score > sb->score) - (sa->score < sb->score);
233}
234
235int
236push_score(struct score_entry **scores, int *count,
237 int newscore, char *newdata)
238{
239 struct score_entry *newscores = realloc(*scores,
240 sizeof(struct score_entry) * ((*count) + 1));
241 if (!newscores)
242 return -1;
243 newscores[*count].score = newscore;
244 newscores[*count].data = newdata;
245 (*count) += 1;
246 *scores = newscores;
247 return 0;
248}
249
250void
251sort_scores(struct score_entry *scores, int count, int reverse)
252{
253 qsort(scores, count, sizeof(struct score_entry),
254 reverse ? score_compare_reverse : score_compare);
255}
256
257int
258write_scores(const char *filename, const struct score_entry *scores,
259 int count)
260{
261 FILE *f;
262 int i;
263 char *tempfile = malloc(strlen(filename) + strlen(".tempXXXXXX") + 1);
264 if (!tempfile)
265 return -1;
266 strcpy(tempfile, filename);
267 strcat(tempfile, ".tempXXXXXX");
268 if (mkstemp(tempfile) < 0
269 || !(f = fopen(tempfile, "w")))
270 return -1;
271 for (i = 0; i < count; i++)
272 if (fprintf(f, "%ld %s\n", scores[i].score, scores[i].data) < 0)
273 return -1;
274 fclose(f);
275 rename(tempfile, filename);
276 return 0;
277}
278
279#if 0
280int
281lock_file(const char *filename, void **state)
282{
283 struct flock lock;
284 int *istate;
285 int fd, attempts = 0, ret = 0;
286 lock.l_type = F_WRLCK;
287 lock.l_whence = SEEK_SET;
288 lock.l_start = 0;
289 lock.l_len = 0;
290 istate = malloc(sizeof(int));
291 if (!istate)
292 return -1;
293 if ((fd = open(filename, O_RDWR, 0600)) < 0)
294 return -1;
295 *istate = fd;
296 trylock:
297 attempts++;
298 if ((ret = fcntl(fd, F_GETLK, &lock)) == -1)
299 {
300 if (ret == EACCES || ret == EAGAIN)
301 if (attempts > MAX_ATTEMPTS)
302 exit(1);
303 else
304 {
305 sleep((rand() % 3)+1);
306 goto trylock;
307 }
308 else
309 ret = 0 ;
310 }
311 else
312 ret = 0;
313 *state = istate;
314 return ret;
315}
316
317int
318unlock_file(const char *filename, void *state)
319{
320 int fd, ret;
321 fd = *((int *) state);
322 free(state);
323 ret = close(fd);
324 return ret;
325}
326
327#else
328
329int
330lock_file(const char *filename, void **state)
331{
332 int fd;
333 int attempts = 0;
334 char *lockext = ".lockfile";
335 char *lockpath = malloc(strlen(filename) + strlen(lockext) + 60);
336 if (!lockpath)
337 return -1;
338 strcpy(lockpath, filename);
339 strcat(lockpath, lockext);
340 *state = lockpath;
341 trylock:
342 attempts++;
343 if ((fd = open(lockpath, O_CREAT | O_EXCL, 0600)) < 0)
344 {
345 if (errno == EEXIST)
346 {
347 /* Break the lock; we won't corrupt the file, but we might
348 lose some scores. */
349 if (attempts > MAX_ATTEMPTS)
350 unlink(lockpath);
351 else
352 sleep((rand() % 2)+1);
353 goto trylock;
354 }
355 else
356 return -1;
357 }
358 close(fd);
359 return 0;
360}
361
362int
363unlock_file(const char *filename, void *state)
364{
365 char *lockpath = (char *) state;
366 int ret = unlink(lockpath);
367 int saved_errno = errno;
368 free(lockpath);
369 errno = saved_errno;
370 return ret;
371}
372
373#endif