xref: /openbsd/games/atc/log.c (revision b65f9321)
1 /*	$OpenBSD: log.c,v 1.26 2024/08/20 15:48:32 deraadt Exp $	*/
2 /*	$NetBSD: log.c,v 1.3 1995/03/21 15:04:21 cgd Exp $	*/
3 
4 /*-
5  * Copyright (c) 1990, 1993
6  *	The Regents of the University of California.  All rights reserved.
7  *
8  * This code is derived from software contributed to Berkeley by
9  * Ed James.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 /*
37  * Copyright (c) 1987 by Ed James, UC Berkeley.  All rights reserved.
38  *
39  * Copy permission is hereby granted provided that this notice is
40  * retained on all partial or complete copies.
41  *
42  * For more info on this and all of my stuff, mail edjames@berkeley.edu.
43  */
44 
45 #include <sys/stat.h>
46 #include <sys/types.h>
47 
48 #include <err.h>
49 #include <errno.h>
50 #include <fcntl.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <time.h>
54 #include <unistd.h>
55 
56 #include "extern.h"
57 #include "pathnames.h"
58 
59 static FILE *score_fp;
60 
61 int
compar(const void * va,const void * vb)62 compar(const void *va, const void *vb)
63 {
64 	const SCORE *a, *b;
65 
66 	a = (const SCORE *)va;
67 	b = (const SCORE *)vb;
68 	if (b->planes == a->planes)
69 		return (b->time - a->time);
70 	else
71 		return (b->planes - a->planes);
72 }
73 
74 #define SECAMIN		60
75 #define MINAHOUR	60
76 #define HOURADAY	24
77 #define SECAHOUR	(SECAMIN * MINAHOUR)
78 #define SECADAY		(SECAHOUR * HOURADAY)
79 #define DAY(t)		((t) / SECADAY)
80 #define HOUR(t)		(((t) % SECADAY) / SECAHOUR)
81 #define MINUTES(t)		(((t) % SECAHOUR) / SECAMIN)
82 #define SEC(t)		((t) % SECAMIN)
83 
84 const char	*
timestr(int t)85 timestr(int t)
86 {
87 	static char	s[80];
88 
89 	if (DAY(t) > 0)
90 		(void)snprintf(s, sizeof s, "%dd+%02dhrs", DAY(t), HOUR(t));
91 	else if (HOUR(t) > 0)
92 		(void)snprintf(s, sizeof s, "%d:%02d:%02d",
93 		    HOUR(t), MINUTES(t), SEC(t));
94 	else if (MINUTES(t) > 0)
95 		(void)snprintf(s, sizeof s, "%d:%02d", MINUTES(t), SEC(t));
96 	else if (SEC(t) > 0)
97 		(void)snprintf(s, sizeof s, ":%02d", SEC(t));
98 	else
99 		*s = '\0';
100 
101 	return (s);
102 }
103 
104 int
open_score_file(void)105 open_score_file(void)
106 {
107 	mode_t old_mode;
108 	char *home;
109 	char scorefile[PATH_MAX];
110 	int ret;
111 	int score_fd;
112 
113 	home = getenv("HOME");
114 	if (home == NULL || *home == '\0')
115 		err(1, "getenv");
116 	ret = snprintf(scorefile, sizeof(scorefile), "%s/%s", home,
117 	    ".atc.scores");
118 	if (ret < 0 || ret >= PATH_MAX)
119 		errc(1, ENAMETOOLONG, "%s/%s", home, ".atc.scores");
120 
121 	old_mode = umask(0);
122 	score_fd = open(scorefile, O_CREAT|O_RDWR, 0644);
123 	if (score_fd == -1)
124 		err(1, "open");
125 	/*
126 	 * This is done to take advantage of stdio, while still
127 	 * allowing a O_CREAT during the open(2) of the log file.
128 	 */
129 	score_fp = fdopen(score_fd, "r+");
130 	if (score_fp == NULL)
131 		err(1, "fdopen");
132 	umask(old_mode);
133 	return (0);
134 }
135 
136 int
log_score(int list_em)137 log_score(int list_em)
138 {
139 	int		i, num_scores = 0, good, changed = 0, found = 0;
140 	const char	*name;
141 	char		*cp;
142 	char		scanstr[50];
143 	SCORE		score[NUM_SCORES], thisscore;
144 
145 	if (score_fp == NULL)
146 		return (-1);
147 	if (flock(fileno(score_fp), LOCK_EX) == -1)
148 		err(1, "flock");
149 	snprintf(scanstr, 50, "%%%zus %%%zus %%d %%d %%d", sizeof(score[0].name)-1,
150 	    sizeof(score[0].game)-1);
151 	for (;;) {
152 		good = fscanf(score_fp, scanstr,
153 			score[num_scores].name,
154 			score[num_scores].game,
155 			&score[num_scores].planes,
156 			&score[num_scores].time,
157 			&score[num_scores].real_time);
158 		if (good != 5 || ++num_scores >= NUM_SCORES)
159 			break;
160 	}
161 	if (!test_mode && !list_em) {
162 		name = getenv("LOGNAME");
163 		if (name == NULL || *name == '\0')
164 			name = getenv("USER");
165 		if (name == NULL || *name == '\0')
166 			name = getlogin();
167 		if (name == NULL || *name == '\0')
168 			name = "  ???";
169 		strlcpy(thisscore.name, name, sizeof(thisscore.name));
170 
171 		cp = strrchr(file, '/');
172 		if (cp == NULL) {
173 			warnx("log: where's the '/' in %s?", file);
174 			return (-1);
175 		}
176 		cp++;
177 		strlcpy(thisscore.game, cp, sizeof(thisscore.game));
178 
179 		thisscore.time = clck;
180 		thisscore.planes = safe_planes;
181 		thisscore.real_time = time(0) - start_time;
182 
183 		for (i = 0; i < num_scores; i++) {
184 			if (strcmp(thisscore.name, score[i].name) == 0 &&
185 			    strcmp(thisscore.game, score[i].game) == 0) {
186 				if (thisscore.time > score[i].time) {
187 					score[i].time = thisscore.time;
188 					score[i].planes = thisscore.planes;
189 					score[i].real_time =
190 						thisscore.real_time;
191 					changed++;
192 				}
193 				found++;
194 				break;
195 			}
196 		}
197 		if (!found) {
198 			for (i = 0; i < num_scores; i++) {
199 				if (thisscore.time > score[i].time) {
200 					if (num_scores < NUM_SCORES)
201 						num_scores++;
202 					memcpy(&score[num_scores - 1],
203 						&score[i],
204 						sizeof (score[i]));
205 					memcpy(&score[i], &thisscore,
206 						sizeof (score[i]));
207 					changed++;
208 					break;
209 				}
210 			}
211 		}
212 		if (!found && !changed && num_scores < NUM_SCORES) {
213 			memcpy(&score[num_scores], &thisscore,
214 				sizeof (score[num_scores]));
215 			num_scores++;
216 			changed++;
217 		}
218 
219 		if (seeded) {
220 			puts("The high score list does not include '-r' seeded games.");
221 		} else if (changed) {
222 			if (found)
223 				puts("You beat your previous score!");
224 			else
225 				puts("You made the top players list!");
226 			qsort(score, num_scores, sizeof (*score), compar);
227 			if (fseek(score_fp, 0L, SEEK_SET) == -1)
228 				err(1, "fseek");
229 			for (i = 0; i < num_scores; i++)
230 				fprintf(score_fp, "%s %s %d %d %d\n",
231 					score[i].name,
232 					score[i].game, score[i].planes,
233 					score[i].time, score[i].real_time);
234 		} else {
235 			if (found)
236 				puts("You didn't beat your previous score.");
237 			else
238 				puts("You didn't make the top players list.");
239 		}
240 		putchar('\n');
241 	}
242 	flock(fileno(score_fp), LOCK_UN);
243 	fflush(score_fp);
244 	fsync(fileno(score_fp));
245 	if (fseek(score_fp, 0L, SEEK_SET) == -1)
246 		err(1, "fseek");
247 	printf("%2s:  %-31s  %-18s  %4s  %9s  %4s\n", "#", "name",
248 		"game", "time", "real time", "safe");
249 	puts("-------------------------------------------------------------------------------");
250 	for (i = 0; i < num_scores; i++) {
251 		printf("%2d:  %-31s  %-18s  %4d  %9s  %4d\n", i + 1,
252 			score[i].name, score[i].game,
253 			score[i].time, timestr(score[i].real_time),
254 			score[i].planes);
255 	}
256 	putchar('\n');
257 	return (0);
258 }
259 
260 void
log_score_quit(int dummy)261 log_score_quit(int dummy)
262 {
263 	(void)log_score(0);	/* XXX signal race */
264 	exit(0);		/* XXX signal race */
265 }
266