Coverage Report

Created: 2023-03-26 07:11

/src/ntp-dev/ntpd/ntp_filegen.c
Line
Count
Source (jump to first uncovered line)
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 Universitaet Erlangen-Nuernberg, 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
static  void  filegen_open  (FILEGEN *, u_int32, const time_t*);
48
static  int valid_fileref (const char *, const char *);
49
static  void  filegen_init  (const char *, const char *, FILEGEN *);
50
#ifdef  DEBUG
51
static  void  filegen_uninit    (FILEGEN *);
52
#endif  /* DEBUG */
53
54
55
/*
56
 * filegen_init
57
 */
58
59
static void
60
filegen_init(
61
  const char *  dir,
62
  const char *  fname,
63
  FILEGEN * fgp
64
  )
65
8
{
66
8
  fgp->fp = NULL;
67
8
  fgp->dir = estrdup(dir);
68
8
  fgp->fname = estrdup(fname);
69
8
  fgp->id_lo = 0;
70
8
  fgp->id_hi = 0;
71
8
  fgp->type = FILEGEN_DAY;
72
8
  fgp->flag = FGEN_FLAG_LINK; /* not yet enabled !!*/
73
8
}
74
75
76
/*
77
 * filegen_uninit - free memory allocated by filegen_init
78
 */
79
#ifdef DEBUG
80
static void
81
filegen_uninit(
82
  FILEGEN *fgp
83
  )
84
6
{
85
6
  free(fgp->dir);
86
6
  free(fgp->fname);
87
6
}
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_int32   stamp,
100
  const time_t *  pivot
101
  )
102
0
{
103
0
  char *savename; /* temp store for name collision handling */
104
0
  char *fullname; /* name with any designation extension */
105
0
  char *filename; /* name without designation extension */
106
0
  char *suffix; /* where to print suffix extension */
107
0
  u_int len, suflen;
108
0
  FILE *fp;
109
0
  struct calendar cal;
110
0
  struct isodate  iso;
111
112
  /* get basic filename in buffer, leave room for extensions */
113
0
  len = strlen(gen->dir) + strlen(gen->fname) + 65;
114
0
  filename = emalloc(len);
115
0
  fullname = emalloc(len);
116
0
  savename = NULL;
117
0
  snprintf(filename, len, "%s%s", gen->dir, gen->fname);
118
119
  /* where to place suffix */
120
0
  suflen = strlcpy(fullname, filename, len);
121
0
  suffix = fullname + suflen;
122
0
  suflen = len - suflen;
123
124
  /* last octet of fullname set to '\0' for truncation check */
125
0
  fullname[len - 1] = '\0';
126
127
0
  switch (gen->type) {
128
129
0
  default:
130
0
    msyslog(LOG_ERR, 
131
0
      "unsupported file generations type %d for "
132
0
      "\"%s\" - reverting to FILEGEN_NONE",
133
0
      gen->type, filename);
134
0
    gen->type = FILEGEN_NONE;
135
0
    break;
136
137
0
  case FILEGEN_NONE:
138
    /* no suffix, all set */
139
0
    break;
140
141
0
  case FILEGEN_PID:
142
0
    gen->id_lo = getpid();
143
0
    gen->id_hi = 0;
144
0
    snprintf(suffix, suflen, "%c#%ld",
145
0
       SUFFIX_SEP, gen->id_lo);
146
0
    break;
147
148
0
  case FILEGEN_DAY:
149
    /*
150
     * You can argue here in favor of using MJD, but I
151
     * would assume it to be easier for humans to interpret
152
     * dates in a format they are used to in everyday life.
153
     */
154
0
    ntpcal_ntp_to_date(&cal, stamp, pivot);
155
0
    snprintf(suffix, suflen, "%c%04d%02d%02d",
156
0
       SUFFIX_SEP, cal.year, cal.month, cal.monthday);
157
0
    cal.hour = cal.minute = cal.second = 0;
158
0
    gen->id_lo = ntpcal_date_to_ntp(&cal); 
159
0
    gen->id_hi = (u_int32)(gen->id_lo + SECSPERDAY);
160
0
    break;
161
162
0
  case FILEGEN_WEEK:
163
0
    isocal_ntp_to_date(&iso, stamp, pivot);
164
0
    snprintf(suffix, suflen, "%c%04dw%02d",
165
0
       SUFFIX_SEP, iso.year, iso.week);
166
0
    iso.hour = iso.minute = iso.second = 0;
167
0
    iso.weekday = 1;
168
0
    gen->id_lo = isocal_date_to_ntp(&iso);
169
0
    gen->id_hi = (u_int32)(gen->id_lo + 7 * SECSPERDAY);
170
0
    break;
171
172
0
  case FILEGEN_MONTH:
173
0
    ntpcal_ntp_to_date(&cal, stamp, pivot);
174
0
    snprintf(suffix, suflen, "%c%04d%02d",
175
0
       SUFFIX_SEP, cal.year, cal.month);
176
0
    cal.hour = cal.minute = cal.second = 0;
177
0
    cal.monthday = 1;
178
0
    gen->id_lo = ntpcal_date_to_ntp(&cal); 
179
0
    cal.month++;
180
0
    gen->id_hi = ntpcal_date_to_ntp(&cal); 
181
0
    break;
182
183
0
  case FILEGEN_YEAR:
184
0
    ntpcal_ntp_to_date(&cal, stamp, pivot);
185
0
    snprintf(suffix, suflen, "%c%04d",
186
0
       SUFFIX_SEP, cal.year);
187
0
    cal.hour = cal.minute = cal.second = 0;
188
0
    cal.month = cal.monthday = 1;
189
0
    gen->id_lo = ntpcal_date_to_ntp(&cal); 
190
0
    cal.year++;
191
0
    gen->id_hi = ntpcal_date_to_ntp(&cal); 
192
0
    break;
193
194
0
  case FILEGEN_AGE:
195
0
    gen->id_lo = current_time - (current_time % SECSPERDAY);
196
0
    gen->id_hi = gen->id_lo + SECSPERDAY;
197
0
    snprintf(suffix, suflen, "%ca%08ld",
198
0
       SUFFIX_SEP, gen->id_lo);
199
0
  }
200
  
201
  /* check possible truncation */
202
0
  if ('\0' != fullname[len - 1]) {
203
0
    fullname[len - 1] = '\0';
204
0
    msyslog(LOG_ERR, "logfile name truncated: \"%s\"",
205
0
      fullname);
206
0
  }
207
208
0
  if (FILEGEN_NONE != gen->type) {
209
    /*
210
     * check for existence of a file with name 'basename'
211
     * as we disallow such a file
212
     * if FGEN_FLAG_LINK is set create a link
213
     */
214
0
    struct stat stats;
215
    /*
216
     * try to resolve name collisions
217
     */
218
0
    static u_long conflicts = 0;
219
220
#ifndef S_ISREG
221
#define S_ISREG(mode) (((mode) & S_IFREG) == S_IFREG)
222
#endif
223
0
    if (stat(filename, &stats) == 0) {
224
      /* Hm, file exists... */
225
0
      if (S_ISREG(stats.st_mode)) {
226
0
        if (stats.st_nlink <= 1) {
227
          /*
228
           * Oh, it is not linked - try to save it
229
           */
230
0
          savename = emalloc(len);
231
0
          snprintf(savename, len,
232
0
            "%s%c%dC%lu",
233
0
            filename, SUFFIX_SEP,
234
0
            (int)getpid(), conflicts++);
235
236
0
          if (rename(filename, savename) != 0)
237
0
            msyslog(LOG_ERR,
238
0
              "couldn't save %s: %m",
239
0
              filename);
240
0
          free(savename);
241
0
        } else {
242
          /*
243
           * there is at least a second link to
244
           * this file.
245
           * just remove the conflicting one
246
           */
247
0
          if (
248
0
#if !defined(VMS)
249
0
            unlink(filename) != 0
250
#else
251
            delete(filename) != 0
252
#endif
253
0
            )
254
0
            msyslog(LOG_ERR, 
255
0
              "couldn't unlink %s: %m",
256
0
              filename);
257
0
        }
258
0
      } else {
259
        /*
260
         * Ehh? Not a regular file ?? strange !!!!
261
         */
262
0
        msyslog(LOG_ERR, 
263
0
          "expected regular file for %s "
264
0
          "(found mode 0%lo)",
265
0
          filename,
266
0
          (unsigned long)stats.st_mode);
267
0
      }
268
0
    } else {
269
      /*
270
       * stat(..) failed, but it is absolutely correct for
271
       * 'basename' not to exist
272
       */
273
0
      if (ENOENT != errno)
274
0
        msyslog(LOG_ERR, "stat(%s) failed: %m",
275
0
             filename);
276
0
    }
277
0
  }
278
279
  /*
280
   * now, try to open new file generation...
281
   */
282
0
  DPRINTF(4, ("opening filegen (type=%d/stamp=%u) \"%s\"\n",
283
0
        gen->type, stamp, fullname));
284
285
0
  fp = fopen(fullname, "a");
286
  
287
0
  if (NULL == fp) {
288
    /* open failed -- keep previous state
289
     *
290
     * If the file was open before keep the previous generation.
291
     * This will cause output to end up in the 'wrong' file,
292
     * but I think this is still better than losing output
293
     *
294
     * ignore errors due to missing directories
295
     */
296
297
0
    if (ENOENT != errno)
298
0
      msyslog(LOG_ERR, "can't open %s: %m", fullname);
299
0
  } else {
300
0
    if (NULL != gen->fp) {
301
0
      fclose(gen->fp);
302
0
      gen->fp = NULL;
303
0
    }
304
0
    gen->fp = fp;
305
306
0
    if (gen->flag & FGEN_FLAG_LINK) {
307
      /*
308
       * need to link file to basename
309
       * have to use hardlink for now as I want to allow
310
       * gen->basename spanning directory levels
311
       * this would make it more complex to get the correct
312
       * fullname for symlink
313
       *
314
       * Ok, it would just mean taking the part following
315
       * the last '/' in the name.... Should add it later....
316
       */
317
318
      /* Windows NT does not support file links -Greg Schueman 1/18/97 */
319
320
#if defined(SYS_WINNT) || defined(SYS_VXWORKS)
321
      SetLastError(0); /* On WinNT, don't support FGEN_FLAG_LINK */
322
#elif defined(VMS)
323
      errno = 0; /* On VMS, don't support FGEN_FLAG_LINK */
324
#else  /* not (VMS) / VXWORKS / WINNT ; DO THE LINK) */
325
0
      if (link(fullname, filename) != 0)
326
0
        if (EEXIST != errno)
327
0
          msyslog(LOG_ERR, 
328
0
            "can't link(%s, %s): %m",
329
0
            fullname, filename);
330
0
#endif /* SYS_WINNT || VXWORKS */
331
0
    }   /* flags & FGEN_FLAG_LINK */
332
0
  }     /* else fp == NULL */
333
  
334
0
  free(filename);
335
0
  free(fullname);
336
0
  return;
337
0
}
338
339
/*
340
 * this function sets up gen->fp to point to the correct
341
 * generation of the file for the time specified by 'now'
342
 *
343
 * 'now' usually is interpreted as second part of a l_fp as is in the cal...
344
 * library routines
345
 */
346
347
void
348
filegen_setup(
349
  FILEGEN * gen,
350
  u_int32   now
351
  )
352
794
{
353
794
  int current;
354
794
  time_t  pivot;
355
356
794
  if (!(gen->flag & FGEN_FLAG_ENABLED)) {
357
794
    if (NULL != gen->fp) {
358
0
      fclose(gen->fp);
359
0
      gen->fp = NULL;
360
0
    }
361
794
    return;
362
794
  }
363
364
0
  switch (gen->type) {
365
366
0
  default:
367
0
  case FILEGEN_NONE:
368
0
    current = TRUE;
369
0
    break;
370
371
0
  case FILEGEN_PID:
372
0
    current = ((int)gen->id_lo == getpid());
373
0
    break;
374
375
0
  case FILEGEN_AGE:
376
0
    current = (gen->id_lo <= current_time) &&
377
0
        (gen->id_hi > current_time);
378
0
    break;
379
380
0
  case FILEGEN_DAY:
381
0
  case FILEGEN_WEEK:
382
0
  case FILEGEN_MONTH:
383
0
  case FILEGEN_YEAR:
384
0
    current = (gen->id_lo <= now) &&
385
0
        (gen->id_hi > now);
386
0
    break;
387
0
  }
388
  /*
389
   * try to open file if not yet open
390
   * reopen new file generation file on change of generation id
391
   */
392
0
  if (NULL == gen->fp || !current) {
393
0
    DPRINTF(1, ("filegen  %0x %u\n", gen->type, now));
394
0
    pivot = time(NULL);
395
0
    filegen_open(gen, now, &pivot);
396
0
  }
397
0
}
398
399
400
/*
401
 * change settings for filegen files
402
 */
403
void
404
filegen_config(
405
  FILEGEN * gen,
406
  const char *  dir,
407
  const char *  fname,
408
  u_int   type,
409
  u_int   flag
410
  )
411
0
{
412
0
  int file_existed;
413
0
  l_fp now;
414
415
416
  /*
417
   * if nothing would be changed...
418
   */
419
0
  if (strcmp(dir, gen->dir) == 0 && strcmp(fname, gen->fname) == 0
420
0
      && type == gen->type && flag == gen->flag)
421
0
    return;
422
423
  /*
424
   * validate parameters
425
   */
426
0
  if (!valid_fileref(dir, fname))
427
0
    return;
428
  
429
0
  if (NULL != gen->fp) {
430
0
    fclose(gen->fp);
431
0
    gen->fp = NULL;
432
0
    file_existed = TRUE;
433
0
  } else {
434
0
    file_existed = FALSE;
435
0
  }
436
437
0
  DPRINTF(3, ("configuring filegen:\n"
438
0
        "\tdir:\t%s -> %s\n"
439
0
        "\tfname:\t%s -> %s\n"
440
0
        "\ttype:\t%d -> %d\n"
441
0
        "\tflag: %x -> %x\n",
442
0
        gen->dir, dir,
443
0
        gen->fname, fname,
444
0
        gen->type, type,
445
0
        gen->flag, flag));
446
447
0
  if (strcmp(gen->dir, dir) != 0) {
448
0
    free(gen->dir);
449
0
    gen->dir = estrdup(dir);
450
0
  }
451
452
0
  if (strcmp(gen->fname, fname) != 0) {
453
0
    free(gen->fname);
454
0
    gen->fname = estrdup(fname);
455
0
  }
456
0
  gen->type = (u_char)type;
457
0
  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
0
  if (file_existed) {
466
0
    get_systime(&now);
467
0
    filegen_setup(gen, now.l_ui);
468
0
  }
469
0
}
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 *  dir,
479
  const char *  fname
480
  )
481
0
{
482
  /*
483
   * dir cannot be changed dynamically
484
   * (within the context of filegen)
485
   * so just reject basenames containing '..'
486
   *
487
   * ASSUMPTION:
488
   *    file system parts 'below' dir may be
489
   *    specified without infringement of security
490
   *
491
   *    restricting dir to legal values
492
   *    has to be ensured by other means
493
   * (however, it would be possible to perform some checks here...)
494
   */
495
0
  const char *p;
496
497
  /*
498
   * Just to catch, dumb errors opening up the world...
499
   */
500
0
  if (NULL == dir || '\0' == dir[0])
501
0
    return FALSE;
502
503
0
  if (NULL == fname)
504
0
    return FALSE;
505
506
#ifdef SYS_WINNT
507
  /*
508
   * Windows treats / equivalent to \, reject any / to ensure
509
   * check below for DIR_SEP (\ on windows) are adequate.
510
   */
511
  if (strchr(fname, '/')) {
512
    msyslog(LOG_ERR,
513
      "filegen filenames must not contain '/': %s",
514
      fname);
515
    return FALSE;
516
  }
517
#endif
518
519
0
  for (p = fname; p != NULL; p = strchr(p, DIR_SEP)) {
520
0
    if ('.' == p[0] && '.' == p[1] 
521
0
        && ('\0' == p[2] || DIR_SEP == p[2]))
522
0
      return FALSE;
523
0
  }
524
525
0
  return TRUE;
526
0
}
527
528
529
/*
530
 * filegen registry
531
 */
532
533
static struct filegen_entry {
534
  char *      name;
535
  FILEGEN *   filegen;
536
  struct filegen_entry *  next;
537
} *filegen_registry = NULL;
538
539
540
FILEGEN *
541
filegen_get(
542
  const char *  name
543
  )
544
0
{
545
0
  struct filegen_entry *f = filegen_registry;
546
547
0
  while (f) {
548
0
    if (f->name == name || strcmp(name, f->name) == 0) {
549
0
      DPRINTF(4, ("filegen_get(%s) = %p\n",
550
0
            name, f->filegen));
551
0
      return f->filegen;
552
0
    }
553
0
    f = f->next;
554
0
  }
555
0
  DPRINTF(4, ("filegen_get(%s) = NULL\n", name));
556
0
  return NULL;
557
0
}
558
559
560
void
561
filegen_register(
562
  const char *  dir,
563
  const char *  name,
564
  FILEGEN * filegen
565
  )
566
8
{
567
8
  struct filegen_entry **ppfe;
568
569
8
  DPRINTF(4, ("filegen_register(%s, %p)\n", name, filegen));
570
571
8
  filegen_init(dir, name, filegen);
572
573
8
  ppfe = &filegen_registry;
574
36
  while (NULL != *ppfe) {
575
28
    if ((*ppfe)->name == name 
576
28
        || !strcmp((*ppfe)->name, name)) {
577
578
0
      DPRINTF(5, ("replacing filegen %p\n",
579
0
            (*ppfe)->filegen));
580
581
0
      (*ppfe)->filegen = filegen;
582
0
      return;
583
0
    }
584
28
    ppfe = &((*ppfe)->next);
585
28
  }
586
587
8
  *ppfe = emalloc(sizeof **ppfe);
588
589
8
  (*ppfe)->next = NULL;
590
8
  (*ppfe)->name = estrdup(name);
591
8
  (*ppfe)->filegen = filegen;
592
593
8
  DPRINTF(6, ("adding new filegen\n"));
594
  
595
8
  return;
596
8
}
597
598
599
/*
600
 * filegen_statsdir() - reset each filegen entry's dir to statsdir.
601
 */
602
void
603
filegen_statsdir(void)
604
0
{
605
0
  struct filegen_entry *f;
606
607
0
  for (f = filegen_registry; f != NULL; f = f->next)
608
0
    filegen_config(f->filegen, statsdir, f->filegen->fname,
609
0
             f->filegen->type, f->filegen->flag);
610
0
}
611
612
613
/*
614
 * filegen_unregister frees memory allocated by filegen_register for
615
 * name.
616
 */
617
#ifdef DEBUG
618
void
619
filegen_unregister(
620
  const char *name
621
  )
622
6
{
623
6
  struct filegen_entry ** ppfe;
624
6
  struct filegen_entry *  pfe;
625
6
  FILEGEN *   fg;
626
      
627
6
  DPRINTF(4, ("filegen_unregister(%s)\n", name));
628
629
6
  ppfe = &filegen_registry;
630
631
6
  while (NULL != *ppfe) {
632
6
    if ((*ppfe)->name == name
633
6
        || !strcmp((*ppfe)->name, name)) {
634
6
      pfe = *ppfe;
635
6
      *ppfe = (*ppfe)->next;
636
6
      fg = pfe->filegen;
637
6
      free(pfe->name);
638
6
      free(pfe);
639
6
      filegen_uninit(fg);
640
6
      break;
641
6
    }
642
0
    ppfe = &((*ppfe)->next);
643
0
  }
644
6
}  
645
#endif  /* DEBUG */