GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
Line | Branch | Exec | Source |
1 |
/* $OpenBSD: newsyslog.c,v 1.108 2017/07/24 12:57:01 jca Exp $ */ |
||
2 |
|||
3 |
/* |
||
4 |
* Copyright (c) 1999, 2002, 2003 Todd C. Miller <Todd.Miller@courtesan.com> |
||
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" |
||
75 |
#define PIDFILE "/var/run/syslog.pid" |
||
76 |
#define COMPRESS "/usr/bin/gzip" |
||
77 |
#define COMPRESS_POSTFIX ".gz" |
||
78 |
#define STATS_DIR "/var/run" |
||
79 |
#define 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.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_ROTATED 0x01 /* Log file has been rotated */ |
||
102 |
#define CE_COMPACT 0x02 /* Compact the archived log files */ |
||
103 |
#define CE_BINARY 0x04 /* Logfile is in binary, don't add */ |
||
104 |
/* status messages */ |
||
105 |
#define CE_MONITOR 0x08 /* Monitor for changes */ |
||
106 |
#define CE_FOLLOW 0x10 /* Follow symbolic links */ |
||
107 |
#define CE_TRIMAT 0x20 /* Trim at a specific time */ |
||
108 |
|||
109 |
#define MIN_PID 2 /* Don't touch pids lower than this */ |
||
110 |
#define MIN_SIZE 256 /* Don't rotate if smaller (in bytes) */ |
||
111 |
|||
112 |
#define DPRINTF(x) 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) next; |
||
131 |
}; |
||
132 |
TAILQ_HEAD(entrylist, conf_entry); |
||
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; /* Configuration file to use */ |
||
145 |
time_t timenow; |
||
146 |
char hostname[HOST_NAME_MAX+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 |
48 |
struct entrylist config, runlist; |
|
179 |
struct conf_entry *p, *q, *tmp; |
||
180 |
struct pidinfo *pidlist, *pl; |
||
181 |
24 |
int status, listlen, ret; |
|
182 |
char **av; |
||
183 |
|||
184 |
24 |
parse_args(argc, argv); |
|
185 |
24 |
argc -= optind; |
|
186 |
24 |
argv += optind; |
|
187 |
|||
188 |
✓✗✗✓ ✗✗ |
48 |
if (needroot && getuid() && geteuid()) |
189 |
errx(1, "You must be root."); |
||
190 |
|||
191 |
24 |
TAILQ_INIT(&config); |
|
192 |
24 |
TAILQ_INIT(&runlist); |
|
193 |
|||
194 |
24 |
ret = parse_file(&config, &listlen); |
|
195 |
✓✗ | 24 |
if (argc == 0) |
196 |
✓✗ | 48 |
TAILQ_CONCAT(&runlist, &config, next); |
197 |
else { |
||
198 |
/* Only rotate specified files. */ |
||
199 |
listlen = 0; |
||
200 |
for (av = argv; *av; av++) { |
||
201 |
TAILQ_FOREACH_SAFE(q, &config, next, tmp) |
||
202 |
if (strcmp(*av, q->log) == 0) { |
||
203 |
TAILQ_REMOVE(&config, q, next); |
||
204 |
TAILQ_INSERT_TAIL(&runlist, q, next); |
||
205 |
listlen++; |
||
206 |
break; |
||
207 |
} |
||
208 |
if (q == NULL) |
||
209 |
warnx("%s: %s not found", conf, *av); |
||
210 |
} |
||
211 |
if (TAILQ_EMPTY(&runlist)) |
||
212 |
errx(1, "%s: no specified log files", conf); |
||
213 |
} |
||
214 |
|||
215 |
24 |
pidlist = calloc(listlen + 1, sizeof(struct pidinfo)); |
|
216 |
✗✓ | 24 |
if (pidlist == NULL) |
217 |
err(1, NULL); |
||
218 |
|||
219 |
24 |
signal(SIGCHLD, child_killer); |
|
220 |
|||
221 |
/* Step 1, rotate all log files */ |
||
222 |
✓✓ | 624 |
TAILQ_FOREACH(q, &runlist, next) |
223 |
288 |
do_entry(q); |
|
224 |
|||
225 |
/* Step 2, make a list of unique pid files */ |
||
226 |
pl = pidlist; |
||
227 |
✓✓ | 624 |
TAILQ_FOREACH(q, &runlist, next) { |
228 |
✓✓ | 288 |
if (q->flags & CE_ROTATED) { |
229 |
struct pidinfo *pltmp; |
||
230 |
|||
231 |
✗✓ | 2 |
for (pltmp = pidlist; pltmp < pl; pltmp++) { |
232 |
if ((q->pidfile && pltmp->file && |
||
233 |
strcmp(pltmp->file, q->pidfile) == 0 && |
||
234 |
pltmp->signal == q->signal) || |
||
235 |
(q->runcmd && pltmp->file && |
||
236 |
strcmp(q->runcmd, pltmp->file) == 0)) |
||
237 |
break; |
||
238 |
} |
||
239 |
✓✗ | 1 |
if (pltmp == pl) { /* unique entry */ |
240 |
✗✓ | 1 |
if (q->runcmd) { |
241 |
pl->file = q->runcmd; |
||
242 |
pl->signal = -1; |
||
243 |
} else { |
||
244 |
1 |
pl->file = q->pidfile; |
|
245 |
1 |
pl->signal = q->signal; |
|
246 |
} |
||
247 |
1 |
pl++; |
|
248 |
1 |
} |
|
249 |
1 |
} |
|
250 |
} |
||
251 |
|||
252 |
/* Step 3, send a signal or run a command */ |
||
253 |
✓✓ | 50 |
for (pl--; pl >= pidlist; pl--) { |
254 |
✓✗ | 1 |
if (pl->file != NULL) { |
255 |
✗✓ | 1 |
if (pl->signal == -1) |
256 |
run_command(pl->file); |
||
257 |
else |
||
258 |
1 |
send_signal(pl->file, pl->signal); |
|
259 |
} |
||
260 |
} |
||
261 |
✓✗ | 24 |
if (!noaction) |
262 |
24 |
sleep(5); |
|
263 |
|||
264 |
/* Step 4, compress the log.0 file if configured to do so and free */ |
||
265 |
✓✓ | 624 |
TAILQ_FOREACH(p, &runlist, next) { |
266 |
✓✓✓✓ ✓✗ |
553 |
if ((p->flags & CE_COMPACT) && (p->flags & CE_ROTATED) && |
267 |
1 |
p->numlogs > 0) |
|
268 |
1 |
compress_log(p); |
|
269 |
} |
||
270 |
|||
271 |
/* Wait for children to finish, then exit */ |
||
272 |
✗✓ | 24 |
while (waitpid(-1, &status, 0) != -1) |
273 |
; |
||
274 |
24 |
return (ret); |
|
275 |
24 |
} |
|
276 |
|||
277 |
void |
||
278 |
do_entry(struct conf_entry *ent) |
||
279 |
{ |
||
280 |
576 |
struct stat sb; |
|
281 |
int modhours; |
||
282 |
off_t size; |
||
283 |
|||
284 |
✓✓ | 288 |
if (lstat(ent->log, &sb) != 0) |
285 |
48 |
return; |
|
286 |
✗✓✗✗ |
240 |
if (!S_ISREG(sb.st_mode) && |
287 |
(!S_ISLNK(sb.st_mode) || !(ent->flags & CE_FOLLOW))) { |
||
288 |
DPRINTF(("--> not a regular file, skipping\n")); |
||
289 |
return; |
||
290 |
} |
||
291 |
✗✓✗✗ |
240 |
if (S_ISLNK(sb.st_mode) && stat(ent->log, &sb) != 0) { |
292 |
DPRINTF(("--> link target does not exist, skipping\n")); |
||
293 |
return; |
||
294 |
} |
||
295 |
✓✓ | 240 |
if (ent->uid == (uid_t)-1) |
296 |
192 |
ent->uid = sb.st_uid; |
|
297 |
✓✓ | 240 |
if (ent->gid == (gid_t)-1) |
298 |
192 |
ent->gid = sb.st_gid; |
|
299 |
|||
300 |
✗✓ | 240 |
DPRINTF(("%s <%d%s%s%s%s>: ", ent->log, ent->numlogs, |
301 |
(ent->flags & CE_COMPACT) ? "Z" : "", |
||
302 |
(ent->flags & CE_BINARY) ? "B" : "", |
||
303 |
(ent->flags & CE_FOLLOW) ? "F" : "", |
||
304 |
(ent->flags & CE_MONITOR) && monitormode ? "M" : "")); |
||
305 |
240 |
size = sizefile(&sb); |
|
306 |
240 |
modhours = age_old_log(ent); |
|
307 |
✓✓ | 240 |
if (ent->flags & CE_TRIMAT && !force) { |
308 |
✗✓✗✗ |
24 |
if (timenow < ent->trim_at || |
309 |
difftime(timenow, ent->trim_at) >= 60 * 60) { |
||
310 |
✗✓ | 24 |
DPRINTF(("--> will trim at %s", |
311 |
ctime(&ent->trim_at))); |
||
312 |
24 |
return; |
|
313 |
} else if (ent->hours <= 0) { |
||
314 |
DPRINTF(("--> time is up\n")); |
||
315 |
} |
||
316 |
} |
||
317 |
✗✓ | 432 |
if (ent->size > 0) |
318 |
216 |
DPRINTF(("size (KB): %.2f [%d] ", size / 1024.0, |
|
319 |
(int)(ent->size / 1024))); |
||
320 |
✗✓ | 432 |
if (ent->hours > 0) |
321 |
216 |
DPRINTF(("age (hr): %d [%d] ", modhours, ent->hours)); |
|
322 |
✗✓✗✗ ✗✗ |
216 |
if (monitormode && (ent->flags & CE_MONITOR) && domonitor(ent)) |
323 |
DPRINTF(("--> monitored\n")); |
||
324 |
✓✗ | 216 |
else if (!monitormode && |
325 |
✓✗✓✓ ✓✗ |
576 |
(force || (ent->size > 0 && size >= ent->size) || |
326 |
✓✓✓✗ |
360 |
(ent->hours <= 0 && (ent->flags & CE_TRIMAT)) || |
327 |
✓✓✓✓ |
288 |
(ent->hours > 0 && (modhours >= ent->hours || modhours < 0) |
328 |
✓✓ | 25 |
&& ((ent->flags & CE_BINARY) || size >= MIN_SIZE)))) { |
329 |
✗✓ | 1 |
DPRINTF(("--> trimming log....\n")); |
330 |
✗✓ | 1 |
if (noaction && !verbose) |
331 |
printf("%s <%d%s%s%s>\n", ent->log, |
||
332 |
ent->numlogs, |
||
333 |
(ent->flags & CE_COMPACT) ? "Z" : "", |
||
334 |
(ent->flags & CE_BINARY) ? "B" : "", |
||
335 |
(ent->flags & CE_FOLLOW) ? "F" : ""); |
||
336 |
1 |
dotrim(ent); |
|
337 |
1 |
ent->flags |= CE_ROTATED; |
|
338 |
1 |
} else |
|
339 |
✗✓ | 215 |
DPRINTF(("--> skipping\n")); |
340 |
504 |
} |
|
341 |
|||
342 |
/* Run the specified command */ |
||
343 |
void |
||
344 |
run_command(char *cmd) |
||
345 |
{ |
||
346 |
if (noaction) |
||
347 |
(void)printf("run %s\n", cmd); |
||
348 |
else |
||
349 |
system(cmd); |
||
350 |
} |
||
351 |
|||
352 |
/* Send a signal to the pid specified by pidfile */ |
||
353 |
void |
||
354 |
send_signal(char *pidfile, int signal) |
||
355 |
{ |
||
356 |
2 |
char line[BUFSIZ], *ep, *err; |
|
357 |
pid_t pid; |
||
358 |
long lval; |
||
359 |
FILE *f; |
||
360 |
|||
361 |
✗✓ | 1 |
if ((f = fopen(pidfile, "r")) == NULL) { |
362 |
warn("can't open %s", pidfile); |
||
363 |
return; |
||
364 |
} |
||
365 |
|||
366 |
pid = 0; |
||
367 |
1 |
errno = 0; |
|
368 |
err = NULL; |
||
369 |
✓✗ | 1 |
if (fgets(line, sizeof(line), f)) { |
370 |
1 |
lval = strtol(line, &ep, 10); |
|
371 |
✓✗✓✗ ✗✓ |
3 |
if (line[0] == '\0' || (*ep != '\0' && *ep != '\n')) |
372 |
err = "invalid number in"; |
||
373 |
✓✗✗✓ |
2 |
else if (lval < 0 || (errno == ERANGE && lval == LONG_MAX)) |
374 |
err = "out of range number in"; |
||
375 |
✗✓ | 1 |
else if (lval == 0) |
376 |
err = "no number in"; |
||
377 |
✗✓ | 1 |
else if (lval < MIN_PID) |
378 |
err = "preposterous process number in"; |
||
379 |
else |
||
380 |
1 |
pid = (pid_t)lval; |
|
381 |
} else { |
||
382 |
if (errno == 0) |
||
383 |
err = "empty"; |
||
384 |
else |
||
385 |
err = "error reading"; |
||
386 |
} |
||
387 |
1 |
(void)fclose(f); |
|
388 |
|||
389 |
✗✓ | 1 |
if (err) |
390 |
warnx("%s pid file: %s", err, pidfile); |
||
391 |
✗✓ | 1 |
else if (noaction) |
392 |
(void)printf("kill -%s %ld\n", sys_signame[signal], (long)pid); |
||
393 |
✗✓ | 1 |
else if (kill(pid, signal)) |
394 |
warnx("warning - could not send SIG%s to PID from pid file %s", |
||
395 |
sys_signame[signal], pidfile); |
||
396 |
2 |
} |
|
397 |
|||
398 |
void |
||
399 |
parse_args(int argc, char **argv) |
||
400 |
{ |
||
401 |
48 |
struct timeval now; |
|
402 |
struct tm *tm; |
||
403 |
size_t l; |
||
404 |
char *p; |
||
405 |
int ch; |
||
406 |
|||
407 |
24 |
gettimeofday(&now, NULL); |
|
408 |
24 |
timenow = now.tv_sec; |
|
409 |
24 |
tm = gmtime(&now.tv_sec); |
|
410 |
24 |
l = strftime(daytime, sizeof(daytime), "%FT%T", tm); |
|
411 |
48 |
snprintf(daytime + l, sizeof(daytime) - l, ".%03ldZ", |
|
412 |
24 |
now.tv_usec / 1000); |
|
413 |
|||
414 |
/* Let's get our hostname */ |
||
415 |
24 |
(void)gethostname(hostname, sizeof(hostname)); |
|
416 |
|||
417 |
/* Truncate domain */ |
||
418 |
✓✗ | 24 |
if ((p = strchr(hostname, '.')) != NULL) |
419 |
24 |
*p = '\0'; |
|
420 |
|||
421 |
✗✓ | 24 |
while ((ch = getopt(argc, argv, "Fmnrva:f:")) != -1) { |
422 |
switch (ch) { |
||
423 |
case 'a': |
||
424 |
arcdir = optarg; |
||
425 |
break; |
||
426 |
case 'n': |
||
427 |
noaction = 1; /* This implies needroot as off */ |
||
428 |
/* fall through */ |
||
429 |
case 'r': |
||
430 |
needroot = 0; |
||
431 |
break; |
||
432 |
case 'v': |
||
433 |
verbose = 1; |
||
434 |
break; |
||
435 |
case 'f': |
||
436 |
conf = optarg; |
||
437 |
break; |
||
438 |
case 'm': |
||
439 |
monitormode = 1; |
||
440 |
break; |
||
441 |
case 'F': |
||
442 |
force = 1; |
||
443 |
break; |
||
444 |
default: |
||
445 |
usage(); |
||
446 |
} |
||
447 |
} |
||
448 |
✗✓ | 24 |
if (monitormode && force) |
449 |
errx(1, "cannot specify both -m and -F flags"); |
||
450 |
24 |
} |
|
451 |
|||
452 |
void |
||
453 |
usage(void) |
||
454 |
{ |
||
455 |
extern const char *__progname; |
||
456 |
|||
457 |
(void)fprintf(stderr, "usage: %s [-Fmnrv] [-a directory] " |
||
458 |
"[-f config_file] [log ...]\n", __progname); |
||
459 |
exit(1); |
||
460 |
} |
||
461 |
|||
462 |
/* |
||
463 |
* Parse a configuration file and build a linked list of all the logs |
||
464 |
* to process |
||
465 |
*/ |
||
466 |
int |
||
467 |
parse_file(struct entrylist *list, int *nentries) |
||
468 |
{ |
||
469 |
48 |
char line[BUFSIZ], *parse, *q, *errline, *group, *tmp, *ep; |
|
470 |
struct conf_entry *working; |
||
471 |
struct passwd *pwd; |
||
472 |
struct group *grp; |
||
473 |
24 |
struct stat sb; |
|
474 |
int lineno = 0; |
||
475 |
int ret = 0; |
||
476 |
FILE *f; |
||
477 |
long l; |
||
478 |
|||
479 |
✗✓ | 24 |
if (strcmp(conf, "-") == 0) |
480 |
f = stdin; |
||
481 |
✗✓ | 24 |
else if ((f = fopen(conf, "r")) == NULL) |
482 |
err(1, "can't open %s", conf); |
||
483 |
|||
484 |
24 |
*nentries = 0; |
|
485 |
|||
486 |
nextline: |
||
487 |
✓✓ | 744 |
while (fgets(line, sizeof(line), f) != NULL) { |
488 |
408 |
lineno++; |
|
489 |
408 |
tmp = sob(line); |
|
490 |
✗✓✓✓ |
816 |
if (*tmp == '\0' || *tmp == '#') |
491 |
continue; |
||
492 |
288 |
errline = strdup(tmp); |
|
493 |
✗✓ | 288 |
if (errline == NULL) |
494 |
err(1, NULL); |
||
495 |
288 |
working = calloc(1, sizeof(*working)); |
|
496 |
✗✓ | 288 |
if (working == NULL) |
497 |
err(1, NULL); |
||
498 |
|||
499 |
288 |
q = parse = missing_field(sob(line), errline, lineno); |
|
500 |
288 |
*(parse = son(line)) = '\0'; |
|
501 |
288 |
working->log = strdup(q); |
|
502 |
✗✓ | 288 |
if (working->log == NULL) |
503 |
err(1, NULL); |
||
504 |
|||
505 |
✓✗ | 288 |
if ((working->logbase = strrchr(working->log, '/')) != NULL) |
506 |
288 |
working->logbase++; |
|
507 |
|||
508 |
288 |
q = parse = missing_field(sob(++parse), errline, lineno); |
|
509 |
288 |
*(parse = son(parse)) = '\0'; |
|
510 |
✓✓✗✓ |
528 |
if ((group = strchr(q, ':')) != NULL || |
511 |
240 |
(group = strrchr(q, '.')) != NULL) { |
|
512 |
48 |
*group++ = '\0'; |
|
513 |
✓✗ | 48 |
if (*q) { |
514 |
✓✗ | 48 |
if (!(isnumberstr(q))) { |
515 |
✗✓ | 48 |
if ((pwd = getpwnam(q)) == NULL) { |
516 |
warnx("%s:%d: unknown user" |
||
517 |
" %s --> skipping", |
||
518 |
conf, lineno, q); |
||
519 |
ret = 1; |
||
520 |
goto nextline; |
||
521 |
} |
||
522 |
48 |
working->uid = pwd->pw_uid; |
|
523 |
48 |
} else |
|
524 |
working->uid = atoi(q); |
||
525 |
} else |
||
526 |
working->uid = (uid_t)-1; |
||
527 |
|||
528 |
q = group; |
||
529 |
✓✗ | 48 |
if (*q) { |
530 |
✓✗ | 48 |
if (!(isnumberstr(q))) { |
531 |
✗✓ | 48 |
if ((grp = getgrnam(q)) == NULL) { |
532 |
warnx("%s:%d: unknown group" |
||
533 |
" %s --> skipping", |
||
534 |
conf, lineno, q); |
||
535 |
ret = 1; |
||
536 |
goto nextline; |
||
537 |
} |
||
538 |
48 |
working->gid = grp->gr_gid; |
|
539 |
48 |
} else |
|
540 |
working->gid = atoi(q); |
||
541 |
} else |
||
542 |
working->gid = (gid_t)-1; |
||
543 |
|||
544 |
48 |
q = parse = missing_field(sob(++parse), errline, lineno); |
|
545 |
48 |
*(parse = son(parse)) = '\0'; |
|
546 |
48 |
} else { |
|
547 |
240 |
working->uid = (uid_t)-1; |
|
548 |
240 |
working->gid = (gid_t)-1; |
|
549 |
} |
||
550 |
|||
551 |
288 |
l = strtol(q, &ep, 8); |
|
552 |
✗✓ | 288 |
if (*ep != '\0' || l < 0 || l > ALLPERMS) { |
553 |
warnx("%s:%d: bad permissions: %s --> skipping", conf, |
||
554 |
lineno, q); |
||
555 |
ret = 1; |
||
556 |
goto nextline; |
||
557 |
} |
||
558 |
288 |
working->permissions = (mode_t)l; |
|
559 |
|||
560 |
288 |
q = parse = missing_field(sob(++parse), errline, lineno); |
|
561 |
288 |
*(parse = son(parse)) = '\0'; |
|
562 |
288 |
l = strtol(q, &ep, 10); |
|
563 |
✗✓ | 288 |
if (*ep != '\0' || l < 0 || l >= INT_MAX) { |
564 |
warnx("%s:%d: bad number: %s --> skipping", conf, |
||
565 |
lineno, q); |
||
566 |
ret = 1; |
||
567 |
goto nextline; |
||
568 |
} |
||
569 |
288 |
working->numlogs = (int)l; |
|
570 |
|||
571 |
288 |
q = parse = missing_field(sob(++parse), errline, lineno); |
|
572 |
288 |
*(parse = son(parse)) = '\0'; |
|
573 |
✓✓ | 288 |
if (isdigit((unsigned char)*q)) |
574 |
168 |
working->size = atoi(q) * 1024; |
|
575 |
else |
||
576 |
working->size = -1; |
||
577 |
|||
578 |
288 |
working->flags = 0; |
|
579 |
288 |
q = parse = missing_field(sob(++parse), errline, lineno); |
|
580 |
288 |
*(parse = son(parse)) = '\0'; |
|
581 |
288 |
l = strtol(q, &ep, 10); |
|
582 |
✗✓ | 288 |
if (l < 0 || l >= INT_MAX) { |
583 |
warnx("%s:%d: interval out of range: %s --> skipping", |
||
584 |
conf, lineno, q); |
||
585 |
ret = 1; |
||
586 |
goto nextline; |
||
587 |
} |
||
588 |
504 |
working->hours = (int)l; |
|
589 |
✗✓✓✗ ✓ |
504 |
switch (*ep) { |
590 |
case '\0': |
||
591 |
break; |
||
592 |
case '@': |
||
593 |
working->trim_at = parse8601(ep + 1); |
||
594 |
if (working->trim_at == (time_t) - 1) { |
||
595 |
warnx("%s:%d: bad time: %s --> skipping", conf, |
||
596 |
lineno, q); |
||
597 |
ret = 1; |
||
598 |
goto nextline; |
||
599 |
} |
||
600 |
working->flags |= CE_TRIMAT; |
||
601 |
break; |
||
602 |
case '$': |
||
603 |
48 |
working->trim_at = parseDWM(ep + 1); |
|
604 |
✗✓ | 48 |
if (working->trim_at == (time_t) - 1) { |
605 |
warnx("%s:%d: bad time: %s --> skipping", conf, |
||
606 |
lineno, q); |
||
607 |
ret = 1; |
||
608 |
goto nextline; |
||
609 |
} |
||
610 |
48 |
working->flags |= CE_TRIMAT; |
|
611 |
48 |
break; |
|
612 |
case '*': |
||
613 |
✗✓ | 168 |
if (q == ep) |
614 |
break; |
||
615 |
/* FALLTHROUGH */ |
||
616 |
default: |
||
617 |
warnx("%s:%d: bad interval/at: %s --> skipping", conf, |
||
618 |
lineno, q); |
||
619 |
ret = 1; |
||
620 |
goto nextline; |
||
621 |
} |
||
622 |
|||
623 |
288 |
q = sob(++parse); /* Optional field */ |
|
624 |
✓✓✓✗ ✗✓✗✗ ✗✗ |
336 |
if (*q == 'Z' || *q == 'z' || *q == 'B' || *q == 'b' || |
625 |
*q == 'M' || *q == 'm') { |
||
626 |
288 |
*(parse = son(q)) = '\0'; |
|
627 |
✓✓ | 1200 |
while (*q) { |
628 |
✗✓✗✓ ✗✗✗✗ ✗ |
312 |
switch (*q) { |
629 |
case 'Z': |
||
630 |
case 'z': |
||
631 |
working->flags |= CE_COMPACT; |
||
632 |
264 |
break; |
|
633 |
case 'B': |
||
634 |
case 'b': |
||
635 |
working->flags |= CE_BINARY; |
||
636 |
48 |
break; |
|
637 |
case 'M': |
||
638 |
case 'm': |
||
639 |
working->flags |= CE_MONITOR; |
||
640 |
break; |
||
641 |
case 'F': |
||
642 |
case 'f': |
||
643 |
working->flags |= CE_FOLLOW; |
||
644 |
break; |
||
645 |
default: |
||
646 |
warnx("%s:%d: illegal flag: `%c'" |
||
647 |
" --> skipping", |
||
648 |
conf, lineno, *q); |
||
649 |
ret = 1; |
||
650 |
goto nextline; |
||
651 |
} |
||
652 |
312 |
q++; |
|
653 |
} |
||
654 |
} else |
||
655 |
parse--; /* no flags so undo */ |
||
656 |
|||
657 |
288 |
working->pidfile = PIDFILE; |
|
658 |
288 |
working->signal = SIGHUP; |
|
659 |
288 |
working->runcmd = NULL; |
|
660 |
288 |
working->whom = NULL; |
|
661 |
288 |
for (;;) { |
|
662 |
360 |
q = parse = sob(++parse); /* Optional field */ |
|
663 |
✓✗✓✓ |
720 |
if (q == NULL || *q == '\0') |
664 |
break; |
||
665 |
✗✓ | 72 |
if (*q == '/') { |
666 |
*(parse = son(parse)) = '\0'; |
||
667 |
if (strlen(q) >= PATH_MAX) { |
||
668 |
warnx("%s:%d: pathname too long: %s" |
||
669 |
" --> skipping", |
||
670 |
conf, lineno, q); |
||
671 |
ret = 1; |
||
672 |
goto nextline; |
||
673 |
} |
||
674 |
working->pidfile = strdup(q); |
||
675 |
if (working->pidfile == NULL) |
||
676 |
err(1, NULL); |
||
677 |
✓✗✓✗ |
144 |
} else if (*q == '"' && (tmp = strchr(q + 1, '"'))) { |
678 |
72 |
*(parse = tmp) = '\0'; |
|
679 |
✓✗ | 72 |
if (*++q != '\0') { |
680 |
72 |
working->runcmd = strdup(q); |
|
681 |
✗✓ | 72 |
if (working->runcmd == NULL) |
682 |
err(1, NULL); |
||
683 |
} |
||
684 |
72 |
working->pidfile = NULL; |
|
685 |
72 |
working->signal = -1; |
|
686 |
✗✗ | 72 |
} else if (strncmp(q, "SIG", 3) == 0) { |
687 |
int i; |
||
688 |
|||
689 |
*(parse = son(parse)) = '\0'; |
||
690 |
for (i = 1; i < NSIG; i++) { |
||
691 |
if (!strcmp(sys_signame[i], q + 3)) { |
||
692 |
working->signal = i; |
||
693 |
break; |
||
694 |
} |
||
695 |
} |
||
696 |
if (i == NSIG) { |
||
697 |
warnx("%s:%d: unknown signal: %s" |
||
698 |
" --> skipping", |
||
699 |
conf, lineno, q); |
||
700 |
ret = 1; |
||
701 |
goto nextline; |
||
702 |
} |
||
703 |
} else if (working->flags & CE_MONITOR) { |
||
704 |
*(parse = son(parse)) = '\0'; |
||
705 |
working->whom = strdup(q); |
||
706 |
if (working->whom == NULL) |
||
707 |
err(1, NULL); |
||
708 |
} else { |
||
709 |
warnx("%s:%d: unrecognized field: %s" |
||
710 |
" --> skipping", |
||
711 |
conf, lineno, q); |
||
712 |
ret = 1; |
||
713 |
goto nextline; |
||
714 |
} |
||
715 |
} |
||
716 |
288 |
free(errline); |
|
717 |
|||
718 |
✗✓✗✗ |
288 |
if ((working->flags & CE_MONITOR) && working->whom == NULL) { |
719 |
warnx("%s:%d: missing monitor notification field" |
||
720 |
" --> skipping", |
||
721 |
conf, lineno); |
||
722 |
ret = 1; |
||
723 |
goto nextline; |
||
724 |
} |
||
725 |
|||
726 |
/* If there is an arcdir, set working->backdir. */ |
||
727 |
✗✓✗✗ |
288 |
if (arcdir != NULL && working->logbase != NULL) { |
728 |
if (*arcdir == '/') { |
||
729 |
/* Fully qualified arcdir */ |
||
730 |
working->backdir = arcdir; |
||
731 |
} else { |
||
732 |
/* arcdir is relative to log's parent dir */ |
||
733 |
*(working->logbase - 1) = '\0'; |
||
734 |
if ((asprintf(&working->backdir, "%s/%s", |
||
735 |
working->log, arcdir)) == -1) |
||
736 |
err(1, NULL); |
||
737 |
*(working->logbase - 1) = '/'; |
||
738 |
} |
||
739 |
/* Ignore arcdir if it doesn't exist. */ |
||
740 |
if (stat(working->backdir, &sb) != 0 || |
||
741 |
!S_ISDIR(sb.st_mode)) { |
||
742 |
if (working->backdir != arcdir) |
||
743 |
free(working->backdir); |
||
744 |
working->backdir = NULL; |
||
745 |
} |
||
746 |
} else |
||
747 |
working->backdir = NULL; |
||
748 |
|||
749 |
/* Make sure we can't oflow PATH_MAX */ |
||
750 |
✗✓ | 576 |
if (working->backdir != NULL) { |
751 |
if (snprintf(line, sizeof(line), "%s/%s.%d%s", |
||
752 |
working->backdir, working->logbase, |
||
753 |
working->numlogs, COMPRESS_POSTFIX) >= PATH_MAX) { |
||
754 |
warnx("%s:%d: pathname too long: %s" |
||
755 |
" --> skipping", conf, lineno, q); |
||
756 |
ret = 1; |
||
757 |
goto nextline; |
||
758 |
} |
||
759 |
} else { |
||
760 |
✗✓ | 576 |
if (snprintf(line, sizeof(line), "%s.%d%s", |
761 |
288 |
working->log, working->numlogs, COMPRESS_POSTFIX) |
|
762 |
288 |
>= PATH_MAX) { |
|
763 |
warnx("%s:%d: pathname too long: %s" |
||
764 |
" --> skipping", conf, lineno, |
||
765 |
working->log); |
||
766 |
ret = 1; |
||
767 |
goto nextline; |
||
768 |
} |
||
769 |
} |
||
770 |
288 |
TAILQ_INSERT_TAIL(list, working, next); |
|
771 |
288 |
(*nentries)++; |
|
772 |
} |
||
773 |
24 |
(void)fclose(f); |
|
774 |
24 |
return (ret); |
|
775 |
24 |
} |
|
776 |
|||
777 |
char * |
||
778 |
missing_field(char *p, char *errline, int lineno) |
||
779 |
{ |
||
780 |
✓✗✗✓ |
4464 |
if (p == NULL || *p == '\0') { |
781 |
warnx("%s:%d: missing field", conf, lineno); |
||
782 |
fputs(errline, stderr); |
||
783 |
exit(1); |
||
784 |
} |
||
785 |
1488 |
return (p); |
|
786 |
} |
||
787 |
|||
788 |
void |
||
789 |
rotate(struct conf_entry *ent, const char *oldlog) |
||
790 |
{ |
||
791 |
2 |
char file1[PATH_MAX], file2[PATH_MAX], *suffix; |
|
792 |
1 |
int numdays = ent->numlogs - 1; |
|
793 |
int done = 0; |
||
794 |
|||
795 |
/* Remove old logs */ |
||
796 |
1 |
do { |
|
797 |
1 |
(void)snprintf(file1, sizeof(file1), "%s.%d", oldlog, numdays); |
|
798 |
1 |
(void)snprintf(file2, sizeof(file2), "%s.%d%s", oldlog, |
|
799 |
numdays, COMPRESS_POSTFIX); |
||
800 |
✗✓ | 1 |
if (noaction) { |
801 |
printf("\trm -f %s %s\n", file1, file2); |
||
802 |
done = access(file1, 0) && access(file2, 0); |
||
803 |
} else { |
||
804 |
✓✗ | 3 |
done = unlink(file1) && unlink(file2); |
805 |
} |
||
806 |
1 |
numdays++; |
|
807 |
✗✓ | 1 |
} while (done == 0); |
808 |
|||
809 |
/* Move down log files */ |
||
810 |
✓✓ | 14 |
for (numdays = ent->numlogs - 2; numdays >= 0; numdays--) { |
811 |
/* |
||
812 |
* If both the compressed archive and the non-compressed archive |
||
813 |
* exist, we decide which to rotate based on the CE_COMPACT flag |
||
814 |
*/ |
||
815 |
6 |
(void)snprintf(file1, sizeof(file1), "%s.%d", oldlog, numdays); |
|
816 |
6 |
suffix = lstat_log(file1, sizeof(file1), ent->flags); |
|
817 |
✓✓ | 6 |
if (suffix == NULL) |
818 |
continue; |
||
819 |
1 |
(void)snprintf(file2, sizeof(file2), "%s.%d%s", oldlog, |
|
820 |
1 |
numdays + 1, suffix); |
|
821 |
|||
822 |
✗✓ | 1 |
if (noaction) { |
823 |
printf("\tmv %s %s\n", file1, file2); |
||
824 |
printf("\tchmod %o %s\n", ent->permissions, file2); |
||
825 |
printf("\tchown %u:%u %s\n", ent->uid, ent->gid, file2); |
||
826 |
} else { |
||
827 |
✗✓ | 1 |
if (rename(file1, file2)) |
828 |
warn("can't mv %s to %s", file1, file2); |
||
829 |
✗✓ | 1 |
if (chmod(file2, ent->permissions)) |
830 |
warn("can't chmod %s", file2); |
||
831 |
✗✓ | 1 |
if (chown(file2, ent->uid, ent->gid)) |
832 |
warn("can't chown %s", file2); |
||
833 |
} |
||
834 |
} |
||
835 |
1 |
} |
|
836 |
|||
837 |
void |
||
838 |
dotrim(struct conf_entry *ent) |
||
839 |
{ |
||
840 |
2 |
char file1[PATH_MAX], file2[PATH_MAX], oldlog[PATH_MAX]; |
|
841 |
int fd; |
||
842 |
|||
843 |
/* Is there a separate backup dir? */ |
||
844 |
✗✓ | 1 |
if (ent->backdir != NULL) |
845 |
snprintf(oldlog, sizeof(oldlog), "%s/%s", ent->backdir, |
||
846 |
ent->logbase); |
||
847 |
else |
||
848 |
1 |
strlcpy(oldlog, ent->log, sizeof(oldlog)); |
|
849 |
|||
850 |
✓✗ | 1 |
if (ent->numlogs > 0) |
851 |
1 |
rotate(ent, oldlog); |
|
852 |
✓✗✓✗ |
2 |
if (!noaction && !(ent->flags & CE_BINARY)) |
853 |
1 |
(void)log_trim(ent->log); |
|
854 |
|||
855 |
1 |
(void)snprintf(file2, sizeof(file2), "%s.XXXXXXXXXX", ent->log); |
|
856 |
✗✓ | 1 |
if (noaction) { |
857 |
printf("\tmktemp %s\n", file2); |
||
858 |
} else { |
||
859 |
✗✓ | 1 |
if ((fd = mkstemp(file2)) < 0) |
860 |
err(1, "can't start '%s' log", file2); |
||
861 |
✗✓ | 1 |
if (fchmod(fd, ent->permissions)) |
862 |
err(1, "can't chmod '%s' log file", file2); |
||
863 |
✗✓ | 1 |
if (fchown(fd, ent->uid, ent->gid)) |
864 |
err(1, "can't chown '%s' log file", file2); |
||
865 |
1 |
(void)close(fd); |
|
866 |
/* Add status message */ |
||
867 |
✓✗✗✓ |
2 |
if (!(ent->flags & CE_BINARY) && log_trim(file2)) |
868 |
err(1, "can't add status message to log '%s'", file2); |
||
869 |
} |
||
870 |
|||
871 |
✗✓ | 1 |
if (ent->numlogs == 0) { |
872 |
if (noaction) |
||
873 |
printf("\trm %s\n", ent->log); |
||
874 |
else if (unlink(ent->log)) |
||
875 |
warn("can't rm %s", ent->log); |
||
876 |
} else { |
||
877 |
1 |
(void)snprintf(file1, sizeof(file1), "%s.0", oldlog); |
|
878 |
✗✓ | 1 |
if (noaction) { |
879 |
printf("\tmv %s to %s\n", ent->log, file1); |
||
880 |
printf("\tchmod %o %s\n", ent->permissions, file1); |
||
881 |
printf("\tchown %u:%u %s\n", ent->uid, ent->gid, file1); |
||
882 |
✗✓ | 2 |
} else if (movefile(ent->log, file1, ent->uid, ent->gid, |
883 |
1 |
ent->permissions)) |
|
884 |
warn("can't mv %s to %s", ent->log, file1); |
||
885 |
} |
||
886 |
|||
887 |
/* Now move the new log file into place */ |
||
888 |
✗✓ | 1 |
if (noaction) |
889 |
printf("\tmv %s to %s\n", file2, ent->log); |
||
890 |
✗✓ | 1 |
else if (rename(file2, ent->log)) |
891 |
warn("can't mv %s to %s", file2, ent->log); |
||
892 |
1 |
} |
|
893 |
|||
894 |
/* Log the fact that the logs were turned over */ |
||
895 |
int |
||
896 |
log_trim(char *log) |
||
897 |
{ |
||
898 |
FILE *f; |
||
899 |
|||
900 |
✗✓ | 4 |
if ((f = fopen(log, "a")) == NULL) |
901 |
return (-1); |
||
902 |
2 |
(void)fprintf(f, "%s %s newsyslog[%ld]: logfile turned over\n", |
|
903 |
2 |
daytime, hostname, (long)getpid()); |
|
904 |
✗✓ | 2 |
if (fclose(f) == EOF) |
905 |
err(1, "log_trim: fclose"); |
||
906 |
2 |
return (0); |
|
907 |
2 |
} |
|
908 |
|||
909 |
/* Fork off compress or gzip to compress the old log file */ |
||
910 |
void |
||
911 |
compress_log(struct conf_entry *ent) |
||
912 |
{ |
||
913 |
2 |
char *base, tmp[PATH_MAX]; |
|
914 |
pid_t pid; |
||
915 |
|||
916 |
✗✓ | 1 |
if (ent->backdir != NULL) |
917 |
snprintf(tmp, sizeof(tmp), "%s/%s.0", ent->backdir, |
||
918 |
ent->logbase); |
||
919 |
else |
||
920 |
1 |
snprintf(tmp, sizeof(tmp), "%s.0", ent->log); |
|
921 |
|||
922 |
✗✓ | 1 |
if ((base = strrchr(COMPRESS, '/')) == NULL) |
923 |
base = COMPRESS; |
||
924 |
else |
||
925 |
1 |
base++; |
|
926 |
✗✓ | 1 |
if (noaction) { |
927 |
printf("%s %s\n", base, tmp); |
||
928 |
return; |
||
929 |
} |
||
930 |
1 |
pid = fork(); |
|
931 |
✗✓ | 1 |
if (pid < 0) { |
932 |
err(1, "fork"); |
||
933 |
✗✓ | 1 |
} else if (pid == 0) { |
934 |
(void)execl(COMPRESS, base, "-f", tmp, (char *)NULL); |
||
935 |
warn(COMPRESS); |
||
936 |
_exit(1); |
||
937 |
} |
||
938 |
2 |
} |
|
939 |
|||
940 |
/* Return size in bytes of a file */ |
||
941 |
off_t |
||
942 |
sizefile(struct stat *sb) |
||
943 |
{ |
||
944 |
/* For sparse files, return the size based on number of blocks used. */ |
||
945 |
✗✓ | 480 |
if (sb->st_size / DEV_BSIZE > sb->st_blocks) |
946 |
return (sb->st_blocks * DEV_BSIZE); |
||
947 |
else |
||
948 |
240 |
return (sb->st_size); |
|
949 |
240 |
} |
|
950 |
|||
951 |
/* Return the age (in hours) of old log file (file.0), or -1 if none */ |
||
952 |
int |
||
953 |
age_old_log(struct conf_entry *ent) |
||
954 |
{ |
||
955 |
480 |
char file[PATH_MAX]; |
|
956 |
240 |
struct stat sb; |
|
957 |
|||
958 |
✗✓ | 240 |
if (ent->backdir != NULL) |
959 |
(void)snprintf(file, sizeof(file), "%s/%s.0", ent->backdir, |
||
960 |
ent->logbase); |
||
961 |
else |
||
962 |
240 |
(void)snprintf(file, sizeof(file), "%s.0", ent->log); |
|
963 |
✓✓ | 240 |
if (ent->flags & CE_COMPACT) { |
964 |
✓✗ | 384 |
if (stat_suffix(file, sizeof(file), COMPRESS_POSTFIX, &sb, |
965 |
✓✓ | 384 |
stat) < 0 && stat(file, &sb) < 0) |
966 |
168 |
return (-1); |
|
967 |
} else { |
||
968 |
✓✗✓✗ |
72 |
if (stat(file, &sb) < 0 && stat_suffix(file, sizeof(file), |
969 |
24 |
COMPRESS_POSTFIX, &sb, stat) < 0) |
|
970 |
24 |
return (-1); |
|
971 |
} |
||
972 |
48 |
return ((int)(timenow - sb.st_mtime + 1800) / 3600); |
|
973 |
240 |
} |
|
974 |
|||
975 |
/* Skip Over Blanks */ |
||
976 |
char * |
||
977 |
sob(char *p) |
||
978 |
{ |
||
979 |
✗✓ | 5088 |
if (p == NULL) |
980 |
return(p); |
||
981 |
✓✓ | 9648 |
while (isspace((unsigned char)*p)) |
982 |
3552 |
p++; |
|
983 |
2544 |
return (p); |
|
984 |
2544 |
} |
|
985 |
|||
986 |
/* Skip Over Non-Blanks */ |
||
987 |
char * |
||
988 |
son(char *p) |
||
989 |
{ |
||
990 |
✓✗✓✗ ✓✓ |
49896 |
while (p && *p && !isspace((unsigned char)*p)) |
991 |
7848 |
p++; |
|
992 |
1776 |
return (p); |
|
993 |
} |
||
994 |
|||
995 |
/* Check if string is actually a number */ |
||
996 |
int |
||
997 |
isnumberstr(char *string) |
||
998 |
{ |
||
999 |
✓✗ | 288 |
while (*string) { |
1000 |
✗✓ | 96 |
if (!isdigit((unsigned char)*string++)) |
1001 |
96 |
return (0); |
|
1002 |
} |
||
1003 |
return (1); |
||
1004 |
96 |
} |
|
1005 |
|||
1006 |
int |
||
1007 |
domonitor(struct conf_entry *ent) |
||
1008 |
{ |
||
1009 |
char fname[PATH_MAX], *flog, *p, *rb = NULL; |
||
1010 |
struct stat sb, tsb; |
||
1011 |
off_t osize; |
||
1012 |
FILE *fp; |
||
1013 |
int rd; |
||
1014 |
|||
1015 |
if (stat(ent->log, &sb) < 0) |
||
1016 |
return (0); |
||
1017 |
|||
1018 |
if (noaction) { |
||
1019 |
if (!verbose) |
||
1020 |
printf("%s: monitored\n", ent->log); |
||
1021 |
return (1); |
||
1022 |
} |
||
1023 |
|||
1024 |
flog = strdup(ent->log); |
||
1025 |
if (flog == NULL) |
||
1026 |
err(1, NULL); |
||
1027 |
|||
1028 |
for (p = flog; *p != '\0'; p++) { |
||
1029 |
if (*p == '/') |
||
1030 |
*p = '_'; |
||
1031 |
} |
||
1032 |
snprintf(fname, sizeof(fname), "%s/newsyslog.%s.size", |
||
1033 |
STATS_DIR, flog); |
||
1034 |
|||
1035 |
/* ..if it doesn't exist, simply record the current size. */ |
||
1036 |
if ((sb.st_size == 0) || stat(fname, &tsb) < 0) |
||
1037 |
goto update; |
||
1038 |
|||
1039 |
fp = fopen(fname, "r"); |
||
1040 |
if (fp == NULL) { |
||
1041 |
warn("%s", fname); |
||
1042 |
goto cleanup; |
||
1043 |
} |
||
1044 |
if (fscanf(fp, "%lld\n", &osize) != 1) { |
||
1045 |
fclose(fp); |
||
1046 |
goto update; |
||
1047 |
} |
||
1048 |
|||
1049 |
fclose(fp); |
||
1050 |
|||
1051 |
/* If the file is smaller, mark the entire thing as changed. */ |
||
1052 |
if (sb.st_size < osize) |
||
1053 |
osize = 0; |
||
1054 |
|||
1055 |
/* Now see if current size is larger. */ |
||
1056 |
if (sb.st_size > osize) { |
||
1057 |
rb = malloc(sb.st_size - osize); |
||
1058 |
if (rb == NULL) |
||
1059 |
err(1, NULL); |
||
1060 |
|||
1061 |
/* Open logfile, seek. */ |
||
1062 |
fp = fopen(ent->log, "r"); |
||
1063 |
if (fp == NULL) { |
||
1064 |
warn("%s", ent->log); |
||
1065 |
goto cleanup; |
||
1066 |
} |
||
1067 |
fseek(fp, osize, SEEK_SET); |
||
1068 |
rd = fread(rb, 1, sb.st_size - osize, fp); |
||
1069 |
if (rd < 1) { |
||
1070 |
warn("fread"); |
||
1071 |
fclose(fp); |
||
1072 |
goto cleanup; |
||
1073 |
} |
||
1074 |
fclose(fp); |
||
1075 |
|||
1076 |
/* Send message. */ |
||
1077 |
fp = popen(SENDMAIL " -t", "w"); |
||
1078 |
if (fp == NULL) { |
||
1079 |
warn("popen"); |
||
1080 |
goto cleanup; |
||
1081 |
} |
||
1082 |
fprintf(fp, "Auto-Submitted: auto-generated\n"); |
||
1083 |
fprintf(fp, "To: %s\nSubject: LOGFILE NOTIFICATION: %s\n\n\n", |
||
1084 |
ent->whom, ent->log); |
||
1085 |
fwrite(rb, 1, rd, fp); |
||
1086 |
fputs("\n\n", fp); |
||
1087 |
|||
1088 |
pclose(fp); |
||
1089 |
} |
||
1090 |
update: |
||
1091 |
/* Reopen for writing and update file. */ |
||
1092 |
fp = fopen(fname, "w"); |
||
1093 |
if (fp == NULL) { |
||
1094 |
warn("%s", fname); |
||
1095 |
goto cleanup; |
||
1096 |
} |
||
1097 |
fprintf(fp, "%lld\n", (long long)sb.st_size); |
||
1098 |
fclose(fp); |
||
1099 |
|||
1100 |
cleanup: |
||
1101 |
free(flog); |
||
1102 |
free(rb); |
||
1103 |
return (1); |
||
1104 |
} |
||
1105 |
|||
1106 |
/* ARGSUSED */ |
||
1107 |
void |
||
1108 |
child_killer(int signo) |
||
1109 |
{ |
||
1110 |
2 |
int save_errno = errno; |
|
1111 |
1 |
int status; |
|
1112 |
|||
1113 |
✓✓ | 3 |
while (waitpid(-1, &status, WNOHANG) > 0) |
1114 |
; |
||
1115 |
1 |
errno = save_errno; |
|
1116 |
1 |
} |
|
1117 |
|||
1118 |
int |
||
1119 |
stat_suffix(char *file, size_t size, char *suffix, struct stat *sp, |
||
1120 |
int (*func)(const char *, struct stat *)) |
||
1121 |
{ |
||
1122 |
size_t n; |
||
1123 |
|||
1124 |
492 |
n = strlcat(file, suffix, size); |
|
1125 |
✓✗✓✓ |
492 |
if (n < size && func(file, sp) == 0) |
1126 |
49 |
return (0); |
|
1127 |
197 |
file[n - strlen(suffix)] = '\0'; |
|
1128 |
197 |
return (-1); |
|
1129 |
246 |
} |
|
1130 |
|||
1131 |
/* |
||
1132 |
* lstat() a log, possibly appending a suffix; order is based on flags. |
||
1133 |
* Returns the suffix appended (may be empty string) or NULL if no file. |
||
1134 |
*/ |
||
1135 |
char * |
||
1136 |
lstat_log(char *file, size_t size, int flags) |
||
1137 |
{ |
||
1138 |
12 |
struct stat sb; |
|
1139 |
|||
1140 |
✓✗ | 6 |
if (flags & CE_COMPACT) { |
1141 |
✓✓ | 6 |
if (stat_suffix(file, size, COMPRESS_POSTFIX, &sb, lstat) == 0) |
1142 |
1 |
return (COMPRESS_POSTFIX); |
|
1143 |
✗✓ | 5 |
if (lstat(file, &sb) == 0) |
1144 |
return (""); |
||
1145 |
} else { |
||
1146 |
if (lstat(file, &sb) == 0) |
||
1147 |
return (""); |
||
1148 |
if (stat_suffix(file, size, COMPRESS_POSTFIX, &sb, lstat) == 0) |
||
1149 |
return (COMPRESS_POSTFIX); |
||
1150 |
|||
1151 |
} |
||
1152 |
5 |
return (NULL); |
|
1153 |
6 |
} |
|
1154 |
|||
1155 |
/* |
||
1156 |
* Parse a limited subset of ISO 8601. The specific format is as follows: |
||
1157 |
* |
||
1158 |
* [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter) |
||
1159 |
* |
||
1160 |
* We don't accept a timezone specification; missing fields (including timezone) |
||
1161 |
* are defaulted to the current date but time zero. |
||
1162 |
*/ |
||
1163 |
time_t |
||
1164 |
parse8601(char *s) |
||
1165 |
{ |
||
1166 |
struct tm tm, *tmp; |
||
1167 |
char *t; |
||
1168 |
long l; |
||
1169 |
|||
1170 |
tmp = localtime(&timenow); |
||
1171 |
tm = *tmp; |
||
1172 |
|||
1173 |
tm.tm_hour = tm.tm_min = tm.tm_sec = 0; |
||
1174 |
|||
1175 |
l = strtol(s, &t, 10); |
||
1176 |
if (l < 0 || l >= INT_MAX || (*t != '\0' && *t != 'T')) |
||
1177 |
return (-1); |
||
1178 |
|||
1179 |
/* |
||
1180 |
* Now t points either to the end of the string (if no time was |
||
1181 |
* provided) or to the letter `T' which separates date and time in |
||
1182 |
* ISO 8601. The pointer arithmetic is the same for either case. |
||
1183 |
*/ |
||
1184 |
switch (t - s) { |
||
1185 |
case 8: |
||
1186 |
tm.tm_year = ((l / 1000000) - 19) * 100; |
||
1187 |
l = l % 1000000; |
||
1188 |
case 6: |
||
1189 |
tm.tm_year -= tm.tm_year % 100; |
||
1190 |
tm.tm_year += l / 10000; |
||
1191 |
l = l % 10000; |
||
1192 |
case 4: |
||
1193 |
tm.tm_mon = (l / 100) - 1; |
||
1194 |
l = l % 100; |
||
1195 |
case 2: |
||
1196 |
tm.tm_mday = l; |
||
1197 |
case 0: |
||
1198 |
break; |
||
1199 |
default: |
||
1200 |
return (-1); |
||
1201 |
} |
||
1202 |
|||
1203 |
/* sanity check */ |
||
1204 |
if (tm.tm_year < 70 || tm.tm_mon < 0 || tm.tm_mon > 12 || |
||
1205 |
tm.tm_mday < 1 || tm.tm_mday > 31) |
||
1206 |
return (-1); |
||
1207 |
|||
1208 |
if (*t != '\0') { |
||
1209 |
s = ++t; |
||
1210 |
l = strtol(s, &t, 10); |
||
1211 |
if (l < 0 || l >= INT_MAX || |
||
1212 |
(*t != '\0' && !isspace((unsigned char)*t))) |
||
1213 |
return (-1); |
||
1214 |
|||
1215 |
switch (t - s) { |
||
1216 |
case 6: |
||
1217 |
tm.tm_sec = l % 100; |
||
1218 |
l /= 100; |
||
1219 |
case 4: |
||
1220 |
tm.tm_min = l % 100; |
||
1221 |
l /= 100; |
||
1222 |
case 2: |
||
1223 |
tm.tm_hour = l; |
||
1224 |
case 0: |
||
1225 |
break; |
||
1226 |
default: |
||
1227 |
return (-1); |
||
1228 |
} |
||
1229 |
|||
1230 |
/* sanity check */ |
||
1231 |
if (tm.tm_sec < 0 || tm.tm_sec > 60 || tm.tm_min < 0 || |
||
1232 |
tm.tm_min > 59 || tm.tm_hour < 0 || tm.tm_hour > 23) |
||
1233 |
return (-1); |
||
1234 |
} |
||
1235 |
return (mktime(&tm)); |
||
1236 |
} |
||
1237 |
|||
1238 |
/*- |
||
1239 |
* Parse a cyclic time specification, the format is as follows: |
||
1240 |
* |
||
1241 |
* [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]] |
||
1242 |
* |
||
1243 |
* to rotate a logfile cyclic at |
||
1244 |
* |
||
1245 |
* - every day (D) within a specific hour (hh) (hh = 0...23) |
||
1246 |
* - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday) |
||
1247 |
* - once a month (M) at a specific day (d) (d = 1..31,l|L) |
||
1248 |
* |
||
1249 |
* We don't accept a timezone specification; missing fields |
||
1250 |
* are defaulted to the current date but time zero. |
||
1251 |
*/ |
||
1252 |
time_t |
||
1253 |
parseDWM(char *s) |
||
1254 |
{ |
||
1255 |
static int mtab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; |
||
1256 |
int WMseen = 0, Dseen = 0, nd; |
||
1257 |
96 |
struct tm tm, *tmp; |
|
1258 |
48 |
char *t; |
|
1259 |
long l; |
||
1260 |
|||
1261 |
48 |
tmp = localtime(&timenow); |
|
1262 |
48 |
tm = *tmp; |
|
1263 |
|||
1264 |
/* set no. of days per month */ |
||
1265 |
|||
1266 |
48 |
nd = mtab[tm.tm_mon]; |
|
1267 |
|||
1268 |
✗✓ | 48 |
if (tm.tm_mon == 1) { |
1269 |
if (((tm.tm_year + 1900) % 4 == 0) && |
||
1270 |
((tm.tm_year + 1900) % 100 != 0) && |
||
1271 |
((tm.tm_year + 1900) % 400 == 0)) { |
||
1272 |
nd++; /* leap year, 29 days in february */ |
||
1273 |
} |
||
1274 |
} |
||
1275 |
48 |
tm.tm_hour = tm.tm_min = tm.tm_sec = 0; |
|
1276 |
|||
1277 |
72 |
for (;;) { |
|
1278 |
✓✓✗✗ |
72 |
switch (*s) { |
1279 |
case 'D': |
||
1280 |
✗✓ | 24 |
if (Dseen) |
1281 |
return (-1); |
||
1282 |
24 |
Dseen++; |
|
1283 |
24 |
s++; |
|
1284 |
24 |
l = strtol(s, &t, 10); |
|
1285 |
✗✓ | 24 |
if (l < 0 || l > 23) |
1286 |
return (-1); |
||
1287 |
24 |
tm.tm_hour = l; |
|
1288 |
24 |
break; |
|
1289 |
|||
1290 |
case 'W': |
||
1291 |
✗✓ | 48 |
if (WMseen) |
1292 |
return (-1); |
||
1293 |
48 |
WMseen++; |
|
1294 |
48 |
s++; |
|
1295 |
48 |
l = strtol(s, &t, 10); |
|
1296 |
✗✓ | 48 |
if (l < 0 || l > 6) |
1297 |
return (-1); |
||
1298 |
✓✗ | 48 |
if (l != tm.tm_wday) { |
1299 |
int save; |
||
1300 |
|||
1301 |
✓✓ | 48 |
if (l < tm.tm_wday) { |
1302 |
24 |
save = 6 - tm.tm_wday; |
|
1303 |
24 |
save += (l + 1); |
|
1304 |
24 |
} else { |
|
1305 |
24 |
save = l - tm.tm_wday; |
|
1306 |
} |
||
1307 |
|||
1308 |
48 |
tm.tm_mday += save; |
|
1309 |
|||
1310 |
✗✓ | 48 |
if (tm.tm_mday > nd) { |
1311 |
tm.tm_mon++; |
||
1312 |
tm.tm_mday = tm.tm_mday - nd; |
||
1313 |
} |
||
1314 |
48 |
} |
|
1315 |
break; |
||
1316 |
|||
1317 |
case 'M': |
||
1318 |
if (WMseen) |
||
1319 |
return (-1); |
||
1320 |
WMseen++; |
||
1321 |
s++; |
||
1322 |
if (tolower((unsigned char)*s) == 'l') { |
||
1323 |
tm.tm_mday = nd; |
||
1324 |
s++; |
||
1325 |
t = s; |
||
1326 |
} else { |
||
1327 |
l = strtol(s, &t, 10); |
||
1328 |
if (l < 1 || l > 31) |
||
1329 |
return (-1); |
||
1330 |
|||
1331 |
if (l > nd) |
||
1332 |
return (-1); |
||
1333 |
if (l < tm.tm_mday) |
||
1334 |
tm.tm_mon++; |
||
1335 |
tm.tm_mday = l; |
||
1336 |
} |
||
1337 |
break; |
||
1338 |
|||
1339 |
default: |
||
1340 |
return (-1); |
||
1341 |
break; |
||
1342 |
} |
||
1343 |
|||
1344 |
✓✓✓✗ |
96 |
if (*t == '\0' || isspace((unsigned char)*t)) |
1345 |
break; |
||
1346 |
else |
||
1347 |
s = t; |
||
1348 |
} |
||
1349 |
48 |
return (mktime(&tm)); |
|
1350 |
48 |
} |
|
1351 |
|||
1352 |
/* |
||
1353 |
* Move a file using rename(2) if possible and copying if not. |
||
1354 |
*/ |
||
1355 |
int |
||
1356 |
movefile(char *from, char *to, uid_t owner_uid, gid_t group_gid, mode_t perm) |
||
1357 |
{ |
||
1358 |
FILE *src, *dst; |
||
1359 |
int i; |
||
1360 |
|||
1361 |
/* try rename(2) first */ |
||
1362 |
✓✗ | 2 |
if (rename(from, to) == 0) { |
1363 |
✗✓ | 1 |
if (chmod(to, perm)) |
1364 |
warn("can't chmod %s", to); |
||
1365 |
✗✓ | 1 |
if (chown(to, owner_uid, group_gid)) |
1366 |
warn("can't chown %s", to); |
||
1367 |
1 |
return (0); |
|
1368 |
} else if (errno != EXDEV) |
||
1369 |
return (-1); |
||
1370 |
|||
1371 |
/* different filesystem, have to copy the file */ |
||
1372 |
if ((src = fopen(from, "r")) == NULL) |
||
1373 |
err(1, "can't fopen %s for reading", from); |
||
1374 |
if ((dst = fopen(to, "w")) == NULL) |
||
1375 |
err(1, "can't fopen %s for writing", to); |
||
1376 |
if (fchmod(fileno(dst), perm)) |
||
1377 |
err(1, "can't fchmod %s", to); |
||
1378 |
if (fchown(fileno(dst), owner_uid, group_gid)) |
||
1379 |
err(1, "can't fchown %s", to); |
||
1380 |
|||
1381 |
while ((i = getc(src)) != EOF) { |
||
1382 |
if ((putc(i, dst)) == EOF) |
||
1383 |
err(1, "error writing to %s", to); |
||
1384 |
} |
||
1385 |
|||
1386 |
if (ferror(src)) |
||
1387 |
err(1, "error reading from %s", from); |
||
1388 |
if ((fclose(src)) != 0) |
||
1389 |
err(1, "can't fclose %s", from); |
||
1390 |
if ((fclose(dst)) != 0) |
||
1391 |
err(1, "can't fclose %s", to); |
||
1392 |
if ((unlink(from)) != 0) |
||
1393 |
err(1, "can't unlink %s", from); |
||
1394 |
|||
1395 |
return (0); |
||
1396 |
1 |
} |
Generated by: GCOVR (Version 3.3) |