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