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 47 /* 48 * other constants 49 */ 50 #define FGEN_AGE_SECS (24*60*60) /* life time of FILEGEN_AGE in seconds */ 51 52 static void filegen_open P((FILEGEN *, u_long)); 53 static int valid_fileref P((char *, char *)); 54 #ifdef UNUSED 55 static FILEGEN *filegen_unregister P((char *)); 56 #endif /* UNUSED */ 57 58 /* 59 * open a file generation according to the current settings of gen 60 * will also provide a link to basename if requested to do so 61 */ 62 63 static void 64 filegen_open( 65 FILEGEN *gen, 66 u_long newid 67 ) 68 { 69 char *filename; 70 char *basename; 71 u_int len; 72 FILE *fp; 73 struct calendar cal; 74 75 len = strlen(gen->prefix) + strlen(gen->basename) + 1; 76 basename = (char*)emalloc(len); 77 sprintf(basename, "%s%s", gen->prefix, gen->basename); 78 79 switch(gen->type) { 80 default: 81 msyslog(LOG_ERR, "unsupported file generations type %d for \"%s\" - reverting to FILEGEN_NONE", 82 gen->type, basename); 83 gen->type = FILEGEN_NONE; 84 85 /*FALLTHROUGH*/ 86 case FILEGEN_NONE: 87 filename = (char*)emalloc(len); 88 sprintf(filename,"%s", basename); 89 break; 90 91 case FILEGEN_PID: 92 filename = (char*)emalloc(len + 1 + 1 + 10); 93 sprintf(filename,"%s%c#%ld", basename, SUFFIX_SEP, newid); 94 break; 95 96 case FILEGEN_DAY: 97 /* You can argue here in favor of using MJD, but 98 * I would assume it to be easier for humans to interpret dates 99 * in a format they are used to in everyday life. 100 */ 101 caljulian(newid,&cal); 102 filename = (char*)emalloc(len + 1 + 4 + 2 + 2); 103 sprintf(filename, "%s%c%04d%02d%02d", 104 basename, SUFFIX_SEP, cal.year, cal.month, cal.monthday); 105 break; 106 107 case FILEGEN_WEEK: 108 /* 109 * This is still a hack 110 * - the term week is not correlated to week as it is used 111 * normally - it just refers to a period of 7 days 112 * starting at Jan 1 - 'weeks' are counted starting from zero 113 */ 114 caljulian(newid,&cal); 115 filename = (char*)emalloc(len + 1 + 4 + 1 + 2); 116 sprintf(filename, "%s%c%04dw%02d", 117 basename, SUFFIX_SEP, cal.year, cal.yearday / 7); 118 break; 119 120 case FILEGEN_MONTH: 121 caljulian(newid,&cal); 122 filename = (char*)emalloc(len + 1 + 4 + 2); 123 sprintf(filename, "%s%c%04d%02d", 124 basename, SUFFIX_SEP, cal.year, cal.month); 125 break; 126 127 case FILEGEN_YEAR: 128 caljulian(newid,&cal); 129 filename = (char*)emalloc(len + 1 + 4); 130 sprintf(filename, "%s%c%04d", basename, SUFFIX_SEP, cal.year); 131 break; 132 133 case FILEGEN_AGE: 134 filename = (char*)emalloc(len + 1 + 2 + 10); 135 sprintf(filename, "%s%ca%08ld", basename, SUFFIX_SEP, newid); 136 break; 137 } 138 139 if (gen->type != FILEGEN_NONE) { 140 /* 141 * check for existence of a file with name 'basename' 142 * as we disallow such a file 143 * if FGEN_FLAG_LINK is set create a link 144 */ 145 struct stat stats; 146 /* 147 * try to resolve name collisions 148 */ 149 static u_long conflicts = 0; 150 151 #ifndef S_ISREG 152 #define S_ISREG(mode) (((mode) & S_IFREG) == S_IFREG) 153 #endif 154 if (stat(basename, &stats) == 0) { 155 /* Hm, file exists... */ 156 if (S_ISREG(stats.st_mode)) { 157 if (stats.st_nlink <= 1) { 158 /* 159 * Oh, it is not linked - try to save it 160 */ 161 char *savename = (char*)emalloc(len + 1 + 1 + 10 + 10); 162 sprintf(savename, "%s%c%dC%lu", 163 basename, 164 SUFFIX_SEP, 165 (int) getpid(), 166 (u_long)conflicts++); 167 if (rename(basename, savename) != 0) 168 msyslog(LOG_ERR," couldn't save %s: %m", basename); 169 free(savename); 170 } else { 171 /* 172 * there is at least a second link tpo this file 173 * just remove the conflicting one 174 */ 175 if ( 176 #if !defined(VMS) 177 unlink(basename) != 0 178 #else 179 delete(basename) != 0 180 #endif 181 ) 182 msyslog(LOG_ERR, "couldn't unlink %s: %m", basename); 183 } 184 } else { 185 /* 186 * Ehh? Not a regular file ?? strange !!!! 187 */ 188 msyslog(LOG_ERR, "expected regular file for %s (found mode 0%lo)", 189 basename, (unsigned long)stats.st_mode); 190 } 191 } else { 192 /* 193 * stat(..) failed, but it is absolutely correct for 194 * 'basename' not to exist 195 */ 196 if (errno != ENOENT) 197 msyslog(LOG_ERR,"stat(%s) failed: %m", basename); 198 } 199 } 200 201 /* 202 * now, try to open new file generation... 203 */ 204 fp = fopen(filename, "a"); 205 206 #ifdef DEBUG 207 if (debug > 3) 208 printf("opening filegen (type=%d/id=%lu) \"%s\"\n", 209 gen->type, (u_long)newid, filename); 210 #endif 211 212 if (fp == NULL) { 213 /* open failed -- keep previous state 214 * 215 * If the file was open before keep the previous generation. 216 * This will cause output to end up in the 'wrong' file, 217 * but I think this is still better than losing output 218 * 219 * ignore errors due to missing directories 220 */ 221 222 if (errno != ENOENT) 223 msyslog(LOG_ERR, "can't open %s: %m", filename); 224 } else { 225 if (gen->fp != NULL) { 226 fclose(gen->fp); 227 } 228 gen->fp = fp; 229 gen->id = newid; 230 231 if (gen->flag & FGEN_FLAG_LINK) { 232 /* 233 * need to link file to basename 234 * have to use hardlink for now as I want to allow 235 * gen->basename spanning directory levels 236 * this would make it more complex to get the correct 237 * filename for symlink 238 * 239 * Ok, it would just mean taking the part following 240 * the last '/' in the name.... Should add it later.... 241 */ 242 243 /* Windows NT does not support file links -Greg Schueman 1/18/97 */ 244 245 #if defined SYS_WINNT || defined SYS_VXWORKS 246 SetLastError(0); /* On WinNT, don't support FGEN_FLAG_LINK */ 247 #elif defined(VMS) 248 errno = 0; /* On VMS, don't support FGEN_FLAG_LINK */ 249 #else /* not (VMS) / VXWORKS / WINNT ; DO THE LINK) */ 250 if (link(filename, basename) != 0) 251 if (errno != EEXIST) 252 msyslog(LOG_ERR, "can't link(%s, %s): %m", filename, basename); 253 #endif /* SYS_WINNT || VXWORKS */ 254 } /* flags & FGEN_FLAG_LINK */ 255 } /* else fp == NULL */ 256 257 free(basename); 258 free(filename); 259 return; 260 } 261 262 /* 263 * this function sets up gen->fp to point to the correct 264 * generation of the file for the time specified by 'now' 265 * 266 * 'now' usually is interpreted as second part of a l_fp as is in the cal... 267 * library routines 268 */ 269 270 void 271 filegen_setup( 272 FILEGEN *gen, 273 u_long now 274 ) 275 { 276 u_long new_gen = ~ (u_long) 0; 277 struct calendar cal; 278 279 if (!(gen->flag & FGEN_FLAG_ENABLED)) { 280 if (gen->fp != NULL) 281 fclose(gen->fp); 282 return; 283 } 284 285 switch (gen->type) { 286 case FILEGEN_NONE: 287 if (gen->fp != NULL) return; /* file already open */ 288 break; 289 290 case FILEGEN_PID: 291 new_gen = getpid(); 292 break; 293 294 case FILEGEN_DAY: 295 caljulian(now, &cal); 296 cal.hour = cal.minute = cal.second = 0; 297 new_gen = caltontp(&cal); 298 break; 299 300 case FILEGEN_WEEK: 301 /* Would be nice to have a calweekstart() routine */ 302 /* so just use a hack ... */ 303 /* just round time to integral 7 day period for actual year */ 304 new_gen = now - (now - calyearstart(now)) % TIMES7(SECSPERDAY) 305 + 60; 306 /* 307 * just to be sure - 308 * the computation above would fail in the presence of leap seconds 309 * so at least carry the date to the next day (+60 (seconds)) 310 * and go back to the start of the day via calendar computations 311 */ 312 caljulian(new_gen, &cal); 313 cal.hour = cal.minute = cal.second = 0; 314 new_gen = caltontp(&cal); 315 break; 316 317 case FILEGEN_MONTH: 318 caljulian(now, &cal); 319 cal.yearday = (u_short) (cal.yearday - cal.monthday + 1); 320 cal.monthday = 1; 321 cal.hour = cal.minute = cal.second = 0; 322 new_gen = caltontp(&cal); 323 break; 324 325 case FILEGEN_YEAR: 326 new_gen = calyearstart(now); 327 break; 328 329 case FILEGEN_AGE: 330 new_gen = current_time - (current_time % FGEN_AGE_SECS); 331 break; 332 } 333 /* 334 * try to open file if not yet open 335 * reopen new file generation file on change of generation id 336 */ 337 if (gen->fp == NULL || gen->id != new_gen) { 338 filegen_open(gen, new_gen); 339 } 340 } 341 342 343 /* 344 * change settings for filegen files 345 */ 346 void 347 filegen_config( 348 FILEGEN *gen, 349 char *basename, 350 u_int type, 351 u_int flag 352 ) 353 { 354 /* 355 * if nothing would be changed... 356 */ 357 if ((basename == gen->basename || strcmp(basename,gen->basename) == 0) && 358 type == gen->type && 359 flag == gen->flag) 360 return; 361 362 /* 363 * validate parameters 364 */ 365 if (!valid_fileref(gen->prefix,basename)) 366 return; 367 368 if (gen->fp != NULL) 369 fclose(gen->fp); 370 371 #ifdef DEBUG 372 if (debug > 2) 373 printf("configuring filegen:\n\tprefix:\t%s\n\tbasename:\t%s -> %s\n\ttype:\t%d -> %d\n\tflag: %x -> %x\n", 374 gen->prefix, gen->basename, basename, gen->type, type, gen->flag, flag); 375 #endif 376 if (gen->basename != basename || strcmp(gen->basename, basename) != 0) { 377 free(gen->basename); 378 gen->basename = (char*)emalloc(strlen(basename) + 1); 379 strcpy(gen->basename, basename); 380 } 381 gen->type = (u_char) type; 382 gen->flag = (u_char) flag; 383 384 /* 385 * make filegen use the new settings 386 * special action is only required when a generation file 387 * is currently open 388 * otherwise the new settings will be used anyway at the next open 389 */ 390 if (gen->fp != NULL) { 391 l_fp now; 392 393 get_systime(&now); 394 filegen_setup(gen, now.l_ui); 395 } 396 } 397 398 399 /* 400 * check whether concatenating prefix and basename 401 * yields a legal filename 402 */ 403 static int 404 valid_fileref( 405 char *prefix, 406 char *basename 407 ) 408 { 409 /* 410 * prefix cannot be changed dynamically 411 * (within the context of filegen) 412 * so just reject basenames containing '..' 413 * 414 * ASSUMPTION: 415 * file system parts 'below' prefix may be 416 * specified without infringement of security 417 * 418 * restricing prefix to legal values 419 * has to be ensured by other means 420 * (however, it would be possible to perform some checks here...) 421 */ 422 register char *p = basename; 423 424 /* 425 * Just to catch, dumb errors opening up the world... 426 */ 427 if (prefix == NULL || *prefix == '\0') 428 return 0; 429 430 if (basename == NULL) 431 return 0; 432 433 for (p = basename; p; p = strchr(p, '/')) { 434 if (*p == '.' && *(p+1) == '.' && (*(p+2) == '\0' || *(p+2) == '/')) 435 return 0; 436 } 437 438 return 1; 439 } 440 441 442 /* 443 * filegen registry 444 */ 445 446 static struct filegen_entry { 447 char *name; 448 FILEGEN *filegen; 449 struct filegen_entry *next; 450 } *filegen_registry = NULL; 451 452 453 FILEGEN * 454 filegen_get( 455 char *name 456 ) 457 { 458 struct filegen_entry *f = filegen_registry; 459 460 while(f) { 461 if (f->name == name || strcmp(name, f->name) == 0) { 462 #ifdef XXX /* this gives the Alpha compiler fits */ 463 if (debug > 3) 464 printf("filegen_get(\"%s\") = %x\n", name, 465 (u_int)f->filegen); 466 #endif 467 return f->filegen; 468 } 469 f = f->next; 470 } 471 #ifdef DEBUG 472 if (debug > 3) 473 printf("filegen_get(\"%s\") = NULL\n", name); 474 #endif 475 return NULL; 476 } 477 478 void 479 filegen_register( 480 const char *name, 481 FILEGEN *filegen 482 ) 483 { 484 struct filegen_entry **f = &filegen_registry; 485 486 #ifdef XXX /* this gives the Alpha compiler fits */ 487 if (debug > 3) 488 printf("filegen_register(\"%s\",%x)\n", name, (u_int)filegen); 489 #endif 490 while (*f) { 491 if ((*f)->name == name || strcmp(name, (*f)->name) == 0) { 492 #ifdef XXX /* this gives the Alpha compiler fits */ 493 if (debug > 4) { 494 printf("replacing filegen %x\n", (u_int)(*f)->filegen); 495 } 496 #endif 497 (*f)->filegen = filegen; 498 return; 499 } 500 f = &((*f)->next); 501 } 502 503 *f = (struct filegen_entry *) emalloc(sizeof(struct filegen_entry)); 504 if (*f) { 505 (*f)->next = NULL; 506 (*f)->name = (char*)emalloc(strlen(name) + 1); 507 strcpy((*f)->name, name); 508 (*f)->filegen = filegen; 509 #ifdef DEBUG 510 if (debug > 5) { 511 printf("adding new filegen\n"); 512 } 513 #endif 514 } 515 516 return; 517 } 518 519 #ifdef UNUSED 520 static FILEGEN * 521 filegen_unregister( 522 char *name 523 ) 524 { 525 struct filegen_entry **f = &filegen_registry; 526 527 #ifdef DEBUG 528 if (debug > 3) 529 printf("filegen_unregister(\"%s\")\n", name); 530 #endif 531 532 while (*f) { 533 if (strcmp((*f)->name,name) == 0) { 534 struct filegen_entry *ff = *f; 535 FILEGEN *fg; 536 537 *f = (*f)->next; 538 fg = ff->filegen; 539 free(ff->name); 540 free(ff); 541 return fg; 542 } 543 f = &((*f)->next); 544 } 545 return NULL; 546 } 547 #endif /* UNUSED */ 548