File: | src/usr.bin/newsyslog/newsyslog.c |
Warning: | line 501, column 7 Although the value stored to 'parse' is used in the enclosing expression, the value is never actually read from 'parse' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* $OpenBSD: newsyslog.c,v 1.113 2023/03/08 04:43:12 guenther 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 | |
114 | struct 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 | }; |
132 | TAILQ_HEAD(entrylist, conf_entry)struct entrylist { struct conf_entry *tqh_first; struct conf_entry **tqh_last; }; |
133 | |
134 | struct pidinfo { |
135 | char *file; |
136 | int signal; |
137 | }; |
138 | |
139 | int verbose = 0; /* Print out what's going on */ |
140 | int needroot = 1; /* Root privs are necessary */ |
141 | int noaction = 0; /* Don't do anything, just show it */ |
142 | int monitormode = 0; /* Don't do monitoring by default */ |
143 | int force = 0; /* Force the logs to be rotated */ |
144 | char *conf = CONF"/etc/newsyslog.conf"; /* Configuration file to use */ |
145 | time_t timenow; |
146 | char hostname[HOST_NAME_MAX255+1]; /* Hostname */ |
147 | char daytime[33]; /* timenow in human readable form */ |
148 | char *arcdir; /* Dir to put archives in (if it exists) */ |
149 | |
150 | char *lstat_log(char *, size_t, int); |
151 | char *missing_field(char *, char *, int); |
152 | char *sob(char *); |
153 | char *son(char *); |
154 | int age_old_log(struct conf_entry *); |
155 | int domonitor(struct conf_entry *); |
156 | int isnumberstr(char *); |
157 | int log_trim(char *); |
158 | int movefile(char *, char *, uid_t, gid_t, mode_t); |
159 | int stat_suffix(char *, size_t, char *, struct stat *, |
160 | int (*)(const char *, struct stat *)); |
161 | off_t sizefile(struct stat *); |
162 | int parse_file(struct entrylist *, int *); |
163 | time_t parse8601(char *); |
164 | time_t parseDWM(char *); |
165 | void child_killer(int); |
166 | void compress_log(struct conf_entry *); |
167 | void do_entry(struct conf_entry *); |
168 | void dotrim(struct conf_entry *); |
169 | void rotate(struct conf_entry *, const char *); |
170 | void parse_args(int, char **); |
171 | void run_command(char *); |
172 | void send_signal(char *, int); |
173 | void usage(void); |
174 | |
175 | int |
176 | main(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 | |
281 | void |
282 | do_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 */ |
347 | void |
348 | run_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 */ |
357 | void |
358 | send_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_MAX0x7fffffffffffffffL)) |
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 | |
402 | void |
403 | parse_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 | |
456 | void |
457 | usage(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 | */ |
470 | int |
471 | parse_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) |
482 | f = stdin(&__sF[0]); |
483 | else if ((f = fopen(conf, "r")) == NULL((void *)0)) |
484 | err(1, "can't open %s", conf); |
485 | |
486 | *nentries = 0; |
487 | |
488 | nextline: |
489 | while (fgets(line, sizeof(line), f) != NULL((void *)0)) { |
490 | lineno++; |
491 | tmp = sob(line); |
492 | if (*tmp == '\0' || *tmp == '#') |
493 | continue; |
494 | errline = strdup(tmp); |
495 | if (errline == NULL((void *)0)) |
496 | err(1, NULL((void *)0)); |
497 | working = calloc(1, sizeof(*working)); |
498 | if (working == NULL((void *)0)) |
499 | err(1, NULL((void *)0)); |
500 | |
501 | q = parse = missing_field(sob(line), errline, lineno); |
Although the value stored to 'parse' is used in the enclosing expression, the value is never actually read from 'parse' | |
502 | *(parse = son(line)) = '\0'; |
503 | working->log = strdup(q); |
504 | if (working->log == NULL((void *)0)) |
505 | err(1, NULL((void *)0)); |
506 | |
507 | if ((working->logbase = strrchr(working->log, '/')) != NULL((void *)0)) |
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) || |
513 | (group = strrchr(q, '.')) != NULL((void *)0)) { |
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)) { |
547 | warnx("%s:%d: bad permissions: %s --> skipping", conf, |
548 | lineno, q); |
549 | ret = 1; |
550 | goto nextline; |
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_MAX0x7fffffff) { |
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_MAX0x7fffffff) { |
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); |
768 | return (ret); |
769 | } |
770 | |
771 | char * |
772 | missing_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 | |
782 | void |
783 | rotate(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 | |
831 | void |
832 | dotrim(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 */ |
889 | int |
890 | log_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 */ |
904 | void |
905 | compress_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 */ |
935 | off_t |
936 | sizefile(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 */ |
946 | int |
947 | age_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 */ |
970 | char * |
971 | sob(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 */ |
981 | char * |
982 | son(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 */ |
990 | int |
991 | isnumberstr(char *string) |
992 | { |
993 | while (*string) { |
994 | if (!isdigit((unsigned char)*string++)) |
995 | return (0); |
996 | } |
997 | return (1); |
998 | } |
999 | |
1000 | int |
1001 | domonitor(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 | } |
1084 | update: |
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 | |
1094 | cleanup: |
1095 | free(flog); |
1096 | free(rb); |
1097 | return (1); |
1098 | } |
1099 | |
1100 | void |
1101 | child_killer(int signo) |
1102 | { |
1103 | int save_errno = errno(*__errno()); |
1104 | int status; |
1105 | |
1106 | while (waitpid(-1, &status, WNOHANG0x01) > 0) |
1107 | ; |
1108 | errno(*__errno()) = save_errno; |
1109 | } |
1110 | |
1111 | int |
1112 | stat_suffix(char *file, size_t size, char *suffix, struct stat *sp, |
1113 | int (*func)(const char *, struct stat *)) |
1114 | { |
1115 | size_t n; |
1116 | |
1117 | n = strlcat(file, suffix, size); |
1118 | if (n < size && func(file, sp) == 0) |
1119 | return (0); |
1120 | file[n - strlen(suffix)] = '\0'; |
1121 | return (-1); |
1122 | } |
1123 | |
1124 | /* |
1125 | * lstat() a log, possibly appending a suffix; order is based on flags. |
1126 | * Returns the suffix appended (may be empty string) or NULL if no file. |
1127 | */ |
1128 | char * |
1129 | lstat_log(char *file, size_t size, int flags) |
1130 | { |
1131 | struct stat sb; |
1132 | |
1133 | if (flags & CE_COMPACT0x02) { |
1134 | if (stat_suffix(file, size, COMPRESS_POSTFIX".gz", &sb, lstat) == 0) |
1135 | return (COMPRESS_POSTFIX".gz"); |
1136 | if (lstat(file, &sb) == 0) |
1137 | return (""); |
1138 | } else { |
1139 | if (lstat(file, &sb) == 0) |
1140 | return (""); |
1141 | if (stat_suffix(file, size, COMPRESS_POSTFIX".gz", &sb, lstat) == 0) |
1142 | return (COMPRESS_POSTFIX".gz"); |
1143 | |
1144 | } |
1145 | return (NULL((void *)0)); |
1146 | } |
1147 | |
1148 | /* |
1149 | * Parse a limited subset of ISO 8601. The specific format is as follows: |
1150 | * |
1151 | * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) |
1152 | * |
1153 | * We don't accept a timezone specification; missing fields (including timezone) |
1154 | * are defaulted to the current date but time zero. |
1155 | */ |
1156 | time_t |
1157 | parse8601(char *s) |
1158 | { |
1159 | struct tm tm, *tmp; |
1160 | char *t; |
1161 | long l; |
1162 | |
1163 | tmp = localtime(&timenow); |
1164 | tm = *tmp; |
1165 | |
1166 | tm.tm_hour = tm.tm_min = tm.tm_sec = 0; |
1167 | |
1168 | l = strtol(s, &t, 10); |
1169 | if (l < 0 || l >= INT_MAX0x7fffffff || (*t != '\0' && *t != 'T')) |
1170 | return (-1); |
1171 | |
1172 | /* |
1173 | * Now t points either to the end of the string (if no time was |
1174 | * provided) or to the letter `T' which separates date and time in |
1175 | * ISO 8601. The pointer arithmetic is the same for either case. |
1176 | */ |
1177 | switch (t - s) { |
1178 | case 8: |
1179 | tm.tm_year = ((l / 1000000) - 19) * 100; |
1180 | l = l % 1000000; |
1181 | case 6: |
1182 | tm.tm_year -= tm.tm_year % 100; |
1183 | tm.tm_year += l / 10000; |
1184 | l = l % 10000; |
1185 | case 4: |
1186 | tm.tm_mon = (l / 100) - 1; |
1187 | l = l % 100; |
1188 | case 2: |
1189 | tm.tm_mday = l; |
1190 | case 0: |
1191 | break; |
1192 | default: |
1193 | return (-1); |
1194 | } |
1195 | |
1196 | /* sanity check */ |
1197 | if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 || |
1198 | tm.tm_mday < 1 || tm.tm_mday > 31) |
1199 | return (-1); |
1200 | |
1201 | if (*t != '\0') { |
1202 | s = ++t; |
1203 | l = strtol(s, &t, 10); |
1204 | if (l < 0 || l >= INT_MAX0x7fffffff || |
1205 | (*t != '\0' && !isspace((unsigned char)*t))) |
1206 | return (-1); |
1207 | |
1208 | switch (t - s) { |
1209 | case 6: |
1210 | tm.tm_sec = l % 100; |
1211 | l /= 100; |
1212 | case 4: |
1213 | tm.tm_min = l % 100; |
1214 | l /= 100; |
1215 | case 2: |
1216 | tm.tm_hour = l; |
1217 | case 0: |
1218 | break; |
1219 | default: |
1220 | return (-1); |
1221 | } |
1222 | |
1223 | /* sanity check */ |
1224 | if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 || |
1225 | tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) |
1226 | return (-1); |
1227 | } |
1228 | return (mktime(&tm)); |
1229 | } |
1230 | |
1231 | /*- |
1232 | * Parse a cyclic time specification, the format is as follows: |
1233 | * |
1234 | * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] |
1235 | * |
1236 | * to rotate a logfile cyclic at |
1237 | * |
1238 | * - every day (D) within a specific hour (hh) (hh = 0...23) |
1239 | * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) |
1240 | * - once a month (M) at a specific day (d) (d = 1..31,l|L) |
1241 | * |
1242 | * We don't accept a timezone specification; missing fields |
1243 | * are defaulted to the current date but time zero. |
1244 | */ |
1245 | time_t |
1246 | parseDWM(char *s) |
1247 | { |
1248 | static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; |
1249 | int WMseen = 0, Dseen = 0, nd; |
1250 | struct tm tm, *tmp; |
1251 | char *t; |
1252 | long l; |
1253 | |
1254 | tmp = localtime(&timenow); |
1255 | tm = *tmp; |
1256 | |
1257 | /* set no. of days per month */ |
1258 | |
1259 | nd = mtab[tm.tm_mon]; |
1260 | |
1261 | if (tm.tm_mon == 1) { |
1262 | if (((tm.tm_year + 1900) % 4 == 0) && |
1263 | ((tm.tm_year + 1900) % 100 != 0) && |
1264 | ((tm.tm_year + 1900) % 400 == 0)) { |
1265 | nd++; /* leap year, 29 days in february */ |
1266 | } |
1267 | } |
1268 | tm.tm_hour = tm.tm_min = tm.tm_sec = 0; |
1269 | |
1270 | for (;;) { |
1271 | switch (*s) { |
1272 | case 'D': |
1273 | if (Dseen) |
1274 | return (-1); |
1275 | Dseen++; |
1276 | s++; |
1277 | l = strtol(s, &t, 10); |
1278 | if (l < 0 || l > 23) |
1279 | return (-1); |
1280 | tm.tm_hour = l; |
1281 | break; |
1282 | |
1283 | case 'W': |
1284 | if (WMseen) |
1285 | return (-1); |
1286 | WMseen++; |
1287 | s++; |
1288 | l = strtol(s, &t, 10); |
1289 | if (l < 0 || l > 6) |
1290 | return (-1); |
1291 | if (l != tm.tm_wday) { |
1292 | int save; |
1293 | |
1294 | if (l < tm.tm_wday) { |
1295 | save = 6 - tm.tm_wday; |
1296 | save += (l + 1); |
1297 | } else { |
1298 | save = l - tm.tm_wday; |
1299 | } |
1300 | |
1301 | tm.tm_mday += save; |
1302 | |
1303 | if (tm.tm_mday > nd) { |
1304 | tm.tm_mon++; |
1305 | tm.tm_mday = tm.tm_mday - nd; |
1306 | } |
1307 | } |
1308 | break; |
1309 | |
1310 | case 'M': |
1311 | if (WMseen) |
1312 | return (-1); |
1313 | WMseen++; |
1314 | s++; |
1315 | if (tolower((unsigned char)*s) == 'l') { |
1316 | tm.tm_mday = nd; |
1317 | s++; |
1318 | t = s; |
1319 | } else { |
1320 | l = strtol(s, &t, 10); |
1321 | if (l < 1 || l > 31) |
1322 | return (-1); |
1323 | |
1324 | if (l > nd) |
1325 | return (-1); |
1326 | if (l < tm.tm_mday) |
1327 | tm.tm_mon++; |
1328 | tm.tm_mday = l; |
1329 | } |
1330 | break; |
1331 | |
1332 | default: |
1333 | return (-1); |
1334 | break; |
1335 | } |
1336 | |
1337 | if (*t == '\0' || isspace((unsigned char)*t)) |
1338 | break; |
1339 | else |
1340 | s = t; |
1341 | } |
1342 | return (mktime(&tm)); |
1343 | } |
1344 | |
1345 | /* |
1346 | * Move a file using rename(2) if possible and copying if not. |
1347 | */ |
1348 | int |
1349 | movefile(char *from, char *to, uid_t owner_uid, gid_t group_gid, mode_t perm) |
1350 | { |
1351 | FILE *src, *dst; |
1352 | int i; |
1353 | |
1354 | /* try rename(2) first */ |
1355 | if (rename(from, to) == 0) { |
1356 | if (chmod(to, perm)) |
1357 | warn("can't chmod %s", to); |
1358 | if (chown(to, owner_uid, group_gid)) |
1359 | warn("can't chown %s", to); |
1360 | return (0); |
1361 | } else if (errno(*__errno()) != EXDEV18) |
1362 | return (-1); |
1363 | |
1364 | /* different filesystem, have to copy the file */ |
1365 | if ((src = fopen(from, "r")) == NULL((void *)0)) |
1366 | err(1, "can't fopen %s for reading", from); |
1367 | if ((dst = fopen(to, "w")) == NULL((void *)0)) |
1368 | err(1, "can't fopen %s for writing", to); |
1369 | if (fchmod(fileno(dst)(!__isthreaded ? ((dst)->_file) : (fileno)(dst)), perm)) |
1370 | err(1, "can't fchmod %s", to); |
1371 | if (fchown(fileno(dst)(!__isthreaded ? ((dst)->_file) : (fileno)(dst)), owner_uid, group_gid)) |
1372 | err(1, "can't fchown %s", to); |
1373 | |
1374 | while ((i = getc(src)(!__isthreaded ? (--(src)->_r < 0 ? __srget(src) : (int )(*(src)->_p++)) : (getc)(src))) != EOF(-1)) { |
1375 | if ((putc(i, dst)(!__isthreaded ? __sputc(i, dst) : (putc)(i, dst))) == EOF(-1)) |
1376 | err(1, "error writing to %s", to); |
1377 | } |
1378 | |
1379 | if (ferror(src)(!__isthreaded ? (((src)->_flags & 0x0040) != 0) : (ferror )(src))) |
1380 | err(1, "error reading from %s", from); |
1381 | if ((fclose(src)) != 0) |
1382 | err(1, "can't fclose %s", from); |
1383 | if ((fclose(dst)) != 0) |
1384 | err(1, "can't fclose %s", to); |
1385 | if ((unlink(from)) != 0) |
1386 | err(1, "can't unlink %s", from); |
1387 | |
1388 | return (0); |
1389 | } |