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