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