Bug Summary

File:src/usr.bin/newsyslog/newsyslog.c
Warning:line 767, column 8
Potential leak of memory pointed to by 'working'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.0 -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name newsyslog.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -fno-rounding-math -mconstructor-aliases -munwind-tables -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/usr.bin/newsyslog/obj -resource-dir /usr/local/lib/clang/13.0.0 -internal-isystem /usr/local/lib/clang/13.0.0/include -internal-externc-isystem /usr/include -O2 -fdebug-compilation-dir=/usr/src/usr.bin/newsyslog/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/vmm/scan-build/2022-01-12-194120-40624-1 -x c /usr/src/usr.bin/newsyslog/newsyslog.c
1/* $OpenBSD: newsyslog.c,v 1.112 2019/06/28 13:35:02 deraadt Exp $ */
2
3/*
4 * Copyright (c) 1999, 2002, 2003 Todd C. Miller <millert@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 *
18 * Sponsored in part by the Defense Advanced Research Projects
19 * Agency (DARPA) and Air Force Research Laboratory, Air Force
20 * Materiel Command, USAF, under agreement number F39502-99-1-0512.
21 */
22
23/*
24 * Copyright (c) 1997, Jason Downs. All rights reserved.
25 *
26 * Redistribution and use in source and binary forms, with or without
27 * modification, are permitted provided that the following conditions
28 * are met:
29 * 1. Redistributions of source code must retain the above copyright
30 * notice, this list of conditions and the following disclaimer.
31 * 2. Redistributions in binary form must reproduce the above copyright
32 * notice, this list of conditions and the following disclaimer in the
33 * documentation and/or other materials provided with the distribution.
34 *
35 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS
36 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
37 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT,
39 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
40 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
41 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
42 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
43 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
44 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
45 * SUCH DAMAGE.
46 */
47
48/*
49 * This file contains changes from the Open Software Foundation.
50 */
51
52/*
53 * Copyright 1988, 1989 by the Massachusetts Institute of Technology
54 *
55 * Permission to use, copy, modify, and distribute this software
56 * and its documentation for any purpose and without fee is
57 * hereby granted, provided that the above copyright notice
58 * appear in all copies and that both that copyright notice and
59 * this permission notice appear in supporting documentation,
60 * and that the names of M.I.T. and the M.I.T. S.I.P.B. not be
61 * used in advertising or publicity pertaining to distribution
62 * of the software without specific, written prior permission.
63 * M.I.T. and the M.I.T. S.I.P.B. make no representations about
64 * the suitability of this software for any purpose. It is
65 * provided "as is" without express or implied warranty.
66 */
67
68/*
69 * newsyslog - roll over selected logs at the appropriate time,
70 * keeping the specified number of backup files around.
71 *
72 */
73
74#define CONF"/etc/newsyslog.conf" "/etc/newsyslog.conf"
75#define PIDFILE"/var/run/syslog.pid" "/var/run/syslog.pid"
76#define COMPRESS"/usr/bin/gzip" "/usr/bin/gzip"
77#define COMPRESS_POSTFIX".gz" ".gz"
78#define STATS_DIR"/var/run" "/var/run"
79#define SENDMAIL"/usr/sbin/sendmail" "/usr/sbin/sendmail"
80
81#include <sys/param.h> /* DEV_BSIZE */
82#include <sys/queue.h>
83#include <sys/stat.h>
84#include <sys/time.h>
85#include <sys/wait.h>
86
87#include <ctype.h>
88#include <err.h>
89#include <errno(*__errno()).h>
90#include <fcntl.h>
91#include <grp.h>
92#include <limits.h>
93#include <pwd.h>
94#include <signal.h>
95#include <stdio.h>
96#include <stdlib.h>
97#include <string.h>
98#include <time.h>
99#include <unistd.h>
100
101#define CE_ROTATED0x01 0x01 /* Log file has been rotated */
102#define CE_COMPACT0x02 0x02 /* Compact the archived log files */
103#define CE_BINARY0x04 0x04 /* Logfile is in binary, don't add */
104 /* status messages */
105#define CE_MONITOR0x08 0x08 /* Monitor for changes */
106#define CE_FOLLOW0x10 0x10 /* Follow symbolic links */
107#define CE_TRIMAT0x20 0x20 /* Trim at a specific time */
108
109#define MIN_PID2 2 /* Don't touch pids lower than this */
110#define MIN_SIZE256 256 /* Don't rotate if smaller (in bytes) */
111
112#define DPRINTF(x)do { if (verbose) printf x ; } while (0) do { if (verbose) printf x ; } while (0)
113
114struct conf_entry {
115 char *log; /* Name of the log */
116 char *logbase; /* Basename of the log */
117 char *backdir; /* Directory in which to store backups */
118 uid_t uid; /* Owner of log */
119 gid_t gid; /* Group of log */
120 int numlogs; /* Number of logs to keep */
121 off_t size; /* Size cutoff to trigger trimming the log */
122 int hours; /* Hours between log trimming */
123 time_t trim_at; /* Specific time at which to do trimming */
124 mode_t permissions; /* File permissions on the log */
125 int signal; /* Signal to send (defaults to SIGHUP) */
126 int flags; /* Flags (CE_COMPACT & CE_BINARY) */
127 char *whom; /* Whom to notify if logfile changes */
128 char *pidfile; /* Path to file containing pid to signal */
129 char *runcmd; /* Command to run instead of sending a signal */
130 TAILQ_ENTRY(conf_entry)struct { struct conf_entry *tqe_next; struct conf_entry **tqe_prev
; }
next;
131};
132TAILQ_HEAD(entrylist, conf_entry)struct entrylist { struct conf_entry *tqh_first; struct conf_entry
**tqh_last; }
;
133
134struct pidinfo {
135 char *file;
136 int signal;
137};
138
139int verbose = 0; /* Print out what's going on */
140int needroot = 1; /* Root privs are necessary */
141int noaction = 0; /* Don't do anything, just show it */
142int monitormode = 0; /* Don't do monitoring by default */
143int force = 0; /* Force the logs to be rotated */
144char *conf = CONF"/etc/newsyslog.conf"; /* Configuration file to use */
145time_t timenow;
146char hostname[HOST_NAME_MAX255+1]; /* Hostname */
147char daytime[33]; /* timenow in human readable form */
148char *arcdir; /* Dir to put archives in (if it exists) */
149
150char *lstat_log(char *, size_t, int);
151char *missing_field(char *, char *, int);
152char *sob(char *);
153char *son(char *);
154int age_old_log(struct conf_entry *);
155int domonitor(struct conf_entry *);
156int isnumberstr(char *);
157int log_trim(char *);
158int movefile(char *, char *, uid_t, gid_t, mode_t);
159int stat_suffix(char *, size_t, char *, struct stat *,
160 int (*)(const char *, struct stat *));
161off_t sizefile(struct stat *);
162int parse_file(struct entrylist *, int *);
163time_t parse8601(char *);
164time_t parseDWM(char *);
165void child_killer(int);
166void compress_log(struct conf_entry *);
167void do_entry(struct conf_entry *);
168void dotrim(struct conf_entry *);
169void rotate(struct conf_entry *, const char *);
170void parse_args(int, char **);
171void run_command(char *);
172void send_signal(char *, int);
173void usage(void);
174
175int
176main(int argc, char **argv)
177{
178 struct entrylist config, runlist;
179 struct conf_entry *p, *q, *tmp;
180 struct pidinfo *pidlist, *pl;
181 int status, listlen, ret;
182 char **av;
183
184 parse_args(argc, argv);
185 argc -= optind;
186 argv += optind;
187
188 if (needroot && getuid() && geteuid())
189 errx(1, "You must be root.");
190
191 TAILQ_INIT(&config)do { (&config)->tqh_first = ((void *)0); (&config)
->tqh_last = &(&config)->tqh_first; } while (0)
;
192 TAILQ_INIT(&runlist)do { (&runlist)->tqh_first = ((void *)0); (&runlist
)->tqh_last = &(&runlist)->tqh_first; } while (
0)
;
193
194 /* Keep passwd and group files open for faster lookups. */
195 setpassent(1);
196 setgroupent(1);
197
198 ret = parse_file(&config, &listlen);
199 if (argc == 0)
200 TAILQ_CONCAT(&runlist, &config, next)do { if (!(((&config)->tqh_first) == ((void *)0))) { *
(&runlist)->tqh_last = (&config)->tqh_first; (&
config)->tqh_first->next.tqe_prev = (&runlist)->
tqh_last; (&runlist)->tqh_last = (&config)->tqh_last
; do { ((&config))->tqh_first = ((void *)0); ((&config
))->tqh_last = &((&config))->tqh_first; } while
(0); } } while (0)
;
201 else {
202 /* Only rotate specified files. */
203 listlen = 0;
204 for (av = argv; *av; av++) {
205 TAILQ_FOREACH_SAFE(q, &config, next, tmp)for ((q) = ((&config)->tqh_first); (q) != ((void *)0) &&
((tmp) = ((q)->next.tqe_next), 1); (q) = (tmp))
206 if (strcmp(*av, q->log) == 0) {
207 TAILQ_REMOVE(&config, q, next)do { if (((q)->next.tqe_next) != ((void *)0)) (q)->next
.tqe_next->next.tqe_prev = (q)->next.tqe_prev; else (&
config)->tqh_last = (q)->next.tqe_prev; *(q)->next.tqe_prev
= (q)->next.tqe_next; ; ; } while (0)
;
208 TAILQ_INSERT_TAIL(&runlist, q, next)do { (q)->next.tqe_next = ((void *)0); (q)->next.tqe_prev
= (&runlist)->tqh_last; *(&runlist)->tqh_last =
(q); (&runlist)->tqh_last = &(q)->next.tqe_next
; } while (0)
;
209 listlen++;
210 break;
211 }
212 if (q == NULL((void *)0))
213 warnx("%s: %s not found", conf, *av);
214 }
215 if (TAILQ_EMPTY(&runlist)(((&runlist)->tqh_first) == ((void *)0)))
216 errx(1, "%s: no specified log files", conf);
217 }
218
219 pidlist = calloc(listlen + 1, sizeof(struct pidinfo));
220 if (pidlist == NULL((void *)0))
221 err(1, NULL((void *)0));
222
223 signal(SIGCHLD20, child_killer);
224
225 /* Step 1, rotate all log files */
226 TAILQ_FOREACH(q, &runlist, next)for((q) = ((&runlist)->tqh_first); (q) != ((void *)0);
(q) = ((q)->next.tqe_next))
227 do_entry(q);
228
229 /* Step 2, make a list of unique pid files */
230 pl = pidlist;
231 TAILQ_FOREACH(q, &runlist, next)for((q) = ((&runlist)->tqh_first); (q) != ((void *)0);
(q) = ((q)->next.tqe_next))
{
232 if (q->flags & CE_ROTATED0x01) {
233 struct pidinfo *pltmp;
234
235 for (pltmp = pidlist; pltmp < pl; pltmp++) {
236 if ((q->pidfile && pltmp->file &&
237 strcmp(pltmp->file, q->pidfile) == 0 &&
238 pltmp->signal == q->signal) ||
239 (q->runcmd && pltmp->file &&
240 strcmp(q->runcmd, pltmp->file) == 0))
241 break;
242 }
243 if (pltmp == pl) { /* unique entry */
244 if (q->runcmd) {
245 pl->file = q->runcmd;
246 pl->signal = -1;
247 } else {
248 pl->file = q->pidfile;
249 pl->signal = q->signal;
250 }
251 pl++;
252 }
253 }
254 }
255
256 /* Step 3, send a signal or run a command */
257 for (pl--; pl >= pidlist; pl--) {
258 if (pl->file != NULL((void *)0)) {
259 if (pl->signal == -1)
260 run_command(pl->file);
261 else
262 send_signal(pl->file, pl->signal);
263 }
264 }
265 if (!noaction)
266 sleep(5);
267
268 /* Step 4, compress the log.0 file if configured to do so and free */
269 TAILQ_FOREACH(p, &runlist, next)for((p) = ((&runlist)->tqh_first); (p) != ((void *)0);
(p) = ((p)->next.tqe_next))
{
270 if ((p->flags & CE_COMPACT0x02) && (p->flags & CE_ROTATED0x01) &&
271 p->numlogs > 0)
272 compress_log(p);
273 }
274
275 /* Wait for children to finish, then exit */
276 while (waitpid(-1, &status, 0) != -1)
277 ;
278 return (ret);
279}
280
281void
282do_entry(struct conf_entry *ent)
283{
284 struct stat sb;
285 int modhours;
286 off_t size;
287
288 if (lstat(ent->log, &sb) != 0)
289 return;
290 if (!S_ISREG(sb.st_mode)((sb.st_mode & 0170000) == 0100000) &&
291 (!S_ISLNK(sb.st_mode)((sb.st_mode & 0170000) == 0120000) || !(ent->flags & CE_FOLLOW0x10))) {
292 DPRINTF(("--> not a regular file, skipping\n"))do { if (verbose) printf ("--> not a regular file, skipping\n"
) ; } while (0)
;
293 return;
294 }
295 if (S_ISLNK(sb.st_mode)((sb.st_mode & 0170000) == 0120000) && stat(ent->log, &sb) != 0) {
296 DPRINTF(("--> link target does not exist, skipping\n"))do { if (verbose) printf ("--> link target does not exist, skipping\n"
) ; } while (0)
;
297 return;
298 }
299 if (ent->uid == (uid_t)-1)
300 ent->uid = sb.st_uid;
301 if (ent->gid == (gid_t)-1)
302 ent->gid = sb.st_gid;
303
304 DPRINTF(("%s <%d%s%s%s%s>: ", ent->log, ent->numlogs,do { if (verbose) printf ("%s <%d%s%s%s%s>: ", ent->
log, ent->numlogs, (ent->flags & 0x02) ? "Z" : "", (
ent->flags & 0x04) ? "B" : "", (ent->flags & 0x10
) ? "F" : "", (ent->flags & 0x08) && monitormode
? "M" : "") ; } while (0)
305 (ent->flags & CE_COMPACT) ? "Z" : "",do { if (verbose) printf ("%s <%d%s%s%s%s>: ", ent->
log, ent->numlogs, (ent->flags & 0x02) ? "Z" : "", (
ent->flags & 0x04) ? "B" : "", (ent->flags & 0x10
) ? "F" : "", (ent->flags & 0x08) && monitormode
? "M" : "") ; } while (0)
306 (ent->flags & CE_BINARY) ? "B" : "",do { if (verbose) printf ("%s <%d%s%s%s%s>: ", ent->
log, ent->numlogs, (ent->flags & 0x02) ? "Z" : "", (
ent->flags & 0x04) ? "B" : "", (ent->flags & 0x10
) ? "F" : "", (ent->flags & 0x08) && monitormode
? "M" : "") ; } while (0)
307 (ent->flags & CE_FOLLOW) ? "F" : "",do { if (verbose) printf ("%s <%d%s%s%s%s>: ", ent->
log, ent->numlogs, (ent->flags & 0x02) ? "Z" : "", (
ent->flags & 0x04) ? "B" : "", (ent->flags & 0x10
) ? "F" : "", (ent->flags & 0x08) && monitormode
? "M" : "") ; } while (0)
308 (ent->flags & CE_MONITOR) && monitormode ? "M" : ""))do { if (verbose) printf ("%s <%d%s%s%s%s>: ", ent->
log, ent->numlogs, (ent->flags & 0x02) ? "Z" : "", (
ent->flags & 0x04) ? "B" : "", (ent->flags & 0x10
) ? "F" : "", (ent->flags & 0x08) && monitormode
? "M" : "") ; } while (0)
;
309 size = sizefile(&sb);
310 modhours = age_old_log(ent);
311 if (ent->flags & CE_TRIMAT0x20 && !force) {
312 if (timenow < ent->trim_at ||
313 difftime(timenow, ent->trim_at) >= 60 * 60) {
314 DPRINTF(("--> will trim at %s",do { if (verbose) printf ("--> will trim at %s", ctime(&
ent->trim_at)) ; } while (0)
315 ctime(&ent->trim_at)))do { if (verbose) printf ("--> will trim at %s", ctime(&
ent->trim_at)) ; } while (0)
;
316 return;
317 } else if (ent->hours <= 0) {
318 DPRINTF(("--> time is up\n"))do { if (verbose) printf ("--> time is up\n") ; } while (0
)
;
319 }
320 }
321 if (ent->size > 0)
322 DPRINTF(("size (KB): %.2f [%d] ", size / 1024.0,do { if (verbose) printf ("size (KB): %.2f [%d] ", size / 1024.0
, (int)(ent->size / 1024)) ; } while (0)
323 (int)(ent->size / 1024)))do { if (verbose) printf ("size (KB): %.2f [%d] ", size / 1024.0
, (int)(ent->size / 1024)) ; } while (0)
;
324 if (ent->hours > 0)
325 DPRINTF(("age (hr): %d [%d] ", modhours, ent->hours))do { if (verbose) printf ("age (hr): %d [%d] ", modhours, ent
->hours) ; } while (0)
;
326 if (monitormode && (ent->flags & CE_MONITOR0x08) && domonitor(ent))
327 DPRINTF(("--> monitored\n"))do { if (verbose) printf ("--> monitored\n") ; } while (0);
328 else if (!monitormode &&
329 (force || (ent->size > 0 && size >= ent->size) ||
330 (ent->hours <= 0 && (ent->flags & CE_TRIMAT0x20)) ||
331 (ent->hours > 0 && (modhours >= ent->hours || modhours < 0)
332 && ((ent->flags & CE_BINARY0x04) || size >= MIN_SIZE256)))) {
333 DPRINTF(("--> trimming log....\n"))do { if (verbose) printf ("--> trimming log....\n") ; } while
(0)
;
334 if (noaction && !verbose)
335 printf("%s <%d%s%s%s>\n", ent->log,
336 ent->numlogs,
337 (ent->flags & CE_COMPACT0x02) ? "Z" : "",
338 (ent->flags & CE_BINARY0x04) ? "B" : "",
339 (ent->flags & CE_FOLLOW0x10) ? "F" : "");
340 dotrim(ent);
341 ent->flags |= CE_ROTATED0x01;
342 } else
343 DPRINTF(("--> skipping\n"))do { if (verbose) printf ("--> skipping\n") ; } while (0);
344}
345
346/* Run the specified command */
347void
348run_command(char *cmd)
349{
350 if (noaction)
351 (void)printf("run %s\n", cmd);
352 else
353 system(cmd);
354}
355
356/* Send a signal to the pid specified by pidfile */
357void
358send_signal(char *pidfile, int signal)
359{
360 char line[BUFSIZ1024], *ep, *err;
361 pid_t pid;
362 long lval;
363 FILE *f;
364
365 if ((f = fopen(pidfile, "r")) == NULL((void *)0)) {
366 warn("can't open %s", pidfile);
367 return;
368 }
369
370 pid = 0;
371 errno(*__errno()) = 0;
372 err = NULL((void *)0);
373 if (fgets(line, sizeof(line), f)) {
374 lval = strtol(line, &ep, 10);
375 if (line[0] == '\0' || (*ep != '\0' && *ep != '\n'))
376 err = "invalid number in";
377 else if (lval < 0 || (errno(*__errno()) == ERANGE34 && lval == LONG_MAX9223372036854775807L))
378 err = "out of range number in";
379 else if (lval == 0)
380 err = "no number in";
381 else if (lval < MIN_PID2)
382 err = "preposterous process number in";
383 else
384 pid = (pid_t)lval;
385 } else {
386 if (errno(*__errno()) == 0)
387 err = "empty";
388 else
389 err = "error reading";
390 }
391 (void)fclose(f);
392
393 if (err)
394 warnx("%s pid file: %s", err, pidfile);
395 else if (noaction)
396 (void)printf("kill -%s %ld\n", sys_signame[signal], (long)pid);
397 else if (kill(pid, signal))
398 warnx("warning - could not send SIG%s to PID from pid file %s",
399 sys_signame[signal], pidfile);
400}
401
402void
403parse_args(int argc, char **argv)
404{
405 struct timeval now;
406 struct tm *tm;
407 size_t l;
408 char *p;
409 int ch;
410
411 gettimeofday(&now, NULL((void *)0));
412 timenow = now.tv_sec;
413 tm = gmtime(&now.tv_sec);
414 l = strftime(daytime, sizeof(daytime), "%FT%T", tm);
415 snprintf(daytime + l, sizeof(daytime) - l, ".%03ldZ",
416 now.tv_usec / 1000);
417
418 /* Let's get our hostname */
419 (void)gethostname(hostname, sizeof(hostname));
420
421 /* Truncate domain */
422 if ((p = strchr(hostname, '.')) != NULL((void *)0))
423 *p = '\0';
424
425 while ((ch = getopt(argc, argv, "Fmnrva:f:")) != -1) {
426 switch (ch) {
427 case 'a':
428 arcdir = optarg;
429 break;
430 case 'n':
431 noaction = 1; /* This implies needroot as off */
432 /* fall through */
433 case 'r':
434 needroot = 0;
435 break;
436 case 'v':
437 verbose = 1;
438 break;
439 case 'f':
440 conf = optarg;
441 break;
442 case 'm':
443 monitormode = 1;
444 break;
445 case 'F':
446 force = 1;
447 break;
448 default:
449 usage();
450 }
451 }
452 if (monitormode && force)
453 errx(1, "cannot specify both -m and -F flags");
454}
455
456void
457usage(void)
458{
459 extern const char *__progname;
460
461 (void)fprintf(stderr(&__sF[2]), "usage: %s [-Fmnrv] [-a directory] "
462 "[-f config_file] [log ...]\n", __progname);
463 exit(1);
464}
465
466/*
467 * Parse a configuration file and build a linked list of all the logs
468 * to process
469 */
470int
471parse_file(struct entrylist *list, int *nentries)
472{
473 char line[BUFSIZ1024], *parse, *q, *errline, *group, *tmp, *ep;
474 struct conf_entry *working;
475 struct stat sb;
476 int lineno = 0;
477 int ret = 0;
478 FILE *f;
479 long l;
480
481 if (strcmp(conf, "-") == 0)
1
Assuming the condition is false
2
Taking false branch
482 f = stdin(&__sF[0]);
483 else if ((f = fopen(conf, "r")) == NULL((void *)0))
3
Assuming the condition is false
4
Taking false branch
484 err(1, "can't open %s", conf);
485
486 *nentries = 0;
487
488nextline:
489 while (fgets(line, sizeof(line), f) != NULL((void *)0)) {
5
Assuming the condition is true
6
Loop condition is true. Entering loop body
24
Assuming the condition is false
25
Loop condition is false. Execution continues on line 767
490 lineno++;
491 tmp = sob(line);
492 if (*tmp == '\0' || *tmp == '#')
7
Assuming the condition is false
8
Assuming the condition is false
9
Taking false branch
493 continue;
494 errline = strdup(tmp);
495 if (errline == NULL((void *)0))
10
Assuming 'errline' is not equal to NULL
11
Taking false branch
496 err(1, NULL((void *)0));
497 working = calloc(1, sizeof(*working));
12
Memory is allocated
498 if (working == NULL((void *)0))
13
Assuming 'working' is not equal to NULL
14
Taking false branch
499 err(1, NULL((void *)0));
500
501 q = parse = missing_field(sob(line), errline, lineno);
502 *(parse = son(line)) = '\0';
503 working->log = strdup(q);
504 if (working->log == NULL((void *)0))
15
Assuming field 'log' is not equal to NULL
16
Taking false branch
505 err(1, NULL((void *)0));
506
507 if ((working->logbase = strrchr(working->log, '/')) != NULL((void *)0))
17
Assuming the condition is false
18
Taking false branch
508 working->logbase++;
509
510 q = parse = missing_field(sob(++parse), errline, lineno);
511 *(parse = son(parse)) = '\0';
512 if ((group = strchr(q, ':')) != NULL((void *)0) ||
19
Assuming the condition is false
21
Taking false branch
513 (group = strrchr(q, '.')) != NULL((void *)0)) {
20
Assuming the condition is false
514 *group++ = '\0';
515 if (*q == '\0') {
516 working->uid = (uid_t)-1;
517 } else if (isnumberstr(q)) {
518 working->uid = atoi(q);
519 } else if (uid_from_user(q, &working->uid) == -1) {
520 warnx("%s:%d: unknown user %s --> skipping",
521 conf, lineno, q);
522 ret = 1;
523 goto nextline;
524 }
525
526 q = group;
527 if (*q == '\0') {
528 working->gid = (gid_t)-1;
529 } else if (isnumberstr(q)) {
530 working->gid = atoi(q);
531 } else if (gid_from_group(q, &working->gid) == -1) {
532 warnx("%s:%d: unknown group %s --> skipping",
533 conf, lineno, q);
534 ret = 1;
535 goto nextline;
536 }
537
538 q = parse = missing_field(sob(++parse), errline, lineno);
539 *(parse = son(parse)) = '\0';
540 } else {
541 working->uid = (uid_t)-1;
542 working->gid = (gid_t)-1;
543 }
544
545 l = strtol(q, &ep, 8);
546 if (*ep != '\0' || l < 0 || l > ALLPERMS(0004000|0002000|0001000|0000700|0000070|0000007)) {
22
Assuming the condition is true
547 warnx("%s:%d: bad permissions: %s --> skipping", conf,
548 lineno, q);
549 ret = 1;
550 goto nextline;
23
Control jumps to line 489
551 }
552 working->permissions = (mode_t)l;
553
554 q = parse = missing_field(sob(++parse), errline, lineno);
555 *(parse = son(parse)) = '\0';
556 l = strtol(q, &ep, 10);
557 if (*ep != '\0' || l < 0 || l >= INT_MAX2147483647) {
558 warnx("%s:%d: bad number: %s --> skipping", conf,
559 lineno, q);
560 ret = 1;
561 goto nextline;
562 }
563 working->numlogs = (int)l;
564
565 q = parse = missing_field(sob(++parse), errline, lineno);
566 *(parse = son(parse)) = '\0';
567 if (isdigit((unsigned char)*q))
568 working->size = atoi(q) * 1024;
569 else
570 working->size = -1;
571
572 working->flags = 0;
573 q = parse = missing_field(sob(++parse), errline, lineno);
574 *(parse = son(parse)) = '\0';
575 l = strtol(q, &ep, 10);
576 if (l < 0 || l >= INT_MAX2147483647) {
577 warnx("%s:%d: interval out of range: %s --> skipping",
578 conf, lineno, q);
579 ret = 1;
580 goto nextline;
581 }
582 working->hours = (int)l;
583 switch (*ep) {
584 case '\0':
585 break;
586 case '@':
587 working->trim_at = parse8601(ep + 1);
588 if (working->trim_at == (time_t) - 1) {
589 warnx("%s:%d: bad time: %s --> skipping", conf,
590 lineno, q);
591 ret = 1;
592 goto nextline;
593 }
594 working->flags |= CE_TRIMAT0x20;
595 break;
596 case '$':
597 working->trim_at = parseDWM(ep + 1);
598 if (working->trim_at == (time_t) - 1) {
599 warnx("%s:%d: bad time: %s --> skipping", conf,
600 lineno, q);
601 ret = 1;
602 goto nextline;
603 }
604 working->flags |= CE_TRIMAT0x20;
605 break;
606 case '*':
607 if (q == ep)
608 break;
609 /* FALLTHROUGH */
610 default:
611 warnx("%s:%d: bad interval/at: %s --> skipping", conf,
612 lineno, q);
613 ret = 1;
614 goto nextline;
615 }
616
617 q = sob(++parse); /* Optional field */
618 if (*q == 'Z' || *q == 'z' || *q == 'B' || *q == 'b' ||
619 *q == 'M' || *q == 'm') {
620 *(parse = son(q)) = '\0';
621 while (*q) {
622 switch (*q) {
623 case 'Z':
624 case 'z':
625 working->flags |= CE_COMPACT0x02;
626 break;
627 case 'B':
628 case 'b':
629 working->flags |= CE_BINARY0x04;
630 break;
631 case 'M':
632 case 'm':
633 working->flags |= CE_MONITOR0x08;
634 break;
635 case 'F':
636 case 'f':
637 working->flags |= CE_FOLLOW0x10;
638 break;
639 default:
640 warnx("%s:%d: illegal flag: `%c'"
641 " --> skipping",
642 conf, lineno, *q);
643 ret = 1;
644 goto nextline;
645 }
646 q++;
647 }
648 } else
649 parse--; /* no flags so undo */
650
651 working->pidfile = PIDFILE"/var/run/syslog.pid";
652 working->signal = SIGHUP1;
653 working->runcmd = NULL((void *)0);
654 working->whom = NULL((void *)0);
655 for (;;) {
656 q = parse = sob(++parse); /* Optional field */
657 if (q == NULL((void *)0) || *q == '\0')
658 break;
659 if (*q == '/') {
660 *(parse = son(parse)) = '\0';
661 if (strlen(q) >= PATH_MAX1024) {
662 warnx("%s:%d: pathname too long: %s"
663 " --> skipping",
664 conf, lineno, q);
665 ret = 1;
666 goto nextline;
667 }
668 working->pidfile = strdup(q);
669 if (working->pidfile == NULL((void *)0))
670 err(1, NULL((void *)0));
671 } else if (*q == '"' && (tmp = strchr(q + 1, '"'))) {
672 *(parse = tmp) = '\0';
673 if (*++q != '\0') {
674 working->runcmd = strdup(q);
675 if (working->runcmd == NULL((void *)0))
676 err(1, NULL((void *)0));
677 }
678 working->pidfile = NULL((void *)0);
679 working->signal = -1;
680 } else if (strncmp(q, "SIG", 3) == 0) {
681 int i;
682
683 *(parse = son(parse)) = '\0';
684 for (i = 1; i < NSIG33; i++) {
685 if (!strcmp(sys_signame[i], q + 3)) {
686 working->signal = i;
687 break;
688 }
689 }
690 if (i == NSIG33) {
691 warnx("%s:%d: unknown signal: %s"
692 " --> skipping",
693 conf, lineno, q);
694 ret = 1;
695 goto nextline;
696 }
697 } else if (working->flags & CE_MONITOR0x08) {
698 *(parse = son(parse)) = '\0';
699 working->whom = strdup(q);
700 if (working->whom == NULL((void *)0))
701 err(1, NULL((void *)0));
702 } else {
703 warnx("%s:%d: unrecognized field: %s"
704 " --> skipping",
705 conf, lineno, q);
706 ret = 1;
707 goto nextline;
708 }
709 }
710 free(errline);
711
712 if ((working->flags & CE_MONITOR0x08) && working->whom == NULL((void *)0)) {
713 warnx("%s:%d: missing monitor notification field"
714 " --> skipping",
715 conf, lineno);
716 ret = 1;
717 goto nextline;
718 }
719
720 /* If there is an arcdir, set working->backdir. */
721 if (arcdir != NULL((void *)0) && working->logbase != NULL((void *)0)) {
722 if (*arcdir == '/') {
723 /* Fully qualified arcdir */
724 working->backdir = arcdir;
725 } else {
726 /* arcdir is relative to log's parent dir */
727 *(working->logbase - 1) = '\0';
728 if ((asprintf(&working->backdir, "%s/%s",
729 working->log, arcdir)) == -1)
730 err(1, NULL((void *)0));
731 *(working->logbase - 1) = '/';
732 }
733 /* Ignore arcdir if it doesn't exist. */
734 if (stat(working->backdir, &sb) != 0 ||
735 !S_ISDIR(sb.st_mode)((sb.st_mode & 0170000) == 0040000)) {
736 if (working->backdir != arcdir)
737 free(working->backdir);
738 working->backdir = NULL((void *)0);
739 }
740 } else
741 working->backdir = NULL((void *)0);
742
743 /* Make sure we can't oflow PATH_MAX */
744 if (working->backdir != NULL((void *)0)) {
745 if (snprintf(line, sizeof(line), "%s/%s.%d%s",
746 working->backdir, working->logbase,
747 working->numlogs, COMPRESS_POSTFIX".gz") >= PATH_MAX1024) {
748 warnx("%s:%d: pathname too long: %s"
749 " --> skipping", conf, lineno, q);
750 ret = 1;
751 goto nextline;
752 }
753 } else {
754 if (snprintf(line, sizeof(line), "%s.%d%s",
755 working->log, working->numlogs, COMPRESS_POSTFIX".gz")
756 >= PATH_MAX1024) {
757 warnx("%s:%d: pathname too long: %s"
758 " --> skipping", conf, lineno,
759 working->log);
760 ret = 1;
761 goto nextline;
762 }
763 }
764 TAILQ_INSERT_TAIL(list, working, next)do { (working)->next.tqe_next = ((void *)0); (working)->
next.tqe_prev = (list)->tqh_last; *(list)->tqh_last = (
working); (list)->tqh_last = &(working)->next.tqe_next
; } while (0)
;
765 (*nentries)++;
766 }
767 (void)fclose(f);
26
Potential leak of memory pointed to by 'working'
768 return (ret);
769}
770
771char *
772missing_field(char *p, char *errline, int lineno)
773{
774 if (p == NULL((void *)0) || *p == '\0') {
775 warnx("%s:%d: missing field", conf, lineno);
776 fputs(errline, stderr(&__sF[2]));
777 exit(1);
778 }
779 return (p);
780}
781
782void
783rotate(struct conf_entry *ent, const char *oldlog)
784{
785 char file1[PATH_MAX1024], file2[PATH_MAX1024], *suffix;
786 int numdays = ent->numlogs - 1;
787 int done = 0;
788
789 /* Remove old logs */
790 do {
791 (void)snprintf(file1, sizeof(file1), "%s.%d", oldlog, numdays);
792 (void)snprintf(file2, sizeof(file2), "%s.%d%s", oldlog,
793 numdays, COMPRESS_POSTFIX".gz");
794 if (noaction) {
795 printf("\trm -f %s %s\n", file1, file2);
796 done = access(file1, 0) && access(file2, 0);
797 } else {
798 done = unlink(file1) && unlink(file2);
799 }
800 numdays++;
801 } while (done == 0);
802
803 /* Move down log files */
804 for (numdays = ent->numlogs - 2; numdays >= 0; numdays--) {
805 /*
806 * If both the compressed archive and the non-compressed archive
807 * exist, we decide which to rotate based on the CE_COMPACT flag
808 */
809 (void)snprintf(file1, sizeof(file1), "%s.%d", oldlog, numdays);
810 suffix = lstat_log(file1, sizeof(file1), ent->flags);
811 if (suffix == NULL((void *)0))
812 continue;
813 (void)snprintf(file2, sizeof(file2), "%s.%d%s", oldlog,
814 numdays + 1, suffix);
815
816 if (noaction) {
817 printf("\tmv %s %s\n", file1, file2);
818 printf("\tchmod %o %s\n", ent->permissions, file2);
819 printf("\tchown %u:%u %s\n", ent->uid, ent->gid, file2);
820 } else {
821 if (rename(file1, file2))
822 warn("can't mv %s to %s", file1, file2);
823 if (chmod(file2, ent->permissions))
824 warn("can't chmod %s", file2);
825 if (chown(file2, ent->uid, ent->gid))
826 warn("can't chown %s", file2);
827 }
828 }
829}
830
831void
832dotrim(struct conf_entry *ent)
833{
834 char file1[PATH_MAX1024], file2[PATH_MAX1024], oldlog[PATH_MAX1024];
835 int fd;
836
837 /* Is there a separate backup dir? */
838 if (ent->backdir != NULL((void *)0))
839 snprintf(oldlog, sizeof(oldlog), "%s/%s", ent->backdir,
840 ent->logbase);
841 else
842 strlcpy(oldlog, ent->log, sizeof(oldlog));
843
844 if (ent->numlogs > 0)
845 rotate(ent, oldlog);
846 if (!noaction && !(ent->flags & CE_BINARY0x04))
847 (void)log_trim(ent->log);
848
849 (void)snprintf(file2, sizeof(file2), "%s.XXXXXXXXXX", ent->log);
850 if (noaction) {
851 printf("\tmktemp %s\n", file2);
852 } else {
853 if ((fd = mkstemp(file2)) == -1)
854 err(1, "can't start '%s' log", file2);
855 if (fchmod(fd, ent->permissions))
856 err(1, "can't chmod '%s' log file", file2);
857 if (fchown(fd, ent->uid, ent->gid))
858 err(1, "can't chown '%s' log file", file2);
859 (void)close(fd);
860 /* Add status message */
861 if (!(ent->flags & CE_BINARY0x04) && log_trim(file2))
862 err(1, "can't add status message to log '%s'", file2);
863 }
864
865 if (ent->numlogs == 0) {
866 if (noaction)
867 printf("\trm %s\n", ent->log);
868 else if (unlink(ent->log))
869 warn("can't rm %s", ent->log);
870 } else {
871 (void)snprintf(file1, sizeof(file1), "%s.0", oldlog);
872 if (noaction) {
873 printf("\tmv %s to %s\n", ent->log, file1);
874 printf("\tchmod %o %s\n", ent->permissions, file1);
875 printf("\tchown %u:%u %s\n", ent->uid, ent->gid, file1);
876 } else if (movefile(ent->log, file1, ent->uid, ent->gid,
877 ent->permissions))
878 warn("can't mv %s to %s", ent->log, file1);
879 }
880
881 /* Now move the new log file into place */
882 if (noaction)
883 printf("\tmv %s to %s\n", file2, ent->log);
884 else if (rename(file2, ent->log))
885 warn("can't mv %s to %s", file2, ent->log);
886}
887
888/* Log the fact that the logs were turned over */
889int
890log_trim(char *log)
891{
892 FILE *f;
893
894 if ((f = fopen(log, "a")) == NULL((void *)0))
895 return (-1);
896 (void)fprintf(f, "%s %s newsyslog[%ld]: logfile turned over\n",
897 daytime, hostname, (long)getpid());
898 if (fclose(f) == EOF(-1))
899 err(1, "log_trim: fclose");
900 return (0);
901}
902
903/* Fork off compress or gzip to compress the old log file */
904void
905compress_log(struct conf_entry *ent)
906{
907 char *base, tmp[PATH_MAX1024];
908 pid_t pid;
909
910 if (ent->backdir != NULL((void *)0))
911 snprintf(tmp, sizeof(tmp), "%s/%s.0", ent->backdir,
912 ent->logbase);
913 else
914 snprintf(tmp, sizeof(tmp), "%s.0", ent->log);
915
916 if ((base = strrchr(COMPRESS"/usr/bin/gzip", '/')) == NULL((void *)0))
917 base = COMPRESS"/usr/bin/gzip";
918 else
919 base++;
920 if (noaction) {
921 printf("%s %s\n", base, tmp);
922 return;
923 }
924 pid = fork();
925 if (pid == -1) {
926 err(1, "fork");
927 } else if (pid == 0) {
928 (void)execl(COMPRESS"/usr/bin/gzip", base, "-f", tmp, (char *)NULL((void *)0));
929 warn(COMPRESS"/usr/bin/gzip");
930 _exit(1);
931 }
932}
933
934/* Return size in bytes of a file */
935off_t
936sizefile(struct stat *sb)
937{
938 /* For sparse files, return the size based on number of blocks used. */
939 if (sb->st_size / DEV_BSIZE(1 << 9) > sb->st_blocks)
940 return (sb->st_blocks * DEV_BSIZE(1 << 9));
941 else
942 return (sb->st_size);
943}
944
945/* Return the age (in hours) of old log file (file.0), or -1 if none */
946int
947age_old_log(struct conf_entry *ent)
948{
949 char file[PATH_MAX1024];
950 struct stat sb;
951
952 if (ent->backdir != NULL((void *)0))
953 (void)snprintf(file, sizeof(file), "%s/%s.0", ent->backdir,
954 ent->logbase);
955 else
956 (void)snprintf(file, sizeof(file), "%s.0", ent->log);
957 if (ent->flags & CE_COMPACT0x02) {
958 if (stat_suffix(file, sizeof(file), COMPRESS_POSTFIX".gz", &sb,
959 stat) < 0 && stat(file, &sb) == -1)
960 return (-1);
961 } else {
962 if (stat(file, &sb) == -1 && stat_suffix(file, sizeof(file),
963 COMPRESS_POSTFIX".gz", &sb, stat) < 0)
964 return (-1);
965 }
966 return ((int)(timenow - sb.st_mtimest_mtim.tv_sec + 1800) / 3600);
967}
968
969/* Skip Over Blanks */
970char *
971sob(char *p)
972{
973 if (p == NULL((void *)0))
974 return(p);
975 while (isspace((unsigned char)*p))
976 p++;
977 return (p);
978}
979
980/* Skip Over Non-Blanks */
981char *
982son(char *p)
983{
984 while (p && *p && !isspace((unsigned char)*p))
985 p++;
986 return (p);
987}
988
989/* Check if string is actually a number */
990int
991isnumberstr(char *string)
992{
993 while (*string) {
994 if (!isdigit((unsigned char)*string++))
995 return (0);
996 }
997 return (1);
998}
999
1000int
1001domonitor(struct conf_entry *ent)
1002{
1003 char fname[PATH_MAX1024], *flog, *p, *rb = NULL((void *)0);
1004 struct stat sb, tsb;
1005 off_t osize;
1006 FILE *fp;
1007 int rd;
1008
1009 if (stat(ent->log, &sb) == -1)
1010 return (0);
1011
1012 if (noaction) {
1013 if (!verbose)
1014 printf("%s: monitored\n", ent->log);
1015 return (1);
1016 }
1017
1018 flog = strdup(ent->log);
1019 if (flog == NULL((void *)0))
1020 err(1, NULL((void *)0));
1021
1022 for (p = flog; *p != '\0'; p++) {
1023 if (*p == '/')
1024 *p = '_';
1025 }
1026 snprintf(fname, sizeof(fname), "%s/newsyslog.%s.size",
1027 STATS_DIR"/var/run", flog);
1028
1029 /* ..if it doesn't exist, simply record the current size. */
1030 if ((sb.st_size == 0) || stat(fname, &tsb) == -1)
1031 goto update;
1032
1033 fp = fopen(fname, "r");
1034 if (fp == NULL((void *)0)) {
1035 warn("%s", fname);
1036 goto cleanup;
1037 }
1038 if (fscanf(fp, "%lld\n", &osize) != 1) {
1039 fclose(fp);
1040 goto update;
1041 }
1042
1043 fclose(fp);
1044
1045 /* If the file is smaller, mark the entire thing as changed. */
1046 if (sb.st_size < osize)
1047 osize = 0;
1048
1049 /* Now see if current size is larger. */
1050 if (sb.st_size > osize) {
1051 rb = malloc(sb.st_size - osize);
1052 if (rb == NULL((void *)0))
1053 err(1, NULL((void *)0));
1054
1055 /* Open logfile, seek. */
1056 fp = fopen(ent->log, "r");
1057 if (fp == NULL((void *)0)) {
1058 warn("%s", ent->log);
1059 goto cleanup;
1060 }
1061 fseek(fp, osize, SEEK_SET0);
1062 rd = fread(rb, 1, sb.st_size - osize, fp);
1063 if (rd < 1) {
1064 warn("fread");
1065 fclose(fp);
1066 goto cleanup;
1067 }
1068 fclose(fp);
1069
1070 /* Send message. */
1071 fp = popen(SENDMAIL"/usr/sbin/sendmail" " -t", "w");
1072 if (fp == NULL((void *)0)) {
1073 warn("popen");
1074 goto cleanup;
1075 }
1076 fprintf(fp, "Auto-Submitted: auto-generated\n");
1077 fprintf(fp, "To: %s\nSubject: LOGFILE NOTIFICATION: %s\n\n\n",
1078 ent->whom, ent->log);
1079 fwrite(rb, 1, rd, fp);
1080 fputs("\n\n", fp);
1081
1082 pclose(fp);
1083 }
1084update:
1085 /* Reopen for writing and update file. */
1086 fp = fopen(fname, "w");
1087 if (fp == NULL((void *)0)) {
1088 warn("%s", fname);
1089 goto cleanup;
1090 }
1091 fprintf(fp, "%lld\n", (long long)sb.st_size);
1092 fclose(fp);
1093
1094cleanup:
1095 free(flog);
1096 free(rb);
1097 return (1);
1098}
1099
1100/* ARGSUSED */
1101void
1102child_killer(int signo)
1103{
1104 int save_errno = errno(*__errno());
1105 int status;
1106
1107 while (waitpid(-1, &status, WNOHANG1) > 0)
1108 ;
1109 errno(*__errno()) = save_errno;
1110}
1111
1112int
1113stat_suffix(char *file, size_t size, char *suffix, struct stat *sp,
1114 int (*func)(const char *, struct stat *))
1115{
1116 size_t n;
1117
1118 n = strlcat(file, suffix, size);
1119 if (n < size && func(file, sp) == 0)
1120 return (0);
1121 file[n - strlen(suffix)] = '\0';
1122 return (-1);
1123}
1124
1125/*
1126 * lstat() a log, possibly appending a suffix; order is based on flags.
1127 * Returns the suffix appended (may be empty string) or NULL if no file.
1128 */
1129char *
1130lstat_log(char *file, size_t size, int flags)
1131{
1132 struct stat sb;
1133
1134 if (flags & CE_COMPACT0x02) {
1135 if (stat_suffix(file, size, COMPRESS_POSTFIX".gz", &sb, lstat) == 0)
1136 return (COMPRESS_POSTFIX".gz");
1137 if (lstat(file, &sb) == 0)
1138 return ("");
1139 } else {
1140 if (lstat(file, &sb) == 0)
1141 return ("");
1142 if (stat_suffix(file, size, COMPRESS_POSTFIX".gz", &sb, lstat) == 0)
1143 return (COMPRESS_POSTFIX".gz");
1144
1145 }
1146 return (NULL((void *)0));
1147}
1148
1149/*
1150 * Parse a limited subset of ISO 8601. The specific format is as follows:
1151 *
1152 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter)
1153 *
1154 * We don't accept a timezone specification; missing fields (including timezone)
1155 * are defaulted to the current date but time zero.
1156 */
1157time_t
1158parse8601(char *s)
1159{
1160 struct tm tm, *tmp;
1161 char *t;
1162 long l;
1163
1164 tmp = localtime(&timenow);
1165 tm = *tmp;
1166
1167 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1168
1169 l = strtol(s, &t, 10);
1170 if (l < 0 || l >= INT_MAX2147483647 || (*t != '\0' && *t != 'T'))
1171 return (-1);
1172
1173 /*
1174 * Now t points either to the end of the string (if no time was
1175 * provided) or to the letter `T' which separates date and time in
1176 * ISO 8601. The pointer arithmetic is the same for either case.
1177 */
1178 switch (t - s) {
1179 case 8:
1180 tm.tm_year = ((l / 1000000) - 19) * 100;
1181 l = l % 1000000;
1182 case 6:
1183 tm.tm_year -= tm.tm_year % 100;
1184 tm.tm_year += l / 10000;
1185 l = l % 10000;
1186 case 4:
1187 tm.tm_mon = (l / 100) - 1;
1188 l = l % 100;
1189 case 2:
1190 tm.tm_mday = l;
1191 case 0:
1192 break;
1193 default:
1194 return (-1);
1195 }
1196
1197 /* sanity check */
1198 if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 ||
1199 tm.tm_mday < 1 || tm.tm_mday > 31)
1200 return (-1);
1201
1202 if (*t != '\0') {
1203 s = ++t;
1204 l = strtol(s, &t, 10);
1205 if (l < 0 || l >= INT_MAX2147483647 ||
1206 (*t != '\0' && !isspace((unsigned char)*t)))
1207 return (-1);
1208
1209 switch (t - s) {
1210 case 6:
1211 tm.tm_sec = l % 100;
1212 l /= 100;
1213 case 4:
1214 tm.tm_min = l % 100;
1215 l /= 100;
1216 case 2:
1217 tm.tm_hour = l;
1218 case 0:
1219 break;
1220 default:
1221 return (-1);
1222 }
1223
1224 /* sanity check */
1225 if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 ||
1226 tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23)
1227 return (-1);
1228 }
1229 return (mktime(&tm));
1230}
1231
1232/*-
1233 * Parse a cyclic time specification, the format is as follows:
1234 *
1235 * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
1236 *
1237 * to rotate a logfile cyclic at
1238 *
1239 * - every day (D) within a specific hour (hh) (hh = 0...23)
1240 * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday)
1241 * - once a month (M) at a specific day (d) (d = 1..31,l|L)
1242 *
1243 * We don't accept a timezone specification; missing fields
1244 * are defaulted to the current date but time zero.
1245 */
1246time_t
1247parseDWM(char *s)
1248{
1249 static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
1250 int WMseen = 0, Dseen = 0, nd;
1251 struct tm tm, *tmp;
1252 char *t;
1253 long l;
1254
1255 tmp = localtime(&timenow);
1256 tm = *tmp;
1257
1258 /* set no. of days per month */
1259
1260 nd = mtab[tm.tm_mon];
1261
1262 if (tm.tm_mon == 1) {
1263 if (((tm.tm_year + 1900) % 4 == 0) &&
1264 ((tm.tm_year + 1900) % 100 != 0) &&
1265 ((tm.tm_year + 1900) % 400 == 0)) {
1266 nd++; /* leap year, 29 days in february */
1267 }
1268 }
1269 tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1270
1271 for (;;) {
1272 switch (*s) {
1273 case 'D':
1274 if (Dseen)
1275 return (-1);
1276 Dseen++;
1277 s++;
1278 l = strtol(s, &t, 10);
1279 if (l < 0 || l > 23)
1280 return (-1);
1281 tm.tm_hour = l;
1282 break;
1283
1284 case 'W':
1285 if (WMseen)
1286 return (-1);
1287 WMseen++;
1288 s++;
1289 l = strtol(s, &t, 10);
1290 if (l < 0 || l > 6)
1291 return (-1);
1292 if (l != tm.tm_wday) {
1293 int save;
1294
1295 if (l < tm.tm_wday) {
1296 save = 6 - tm.tm_wday;
1297 save += (l + 1);
1298 } else {
1299 save = l - tm.tm_wday;
1300 }
1301
1302 tm.tm_mday += save;
1303
1304 if (tm.tm_mday > nd) {
1305 tm.tm_mon++;
1306 tm.tm_mday = tm.tm_mday - nd;
1307 }
1308 }
1309 break;
1310
1311 case 'M':
1312 if (WMseen)
1313 return (-1);
1314 WMseen++;
1315 s++;
1316 if (tolower((unsigned char)*s) == 'l') {
1317 tm.tm_mday = nd;
1318 s++;
1319 t = s;
1320 } else {
1321 l = strtol(s, &t, 10);
1322 if (l < 1 || l > 31)
1323 return (-1);
1324
1325 if (l > nd)
1326 return (-1);
1327 if (l < tm.tm_mday)
1328 tm.tm_mon++;
1329 tm.tm_mday = l;
1330 }
1331 break;
1332
1333 default:
1334 return (-1);
1335 break;
1336 }
1337
1338 if (*t == '\0' || isspace((unsigned char)*t))
1339 break;
1340 else
1341 s = t;
1342 }
1343 return (mktime(&tm));
1344}
1345
1346/*
1347 * Move a file using rename(2) if possible and copying if not.
1348 */
1349int
1350movefile(char *from, char *to, uid_t owner_uid, gid_t group_gid, mode_t perm)
1351{
1352 FILE *src, *dst;
1353 int i;
1354
1355 /* try rename(2) first */
1356 if (rename(from, to) == 0) {
1357 if (chmod(to, perm))
1358 warn("can't chmod %s", to);
1359 if (chown(to, owner_uid, group_gid))
1360 warn("can't chown %s", to);
1361 return (0);
1362 } else if (errno(*__errno()) != EXDEV18)
1363 return (-1);
1364
1365 /* different filesystem, have to copy the file */
1366 if ((src = fopen(from, "r")) == NULL((void *)0))
1367 err(1, "can't fopen %s for reading", from);
1368 if ((dst = fopen(to, "w")) == NULL((void *)0))
1369 err(1, "can't fopen %s for writing", to);
1370 if (fchmod(fileno(dst)(!__isthreaded ? ((dst)->_file) : (fileno)(dst)), perm))
1371 err(1, "can't fchmod %s", to);
1372 if (fchown(fileno(dst)(!__isthreaded ? ((dst)->_file) : (fileno)(dst)), owner_uid, group_gid))
1373 err(1, "can't fchown %s", to);
1374
1375 while ((i = getc(src)(!__isthreaded ? (--(src)->_r < 0 ? __srget(src) : (int
)(*(src)->_p++)) : (getc)(src))
) != EOF(-1)) {
1376 if ((putc(i, dst)(!__isthreaded ? __sputc(i, dst) : (putc)(i, dst))) == EOF(-1))
1377 err(1, "error writing to %s", to);
1378 }
1379
1380 if (ferror(src)(!__isthreaded ? (((src)->_flags & 0x0040) != 0) : (ferror
)(src))
)
1381 err(1, "error reading from %s", from);
1382 if ((fclose(src)) != 0)
1383 err(1, "can't fclose %s", from);
1384 if ((fclose(dst)) != 0)
1385 err(1, "can't fclose %s", to);
1386 if ((unlink(from)) != 0)
1387 err(1, "can't unlink %s", from);
1388
1389 return (0);
1390}