ntp_filegen.c revision 1.1 1 /* $NetBSD: ntp_filegen.c,v 1.1 2009/12/13 16:55:31 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 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 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