1/*
2 * ntp_filegen.c,v 3.12 1994/01/25 19:06:11 kardel Exp
3 *
4 *  implements file generations support for NTP
5 *  logfiles and statistic files
6 *
7 *
8 * Copyright (C) 1992, 1996 by Rainer Pruy
9 * Friedrich-Alexander Universit�t Erlangen-N�rnberg, Germany
10 *
11 * This code may be modified and used freely
12 * provided credits remain intact.
13 */
14
15#ifdef HAVE_CONFIG_H
16# include <config.h>
17#endif
18
19#include <stdio.h>
20#include <sys/types.h>
21#include <sys/stat.h>
22
23#include "ntpd.h"
24#include "ntp_io.h"
25#include "ntp_string.h"
26#include "ntp_calendar.h"
27#include "ntp_filegen.h"
28#include "ntp_stdlib.h"
29
30/*
31 * NTP is intended to run long periods of time without restart.
32 * Thus log and statistic files generated by NTP will grow large.
33 *
34 * this set of routines provides a central interface
35 * to generating files using file generations
36 *
37 * the generation of a file is changed according to file generation type
38 */
39
40
41/*
42 * redefine this if your system dislikes filename suffixes like
43 * X.19910101 or X.1992W50 or ....
44 */
45#define SUFFIX_SEP '.'
46
47/*
48 * other constants
49 */
50#define FGEN_AGE_SECS   (24*60*60) /* life time of FILEGEN_AGE in seconds */
51
52static	void	filegen_open	P((FILEGEN *, u_long));
53static	int	valid_fileref	P((char *, char *));
54#ifdef	UNUSED
55static	FILEGEN *filegen_unregister P((char *));
56#endif	/* UNUSED */
57
58static void	filegen_init	P((char *, const char *, FILEGEN *));
59
60/*
61 * filegen_init
62 */
63
64static void
65filegen_init(char *prefix, const char *basename, FILEGEN *fp)
66{
67	fp->fp       = NULL;
68	fp->prefix   = prefix;		/* Yes, this is TOTALLY lame! */
69	fp->basename = (char*)emalloc(strlen(basename) + 1);
70	strcpy(fp->basename, basename);
71	fp->id       = 0;
72	fp->type     = FILEGEN_DAY;
73	fp->flag     = FGEN_FLAG_LINK; /* not yet enabled !!*/
74}
75
76
77/*
78 * open a file generation according to the current settings of gen
79 * will also provide a link to basename if requested to do so
80 */
81
82static void
83filegen_open(
84	FILEGEN *gen,
85	u_long  newid
86	)
87{
88	char *filename;
89	char *basename;
90	u_int len;
91	FILE *fp;
92	struct calendar cal;
93
94	len = strlen(gen->prefix) + strlen(gen->basename) + 1;
95	basename = (char*)emalloc(len);
96	sprintf(basename, "%s%s", gen->prefix, gen->basename);
97
98	switch(gen->type) {
99	    default:
100		msyslog(LOG_ERR, "unsupported file generations type %d for \"%s\" - reverting to FILEGEN_NONE",
101			gen->type, basename);
102		gen->type = FILEGEN_NONE;
103
104		/*FALLTHROUGH*/
105	    case FILEGEN_NONE:
106		filename = (char*)emalloc(len);
107		sprintf(filename,"%s", basename);
108		break;
109
110	    case FILEGEN_PID:
111		filename = (char*)emalloc(len + 1 + 1 + 10);
112		sprintf(filename,"%s%c#%ld", basename, SUFFIX_SEP, newid);
113		break;
114
115	    case FILEGEN_DAY:
116		/* You can argue here in favor of using MJD, but
117		 * I would assume it to be easier for humans to interpret dates
118		 * in a format they are used to in everyday life.
119		 */
120		caljulian(newid,&cal);
121		filename = (char*)emalloc(len + 1 + 4 + 2 + 2);
122		sprintf(filename, "%s%c%04d%02d%02d",
123			basename, SUFFIX_SEP, cal.year, cal.month, cal.monthday);
124		break;
125
126	    case FILEGEN_WEEK:
127		/*
128		 * This is still a hack
129		 * - the term week is not correlated to week as it is used
130		 *   normally - it just refers to a period of 7 days
131		 *   starting at Jan 1 - 'weeks' are counted starting from zero
132		 */
133		caljulian(newid,&cal);
134		filename = (char*)emalloc(len + 1 + 4 + 1 + 2);
135		sprintf(filename, "%s%c%04dw%02d",
136			basename, SUFFIX_SEP, cal.year, cal.yearday / 7);
137		break;
138
139	    case FILEGEN_MONTH:
140		caljulian(newid,&cal);
141		filename = (char*)emalloc(len + 1 + 4 + 2);
142		sprintf(filename, "%s%c%04d%02d",
143			basename, SUFFIX_SEP, cal.year, cal.month);
144		break;
145
146	    case FILEGEN_YEAR:
147		caljulian(newid,&cal);
148		filename = (char*)emalloc(len + 1 + 4);
149		sprintf(filename, "%s%c%04d", basename, SUFFIX_SEP, cal.year);
150		break;
151
152	    case FILEGEN_AGE:
153		filename = (char*)emalloc(len + 1 + 2 + 10);
154		sprintf(filename, "%s%ca%08ld", basename, SUFFIX_SEP, newid);
155		break;
156	}
157
158	if (gen->type != FILEGEN_NONE) {
159		/*
160		 * check for existence of a file with name 'basename'
161		 * as we disallow such a file
162		 * if FGEN_FLAG_LINK is set create a link
163		 */
164		struct stat stats;
165		/*
166		 * try to resolve name collisions
167		 */
168		static u_long conflicts = 0;
169
170#ifndef	S_ISREG
171#define	S_ISREG(mode)	(((mode) & S_IFREG) == S_IFREG)
172#endif
173		if (stat(basename, &stats) == 0) {
174			/* Hm, file exists... */
175			if (S_ISREG(stats.st_mode)) {
176				if (stats.st_nlink <= 1)	{
177					/*
178					 * Oh, it is not linked - try to save it
179					 */
180					char *savename = (char*)emalloc(len + 1 + 1 + 10 + 10);
181					sprintf(savename, "%s%c%dC%lu",
182						basename,
183						SUFFIX_SEP,
184						(int) getpid(),
185						(u_long)conflicts++);
186					if (rename(basename, savename) != 0)
187					    msyslog(LOG_ERR," couldn't save %s: %m", basename);
188					free(savename);
189				} else {
190					/*
191					 * there is at least a second link to
192					 * this file.
193					 * just remove the conflicting one
194					 */
195					if (
196#if !defined(VMS)
197						unlink(basename) != 0
198#else
199						delete(basename) != 0
200#endif
201						)
202					    msyslog(LOG_ERR, "couldn't unlink %s: %m", basename);
203				}
204			} else {
205				/*
206				 * Ehh? Not a regular file ?? strange !!!!
207				 */
208				msyslog(LOG_ERR, "expected regular file for %s (found mode 0%lo)",
209					basename, (unsigned long)stats.st_mode);
210			}
211		} else {
212			/*
213			 * stat(..) failed, but it is absolutely correct for
214			 * 'basename' not to exist
215			 */
216			if (errno != ENOENT)
217			    msyslog(LOG_ERR,"stat(%s) failed: %m", basename);
218		}
219	}
220
221	/*
222	 * now, try to open new file generation...
223	 */
224	fp = fopen(filename, "a");
225
226#ifdef DEBUG
227	if (debug > 3)
228	    printf("opening filegen (type=%d/id=%lu) \"%s\"\n",
229		   gen->type, (u_long)newid, filename);
230#endif
231
232	if (fp == NULL)	{
233		/* open failed -- keep previous state
234		 *
235		 * If the file was open before keep the previous generation.
236		 * This will cause output to end up in the 'wrong' file,
237		 * but I think this is still better than losing output
238		 *
239		 * ignore errors due to missing directories
240		 */
241
242		if (errno != ENOENT)
243		    msyslog(LOG_ERR, "can't open %s: %m", filename);
244	} else {
245		if (gen->fp != NULL) {
246			fclose(gen->fp);
247		}
248		gen->fp = fp;
249		gen->id = newid;
250
251		if (gen->flag & FGEN_FLAG_LINK) {
252			/*
253			 * need to link file to basename
254			 * have to use hardlink for now as I want to allow
255			 * gen->basename spanning directory levels
256			 * this would make it more complex to get the correct
257			 * filename for symlink
258			 *
259			 * Ok, it would just mean taking the part following
260			 * the last '/' in the name.... Should add it later....
261			 */
262
263			/* Windows NT does not support file links -Greg Schueman 1/18/97 */
264
265#if defined SYS_WINNT || defined SYS_VXWORKS
266			SetLastError(0); /* On WinNT, don't support FGEN_FLAG_LINK */
267#elif defined(VMS)
268			errno = 0; /* On VMS, don't support FGEN_FLAG_LINK */
269#else  /* not (VMS) / VXWORKS / WINNT ; DO THE LINK) */
270			if (link(filename, basename) != 0)
271			    if (errno != EEXIST)
272				msyslog(LOG_ERR, "can't link(%s, %s): %m", filename, basename);
273#endif /* SYS_WINNT || VXWORKS */
274		}		/* flags & FGEN_FLAG_LINK */
275	}			/* else fp == NULL */
276
277	free(basename);
278	free(filename);
279	return;
280}
281
282/*
283 * this function sets up gen->fp to point to the correct
284 * generation of the file for the time specified by 'now'
285 *
286 * 'now' usually is interpreted as second part of a l_fp as is in the cal...
287 * library routines
288 */
289
290void
291filegen_setup(
292	FILEGEN *gen,
293	u_long   now
294	)
295{
296	u_long new_gen = ~ (u_long) 0;
297	struct calendar cal;
298
299	if (!(gen->flag & FGEN_FLAG_ENABLED)) {
300		if (gen->fp != NULL)
301		    fclose(gen->fp);
302		return;
303	}
304
305	switch (gen->type) {
306	    case FILEGEN_NONE:
307		if (gen->fp != NULL) return; /* file already open */
308		break;
309
310	    case FILEGEN_PID:
311		new_gen = getpid();
312		break;
313
314	    case FILEGEN_DAY:
315		caljulian(now, &cal);
316		cal.hour = cal.minute = cal.second = 0;
317		new_gen = caltontp(&cal);
318		break;
319
320	    case FILEGEN_WEEK:
321		/* Would be nice to have a calweekstart() routine */
322		/* so just use a hack ... */
323		/* just round time to integral 7 day period for actual year  */
324		new_gen = now - (now - calyearstart(now)) % TIMES7(SECSPERDAY)
325			+ 60;
326		/*
327		 * just to be sure -
328		 * the computation above would fail in the presence of leap seconds
329		 * so at least carry the date to the next day (+60 (seconds))
330		 * and go back to the start of the day via calendar computations
331		 */
332		caljulian(new_gen, &cal);
333		cal.hour = cal.minute = cal.second = 0;
334		new_gen = caltontp(&cal);
335		break;
336
337	    case FILEGEN_MONTH:
338		caljulian(now, &cal);
339		cal.yearday = (u_short) (cal.yearday - cal.monthday + 1);
340		cal.monthday = 1;
341		cal.hour = cal.minute = cal.second = 0;
342		new_gen = caltontp(&cal);
343		break;
344
345	    case FILEGEN_YEAR:
346		new_gen = calyearstart(now);
347		break;
348
349	    case FILEGEN_AGE:
350		new_gen = current_time  - (current_time % FGEN_AGE_SECS);
351		break;
352	}
353	/*
354	 * try to open file if not yet open
355	 * reopen new file generation file on change of generation id
356	 */
357	if (gen->fp == NULL || gen->id != new_gen) {
358#if DEBUG
359	if (debug)
360		printf("filegen  %0x %lu %lu %lu\n", gen->type, now,
361		    gen->id, new_gen);
362#endif
363		filegen_open(gen, new_gen);
364	}
365}
366
367
368/*
369 * change settings for filegen files
370 */
371void
372filegen_config(
373	FILEGEN *gen,
374	char    *basename,
375	u_int   type,
376	u_int   flag
377	)
378{
379	/*
380	 * if nothing would be changed...
381	 */
382	if ((basename == gen->basename || strcmp(basename,gen->basename) == 0) &&
383	    type == gen->type &&
384	    flag == gen->flag)
385	    return;
386
387	/*
388	 * validate parameters
389	 */
390	if (!valid_fileref(gen->prefix,basename))
391	    return;
392
393	if (gen->fp != NULL)
394	    fclose(gen->fp);
395
396#ifdef DEBUG
397	if (debug > 2)
398	    printf("configuring filegen:\n\tprefix:\t%s\n\tbasename:\t%s -> %s\n\ttype:\t%d -> %d\n\tflag: %x -> %x\n",
399		   gen->prefix, gen->basename, basename, gen->type, type, gen->flag, flag);
400#endif
401	if (gen->basename != basename || strcmp(gen->basename, basename) != 0) {
402		free(gen->basename);
403		gen->basename = (char*)emalloc(strlen(basename) + 1);
404		strcpy(gen->basename, basename);
405	}
406	gen->type = (u_char) type;
407	gen->flag = (u_char) flag;
408
409	/*
410	 * make filegen use the new settings
411	 * special action is only required when a generation file
412	 * is currently open
413	 * otherwise the new settings will be used anyway at the next open
414	 */
415	if (gen->fp != NULL) {
416		l_fp now;
417
418		get_systime(&now);
419		filegen_setup(gen, now.l_ui);
420	}
421}
422
423
424/*
425 * check whether concatenating prefix and basename
426 * yields a legal filename
427 */
428static int
429valid_fileref(
430	char *prefix,
431	char *basename
432	)
433{
434	/*
435	 * prefix cannot be changed dynamically
436	 * (within the context of filegen)
437	 * so just reject basenames containing '..'
438	 *
439	 * ASSUMPTION:
440	 * 		file system parts 'below' prefix may be
441	 *		specified without infringement of security
442	 *
443	 *              restricing prefix to legal values
444	 *		has to be ensured by other means
445	 * (however, it would be possible to perform some checks here...)
446	 */
447	register char *p = basename;
448
449	/*
450	 * Just to catch, dumb errors opening up the world...
451	 */
452	if (prefix == NULL || *prefix == '\0')
453	    return 0;
454
455	if (basename == NULL)
456	    return 0;
457
458	for (p = basename; p; p = strchr(p, '/')) {
459		if (*p == '.' && *(p+1) == '.' && (*(p+2) == '\0' || *(p+2) == '/'))
460		    return 0;
461	}
462
463	return 1;
464}
465
466
467/*
468 * filegen registry
469 */
470
471static struct filegen_entry {
472	char *name;
473	FILEGEN *filegen;
474	struct filegen_entry *next;
475} *filegen_registry = NULL;
476
477
478FILEGEN *
479filegen_get(
480	char *name
481	)
482{
483	struct filegen_entry *f = filegen_registry;
484
485	while(f) {
486		if (f->name == name || strcmp(name, f->name) == 0) {
487#ifdef XXX	/* this gives the Alpha compiler fits */
488			if (debug > 3)
489			    printf("filegen_get(\"%s\") = %x\n", name,
490				   (u_int)f->filegen);
491#endif
492			return f->filegen;
493		}
494		f = f->next;
495	}
496#ifdef DEBUG
497	if (debug > 3)
498	    printf("filegen_get(\"%s\") = NULL\n", name);
499#endif
500	return NULL;
501}
502
503void
504filegen_register(
505	char *prefix,
506	const char *name,
507	FILEGEN *filegen
508	)
509{
510	struct filegen_entry **f = &filegen_registry;
511
512#ifdef XXX		/* this gives the Alpha compiler fits */
513	if (debug > 3)
514	    printf("filegen_register(\"%s\",%x)\n", name, (u_int)filegen);
515#endif
516
517	filegen_init(prefix, name, filegen);
518
519	while (*f) {
520		if ((*f)->name == name || strcmp(name, (*f)->name) == 0) {
521#ifdef XXX	 /* this gives the Alpha compiler fits */
522			if (debug > 4) {
523				printf("replacing filegen %x\n", (u_int)(*f)->filegen);
524			}
525#endif
526			(*f)->filegen = filegen;
527			return;
528		}
529		f = &((*f)->next);
530	}
531
532	*f = (struct filegen_entry *) emalloc(sizeof(struct filegen_entry));
533	if (*f) {
534		(*f)->next = NULL;
535		(*f)->name = (char*)emalloc(strlen(name) + 1);
536		strcpy((*f)->name, name);
537		(*f)->filegen = filegen;
538#ifdef DEBUG
539		if (debug > 5) {
540			printf("adding new filegen\n");
541		}
542#endif
543	}
544
545	return;
546}
547
548#ifdef	UNUSED
549static FILEGEN *
550filegen_unregister(
551	char *name
552	)
553{
554	struct filegen_entry **f = &filegen_registry;
555
556#ifdef DEBUG
557	if (debug > 3)
558	    printf("filegen_unregister(\"%s\")\n", name);
559#endif
560
561	while (*f) {
562		if (strcmp((*f)->name,name) == 0) {
563			struct filegen_entry *ff = *f;
564			FILEGEN *fg;
565
566			*f = (*f)->next;
567			fg = ff->filegen;
568			free(ff->name);
569			free(ff);
570			return fg;
571		}
572		f = &((*f)->next);
573	}
574	return NULL;
575}
576#endif	/* UNUSED */
577