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