]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/newsyslog/newsyslog.c
Remove unused includes, remove the duplicated definitions of AUTHNAMELEN
[FreeBSD/FreeBSD.git] / usr.sbin / newsyslog / newsyslog.c
1 /*
2  * This file contains changes from the Open Software Foundation.
3  */
4
5 /*
6  * Copyright 1988, 1989 by the Massachusetts Institute of Technology
7  *
8  * Permission to use, copy, modify, and distribute this software and its
9  * documentation for any purpose and without fee is hereby granted, provided
10  * that the above copyright notice appear in all copies and that both that
11  * copyright notice and this permission notice appear in supporting
12  * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
13  * used in advertising or publicity pertaining to distribution of the
14  * software without specific, written prior permission. M.I.T. and the M.I.T.
15  * S.I.P.B. make no representations about the suitability of this software
16  * for any purpose.  It is provided "as is" without express or implied
17  * warranty.
18  *
19  */
20
21 /*
22  * newsyslog - roll over selected logs at the appropriate time, keeping the a
23  * specified number of backup files around.
24  */
25
26 #ifndef lint
27 static const char rcsid[] =
28 "$FreeBSD$";
29 #endif  /* not lint */
30
31 #define OSF
32 #ifndef COMPRESS_POSTFIX
33 #define COMPRESS_POSTFIX ".gz"
34 #endif
35 #ifndef BZCOMPRESS_POSTFIX
36 #define BZCOMPRESS_POSTFIX ".bz2"
37 #endif
38
39 #include <ctype.h>
40 #include <err.h>
41 #include <fcntl.h>
42 #include <grp.h>
43 #include <paths.h>
44 #include <pwd.h>
45 #include <signal.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <time.h>
50 #include <unistd.h>
51 #include <sys/types.h>
52 #include <sys/stat.h>
53 #include <sys/param.h>
54 #include <sys/wait.h>
55
56 #include "pathnames.h"
57
58 #define kbytes(size)  (((size) + 1023) >> 10)
59
60 #ifdef _IBMR2
61 /* Calculates (db * DEV_BSIZE) */
62 #define dbtob(db)  ((unsigned)(db) << UBSHIFT)
63 #endif
64
65 #define CE_COMPACT 1            /* Compact the achived log files */
66 #define CE_BINARY  2            /* Logfile is in binary, don't add */
67 #define CE_BZCOMPACT 8          /* Compact the achived log files with bzip2 */
68                                 /*  status messages */
69 #define CE_TRIMAT  4            /* trim at a specific time */
70
71 #define NONE -1
72
73 struct conf_entry {
74         char *log;              /* Name of the log */
75         char *pid_file;         /* PID file */
76         int uid;                /* Owner of log */
77         int gid;                /* Group of log */
78         int numlogs;            /* Number of logs to keep */
79         int size;               /* Size cutoff to trigger trimming the log */
80         int hours;              /* Hours between log trimming */
81         time_t trim_at;         /* Specific time to do trimming */
82         int permissions;        /* File permissions on the log */
83         int flags;              /* CE_COMPACT, CE_BZCOMPACT, CE_BINARY */
84         int sig;                /* Signal to send */
85         struct conf_entry *next;/* Linked list pointer */
86 };
87
88 int archtodir = 0;              /* Archive old logfiles to other directory */
89 int verbose = 0;                /* Print out what's going on */
90 int needroot = 1;               /* Root privs are necessary */
91 int noaction = 0;               /* Don't do anything, just show it */
92 int force = 0;                  /* Force the trim no matter what */
93 char *archdirname;              /* Directory path to old logfiles archive */
94 const char *conf = _PATH_CONF;  /* Configuration file to use */
95 time_t timenow;
96
97 #define MIN_PID         5
98 #define MAX_PID         99999   /* was lower, see /usr/include/sys/proc.h */
99 char hostname[MAXHOSTNAMELEN];  /* hostname */
100 char *daytime;                  /* timenow in human readable form */
101
102 static struct conf_entry *parse_file(char **files);
103 static char *sob(char *p);
104 static char *son(char *p);
105 static char *missing_field(char *p, char *errline);
106 static void do_entry(struct conf_entry * ent);
107 static void PRS(int argc, char **argv);
108 static void usage(void);
109 static void dotrim(char *log, const char *pid_file, int numdays, int falgs,
110                 int perm, int owner_uid, int group_gid, int sig);
111 static int log_trim(char *log);
112 static void compress_log(char *log);
113 static void bzcompress_log(char *log);
114 static int sizefile(char *file);
115 static int age_old_log(char *file);
116 static pid_t get_pid(const char *pid_file);
117 static time_t parse8601(char *s);
118 static void movefile(char *from, char *to, int perm, int owner_uid,
119                 int group_gid);
120 static void createdir(char *dirpart);
121 static time_t parseDWM(char *s);
122
123 int
124 main(int argc, char **argv)
125 {
126         struct conf_entry *p, *q;
127
128         PRS(argc, argv);
129         if (needroot && getuid() && geteuid())
130                 errx(1, "must have root privs");
131         p = q = parse_file(argv + optind);
132
133         while (p) {
134                 do_entry(p);
135                 p = p->next;
136                 free((char *) q);
137                 q = p;
138         }
139         return (0);
140 }
141
142 static void
143 do_entry(struct conf_entry * ent)
144 {
145         int size, modtime;
146         const char *pid_file;
147
148         if (verbose) {
149                 if (ent->flags & CE_COMPACT)
150                         printf("%s <%dZ>: ", ent->log, ent->numlogs);
151                 else if (ent->flags & CE_BZCOMPACT)
152                         printf("%s <%dJ>: ", ent->log, ent->numlogs);
153                 else
154                         printf("%s <%d>: ", ent->log, ent->numlogs);
155         }
156         size = sizefile(ent->log);
157         modtime = age_old_log(ent->log);
158         if (size < 0) {
159                 if (verbose)
160                         printf("does not exist.\n");
161         } else {
162                 if (ent->flags & CE_TRIMAT) {
163                         if (timenow < ent->trim_at
164                             || difftime(timenow, ent->trim_at) >= 60 * 60) {
165                                 if (verbose)
166                                         printf("--> will trim at %s",
167                                             ctime(&ent->trim_at));
168                                 return;
169                         } else if (verbose && ent->hours <= 0) {
170                                 printf("--> time is up\n");
171                         }
172                 }
173                 if (verbose && (ent->size > 0))
174                         printf("size (Kb): %d [%d] ", size, ent->size);
175                 if (verbose && (ent->hours > 0))
176                         printf(" age (hr): %d [%d] ", modtime, ent->hours);
177                 if (force || ((ent->size > 0) && (size >= ent->size)) ||
178                     (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) ||
179                     ((ent->hours > 0) && ((modtime >= ent->hours)
180                             || (modtime < 0)))) {
181                         if (verbose)
182                                 printf("--> trimming log....\n");
183                         if (noaction && !verbose) {
184                                 if (ent->flags & CE_COMPACT)
185                                         printf("%s <%dZ>: trimming\n",
186                                             ent->log, ent->numlogs);
187                                 else if (ent->flags & CE_BZCOMPACT)
188                                         printf("%s <%dJ>: trimming\n",
189                                             ent->log, ent->numlogs);
190                                 else
191                                         printf("%s <%d>: trimming\n",
192                                             ent->log, ent->numlogs);
193                         }
194                         if (ent->pid_file) {
195                                 pid_file = ent->pid_file;
196                         } else {
197                                 /* Only try to notify syslog if we are root */
198                                 if (needroot)
199                                         pid_file = _PATH_SYSLOGPID;
200                                 else
201                                         pid_file = NULL;
202                         }
203                         dotrim(ent->log, pid_file, ent->numlogs,
204                             ent->flags, ent->permissions, ent->uid, ent->gid,
205                             ent->sig);
206                 } else {
207                         if (verbose)
208                                 printf("--> skipping\n");
209                 }
210         }
211 }
212
213 static void
214 PRS(int argc, char **argv)
215 {
216         int c;
217         char *p;
218
219         timenow = time((time_t *) 0);
220         daytime = ctime(&timenow) + 4;
221         daytime[15] = '\0';
222
223         /* Let's get our hostname */
224         (void) gethostname(hostname, sizeof(hostname));
225
226         /* Truncate domain */
227         if ((p = strchr(hostname, '.'))) {
228                 *p = '\0';
229         }
230         while ((c = getopt(argc, argv, "nrvFf:a:t:")) != -1)
231                 switch (c) {
232                 case 'n':
233                         noaction++;
234                         break;
235                 case 'a':
236                         archtodir++;
237                         archdirname = optarg;
238                         break;
239                 case 'r':
240                         needroot = 0;
241                         break;
242                 case 'v':
243                         verbose++;
244                         break;
245                 case 'f':
246                         conf = optarg;
247                         break;
248                 case 'F':
249                         force++;
250                         break;
251                 default:
252                         usage();
253                 }
254 }
255
256 static void
257 usage(void)
258 {
259
260         fprintf(stderr,
261             "usage: newsyslog [-Fnrv] [-f config-file] [-a directory]\n");
262         exit(1);
263 }
264
265 /*
266  * Parse a configuration file and return a linked list of all the logs to
267  * process
268  */
269 static struct conf_entry *
270 parse_file(char **files)
271 {
272         FILE *f;
273         char line[BUFSIZ], *parse, *q;
274         char *errline, *group;
275         char **p;
276         struct conf_entry *first, *working;
277         struct passwd *pass;
278         struct group *grp;
279         int eol;
280
281         first = working = NULL;
282
283         if (strcmp(conf, "-"))
284                 f = fopen(conf, "r");
285         else
286                 f = stdin;
287         if (!f)
288                 err(1, "%s", conf);
289         while (fgets(line, BUFSIZ, f)) {
290                 if ((line[0] == '\n') || (line[0] == '#'))
291                         continue;
292                 errline = strdup(line);
293
294                 q = parse = missing_field(sob(line), errline);
295                 parse = son(line);
296                 if (!*parse)
297                         errx(1, "malformed line (missing fields):\n%s",
298                             errline);
299                 *parse = '\0';
300
301                 if (*files) {
302                         for (p = files; *p; ++p)
303                                 if (strcmp(*p, q) == 0)
304                                         break;
305                         if (!*p)
306                                 continue;
307                 }
308
309                 if (!first) {
310                         if ((working = malloc(sizeof(struct conf_entry))) ==
311                             NULL)
312                                 err(1, "malloc");
313                         first = working;
314                 } else {
315                         if ((working->next = malloc(sizeof(struct conf_entry)))
316                             == NULL)
317                                 err(1, "malloc");
318                         working = working->next;
319                 }
320                 if ((working->log = strdup(q)) == NULL)
321                         err(1, "strdup");
322
323                 q = parse = missing_field(sob(++parse), errline);
324                 parse = son(parse);
325                 if (!*parse)
326                         errx(1, "malformed line (missing fields):\n%s",
327                             errline);
328                 *parse = '\0';
329                 if ((group = strchr(q, ':')) != NULL ||
330                     (group = strrchr(q, '.')) != NULL) {
331                         *group++ = '\0';
332                         if (*q) {
333                                 if (!(isnumber(*q))) {
334                                         if ((pass = getpwnam(q)) == NULL)
335                                                 errx(1,
336                                      "error in config file; unknown user:\n%s",
337                                                     errline);
338                                         working->uid = pass->pw_uid;
339                                 } else
340                                         working->uid = atoi(q);
341                         } else
342                                 working->uid = NONE;
343
344                         q = group;
345                         if (*q) {
346                                 if (!(isnumber(*q))) {
347                                         if ((grp = getgrnam(q)) == NULL)
348                                                 errx(1,
349                                     "error in config file; unknown group:\n%s",
350                                                     errline);
351                                         working->gid = grp->gr_gid;
352                                 } else
353                                         working->gid = atoi(q);
354                         } else
355                                 working->gid = NONE;
356
357                         q = parse = missing_field(sob(++parse), errline);
358                         parse = son(parse);
359                         if (!*parse)
360                                 errx(1, "malformed line (missing fields):\n%s",
361                                     errline);
362                         *parse = '\0';
363                 } else
364                         working->uid = working->gid = NONE;
365
366                 if (!sscanf(q, "%o", &working->permissions))
367                         errx(1, "error in config file; bad permissions:\n%s",
368                             errline);
369
370                 q = parse = missing_field(sob(++parse), errline);
371                 parse = son(parse);
372                 if (!*parse)
373                         errx(1, "malformed line (missing fields):\n%s",
374                             errline);
375                 *parse = '\0';
376                 if (!sscanf(q, "%d", &working->numlogs))
377                         errx(1, "error in config file; bad number:\n%s",
378                             errline);
379
380                 q = parse = missing_field(sob(++parse), errline);
381                 parse = son(parse);
382                 if (!*parse)
383                         errx(1, "malformed line (missing fields):\n%s",
384                             errline);
385                 *parse = '\0';
386                 if (isdigit(*q))
387                         working->size = atoi(q);
388                 else
389                         working->size = -1;
390
391                 working->flags = 0;
392                 q = parse = missing_field(sob(++parse), errline);
393                 parse = son(parse);
394                 eol = !*parse;
395                 *parse = '\0';
396                 {
397                         char *ep;
398                         u_long ul;
399
400                         ul = strtoul(q, &ep, 10);
401                         if (ep == q)
402                                 working->hours = 0;
403                         else if (*ep == '*')
404                                 working->hours = -1;
405                         else if (ul > INT_MAX)
406                                 errx(1, "interval is too large:\n%s", errline);
407                         else
408                                 working->hours = ul;
409
410                         if (*ep != '\0' && *ep != '@' && *ep != '*' &&
411                             *ep != '$')
412                                 errx(1, "malformed interval/at:\n%s", errline);
413                         if (*ep == '@') {
414                                 if ((working->trim_at = parse8601(ep + 1))
415                                     == (time_t) - 1)
416                                         errx(1, "malformed at:\n%s", errline);
417                                 working->flags |= CE_TRIMAT;
418                         } else if (*ep == '$') {
419                                 if ((working->trim_at = parseDWM(ep + 1))
420                                     == (time_t) - 1)
421                                         errx(1, "malformed at:\n%s", errline);
422                                 working->flags |= CE_TRIMAT;
423                         }
424                 }
425
426                 if (eol)
427                         q = NULL;
428                 else {
429                         q = parse = sob(++parse);       /* Optional field */
430                         parse = son(parse);
431                         if (!*parse)
432                                 eol = 1;
433                         *parse = '\0';
434                 }
435
436                 while (q && *q && !isspace(*q)) {
437                         if ((*q == 'Z') || (*q == 'z'))
438                                 working->flags |= CE_COMPACT;
439                         else if ((*q == 'J') || (*q == 'j'))
440                                 working->flags |= CE_BZCOMPACT;
441                         else if ((*q == 'B') || (*q == 'b'))
442                                 working->flags |= CE_BINARY;
443                         else if (*q != '-')
444                                 errx(1, "illegal flag in config file -- %c",
445                                     *q);
446                         q++;
447                 }
448
449                 if (eol)
450                         q = NULL;
451                 else {
452                         q = parse = sob(++parse);       /* Optional field */
453                         parse = son(parse);
454                         if (!*parse)
455                                 eol = 1;
456                         *parse = '\0';
457                 }
458
459                 working->pid_file = NULL;
460                 if (q && *q) {
461                         if (*q == '/')
462                                 working->pid_file = strdup(q);
463                         else if (isdigit(*q))
464                                 goto got_sig;
465                         else
466                                 errx(1,
467                         "illegal pid file or signal number in config file:\n%s",
468                                     errline);
469                 }
470                 if (eol)
471                         q = NULL;
472                 else {
473                         q = parse = sob(++parse);       /* Optional field */
474                         *(parse = son(parse)) = '\0';
475                 }
476
477                 working->sig = SIGHUP;
478                 if (q && *q) {
479                         if (isdigit(*q)) {
480                 got_sig:
481                                 working->sig = atoi(q);
482                         } else {
483                 err_sig:
484                                 errx(1,
485                                     "illegal signal number in config file:\n%s",
486                                     errline);
487                         }
488                         if (working->sig < 1 || working->sig >= NSIG)
489                                 goto err_sig;
490                 }
491                 free(errline);
492         }
493         if (working)
494                 working->next = (struct conf_entry *) NULL;
495         (void) fclose(f);
496         return (first);
497 }
498
499 static char *
500 missing_field(char *p, char *errline)
501 {
502
503         if (!p || !*p)
504                 errx(1, "missing field in config file:\n%s", errline);
505         return (p);
506 }
507
508 static void
509 dotrim(char *log, const char *pid_file, int numdays, int flags, int perm,
510     int owner_uid, int group_gid, int sig)
511 {
512         char dirpart[MAXPATHLEN], namepart[MAXPATHLEN];
513         char file1[MAXPATHLEN], file2[MAXPATHLEN];
514         char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN];
515         char jfile1[MAXPATHLEN];
516         int notified, need_notification, fd, _numdays;
517         struct stat st;
518         pid_t pid;
519
520 #ifdef _IBMR2
521         /*
522          * AIX 3.1 has a broken fchown- if the owner_uid is -1, it will
523          * actually change it to be owned by uid -1, instead of leaving it
524          * as is, as it is supposed to.
525          */
526         if (owner_uid == -1)
527                 owner_uid = geteuid();
528 #endif
529
530         if (archtodir) {
531                 char *p;
532
533                 /* build complete name of archive directory into dirpart */
534                 if (*archdirname == '/') {      /* absolute */
535                         strlcpy(dirpart, archdirname, sizeof(dirpart));
536                 } else {        /* relative */
537                         /* get directory part of logfile */
538                         strlcpy(dirpart, log, sizeof(dirpart));
539                         if ((p = rindex(dirpart, '/')) == NULL)
540                                 dirpart[0] = '\0';
541                         else
542                                 *(p + 1) = '\0';
543                         strlcat(dirpart, archdirname, sizeof(dirpart));
544                 }
545
546                 /* check if archive directory exists, if not, create it */
547                 if (lstat(dirpart, &st))
548                         createdir(dirpart);
549
550                 /* get filename part of logfile */
551                 if ((p = rindex(log, '/')) == NULL)
552                         strlcpy(namepart, log, sizeof(namepart));
553                 else
554                         strlcpy(namepart, p + 1, sizeof(namepart));
555
556                 /* name of oldest log */
557                 (void) snprintf(file1, sizeof(file1), "%s/%s.%d", dirpart,
558                     namepart, numdays);
559                 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
560                     COMPRESS_POSTFIX);
561                 snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
562                     BZCOMPRESS_POSTFIX);
563         } else {
564                 /* name of oldest log */
565                 (void) snprintf(file1, sizeof(file1), "%s.%d", log, numdays);
566                 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1,
567                     COMPRESS_POSTFIX);
568                 snprintf(jfile1, sizeof(jfile1), "%s%s", file1,
569                     BZCOMPRESS_POSTFIX);
570         }
571
572         if (noaction) {
573                 printf("rm -f %s\n", file1);
574                 printf("rm -f %s\n", zfile1);
575                 printf("rm -f %s\n", jfile1);
576         } else {
577                 (void) unlink(file1);
578                 (void) unlink(zfile1);
579                 (void) unlink(jfile1);
580         }
581
582         /* Move down log files */
583         _numdays = numdays;     /* preserve */
584         while (numdays--) {
585
586                 (void) strlcpy(file2, file1, sizeof(file2));
587
588                 if (archtodir)
589                         (void) snprintf(file1, sizeof(file1), "%s/%s.%d",
590                             dirpart, namepart, numdays);
591                 else
592                         (void) snprintf(file1, sizeof(file1), "%s.%d", log,
593                             numdays);
594
595                 (void) strlcpy(zfile1, file1, sizeof(zfile1));
596                 (void) strlcpy(zfile2, file2, sizeof(zfile2));
597                 if (lstat(file1, &st)) {
598                         (void) strlcat(zfile1, COMPRESS_POSTFIX,
599                             sizeof(zfile1));
600                         (void) strlcat(zfile2, COMPRESS_POSTFIX,
601                             sizeof(zfile2));
602                         if (lstat(zfile1, &st)) {
603                                 strlcpy(zfile1, file1, sizeof(zfile1));
604                                 strlcpy(zfile2, file2, sizeof(zfile2));
605                                 strlcat(zfile1, BZCOMPRESS_POSTFIX,
606                                     sizeof(zfile1));
607                                 strlcat(zfile2, BZCOMPRESS_POSTFIX,
608                                     sizeof(zfile2));
609                                 if (lstat(zfile1, &st))
610                                         continue;
611                         }
612                 }
613                 if (noaction) {
614                         printf("mv %s %s\n", zfile1, zfile2);
615                         printf("chmod %o %s\n", perm, zfile2);
616                         printf("chown %d:%d %s\n",
617                             owner_uid, group_gid, zfile2);
618                 } else {
619                         (void) rename(zfile1, zfile2);
620                         (void) chmod(zfile2, perm);
621                         (void) chown(zfile2, owner_uid, group_gid);
622                 }
623         }
624         if (!noaction && !(flags & CE_BINARY))
625                 (void) log_trim(log);   /* Report the trimming to the old log */
626
627         if (!_numdays) {
628                 if (noaction)
629                         printf("rm %s\n", log);
630                 else
631                         (void) unlink(log);
632         } else {
633                 if (noaction)
634                         printf("mv %s to %s\n", log, file1);
635                 else {
636                         if (archtodir)
637                                 movefile(log, file1, perm, owner_uid,
638                                     group_gid);
639                         else
640                                 (void) rename(log, file1);
641                 }
642         }
643
644         if (noaction)
645                 printf("Start new log...");
646         else {
647                 fd = creat(log, perm);
648                 if (fd < 0)
649                         err(1, "can't start new log");
650                 if (fchown(fd, owner_uid, group_gid))
651                         err(1, "can't chmod new log file");
652                 (void) close(fd);
653                 if (!(flags & CE_BINARY))
654                         if (log_trim(log))      /* Add status message */
655                                 err(1, "can't add status message to log");
656         }
657         if (noaction)
658                 printf("chmod %o %s...\n", perm, log);
659         else
660                 (void) chmod(log, perm);
661
662         pid = 0;
663         need_notification = notified = 0;
664         if (pid_file != NULL) {
665                 need_notification = 1;
666                 pid = get_pid(pid_file);
667         }
668         if (pid) {
669                 if (noaction) {
670                         notified = 1;
671                         printf("kill -%d %d\n", sig, (int) pid);
672                 } else if (kill(pid, sig))
673                         warn("can't notify daemon, pid %d", (int) pid);
674                 else {
675                         notified = 1;
676                         if (verbose)
677                                 printf("daemon pid %d notified\n", (int) pid);
678                 }
679         }
680         if ((flags & CE_COMPACT) || (flags & CE_BZCOMPACT)) {
681                 if (need_notification && !notified)
682                         warnx(
683                             "log %s not compressed because daemon not notified",
684                             log);
685                 else if (noaction)
686                         printf("Compress %s.0\n", log);
687                 else {
688                         if (notified) {
689                                 if (verbose)
690                                         printf("small pause to allow daemon to close log\n");
691                                 sleep(10);
692                         }
693                         if (archtodir) {
694                                 (void) snprintf(file1, sizeof(file1), "%s/%s",
695                                     dirpart, namepart);
696                                 if (flags & CE_COMPACT)
697                                         compress_log(file1);
698                                 else if (flags & CE_BZCOMPACT)
699                                         bzcompress_log(file1);
700                         } else {
701                                 if (flags & CE_COMPACT)
702                                         compress_log(log);
703                                 else if (flags & CE_BZCOMPACT)
704                                         bzcompress_log(log);
705                         }
706                 }
707         }
708 }
709
710 /* Log the fact that the logs were turned over */
711 static int
712 log_trim(char *log)
713 {
714         FILE *f;
715
716         if ((f = fopen(log, "a")) == NULL)
717                 return (-1);
718         fprintf(f, "%s %s newsyslog[%d]: logfile turned over\n",
719             daytime, hostname, (int) getpid());
720         if (fclose(f) == EOF)
721                 err(1, "log_trim: fclose:");
722         return (0);
723 }
724
725 /* Fork of gzip to compress the old log file */
726 static void
727 compress_log(char *log)
728 {
729         pid_t pid;
730         char tmp[MAXPATHLEN];
731
732         (void) snprintf(tmp, sizeof(tmp), "%s.0", log);
733         pid = fork();
734         if (pid < 0)
735                 err(1, "gzip fork");
736         else if (!pid) {
737                 (void) execl(_PATH_GZIP, _PATH_GZIP, "-f", tmp, (char *)0);
738                 err(1, _PATH_GZIP);
739         }
740 }
741
742 /* Fork of bzip2 to compress the old log file */
743 static void
744 bzcompress_log(char *log)
745 {
746         pid_t pid;
747         char tmp[MAXPATHLEN];
748
749         snprintf(tmp, sizeof(tmp), "%s.0", log);
750         pid = fork();
751         if (pid < 0)
752                 err(1, "bzip2 fork");
753         else if (!pid) {
754                 execl(_PATH_BZIP2, _PATH_BZIP2, "-f", tmp, (char *)0);
755                 err(1, _PATH_BZIP2);
756         }
757 }
758
759 /* Return size in kilobytes of a file */
760 static int
761 sizefile(char *file)
762 {
763         struct stat sb;
764
765         if (stat(file, &sb) < 0)
766                 return (-1);
767         return (kbytes(dbtob(sb.st_blocks)));
768 }
769
770 /* Return the age of old log file (file.0) */
771 static int
772 age_old_log(char *file)
773 {
774         struct stat sb;
775         char tmp[MAXPATHLEN + sizeof(".0") + sizeof(COMPRESS_POSTFIX) + 1];
776
777         if (archtodir) {
778                 char *p;
779
780                 /* build name of archive directory into tmp */
781                 if (*archdirname == '/') {      /* absolute */
782                         strlcpy(tmp, archdirname, sizeof(tmp));
783                 } else {        /* relative */
784                         /* get directory part of logfile */
785                         strlcpy(tmp, file, sizeof(tmp));
786                         if ((p = rindex(tmp, '/')) == NULL)
787                                 tmp[0] = '\0';
788                         else
789                                 *(p + 1) = '\0';
790                         strlcat(tmp, archdirname, sizeof(tmp));
791                 }
792
793                 strlcat(tmp, "/", sizeof(tmp));
794
795                 /* get filename part of logfile */
796                 if ((p = rindex(file, '/')) == NULL)
797                         strlcat(tmp, file, sizeof(tmp));
798                 else
799                         strlcat(tmp, p + 1, sizeof(tmp));
800         } else {
801                 (void) strlcpy(tmp, file, sizeof(tmp));
802         }
803
804         if (stat(strcat(tmp, ".0"), &sb) < 0)
805                 if (stat(strcat(tmp, COMPRESS_POSTFIX), &sb) < 0)
806                         return (-1);
807         return ((int) (timenow - sb.st_mtime + 1800) / 3600);
808 }
809
810 static pid_t
811 get_pid(const char *pid_file)
812 {
813         FILE *f;
814         char line[BUFSIZ];
815         pid_t pid = 0;
816
817         if ((f = fopen(pid_file, "r")) == NULL)
818                 warn("can't open %s pid file to restart a daemon",
819                     pid_file);
820         else {
821                 if (fgets(line, BUFSIZ, f)) {
822                         pid = atol(line);
823                         if (pid < MIN_PID || pid > MAX_PID) {
824                                 warnx("preposterous process number: %d",
825                                    (int)pid);
826                                 pid = 0;
827                         }
828                 } else
829                         warn("can't read %s pid file to restart a daemon",
830                             pid_file);
831                 (void) fclose(f);
832         }
833         return pid;
834 }
835
836 /* Skip Over Blanks */
837 char *
838 sob(char *p)
839 {
840         while (p && *p && isspace(*p))
841                 p++;
842         return (p);
843 }
844
845 /* Skip Over Non-Blanks */
846 char *
847 son(char *p)
848 {
849         while (p && *p && !isspace(*p))
850                 p++;
851         return (p);
852 }
853
854 /*
855  * Parse a limited subset of ISO 8601. The specific format is as follows:
856  *
857  * [CC[YY[MM[DD]]]][THH[MM[SS]]]        (where `T' is the literal letter)
858  *
859  * We don't accept a timezone specification; missing fields (including timezone)
860  * are defaulted to the current date but time zero.
861  */
862 static time_t
863 parse8601(char *s)
864 {
865         char *t;
866         struct tm tm, *tmp;
867         u_long ul;
868
869         tmp = localtime(&timenow);
870         tm = *tmp;
871
872         tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
873
874         ul = strtoul(s, &t, 10);
875         if (*t != '\0' && *t != 'T')
876                 return -1;
877
878         /*
879          * Now t points either to the end of the string (if no time was
880          * provided) or to the letter `T' which separates date and time in
881          * ISO 8601.  The pointer arithmetic is the same for either case.
882          */
883         switch (t - s) {
884         case 8:
885                 tm.tm_year = ((ul / 1000000) - 19) * 100;
886                 ul = ul % 1000000;
887         case 6:
888                 tm.tm_year -= tm.tm_year % 100;
889                 tm.tm_year += ul / 10000;
890                 ul = ul % 10000;
891         case 4:
892                 tm.tm_mon = (ul / 100) - 1;
893                 ul = ul % 100;
894         case 2:
895                 tm.tm_mday = ul;
896         case 0:
897                 break;
898         default:
899                 return -1;
900         }
901
902         /* sanity check */
903         if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12
904             || tm.tm_mday < 1 || tm.tm_mday > 31)
905                 return -1;
906
907         if (*t != '\0') {
908                 s = ++t;
909                 ul = strtoul(s, &t, 10);
910                 if (*t != '\0' && !isspace(*t))
911                         return -1;
912
913                 switch (t - s) {
914                 case 6:
915                         tm.tm_sec = ul % 100;
916                         ul /= 100;
917                 case 4:
918                         tm.tm_min = ul % 100;
919                         ul /= 100;
920                 case 2:
921                         tm.tm_hour = ul;
922                 case 0:
923                         break;
924                 default:
925                         return -1;
926                 }
927
928                 /* sanity check */
929                 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0
930                     || tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
931                         return -1;
932         }
933         return mktime(&tm);
934 }
935
936 /* physically move file */
937 static void
938 movefile(char *from, char *to, int perm, int owner_uid, int group_gid)
939 {
940         FILE *src, *dst;
941         int c;
942
943         if ((src = fopen(from, "r")) == NULL)
944                 err(1, "can't fopen %s for reading", from);
945         if ((dst = fopen(to, "w")) == NULL)
946                 err(1, "can't fopen %s for writing", to);
947         if (fchown(fileno(dst), owner_uid, group_gid))
948                 err(1, "can't fchown %s", to);
949         if (fchmod(fileno(dst), perm))
950                 err(1, "can't fchmod %s", to);
951
952         while ((c = getc(src)) != EOF) {
953                 if ((putc(c, dst)) == EOF)
954                         err(1, "error writing to %s", to);
955         }
956
957         if (ferror(src))
958                 err(1, "error reading from %s", from);
959         if ((fclose(src)) != 0)
960                 err(1, "can't fclose %s", to);
961         if ((fclose(dst)) != 0)
962                 err(1, "can't fclose %s", from);
963         if ((unlink(from)) != 0)
964                 err(1, "can't unlink %s", from);
965 }
966
967 /* create one or more directory components of a path */
968 static void
969 createdir(char *dirpart)
970 {
971         char *s, *d;
972         char mkdirpath[MAXPATHLEN];
973         struct stat st;
974
975         s = dirpart;
976         d = mkdirpath;
977
978         for (;;) {
979                 *d++ = *s++;
980                 if (*s == '/' || *s == '\0') {
981                         *d = '\0';
982                         if (lstat(mkdirpath, &st))
983                                 mkdir(mkdirpath, 0755);
984                 }
985                 if (*s == '\0')
986                         break;
987         }
988 }
989
990 /*-
991  * Parse a cyclic time specification, the format is as follows:
992  *
993  *      [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
994  *
995  * to rotate a logfile cyclic at
996  *
997  *      - every day (D) within a specific hour (hh)     (hh = 0...23)
998  *      - once a week (W) at a specific day (d)     OR  (d = 0..6, 0 = Sunday)
999  *      - once a month (M) at a specific day (d)        (d = 1..31,l|L)
1000  *
1001  * We don't accept a timezone specification; missing fields
1002  * are defaulted to the current date but time zero.
1003  */
1004 static time_t
1005 parseDWM(char *s)
1006 {
1007         char *t;
1008         struct tm tm, *tmp;
1009         long l;
1010         int nd;
1011         static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1012         int WMseen = 0;
1013         int Dseen = 0;
1014
1015         tmp = localtime(&timenow);
1016         tm = *tmp;
1017
1018         /* set no. of days per month */
1019
1020         nd = mtab[tm.tm_mon];
1021
1022         if (tm.tm_mon == 1) {
1023                 if (((tm.tm_year + 1900) % 4 == 0) &&
1024                     ((tm.tm_year + 1900) % 100 != 0) &&
1025                     ((tm.tm_year + 1900) % 400 == 0)) {
1026                         nd++;   /* leap year, 29 days in february */
1027                 }
1028         }
1029         tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1030
1031         for (;;) {
1032                 switch (*s) {
1033                 case 'D':
1034                         if (Dseen)
1035                                 return -1;
1036                         Dseen++;
1037                         s++;
1038                         l = strtol(s, &t, 10);
1039                         if (l < 0 || l > 23)
1040                                 return -1;
1041                         tm.tm_hour = l;
1042                         break;
1043
1044                 case 'W':
1045                         if (WMseen)
1046                                 return -1;
1047                         WMseen++;
1048                         s++;
1049                         l = strtol(s, &t, 10);
1050                         if (l < 0 || l > 6)
1051                                 return -1;
1052                         if (l != tm.tm_wday) {
1053                                 int save;
1054
1055                                 if (l < tm.tm_wday) {
1056                                         save = 6 - tm.tm_wday;
1057                                         save += (l + 1);
1058                                 } else {
1059                                         save = l - tm.tm_wday;
1060                                 }
1061
1062                                 tm.tm_mday += save;
1063
1064                                 if (tm.tm_mday > nd) {
1065                                         tm.tm_mon++;
1066                                         tm.tm_mday = tm.tm_mday - nd;
1067                                 }
1068                         }
1069                         break;
1070
1071                 case 'M':
1072                         if (WMseen)
1073                                 return -1;
1074                         WMseen++;
1075                         s++;
1076                         if (tolower(*s) == 'l') {
1077                                 tm.tm_mday = nd;
1078                                 s++;
1079                                 t = s;
1080                         } else {
1081                                 l = strtol(s, &t, 10);
1082                                 if (l < 1 || l > 31)
1083                                         return -1;
1084
1085                                 if (l > nd)
1086                                         return -1;
1087                                 tm.tm_mday = l;
1088                         }
1089                         break;
1090
1091                 default:
1092                         return (-1);
1093                         break;
1094                 }
1095
1096                 if (*t == '\0' || isspace(*t))
1097                         break;
1098                 else
1099                         s = t;
1100         }
1101         return mktime(&tm);
1102 }