1 /* $Id: score.c,v 1.5 2002/03/02 21:02:21 sverrehu Exp $ */
2 /**************************************************************************
3  *
4  *  FILE            score.c
5  *  MODULE OF       Card game.
6  *
7  *  WRITTEN BY      Sverre H. Huseby <shh@thathost.com>
8  *
9  **************************************************************************/
10 
11 #include <stdlib.h>
12 #include <string.h>
13 #include <time.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17 #include <fcntl.h>
18 #include <pwd.h>
19 
20 #include <shhmsg.h>
21 #include <xalloc.h>
22 
23 #include "game.h"
24 #include "board.h"
25 #include "suid.h"
26 #include "score.h"
27 
28 /**************************************************************************
29  *                                                                        *
30  *                       P R I V A T E    D A T A                         *
31  *                                                                        *
32  **************************************************************************/
33 
34 typedef struct {
35     char *userName;
36     char *realName;
37     long score;
38     char *dateTime;
39 } Highscore;
40 
41 static Highscore *highscore[MAX_HIGHSCORES];
42 static int numHighscores;
43 static time_t lastUpdate = 0;
44 static void (*highscoresChangedCallback)(void) = NULL;
45 
46 
47 /**************************************************************************
48  *                                                                        *
49  *                   P R I V A T E    F U N C T I O N S                   *
50  *                                                                        *
51  **************************************************************************/
52 
53 static Highscore *
scoreNew(void)54 scoreNew(void)
55 {
56     Highscore *hs;
57 
58     if ((hs = malloc(sizeof(Highscore))) == NULL)
59 	msgFatal("out of memory\n");
60     hs->userName = NULL;
61     hs->realName = NULL;
62     hs->score = 0L;
63     hs->dateTime = NULL;
64     return hs;
65 }
66 
67 static Highscore *
scoreNewInit(char * uname,char * rname,long score,char * dt)68 scoreNewInit(char *uname, char *rname, long score, char *dt)
69 {
70     Highscore *hs;
71 
72     if ((hs = malloc(sizeof(Highscore))) == NULL)
73 	msgFatal("out of memory\n");
74     hs->userName = xstrdup(uname);
75     hs->realName = xstrdup(rname);
76     hs->score = score;
77     hs->dateTime = xstrdup(dt);
78     return hs;
79 }
80 
81 static Highscore *
scoreNewCurrent(void)82 scoreNewCurrent(void)
83 {
84     Highscore *hs;
85     struct passwd *pw;
86     char dateTime[20], *rname, *s;
87     struct tm *tm;
88     time_t tt;
89 
90     if ((pw = getpwuid(getuid())) == NULL) {
91 	msgError("unable to get password info\n");
92 	return NULL;
93     }
94     rname = xstrdup(pw->pw_gecos);
95     if ((s = strchr(rname, ',')) != NULL)
96 	*s = '\0';
97     time(&tt);
98     tm = localtime(&tt);
99     strftime(dateTime, sizeof(dateTime), "%Y-%m-%d %H:%M:%S", tm);
100     hs = scoreNewInit(pw->pw_name, rname, gameTime, dateTime);
101     free(rname);
102     return hs;
103 }
104 
105 void
scoreFree(Highscore * hs)106 scoreFree(Highscore *hs)
107 {
108     if (!hs)
109 	return;
110     free(hs->userName);
111     free(hs->realName);
112     free(hs->dateTime);
113     free(hs);
114 }
115 
116 static time_t
scoreGetUpdateTime(void)117 scoreGetUpdateTime(void)
118 {
119     struct stat st;
120 
121     if (stat(SCOREFILE, &st) < 0)
122 	return 0;
123     return st.st_mtime;
124 }
125 
126 static void
scoreDelete(int n)127 scoreDelete(int n)
128 {
129     int q;
130 
131     if (n >= MAX_HIGHSCORES)
132 	return;
133     scoreFree(highscore[n]);
134     for (q = n; q < numHighscores - 1; q++)
135 	highscore[q] = highscore[q + 1];
136     highscore[numHighscores - 1] = scoreNew();
137     if (n < numHighscores)
138 	--numHighscores;
139 }
140 
141 static void
scoreInsert(int n,Highscore * hs)142 scoreInsert(int n, Highscore *hs)
143 {
144     int q;
145 
146     if (numHighscores == MAX_HIGHSCORES) {
147 	scoreFree(highscore[numHighscores - 1]);
148 	--numHighscores;
149     }
150     for (q = numHighscores; q > n; q--)
151 	highscore[q] = highscore[q - 1];
152     highscore[n] = hs;
153     ++numHighscores;
154 }
155 
156 static int
scoreFindUser(const char * user)157 scoreFindUser(const char *user)
158 {
159     int q;
160 
161     for (q = 0; q < numHighscores; q++)
162 	if (strcmp(highscore[q]->userName, user) == 0)
163 	    return q;
164     return -1;
165 }
166 
167 static void
scoreLockHelp(FILE * f,short type)168 scoreLockHelp(FILE *f, short type)
169 {
170     struct flock fl;
171 
172     fl.l_type = type;
173     fl.l_start = 0;
174     fl.l_whence = SEEK_SET;
175     fl.l_len = 0; /* lock to EOF */
176     if (fcntl(fileno(f), F_SETLKW, &fl) < 0)
177 	msgFatal("error locking highscore file\n");
178 }
179 
180 static void
scoreReadLockFile(FILE * f)181 scoreReadLockFile(FILE *f)
182 {
183     scoreLockHelp(f, F_RDLCK);
184 }
185 
186 static void
scoreWriteLockFile(FILE * f)187 scoreWriteLockFile(FILE *f)
188 {
189     scoreLockHelp(f, F_WRLCK);
190 }
191 
192 static void
scoreUnlockFile(FILE * f)193 scoreUnlockFile(FILE *f)
194 {
195     fflush(f);
196     scoreLockHelp(f, F_UNLCK);
197 }
198 
199 static Highscore *
scoreReadScoreLine(FILE * f)200 scoreReadScoreLine(FILE *f)
201 {
202     char line[100], *uname, *rname, *score, *dateTime;
203     Highscore *hs = NULL;
204 
205     if (fgets(line, sizeof(line), f)) {
206         if (line[strlen(line) - 1] == '\n')
207 	    line[strlen(line) - 1] = '\0';
208 	if ((uname = strtok(line, ",")) == NULL)
209 	    msgFatal("error in highscore file\n");
210 	if ((rname = strtok(NULL, ",")) == NULL)
211 	    msgFatal("error in highscore file\n");
212 	if ((score = strtok(NULL, ",")) == NULL)
213 	    msgFatal("error in highscore file\n");
214 	if ((dateTime = strtok(NULL, ",")) == NULL)
215 	    msgFatal("error in highscore file\n");
216 	hs = scoreNewInit(uname, rname,	atol(score), dateTime);
217     }
218     return hs;
219 }
220 
221 static void
scoreReadScoreLines(FILE * f)222 scoreReadScoreLines(FILE *f)
223 {
224     int  n;
225     char line[81];
226     Highscore *hs;
227 
228     numHighscores = 0;
229     if (fgets(line, sizeof(line), f) == NULL)
230 	return;
231     n = atoi(line);
232     while (numHighscores < n && (hs = scoreReadScoreLine(f)) != NULL) {
233 	scoreFree(highscore[numHighscores]);
234 	highscore[numHighscores] = hs;
235 	if (++numHighscores == MAX_HIGHSCORES)
236 	    break;
237     }
238 }
239 
240 static void
scoreWriteScoreLines(FILE * f)241 scoreWriteScoreLines(FILE *f)
242 {
243     Highscore *hs;
244     int q;
245 
246     fprintf(f, "%d\n", numHighscores);
247     for (q = 0; q < numHighscores; q++) {
248 	hs = highscore[q];
249 	/* strtok (used when reading score lines) doesn't like empty
250 	 * tokens, so we make sure no empty tokens are written out. */
251 	fprintf(f, "%s,%s,%ld,%s\n",
252 		strlen(hs->userName) ? hs->userName : "nobody",
253 		strlen(hs->realName) ? hs->realName : "An Anonymous Gamer",
254 		hs->score, hs->dateTime);
255     }
256 }
257 
258 static void
scoreReadScoreFile(void)259 scoreReadScoreFile(void)
260 {
261     FILE *f;
262 
263     suidStartPrivilegedAction();
264     numHighscores = 0;
265     if ((f = fopen(SCOREFILE, "r")) == NULL)
266 	goto finish;
267     scoreReadLockFile(f);
268     scoreReadScoreLines(f);
269     scoreUnlockFile(f);
270     fclose(f);
271   finish:
272     suidEndPrivilegedAction();
273 }
274 
275 static int
scorePossiblyAddEntry(Highscore * hs)276 scorePossiblyAddEntry(Highscore *hs)
277 {
278     int ret = 0, q, n;
279 
280     if ((n = scoreFindUser(hs->userName)) >= 0) {
281 	if (highscore[n]->score <= hs->score)
282 	    goto finish;
283 	scoreDelete(n);
284     }
285     for (q = 0; q < numHighscores; q++)
286 	if (highscore[q]->score > hs->score)
287 	    break;
288     if (q < MAX_HIGHSCORES) {
289 	scoreInsert(q, hs);
290 	ret = 1;
291     }
292   finish:
293     return ret;
294 }
295 
296 static char *
scoreToTime(long score)297 scoreToTime(long score)
298 {
299     static char ret[40];
300     int    h, m, s;
301 
302     h = score / 3600;
303     m = (score % 3600) / 60;
304     s = score % 60;
305     if (h)
306 	sprintf(ret, "%2d:%02d:%02d", h, m, s);
307     else
308 	sprintf(ret, "   %2d:%02d", m, s);
309     return ret;
310 }
311 
312 
313 
314 /**************************************************************************
315  *                                                                        *
316  *                    P U B L I C    F U N C T I O N S                    *
317  *                                                                        *
318  **************************************************************************/
319 
320 void
scoreInit(void)321 scoreInit(void)
322 {
323     int q;
324 
325     for (q = 0; q < MAX_HIGHSCORES; q++)
326 	highscore[q] = scoreNew();
327     numHighscores = 0;
328     lastUpdate = scoreGetUpdateTime();
329     scoreReadScoreFile();
330 }
331 
332 void
scoreFinish(void)333 scoreFinish(void)
334 {
335     int q;
336 
337     for (q = 0; q < MAX_HIGHSCORES; q++)
338 	scoreFree(highscore[q]);
339 }
340 
341 void
scoreInitGame(void)342 scoreInitGame(void)
343 {
344 }
345 
346 void
scoreFinishGame(void)347 scoreFinishGame(void)
348 {
349 }
350 
351 void
scoreStatHighscoreFile(void)352 scoreStatHighscoreFile(void)
353 {
354     time_t ut;
355 
356     if (!lastUpdate)
357 	return;
358     if ((ut = scoreGetUpdateTime()) > lastUpdate) {
359 	lastUpdate = ut;
360 	scoreReadScoreFile();
361 	if (highscoresChangedCallback)
362 	    highscoresChangedCallback();
363     }
364 }
365 
366 int
scoreGetThisPlayerIndex(void)367 scoreGetThisPlayerIndex(void)
368 {
369     struct passwd *pw;
370 
371     if ((pw = getpwuid(getuid())) == NULL) {
372 	msgError("unable to get password info\n");
373 	return -1;
374     }
375     return scoreFindUser(pw->pw_name);
376 }
377 
378 char *
scoreGetHeadStr(void)379 scoreGetHeadStr(void)
380 {
381     static char line[81];
382 
383     sprintf(line, "  #  %-10.10s %-25.25s %9.9s  %s",
384 	    "user", "name", " how long", "date       time");
385     return line;
386 }
387 
388 char *
scoreGetHeadSepStr(void)389 scoreGetHeadSepStr(void)
390 {
391     static char line[81];
392     int q;
393 
394     for (q = 0; q < 72; q++)
395 	line[q] = '-';
396     line[q] = '\0';
397     return line;
398 }
399 
400 char *
scoreGetEntryStr(int n)401 scoreGetEntryStr(int n)
402 {
403     static char line[81];
404     Highscore *hs;
405 
406     if (n < 0 || n >= MAX_HIGHSCORES)
407 	return NULL;
408     hs = highscore[n];
409     if (n >= numHighscores)
410 	sprintf(line, "%3d.", n + 1);
411     else
412 	sprintf(line, "%3d. %-10.10s %-25.25s %9.9s  %s", n + 1,
413 		hs->userName, hs->realName,
414 		scoreToTime(hs->score), hs->dateTime);
415     return line;
416 }
417 
418 void
scoreDumpHighscores(void)419 scoreDumpHighscores(void)
420 {
421     int q;
422 
423     if (!numHighscores)
424 	scoreReadScoreFile();
425     printf("%s\n", scoreGetHeadStr());
426     printf("%s\n", scoreGetHeadSepStr());
427     for (q = 0; q < numHighscores; q++)
428 	printf("%s\n", scoreGetEntryStr(q));
429     printf("\n");
430 }
431 
432 void
scorePossiblyUpdateScores(void)433 scorePossiblyUpdateScores(void)
434 {
435     FILE *f;
436     Highscore *hs;
437 
438     suidStartPrivilegedAction();
439     if ((hs = scoreNewCurrent()) == NULL)
440 	goto finish;
441     numHighscores = 0;
442     if ((f = fopen(SCOREFILE, "r+")) == NULL) {
443 	msgPerror("unable to write `%s'", SCOREFILE);
444 	goto finish;
445     }
446     scoreWriteLockFile(f);
447     scoreReadScoreLines(f);
448     if (scorePossiblyAddEntry(hs)) {
449 	rewind(f);
450 	scoreWriteScoreLines(f);
451 	scoreStatHighscoreFile();
452     } else
453 	scoreFree(hs);
454     scoreUnlockFile(f);
455     fclose(f);
456   finish:
457     suidEndPrivilegedAction();
458 }
459 
460 void
scoreMergeScoreFile(char * file)461 scoreMergeScoreFile(char *file)
462 {
463     int  q, n, changed = 0;
464     FILE *orig, *merge;
465     char line[81];
466     Highscore *hs;
467 
468     suidStartPrivilegedAction();
469     if (getuid() != 0 && getuid() != geteuid())
470 	msgFatal("scorefile merging only allowed for game owner or root\n");
471 
472     if ((merge = fopen(file, "r")) == NULL)
473 	msgFatalPerror("unable to open `%s'", file);
474     scoreReadLockFile(merge);
475 
476     /* read current highscores */
477     numHighscores = 0;
478     if ((orig = fopen(SCOREFILE, "r+")) == NULL) {
479 	msgFatalPerror("unable to write `%s'", SCOREFILE);
480 	goto finish;
481     }
482     scoreWriteLockFile(orig);
483     scoreReadScoreLines(orig);
484 
485     /* merge new entries */
486     if (fgets(line, sizeof(line), merge) == NULL)
487 	goto unlock;
488     n = atoi(line);
489     for (q = 0; q < n; q++)
490         if ((hs = scoreReadScoreLine(merge)) != NULL)
491 	    if (scorePossiblyAddEntry(hs))
492 		changed = 1;
493 	    else
494 		scoreFree(hs);
495 
496     /* possibly save new highscore table */
497     if (changed) {
498 	rewind(orig);
499 	scoreWriteScoreLines(orig);
500     }
501 
502   unlock:
503     scoreUnlockFile(orig);
504     fclose(orig);
505     scoreUnlockFile(merge);
506     fclose(merge);
507   finish:
508     suidEndPrivilegedAction();
509 }
510 
scoreSetHigscoresChangedCallback(void (* f)(void))511 void scoreSetHigscoresChangedCallback(void (*f)(void))
512 {
513     highscoresChangedCallback = f;
514 }
515