1 /* $NetBSD: ntp_filegen.c,v 1.3 2012/02/01 07:46:22 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 Universit�t Erlangen-N�rnberg, 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 421 /* 422 * if nothing would be changed... 423 */ 424 if ((strcmp(basename, gen->basename) == 0) && type == gen->type 425 && flag == gen->flag) 426 return; 427 428 /* 429 * validate parameters 430 */ 431 if (!valid_fileref(gen->prefix, basename)) 432 return; 433 434 if (NULL != gen->fp) { 435 fclose(gen->fp); 436 gen->fp = NULL; 437 file_existed = 1; 438 } 439 440 DPRINTF(3, ("configuring filegen:\n" 441 "\tprefix:\t%s\n" 442 "\tbasename:\t%s -> %s\n" 443 "\ttype:\t%d -> %d\n" 444 "\tflag: %x -> %x\n", 445 gen->prefix, 446 gen->basename, basename, 447 gen->type, type, 448 gen->flag, flag)); 449 450 if (strcmp(gen->basename, basename) != 0) { 451 free(gen->basename); 452 gen->basename = estrdup(basename); 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 */ 476 static int 477 valid_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 520 static struct filegen_entry { 521 char * name; 522 FILEGEN * filegen; 523 struct filegen_entry * next; 524 } *filegen_registry = NULL; 525 526 527 FILEGEN * 528 filegen_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 547 void 548 filegen_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 591 void 592 filegen_unregister( 593 const 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