1/* update-game-score.c --- Update a score file 2 Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 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., 51 Franklin Street, Fifth Floor, 19Boston, MA 02110-1301, USA. */ 20 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 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). 28 29 Created 2002/03/22, by Colin Walters <walters@debian.org> 30*/ 31 32#include <config.h> 33 34#ifdef HAVE_UNISTD_H 35#include <unistd.h> 36#endif 37#include <errno.h> 38#ifdef HAVE_STRING_H 39#include <string.h> 40#endif 41#ifdef HAVE_STDLIB_H 42#include <stdlib.h> 43#endif 44#include <stdio.h> 45#include <time.h> 46#include <pwd.h> 47#include <ctype.h> 48#ifdef HAVE_FCNTL_H 49#include <fcntl.h> 50#endif 51#ifdef STDC_HEADERS 52#include <stdarg.h> 53#endif 54#include <sys/stat.h> 55 56/* Needed for SunOS4, for instance. */ 57extern char *optarg; 58extern int optind, opterr; 59 60#define MAX_ATTEMPTS 5 61#define MAX_SCORES 200 62#define MAX_DATA_LEN 1024 63 64/* Declare the prototype for a general external function. */ 65#if defined (PROTOTYPES) || defined (WINDOWSNT) 66#define P_(proto) proto 67#else 68#define P_(proto) () 69#endif 70 71#ifndef HAVE_DIFFTIME 72/* OK on POSIX (time_t is arithmetic type) modulo overflow in subtraction. */ 73#define difftime(t1, t0) (double)((t1) - (t0)) 74#endif 75 76int 77usage (err) 78 int err; 79{ 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"); 86 exit (err); 87} 88 89int lock_file P_ ((const char *filename, void **state)); 90int unlock_file P_ ((const char *filename, void *state)); 91 92struct score_entry 93{ 94 long score; 95 char *username; 96 char *data; 97}; 98 99int read_scores P_ ((const char *filename, struct score_entry **scores, 100 int *count)); 101int push_score P_ ((struct score_entry **scores, int *count, 102 int newscore, char *username, char *newdata)); 103void sort_scores P_ ((struct score_entry *scores, int count, int reverse)); 104int write_scores P_ ((const char *filename, const struct score_entry *scores, 105 int count)); 106 107void lose P_ ((const char *msg)) NO_RETURN; 108 109void 110lose (msg) 111 const char *msg; 112{ 113 fprintf (stderr, "%s\n", msg); 114 exit (EXIT_FAILURE); 115} 116 117void lose_syserr P_ ((const char *msg)) NO_RETURN; 118 119/* Taken from sysdep.c. */ 120#ifndef HAVE_STRERROR 121#ifndef WINDOWSNT 122char * 123strerror (errnum) 124 int errnum; 125{ 126 extern char *sys_errlist[]; 127 extern int sys_nerr; 128 129 if (errnum >= 0 && errnum < sys_nerr) 130 return sys_errlist[errnum]; 131 return (char *) "Unknown error"; 132} 133#endif /* not WINDOWSNT */ 134#endif /* ! HAVE_STRERROR */ 135 136void 137lose_syserr (msg) 138 const char *msg; 139{ 140 fprintf (stderr, "%s: %s\n", msg, strerror (errno)); 141 exit (EXIT_FAILURE); 142} 143 144char * 145get_user_id P_ ((void)) 146{ 147 char *name; 148 struct passwd *buf = getpwuid (getuid ()); 149 if (!buf) 150 { 151 int count = 1; 152 int uid = (int) getuid (); 153 int tuid = uid; 154 while (tuid /= 10) 155 count++; 156 name = malloc (count+1); 157 if (!name) 158 return NULL; 159 sprintf (name, "%d", uid); 160 return name; 161 } 162 return buf->pw_name; 163} 164 165char * 166get_prefix (running_suid, user_prefix) 167 int running_suid; 168 char *user_prefix; 169{ 170 if (!running_suid && user_prefix == NULL) 171 lose ("Not using a shared game directory, and no prefix given."); 172 if (running_suid) 173 { 174#ifdef HAVE_SHARED_GAME_DIR 175 return HAVE_SHARED_GAME_DIR; 176#else 177 lose ("This program was compiled without HAVE_SHARED_GAME_DIR,\n and should not be suid."); 178#endif 179 } 180 return user_prefix; 181} 182 183int 184main (argc, argv) 185 int argc; 186 char **argv; 187{ 188 int c, running_suid; 189 void *lockstate; 190 char *user_id, *scorefile, *prefix, *user_prefix = NULL; 191 struct stat buf; 192 struct score_entry *scores; 193 int newscore, scorecount, reverse = 0, max = MAX_SCORES; 194 char *newdata; 195 196 srand (time (0)); 197 198 while ((c = getopt (argc, argv, "hrm:d:")) != -1) 199 switch (c) 200 { 201 case 'h': 202 usage (EXIT_SUCCESS); 203 break; 204 case 'd': 205 user_prefix = optarg; 206 break; 207 case 'r': 208 reverse = 1; 209 break; 210 case 'm': 211 max = atoi (optarg); 212 if (max > MAX_SCORES) 213 max = MAX_SCORES; 214 break; 215 default: 216 usage (EXIT_FAILURE); 217 } 218 219 if (optind+3 != argc) 220 usage (EXIT_FAILURE); 221 222 running_suid = (getuid () != geteuid ()); 223 224 prefix = get_prefix (running_suid, user_prefix); 225 226 scorefile = malloc (strlen (prefix) + strlen (argv[optind]) + 2); 227 if (!scorefile) 228 lose_syserr ("Couldn't allocate score file"); 229 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'; 237 238 user_id = get_user_id (); 239 if (user_id == NULL) 240 lose_syserr ("Couldn't determine user id"); 241 242 if (stat (scorefile, &buf) < 0) 243 lose_syserr ("Failed to access scores file"); 244 245 if (lock_file (scorefile, &lockstate) < 0) 246 lose_syserr ("Failed to lock scores file"); 247 248 if (read_scores (scorefile, &scores, &scorecount) < 0) 249 { 250 unlock_file (scorefile, lockstate); 251 lose_syserr ("Failed to read scores file"); 252 } 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); 260 if (reverse) 261 scores += (scorecount - MAX_SCORES); 262 sort_scores (scores, scorecount, reverse); 263 if (write_scores (scorefile, scores, scorecount) < 0) 264 { 265 unlock_file (scorefile, lockstate); 266 lose_syserr ("Failed to write scores file"); 267 } 268 unlock_file (scorefile, lockstate); 269 exit (EXIT_SUCCESS); 270} 271 272int 273read_score (f, score) 274 FILE *f; 275 struct score_entry *score; 276{ 277 int c; 278 if (feof (f)) 279 return 1; 280 while ((c = getc (f)) != EOF 281 && isdigit (c)) 282 { 283 score->score *= 10; 284 score->score += (c-48); 285 } 286 while ((c = getc (f)) != EOF 287 && isspace (c)) 288 ; 289 if (c == EOF) 290 return -1; 291 ungetc (c, f); 292#ifdef HAVE_GETDELIM 293 { 294 size_t count = 0; 295 if (getdelim (&score->username, &count, ' ', f) < 1 296 || score->username == NULL) 297 return -1; 298 /* Trim the space */ 299 score->username[strlen (score->username)-1] = '\0'; 300 } 301#else 302 { 303 int unameread = 0; 304 int unamelen = 30; 305 char *username = malloc (unamelen); 306 if (!username) 307 return -1; 308 309 while ((c = getc (f)) != EOF 310 && !isspace (c)) 311 { 312 if (unameread >= unamelen-1) 313 if (!(username = realloc (username, unamelen *= 2))) 314 return -1; 315 username[unameread] = c; 316 unameread++; 317 } 318 if (c == EOF) 319 return -1; 320 username[unameread] = '\0'; 321 score->username = username; 322 } 323#endif 324#ifdef HAVE_GETLINE 325 score->data = NULL; 326 errno = 0; 327 { 328 size_t len; 329 if (getline (&score->data, &len, f) < 0) 330 return -1; 331 score->data[strlen (score->data)-1] = '\0'; 332 } 333#else 334 { 335 int cur = 0; 336 int len = 16; 337 char *buf = malloc (len); 338 if (!buf) 339 return -1; 340 while ((c = getc (f)) != EOF 341 && c != '\n') 342 { 343 if (cur >= len-1) 344 { 345 if (!(buf = realloc (buf, len *= 2))) 346 return -1; 347 } 348 buf[cur] = c; 349 cur++; 350 } 351 score->data = buf; 352 score->data[cur] = '\0'; 353 } 354#endif 355 return 0; 356} 357 358int 359read_scores (filename, scores, count) 360 const char *filename; 361 struct score_entry **scores; 362 int *count; 363{ 364 int readval, scorecount, cursize; 365 struct score_entry *ret; 366 FILE *f = fopen (filename, "r"); 367 if (!f) 368 return -1; 369 scorecount = 0; 370 cursize = 16; 371 ret = (struct score_entry *) calloc (sizeof (struct score_entry), cursize); 372 if (!ret) 373 return -1; 374 while ((readval = read_score (f, &ret[scorecount])) == 0) 375 { 376 /* We encoutered an error */ 377 if (readval < 0) 378 return -1; 379 scorecount++; 380 if (scorecount >= cursize) 381 { 382 cursize *= 2; 383 ret = (struct score_entry *) 384 realloc (ret, (sizeof (struct score_entry) * cursize)); 385 if (!ret) 386 return -1; 387 memset(&ret[scorecount], 0, sizeof(struct score_entry)*(cursize-scorecount)); 388 } 389 } 390 *count = scorecount; 391 *scores = ret; 392 return 0; 393} 394 395int 396score_compare (a, b) 397 const void *a; 398 const void *b; 399{ 400 const struct score_entry *sa = (const struct score_entry *) a; 401 const struct score_entry *sb = (const struct score_entry *) b; 402 return (sb->score > sa->score) - (sb->score < sa->score); 403} 404 405int 406score_compare_reverse (a, b) 407 const void *a; 408 const void *b; 409{ 410 const struct score_entry *sa = (const struct score_entry *) a; 411 const struct score_entry *sb = (const struct score_entry *) b; 412 return (sa->score > sb->score) - (sa->score < sb->score); 413} 414 415int 416push_score (scores, count, newscore, username, newdata) 417 struct score_entry **scores; 418 int *count; int newscore; 419 char *username; 420 char *newdata; 421{ 422 struct score_entry *newscores 423 = (struct score_entry *) realloc (*scores, 424 sizeof (struct score_entry) * ((*count) + 1)); 425 if (!newscores) 426 return -1; 427 newscores[*count].score = newscore; 428 newscores[*count].username = username; 429 newscores[*count].data = newdata; 430 (*count) += 1; 431 *scores = newscores; 432 return 0; 433} 434 435void 436sort_scores (scores, count, reverse) 437 struct score_entry *scores; 438 int count; 439 int reverse; 440{ 441 qsort (scores, count, sizeof (struct score_entry), 442 reverse ? score_compare_reverse : score_compare); 443} 444 445int 446write_scores (filename, scores, count) 447 const char *filename; 448 const struct score_entry * scores; 449 int count; 450{ 451 FILE *f; 452 int i; 453 char *tempfile = malloc (strlen (filename) + strlen (".tempXXXXXX") + 1); 454 if (!tempfile) 455 return -1; 456 strcpy (tempfile, filename); 457 strcat (tempfile, ".tempXXXXXX"); 458#ifdef HAVE_MKSTEMP 459 if (mkstemp (tempfile) < 0 460#else 461 if (mktemp (tempfile) != tempfile 462#endif 463 || !(f = fopen (tempfile, "w"))) 464 return -1; 465 for (i = 0; i < count; i++) 466 if (fprintf (f, "%ld %s %s\n", scores[i].score, scores[i].username, 467 scores[i].data) < 0) 468 return -1; 469 fclose (f); 470 if (rename (tempfile, filename) < 0) 471 return -1; 472 if (chmod (filename, 0644) < 0) 473 return -1; 474 return 0; 475} 476 477int 478lock_file (filename, state) 479 const char *filename; 480 void **state; 481{ 482 int fd; 483 struct stat buf; 484 int attempts = 0; 485 char *lockext = ".lockfile"; 486 char *lockpath = malloc (strlen (filename) + strlen (lockext) + 60); 487 if (!lockpath) 488 return -1; 489 strcpy (lockpath, filename); 490 strcat (lockpath, lockext); 491 *state = lockpath; 492 trylock: 493 attempts++; 494 /* If the lock is over an hour old, delete it. */ 495 if (stat (lockpath, &buf) == 0 496 && (difftime (buf.st_ctime, time (NULL) > 60*60))) 497 unlink (lockpath); 498 fd = open (lockpath, O_CREAT | O_EXCL, 0600); 499 if (fd < 0) 500 { 501 if (errno == EEXIST) 502 { 503 /* Break the lock; we won't corrupt the file, but we might 504 lose some scores. */ 505 if (attempts > MAX_ATTEMPTS) 506 { 507 unlink (lockpath); 508 attempts = 0; 509 } 510 sleep ((rand () % 2)+1); 511 goto trylock; 512 } 513 else 514 return -1; 515 } 516 close (fd); 517 return 0; 518} 519 520int 521unlock_file (filename, state) 522 const char *filename; 523 void *state; 524{ 525 char *lockpath = (char *) state; 526 int ret = unlink (lockpath); 527 int saved_errno = errno; 528 free (lockpath); 529 errno = saved_errno; 530 return ret; 531} 532 533/* arch-tag: 2bf5c52e-4beb-463a-954e-c58b9c64736b 534 (do not change this comment) */ 535 536/* update-game-score.c ends here */ 537