ntp_filegen.c revision 1.3 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 Universitt Erlangen-Nrnberg, 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