File: | src/usr.bin/newsyslog/newsyslog.c |
Warning: | line 767, column 8 Potential leak of memory pointed to by 'working' |
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); | |||
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 | } |