X-Git-Url: http://git.hcoop.net/bpt/emacs.git/blobdiff_plain/06b583dec7cbde714c8fb991a1e123f612b66e3a..refs/heads/wip:/lib-src/update-game-score.c diff --git a/lib-src/update-game-score.c b/lib-src/update-game-score.c index 29c81a8936..7a64cd04f4 100644 --- a/lib-src/update-game-score.c +++ b/lib-src/update-game-score.c @@ -1,6 +1,6 @@ /* update-game-score.c --- Update a score file -Copyright (C) 2002-2013 Free Software Foundation, Inc. +Copyright (C) 2002-2014 Free Software Foundation, Inc. Author: Colin Walters @@ -35,7 +35,9 @@ along with GNU Emacs. If not, see . */ #include #include +#include #include +#include #include #include #include @@ -46,15 +48,17 @@ along with GNU Emacs. If not, see . */ #include #include -#define MAX_ATTEMPTS 5 -#define MAX_SCORES 200 -#define MAX_DATA_LEN 1024 +#ifdef WINDOWSNT +#include "ntlib.h" +#endif -#ifndef HAVE_DIFFTIME -/* OK on POSIX (time_t is arithmetic type) modulo overflow in subtraction. */ -#define difftime(t1, t0) (double)((t1) - (t0)) +#ifndef min +# define min(a,b) ((a) < (b) ? (a) : (b)) #endif +#define MAX_ATTEMPTS 5 +#define MAX_DATA_LEN 1024 + static _Noreturn void usage (int err) { @@ -72,18 +76,20 @@ static int unlock_file (const char *filename, void *state); struct score_entry { - long score; - char *username; - char *data; + char *score; + char *user_data; }; +#define MAX_SCORES min (PTRDIFF_MAX, SIZE_MAX / sizeof (struct score_entry)) + static int read_scores (const char *filename, struct score_entry **scores, - int *count); -static int push_score (struct score_entry **scores, int *count, - int newscore, char *username, char *newdata); -static void sort_scores (struct score_entry *scores, int count, int reverse); + ptrdiff_t *count, ptrdiff_t *alloc); +static int push_score (struct score_entry **scores, ptrdiff_t *count, + ptrdiff_t *size, struct score_entry const *newscore); +static void sort_scores (struct score_entry *scores, ptrdiff_t count, + bool reverse); static int write_scores (const char *filename, - const struct score_entry *scores, int count); + const struct score_entry *scores, ptrdiff_t count); static _Noreturn void lose (const char *msg) @@ -95,7 +101,8 @@ lose (const char *msg) static _Noreturn void lose_syserr (const char *msg) { - fprintf (stderr, "%s: %s\n", msg, strerror (errno)); + fprintf (stderr, "%s: %s\n", msg, + errno ? strerror (errno) : "Invalid data in score file"); exit (EXIT_FAILURE); } @@ -103,19 +110,19 @@ static char * get_user_id (void) { struct passwd *buf = getpwuid (getuid ()); - if (!buf) + if (!buf || strchr (buf->pw_name, ' ') || strchr (buf->pw_name, '\n')) { - long uid = getuid (); - char *name = malloc (sizeof uid * CHAR_BIT / 3 + 1); + intmax_t uid = getuid (); + char *name = malloc (sizeof uid * CHAR_BIT / 3 + 4); if (name) - sprintf (name, "%ld", uid); + sprintf (name, "%"PRIdMAX, uid); return name; } return buf->pw_name; } static const char * -get_prefix (int running_suid, const char *user_prefix) +get_prefix (bool running_suid, const char *user_prefix) { if (!running_suid && user_prefix == NULL) lose ("Not using a shared game directory, and no prefix given."); @@ -130,17 +137,52 @@ get_prefix (int running_suid, const char *user_prefix) return user_prefix; } +static char * +normalize_integer (char *num) +{ + bool neg; + char *p; + while (*num != '\n' && isspace (*num)) + num++; + neg = *num == '-'; + num += neg || *num == '-'; + + if (*num == '0') + { + while (*++num == '0') + continue; + neg &= !!*num; + num -= !*num; + } + + for (p = num; '0' <= *p && *p <= '9'; p++) + continue; + + if (*p || p == num) + { + errno = 0; + return 0; + } + + if (neg) + *--num = '-'; + return num; +} + int main (int argc, char **argv) { - int c, running_suid; + int c; + bool running_suid; void *lockstate; - char *user_id, *scorefile; + char *scorefile; + char *end, *nl, *user, *data; const char *prefix, *user_prefix = NULL; - struct stat buf; struct score_entry *scores; - int newscore, scorecount, reverse = 0, max = MAX_SCORES; - char *newdata; + struct score_entry newscore; + bool reverse = false; + ptrdiff_t scorecount, scorealloc; + ptrdiff_t max_scores = MAX_SCORES; srand (time (0)); @@ -157,15 +199,18 @@ main (int argc, char **argv) reverse = 1; break; case 'm': - max = atoi (optarg); - if (max > MAX_SCORES) - max = MAX_SCORES; + { + intmax_t m = strtoimax (optarg, &end, 10); + if (optarg == end || *end || m < 0) + usage (EXIT_FAILURE); + max_scores = min (m, MAX_SCORES); + } break; default: usage (EXIT_FAILURE); } - if (optind+3 != argc) + if (argc - optind != 3) usage (EXIT_FAILURE); running_suid = (getuid () != geteuid ()); @@ -179,161 +224,130 @@ main (int argc, char **argv) strcpy (scorefile, prefix); strcat (scorefile, "/"); strcat (scorefile, argv[optind]); - newscore = atoi (argv[optind+1]); - newdata = argv[optind+2]; - if (strlen (newdata) > MAX_DATA_LEN) - newdata[MAX_DATA_LEN] = '\0'; - user_id = get_user_id (); - if (user_id == NULL) - lose_syserr ("Couldn't determine user id"); + newscore.score = normalize_integer (argv[optind + 1]); + if (! newscore.score) + { + fprintf (stderr, "%s: Invalid score\n", argv[optind + 1]); + return EXIT_FAILURE; + } - if (stat (scorefile, &buf) < 0) - lose_syserr ("Failed to access scores file"); + user = get_user_id (); + if (! user) + lose_syserr ("Couldn't determine user id"); + data = argv[optind + 2]; + if (strlen (data) > MAX_DATA_LEN) + data[MAX_DATA_LEN] = '\0'; + nl = strchr (data, '\n'); + if (nl) + *nl = '\0'; + newscore.user_data = malloc (strlen (user) + 1 + strlen (data) + 1); + if (! newscore.user_data + || sprintf (newscore.user_data, "%s %s", user, data) < 0) + lose_syserr ("Memory exhausted"); if (lock_file (scorefile, &lockstate) < 0) lose_syserr ("Failed to lock scores file"); - if (read_scores (scorefile, &scores, &scorecount) < 0) + if (read_scores (scorefile, &scores, &scorecount, &scorealloc) < 0) { unlock_file (scorefile, lockstate); lose_syserr ("Failed to read scores file"); } - push_score (&scores, &scorecount, newscore, user_id, newdata); + if (push_score (&scores, &scorecount, &scorealloc, &newscore) < 0) + { + unlock_file (scorefile, lockstate); + lose_syserr ("Failed to add score"); + } sort_scores (scores, scorecount, reverse); /* Limit the number of scores. If we're using reverse sorting, then also increment the beginning of the array, to skip over the *smallest* scores. Otherwise, just decrementing the number of scores suffices, since the smallest is at the end. */ - if (scorecount > MAX_SCORES) + if (scorecount > max_scores) { if (reverse) - scores += (scorecount - MAX_SCORES); - scorecount = MAX_SCORES; + scores += scorecount - max_scores; + scorecount = max_scores; } if (write_scores (scorefile, scores, scorecount) < 0) { unlock_file (scorefile, lockstate); lose_syserr ("Failed to write scores file"); } - unlock_file (scorefile, lockstate); + if (unlock_file (scorefile, lockstate) < 0) + lose_syserr ("Failed to unlock scores file"); exit (EXIT_SUCCESS); } -static int -read_score (FILE *f, struct score_entry *score) +static char * +read_score (char *p, struct score_entry *score) { - int c; - if (feof (f)) - return 1; - while ((c = getc (f)) != EOF - && isdigit (c)) - { - score->score *= 10; - score->score += (c-48); - } - while ((c = getc (f)) != EOF - && isspace (c)) - ; - if (c == EOF) - return -1; - ungetc (c, f); -#ifdef HAVE_GETDELIM - { - size_t count = 0; - if (getdelim (&score->username, &count, ' ', f) < 1 - || score->username == NULL) - return -1; - /* Trim the space */ - score->username[strlen (score->username)-1] = '\0'; - } -#else - { - int unameread = 0; - int unamelen = 30; - char *username = malloc (unamelen); - if (!username) - return -1; - - while ((c = getc (f)) != EOF - && !isspace (c)) - { - if (unameread >= unamelen-1) - if (!(username = realloc (username, unamelen *= 2))) - return -1; - username[unameread] = c; - unameread++; - } - if (c == EOF) - return -1; - username[unameread] = '\0'; - score->username = username; - } -#endif -#ifdef HAVE_GETLINE - score->data = NULL; - errno = 0; - { - size_t len; - if (getline (&score->data, &len, f) < 0) - return -1; - score->data[strlen (score->data)-1] = '\0'; - } -#else - { - int cur = 0; - int len = 16; - char *buf = malloc (len); - if (!buf) - return -1; - while ((c = getc (f)) != EOF - && c != '\n') - { - if (cur >= len-1) - { - if (!(buf = realloc (buf, len *= 2))) - return -1; - } - buf[cur] = c; - cur++; - } - score->data = buf; - score->data[cur] = '\0'; - } -#endif - return 0; + score->score = p; + p = strchr (p, ' '); + if (!p) + return p; + *p++ = 0; + score->user_data = p; + p = strchr (p, '\n'); + if (!p) + return p; + *p++ = 0; + return p; } static int -read_scores (const char *filename, struct score_entry **scores, int *count) +read_scores (const char *filename, struct score_entry **scores, + ptrdiff_t *count, ptrdiff_t *alloc) { - int readval, scorecount, cursize; - struct score_entry *ret; + char *p, *filedata; + ptrdiff_t filesize, nread; + struct stat st; FILE *f = fopen (filename, "r"); if (!f) return -1; - scorecount = 0; - cursize = 16; - ret = (struct score_entry *) malloc (sizeof (struct score_entry) * cursize); - if (!ret) + if (fstat (fileno (f), &st) != 0) return -1; - while ((readval = read_score (f, &ret[scorecount])) == 0) + if (! (0 <= st.st_size && st.st_size < min (PTRDIFF_MAX, SIZE_MAX))) { - /* We encountered an error. */ - if (readval < 0) - return -1; - scorecount++; - if (scorecount >= cursize) + errno = EOVERFLOW; + return -1; + } + filesize = st.st_size; + filedata = malloc (filesize + 1); + if (! filedata) + return -1; + nread = fread (filedata, 1, filesize + 1, f); + if (filesize < nread) + { + errno = 0; + return -1; + } + if (nread < filesize) + filesize = nread; + if (ferror (f) || fclose (f) != 0) + return -1; + filedata[filesize] = 0; + if (strlen (filedata) != filesize) + { + errno = 0; + return -1; + } + + *scores = 0; + *count = *alloc = 0; + for (p = filedata; p < filedata + filesize; ) + { + struct score_entry entry; + p = read_score (p, &entry); + if (!p) { - cursize *= 2; - ret = (struct score_entry *) - realloc (ret, (sizeof (struct score_entry) * cursize)); - if (!ret) - return -1; + errno = 0; + return -1; } + if (push_score (scores, count, alloc, &entry) < 0) + return -1; } - *count = scorecount; - *scores = ret; return 0; } @@ -342,65 +356,98 @@ score_compare (const void *a, const void *b) { const struct score_entry *sa = (const struct score_entry *) a; const struct score_entry *sb = (const struct score_entry *) b; - return (sb->score > sa->score) - (sb->score < sa->score); + char *sca = sa->score; + char *scb = sb->score; + size_t lena, lenb; + bool nega = *sca == '-'; + bool negb = *scb == '-'; + int diff = nega - negb; + if (diff) + return diff; + if (nega) + { + char *tmp = sca; + sca = scb + 1; + scb = tmp + 1; + } + lena = strlen (sca); + lenb = strlen (scb); + if (lena != lenb) + return lenb < lena ? -1 : 1; + return strcmp (scb, sca); } static int score_compare_reverse (const void *a, const void *b) { - const struct score_entry *sa = (const struct score_entry *) a; - const struct score_entry *sb = (const struct score_entry *) b; - return (sa->score > sb->score) - (sa->score < sb->score); + return score_compare (b, a); } int -push_score (struct score_entry **scores, int *count, int newscore, char *username, char *newdata) +push_score (struct score_entry **scores, ptrdiff_t *count, ptrdiff_t *size, + struct score_entry const *newscore) { - struct score_entry *newscores - = (struct score_entry *) realloc (*scores, - sizeof (struct score_entry) * ((*count) + 1)); - if (!newscores) - return -1; - newscores[*count].score = newscore; - newscores[*count].username = username; - newscores[*count].data = newdata; + struct score_entry *newscores = *scores; + if (*count == *size) + { + ptrdiff_t newsize = *size; + if (newsize <= 0) + newsize = 1; + else if (newsize <= MAX_SCORES / 2) + newsize *= 2; + else if (newsize < MAX_SCORES) + newsize = MAX_SCORES; + else + { + errno = ENOMEM; + return -1; + } + newscores = realloc (newscores, sizeof *newscores * newsize); + if (!newscores) + return -1; + *scores = newscores; + *size = newsize; + } + newscores[*count] = *newscore; (*count) += 1; - *scores = newscores; return 0; } static void -sort_scores (struct score_entry *scores, int count, int reverse) +sort_scores (struct score_entry *scores, ptrdiff_t count, bool reverse) { - qsort (scores, count, sizeof (struct score_entry), - reverse ? score_compare_reverse : score_compare); + qsort (scores, count, sizeof *scores, + reverse ? score_compare_reverse : score_compare); } static int -write_scores (const char *filename, const struct score_entry *scores, int count) +write_scores (const char *filename, const struct score_entry *scores, + ptrdiff_t count) { + int fd; FILE *f; - int i; + ptrdiff_t i; char *tempfile = malloc (strlen (filename) + strlen (".tempXXXXXX") + 1); if (!tempfile) return -1; strcpy (tempfile, filename); strcat (tempfile, ".tempXXXXXX"); -#ifdef HAVE_MKSTEMP - if (mkstemp (tempfile) < 0 -#else - if (mktemp (tempfile) != tempfile + fd = mkostemp (tempfile, 0); + if (fd < 0) + return -1; +#ifndef DOS_NT + if (fchmod (fd, 0644) != 0) + return -1; #endif - || !(f = fopen (tempfile, "w"))) + f = fdopen (fd, "w"); + if (! f) return -1; for (i = 0; i < count; i++) - if (fprintf (f, "%ld %s %s\n", scores[i].score, scores[i].username, - scores[i].data) < 0) + if (fprintf (f, "%s %s\n", scores[i].score, scores[i].user_data) < 0) return -1; - fclose (f); - if (rename (tempfile, filename) < 0) + if (fclose (f) != 0) return -1; - if (chmod (filename, 0644) < 0) + if (rename (tempfile, filename) != 0) return -1; return 0; } @@ -418,30 +465,27 @@ lock_file (const char *filename, void **state) strcpy (lockpath, filename); strcat (lockpath, lockext); *state = lockpath; - trylock: - attempts++; - /* If the lock is over an hour old, delete it. */ - if (stat (lockpath, &buf) == 0 - && (difftime (buf.st_ctime, time (NULL) > 60*60))) - unlink (lockpath); - fd = open (lockpath, O_CREAT | O_EXCL, 0600); - if (fd < 0) + + while ((fd = open (lockpath, O_CREAT | O_EXCL, 0600)) < 0) { - if (errno == EEXIST) + if (errno != EEXIST) + return -1; + attempts++; + + /* Break the lock if it is over an hour old, or if we've tried + more than MAX_ATTEMPTS times. We won't corrupt the file, but + we might lose some scores. */ + if (MAX_ATTEMPTS < attempts + || (stat (lockpath, &buf) == 0 && 60 * 60 < time (0) - buf.st_ctime)) { - /* Break the lock; we won't corrupt the file, but we might - lose some scores. */ - if (attempts > MAX_ATTEMPTS) - { - unlink (lockpath); - attempts = 0; - } - sleep ((rand () % 2)+1); - goto trylock; + if (unlink (lockpath) != 0 && errno != ENOENT) + return -1; + attempts = 0; } - else - return -1; + + sleep ((rand () & 1) + 1); } + close (fd); return 0; } @@ -450,12 +494,12 @@ static int unlock_file (const char *filename, void *state) { char *lockpath = (char *) state; - int ret = unlink (lockpath); int saved_errno = errno; + int ret = unlink (lockpath); + int unlink_errno = errno; free (lockpath); - errno = saved_errno; + errno = ret < 0 ? unlink_errno : saved_errno; return ret; } - /* update-game-score.c ends here */