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