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