1 /* $NetBSD: ntp_filegen.c,v 1.2 2010/12/04 23:08:35 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 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 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 const 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