Print this page
Bump Apache dependency to Apache 2
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/cmd/logadm/main.c
+++ new/usr/src/cmd/logadm/main.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
13 13 * When distributing Covered Code, include this CDDL HEADER in each
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21 /*
22 22 * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
23 23 *
24 24 * logadm/main.c -- main routines for logadm
25 25 *
26 26 * this program is 90% argument processing, 10% actions...
27 27 */
28 28
29 29 #include <stdio.h>
30 30 #include <stdlib.h>
31 31 #include <unistd.h>
32 32 #include <strings.h>
33 33 #include <libintl.h>
34 34 #include <locale.h>
35 35 #include <fcntl.h>
36 36 #include <sys/types.h>
37 37 #include <sys/stat.h>
38 38 #include <sys/wait.h>
39 39 #include <sys/filio.h>
40 40 #include <time.h>
41 41 #include <utime.h>
42 42 #include "err.h"
43 43 #include "lut.h"
44 44 #include "fn.h"
45 45 #include "opts.h"
46 46 #include "conf.h"
47 47 #include "glob.h"
48 48 #include "kw.h"
49 49
50 50 /* forward declarations for functions in this file */
51 51 static void usage(const char *msg);
52 52 static void commajoin(const char *lhs, void *rhs, void *arg);
53 53 static void doaftercmd(const char *lhs, void *rhs, void *arg);
54 54 static void dologname(struct fn *fnp, struct opts *clopts);
55 55 static boolean_t rotatelog(struct fn *fnp, struct opts *opts);
56 56 static void rotateto(struct fn *fnp, struct opts *opts, int n,
57 57 struct fn *recentlog, boolean_t isgz);
58 58 static void do_delayed_gzip(const char *lhs, void *rhs, void *arg);
59 59 static void expirefiles(struct fn *fnp, struct opts *opts);
60 60 static void dorm(struct opts *opts, const char *msg, struct fn *fnp);
61 61 static void docmd(struct opts *opts, const char *msg, const char *cmd,
62 62 const char *arg1, const char *arg2, const char *arg3);
63 63 static void docopytruncate(struct opts *opts, const char *file,
64 64 const char *file_copy);
65 65
66 66 /* our configuration file, unless otherwise specified by -f */
67 67 static char *Default_conffile = "/etc/logadm.conf";
68 68 /* our timestamps file, unless otherwise specified by -F */
69 69 static char *Default_timestamps = "/var/logadm/timestamps";
70 70
71 71 /* default pathnames to the commands we invoke */
72 72 static char *Sh = "/bin/sh";
73 73 static char *Mv = "/bin/mv";
74 74 static char *Rm = "/bin/rm";
75 75 static char *Touch = "/bin/touch";
76 76 static char *Chmod = "/bin/chmod";
77 77 static char *Chown = "/bin/chown";
78 78 static char *Gzip = "/bin/gzip";
79 79 static char *Mkdir = "/bin/mkdir";
80 80
81 81 /* return from time(0), gathered early on to avoid slewed timestamps */
82 82 time_t Now;
83 83
84 84 /* list of before commands that have been executed */
85 85 static struct lut *Beforecmds;
86 86
87 87 /* list of after commands to execute before exiting */
88 88 static struct lut *Aftercmds;
89 89
90 90 /* list of conffile entry names that are considered "done" */
91 91 static struct lut *Donenames;
92 92
93 93 /* A list of names of files to be gzipped */
94 94 static struct lut *Gzipnames = NULL;
95 95
96 96 /*
97 97 * only the "FfhnVv" options are allowed in the first form of this command,
98 98 * so this defines the list of options that are an error in they appear
99 99 * in the first form. In other words, it is not allowed to run logadm
100 100 * with any of these options unless at least one logname is also provided.
101 101 */
102 102 #define OPTIONS_NOT_FIRST_FORM "eNrwpPsabcglmoRtzACEST"
103 103
104 104 /* text that we spew with the -h flag */
105 105 #define HELP1 \
106 106 "Usage: logadm [options]\n"\
107 107 " (processes all entries in /etc/logadm.conf or conffile given by -f)\n"\
108 108 " or: logadm [options] logname...\n"\
109 109 " (processes the given lognames)\n"\
110 110 "\n"\
111 111 "General options:\n"\
112 112 " -e mailaddr mail errors to given address\n"\
113 113 " -F timestamps use timestamps instead of /var/logadm/timestamps\n"\
114 114 " -f conffile use conffile instead of /etc/logadm.conf\n"\
115 115 " -h display help\n"\
116 116 " -N not an error if log file nonexistent\n"\
117 117 " -n show actions, don't perform them\n"\
118 118 " -r remove logname entry from conffile\n"\
119 119 " -V ensure conffile entries exist, correct\n"\
120 120 " -v print info about actions happening\n"\
121 121 " -w entryname write entry to config file\n"\
122 122 "\n"\
123 123 "Options which control when a logfile is rotated:\n"\
124 124 "(default is: -s1b -p1w if no -s or -p)\n"\
125 125 " -p period only rotate if period passed since last rotate\n"\
126 126 " -P timestamp used to store rotation date in conffile\n"\
127 127 " -s size only rotate if given size or greater\n"\
128 128 "\n"
129 129 #define HELP2 \
130 130 "Options which control how a logfile is rotated:\n"\
131 131 "(default is: -t '$file.$n', owner/group/mode taken from log file)\n"\
132 132 " -a cmd execute cmd after taking actions\n"\
133 133 " -b cmd execute cmd before taking actions\n"\
134 134 " -c copy & truncate logfile, don't rename\n"\
135 135 " -g group new empty log file group\n"\
136 136 " -l rotate log file with local time rather than UTC\n"\
137 137 " -m mode new empty log file mode\n"\
138 138 " -M cmd execute cmd to rotate the log file\n"\
139 139 " -o owner new empty log file owner\n"\
140 140 " -R cmd run cmd on file after rotate\n"\
141 141 " -t template template for naming old logs\n"\
142 142 " -z count gzip old logs except most recent count\n"\
143 143 "\n"\
144 144 "Options which control the expiration of old logfiles:\n"\
145 145 "(default is: -C10 if no -A, -C, or -S)\n"\
146 146 " -A age expire logs older than age\n"\
147 147 " -C count expire old logs until count remain\n"\
148 148 " -E cmd run cmd on file to expire\n"\
149 149 " -S size expire until space used is below size \n"\
150 150 " -T pattern pattern for finding old logs\n"
151 151
152 152 /*
153 153 * main -- where it all begins
154 154 */
155 155 /*ARGSUSED*/
156 156 int
157 157 main(int argc, char *argv[])
158 158 {
159 159 struct opts *clopts; /* from parsing command line */
160 160 const char *conffile; /* our configuration file */
161 161 const char *timestamps; /* our timestamps file */
162 162 struct fn_list *lognames; /* list of lognames we're processing */
163 163 struct fn *fnp;
164 164 char *val;
165 165 char *buf;
166 166 int status;
167 167
168 168 (void) setlocale(LC_ALL, "");
169 169
170 170 #if !defined(TEXT_DOMAIN)
171 171 #define TEXT_DOMAIN "SYS_TEST" /* only used if Makefiles don't define it */
172 172 #endif
173 173
174 174 (void) textdomain(TEXT_DOMAIN);
175 175
176 176 /* we only print times into the timestamps file, so make them uniform */
177 177 (void) setlocale(LC_TIME, "C");
178 178
179 179 /* give our name to error routines & skip it for arg parsing */
180 180 err_init(*argv++);
181 181 (void) setlinebuf(stdout);
182 182
183 183 if (putenv("PATH=/bin"))
184 184 err(EF_SYS, "putenv PATH");
185 185 if (putenv("TZ=UTC"))
186 186 err(EF_SYS, "putenv TZ");
187 187 tzset();
188 188
189 189 (void) umask(0);
190 190
191 191 Now = time(0);
192 192
193 193 /* check for (undocumented) debugging environment variables */
194 194 if (val = getenv("_LOGADM_DEFAULT_CONFFILE"))
195 195 Default_conffile = val;
196 196 if (val = getenv("_LOGADM_DEFAULT_TIMESTAMPS"))
197 197 Default_timestamps = val;
198 198 if (val = getenv("_LOGADM_DEBUG"))
199 199 Debug = atoi(val);
200 200 if (val = getenv("_LOGADM_SH"))
201 201 Sh = val;
202 202 if (val = getenv("_LOGADM_MV"))
203 203 Mv = val;
204 204 if (val = getenv("_LOGADM_RM"))
205 205 Rm = val;
206 206 if (val = getenv("_LOGADM_TOUCH"))
207 207 Touch = val;
208 208 if (val = getenv("_LOGADM_CHMOD"))
209 209 Chmod = val;
210 210 if (val = getenv("_LOGADM_CHOWN"))
211 211 Chown = val;
212 212 if (val = getenv("_LOGADM_GZIP"))
213 213 Gzip = val;
214 214 if (val = getenv("_LOGADM_MKDIR"))
215 215 Mkdir = val;
216 216
217 217 opts_init(Opttable, Opttable_cnt);
218 218
219 219 /* parse command line arguments */
220 220 if (SETJMP)
221 221 usage("bailing out due to command line errors");
222 222 else
223 223 clopts = opts_parse(NULL, argv, OPTF_CLI);
224 224
225 225 if (Debug) {
226 226 (void) fprintf(stderr, "command line opts:");
227 227 opts_print(clopts, stderr, NULL);
228 228 (void) fprintf(stderr, "\n");
229 229 }
230 230
231 231 /*
232 232 * There are many moods of logadm:
233 233 *
234 234 * 1. "-h" for help was given. We spew a canned help
235 235 * message and exit, regardless of any other options given.
236 236 *
237 237 * 2. "-r" or "-w" asking us to write to the conffile. Lots
238 238 * of argument checking, then we make the change to conffile
239 239 * and exit. (-r processing actually happens in dologname().)
240 240 *
241 241 * 3. "-V" to search/verify the conffile was given. We do
242 242 * the appropriate run through the conffile and exit.
243 243 * (-V processing actually happens in dologname().)
244 244 *
245 245 * 4. No lognames were given, so we're being asked to go through
246 246 * every entry in conffile. We verify that only the options
247 247 * that make sense for this form of the command are present
248 248 * and fall into the main processing loop below.
249 249 *
250 250 * 5. lognames were given, so we fall into the main processing
251 251 * loop below to work our way through them.
252 252 *
253 253 * The last two cases are where the option processing gets more
254 254 * complex. Each time around the main processing loop, we're
255 255 * in one of these cases:
256 256 *
257 257 * A. No cmdargs were found (we're in case 4), the entry
258 258 * in conffile supplies no log file names, so the entry
259 259 * name itself is the logfile name (or names, if it globs
260 260 * to multiple file names).
261 261 *
262 262 * B. No cmdargs were found (we're in case 4), the entry
263 263 * in conffile gives log file names that we then loop
264 264 * through and rotate/expire. In this case, the entry
265 265 * name is specifically NOT one of the log file names.
266 266 *
267 267 * C. We're going through the cmdargs (we're in case 5),
268 268 * the entry in conffile either doesn't exist or it exists
269 269 * but supplies no log file names, so the cmdarg itself
270 270 * is the log file name.
271 271 *
272 272 * D. We're going through the cmdargs (we're in case 5),
273 273 * a matching entry in conffile supplies log file names
274 274 * that we then loop through and rotate/expire. In this
275 275 * case the entry name is specifically NOT one of the log
276 276 * file names.
277 277 *
278 278 * As we're doing all this, any options given on the command line
279 279 * override any found in the conffile, and we apply the defaults
280 280 * for rotation conditions and expiration conditions, etc. at the
281 281 * last opportunity, when we're sure they haven't been overridden
282 282 * by an option somewhere along the way.
283 283 *
284 284 */
285 285
286 286 /* help option overrides anything else */
287 287 if (opts_count(clopts, "h")) {
288 288 (void) fputs(HELP1, stderr);
289 289 (void) fputs(HELP2, stderr);
290 290 err_done(0);
291 291 /*NOTREACHED*/
292 292 }
293 293
294 294 /* detect illegal option combinations */
295 295 if (opts_count(clopts, "rwV") > 1)
296 296 usage("Only one of -r, -w, or -V may be used at a time.");
297 297 if (opts_count(clopts, "cM") > 1)
298 298 usage("Only one of -c or -M may be used at a time.");
299 299
300 300 /* arrange for error output to be mailed if clopts includes -e */
301 301 if (opts_count(clopts, "e"))
302 302 err_mailto(opts_optarg(clopts, "e"));
303 303
304 304 /* this implements the default conffile and timestamps */
305 305 if ((conffile = opts_optarg(clopts, "f")) == NULL)
306 306 conffile = Default_conffile;
307 307 if ((timestamps = opts_optarg(clopts, "F")) == NULL)
308 308 timestamps = Default_timestamps;
309 309 if (opts_count(clopts, "v"))
310 310 (void) out("# loading %s\n", conffile);
311 311 status = conf_open(conffile, timestamps, clopts);
312 312 if (!status && opts_count(clopts, "V"))
313 313 err_done(0);
314 314
315 315 /* handle conffile write option */
316 316 if (opts_count(clopts, "w")) {
317 317 if (Debug)
318 318 (void) fprintf(stderr,
319 319 "main: add/replace conffile entry: <%s>\n",
320 320 opts_optarg(clopts, "w"));
321 321 conf_replace(opts_optarg(clopts, "w"), clopts);
322 322 conf_close(clopts);
323 323 err_done(0);
324 324 /*NOTREACHED*/
325 325 }
326 326
327 327 /*
328 328 * lognames is either a list supplied on the command line,
329 329 * or every entry in the conffile if none were supplied.
330 330 */
331 331 lognames = opts_cmdargs(clopts);
332 332 if (fn_list_empty(lognames)) {
333 333 /*
334 334 * being asked to do all entries in conffile
335 335 *
336 336 * check to see if any options were given that only
337 337 * make sense when lognames are given specifically
338 338 * on the command line.
339 339 */
340 340 if (opts_count(clopts, OPTIONS_NOT_FIRST_FORM))
341 341 usage("some options require logname argument");
342 342 if (Debug)
343 343 (void) fprintf(stderr,
344 344 "main: run all entries in conffile\n");
345 345 lognames = conf_entries();
346 346 }
347 347
348 348 /* foreach logname... */
349 349 fn_list_rewind(lognames);
350 350 while ((fnp = fn_list_next(lognames)) != NULL) {
351 351 buf = fn_s(fnp);
352 352 if (buf != NULL && lut_lookup(Donenames, buf) != NULL) {
353 353 if (Debug)
354 354 (void) fprintf(stderr,
355 355 "main: logname already done: <%s>\n",
356 356 buf);
357 357 continue;
358 358 }
359 359 if (buf != NULL && SETJMP)
360 360 err(EF_FILE, "bailing out on logname \"%s\" "
361 361 "due to errors", buf);
362 362 else
363 363 dologname(fnp, clopts);
364 364 }
365 365
366 366 /* execute any after commands */
367 367 lut_walk(Aftercmds, doaftercmd, clopts);
368 368
369 369 /* execute any gzip commands */
370 370 lut_walk(Gzipnames, do_delayed_gzip, clopts);
371 371
372 372 /* write out any conffile changes */
373 373 conf_close(clopts);
374 374
375 375 err_done(0);
376 376 /*NOTREACHED*/
377 377 return (0); /* for lint's little mind */
378 378 }
379 379
380 380 /* spew a message, then a usage message, then exit */
381 381 static void
382 382 usage(const char *msg)
383 383 {
384 384 if (msg)
385 385 err(0, "%s\nUse \"logadm -h\" for help.", msg);
386 386 else
387 387 err(EF_RAW, "Use \"logadm -h\" for help.\n");
388 388 }
389 389
390 390 /* helper function used by doaftercmd() to join mail addrs with commas */
391 391 /*ARGSUSED1*/
392 392 static void
393 393 commajoin(const char *lhs, void *rhs, void *arg)
394 394 {
395 395 struct fn *fnp = (struct fn *)arg;
396 396 char *buf;
397 397
398 398 buf = fn_s(fnp);
399 399 if (buf != NULL && *buf)
400 400 fn_putc(fnp, ',');
401 401 fn_puts(fnp, lhs);
402 402 }
403 403
404 404 /* helper function used by main() to run "after" commands */
405 405 static void
406 406 doaftercmd(const char *lhs, void *rhs, void *arg)
407 407 {
408 408 struct opts *opts = (struct opts *)arg;
409 409 struct lut *addrs = (struct lut *)rhs;
410 410
411 411 if (addrs) {
412 412 struct fn *fnp = fn_new(NULL);
413 413
414 414 /*
415 415 * addrs contains list of email addrs that should get
416 416 * the error output when this after command is executed.
417 417 */
418 418 lut_walk(addrs, commajoin, fnp);
419 419 err_mailto(fn_s(fnp));
420 420 }
421 421
422 422 docmd(opts, "-a cmd", Sh, "-c", lhs, NULL);
423 423 }
424 424
425 425 /* perform delayed gzip */
426 426
427 427 static void
428 428 do_delayed_gzip(const char *lhs, void *rhs, void *arg)
429 429 {
430 430 struct opts *opts = (struct opts *)arg;
431 431
432 432 if (rhs == NULL) {
433 433 if (Debug) {
434 434 (void) fprintf(stderr, "do_delayed_gzip: not gzipping "
435 435 "expired file <%s>\n", lhs);
436 436 }
437 437 return;
438 438 }
439 439 docmd(opts, "compress old log (-z flag)", Gzip, "-f", lhs, NULL);
440 440 }
441 441
442 442
443 443 /* main logname processing */
444 444 static void
445 445 dologname(struct fn *fnp, struct opts *clopts)
446 446 {
447 447 const char *logname = fn_s(fnp);
448 448 struct opts *cfopts;
449 449 struct opts *allopts;
450 450 struct fn_list *logfiles;
451 451 struct fn_list *globbedfiles;
452 452 struct fn *nextfnp;
453 453
454 454 /* look up options set by config file */
455 455 cfopts = conf_opts(logname);
456 456
457 457 if (opts_count(clopts, "v"))
458 458 (void) out("# processing logname: %s\n", logname);
459 459
460 460 if (Debug) {
461 461 if (logname != NULL)
462 462 (void) fprintf(stderr, "dologname: logname <%s>\n",
463 463 logname);
464 464 (void) fprintf(stderr, "conffile opts:");
465 465 opts_print(cfopts, stderr, NULL);
466 466 (void) fprintf(stderr, "\n");
467 467 }
468 468
469 469 /* handle conffile lookup option */
470 470 if (opts_count(clopts, "V")) {
471 471 /* lookup an entry in conffile */
472 472 if (Debug)
473 473 (void) fprintf(stderr,
474 474 "dologname: lookup conffile entry\n");
475 475 if (conf_lookup(logname)) {
476 476 opts_printword(logname, stdout);
477 477 opts_print(cfopts, stdout, NULL);
478 478 (void) out("\n");
479 479 } else
480 480 err_exitcode(1);
481 481 return;
482 482 }
483 483
484 484 /* handle conffile removal option */
485 485 if (opts_count(clopts, "r")) {
486 486 if (Debug)
487 487 (void) fprintf(stderr,
488 488 "dologname: remove conffile entry\n");
489 489 if (conf_lookup(logname))
490 490 conf_replace(logname, NULL);
491 491 else
492 492 err_exitcode(1);
493 493 return;
494 494 }
495 495
496 496 /* generate combined options */
497 497 allopts = opts_merge(cfopts, clopts);
498 498
499 499 /* arrange for error output to be mailed if allopts includes -e */
500 500 if (opts_count(allopts, "e"))
501 501 err_mailto(opts_optarg(allopts, "e"));
502 502 else
503 503 err_mailto(NULL);
504 504
505 505 /* this implements the default rotation rules */
506 506 if (opts_count(allopts, "sp") == 0) {
507 507 if (opts_count(clopts, "v"))
508 508 (void) out(
509 509 "# using default rotate rules: -s1b -p1w\n");
510 510 (void) opts_set(allopts, "s", "1b");
511 511 (void) opts_set(allopts, "p", "1w");
512 512 }
513 513
514 514 /* this implements the default expiration rules */
515 515 if (opts_count(allopts, "ACS") == 0) {
516 516 if (opts_count(clopts, "v"))
517 517 (void) out("# using default expire rule: -C10\n");
518 518 (void) opts_set(allopts, "C", "10");
519 519 }
520 520
521 521 /* this implements the default template */
522 522 if (opts_count(allopts, "t") == 0) {
523 523 if (opts_count(clopts, "v"))
524 524 (void) out("# using default template: $file.$n\n");
525 525 (void) opts_set(allopts, "t", "$file.$n");
526 526 }
527 527
528 528 if (Debug) {
529 529 (void) fprintf(stderr, "merged opts:");
530 530 opts_print(allopts, stderr, NULL);
531 531 (void) fprintf(stderr, "\n");
532 532 }
533 533
534 534 /*
535 535 * if the conffile entry supplied log file names, then
536 536 * logname is NOT one of the log file names (it was just
537 537 * the entry name in conffile).
538 538 */
539 539 logfiles = opts_cmdargs(cfopts);
540 540 if (Debug) {
541 541 char *buf;
542 542 (void) fprintf(stderr, "dologname: logfiles from cfopts:\n");
543 543 fn_list_rewind(logfiles);
544 544 while ((nextfnp = fn_list_next(logfiles)) != NULL)
545 545 buf = fn_s(nextfnp);
546 546 if (buf != NULL)
547 547 (void) fprintf(stderr, " <%s>\n", buf);
548 548 }
549 549 if (fn_list_empty(logfiles))
550 550 globbedfiles = glob_glob(fnp);
551 551 else
552 552 globbedfiles = glob_glob_list(logfiles);
553 553
554 554 /* go through the list produced by glob expansion */
555 555 fn_list_rewind(globbedfiles);
556 556 while ((nextfnp = fn_list_next(globbedfiles)) != NULL)
557 557 if (rotatelog(nextfnp, allopts))
558 558 expirefiles(nextfnp, allopts);
559 559
560 560 fn_list_free(globbedfiles);
561 561 opts_free(allopts);
562 562 }
563 563
564 564
565 565 /* absurdly long buffer lengths for holding user/group/mode strings */
566 566 #define TIMESTRMAX 100
567 567 #define MAXATTR 100
568 568
569 569 /* rotate a log file if necessary, returns true if ok to go on to expire step */
570 570 static boolean_t
571 571 rotatelog(struct fn *fnp, struct opts *opts)
572 572 {
573 573 char *fname = fn_s(fnp);
574 574 struct stat stbuf;
575 575 char nowstr[TIMESTRMAX];
576 576 struct fn *recentlog = fn_new(NULL); /* for -R cmd */
577 577 char ownerbuf[MAXATTR];
578 578 char groupbuf[MAXATTR];
579 579 char modebuf[MAXATTR];
580 580 const char *owner;
581 581 const char *group;
582 582 const char *mode;
583 583
584 584 if (Debug && fname != NULL)
585 585 (void) fprintf(stderr, "rotatelog: fname <%s>\n", fname);
586 586
587 587 if (opts_count(opts, "p") && opts_optarg_int(opts, "p") == OPTP_NEVER)
588 588 return (B_TRUE); /* "-p never" forced no rotate */
589 589
590 590 /* prepare the keywords */
591 591 kw_init(fnp, NULL);
592 592 if (Debug > 1) {
593 593 (void) fprintf(stderr, "rotatelog keywords:\n");
594 594 kw_print(stderr);
595 595 }
596 596
597 597 if (lstat(fname, &stbuf) < 0) {
598 598 if (opts_count(opts, "N"))
599 599 return (1);
600 600 err(EF_WARN|EF_SYS, "%s", fname);
601 601 return (B_FALSE);
602 602 }
603 603
604 604 if ((stbuf.st_mode & S_IFMT) == S_IFLNK) {
605 605 err(EF_WARN, "%s is a symlink", fname);
606 606 return (B_FALSE);
607 607 }
608 608
609 609 if ((stbuf.st_mode & S_IFMT) != S_IFREG) {
610 610 err(EF_WARN, "%s is not a regular file", fname);
611 611 return (B_FALSE);
612 612 }
613 613
614 614 /* even if size condition is not met, this entry is "done" */
615 615 if (opts_count(opts, "s") &&
616 616 stbuf.st_size < opts_optarg_int(opts, "s")) {
617 617 Donenames = lut_add(Donenames, fname, "1");
618 618 return (B_TRUE);
619 619 }
620 620
621 621 /* see if age condition is present, and return if not met */
622 622 if (opts_count(opts, "p")) {
623 623 off_t when = opts_optarg_int(opts, "p");
624 624 struct opts *cfopts;
625 625
626 626 /* unless rotate forced by "-p now", see if period has passed */
627 627 if (when != OPTP_NOW) {
628 628 /*
629 629 * "when" holds the number of seconds that must have
630 630 * passed since the last time this log was rotated.
631 631 * of course, running logadm can take a little time
632 632 * (typically a second or two, but longer if the
633 633 * conffile has lots of stuff in it) and that amount
634 634 * of time is variable, depending on system load, etc.
635 635 * so we want to allow a little "slop" in the value of
636 636 * "when". this way, if a log should be rotated every
637 637 * week, and the number of seconds passed is really a
638 638 * few seconds short of a week, we'll go ahead and
639 639 * rotate the log as expected.
640 640 *
641 641 */
642 642 if (when >= 60 * 60)
643 643 when -= 59;
↓ open down ↓ |
643 lines elided |
↑ open up ↑ |
644 644
645 645 /*
646 646 * last rotation is recorded as argument to -P,
647 647 * but if logname isn't the same as log file name
648 648 * then the timestamp would be recorded on a
649 649 * separate line in the conf file. so if we
650 650 * haven't seen a -P already, we check to see if
651 651 * it is part of a specific entry for the log
652 652 * file name. this handles the case where the
653 653 * logname is "apache", it supplies a log file
654 - * name like "/var/apache/logs/[a-z]*_log",
655 - * which expands to multiple file names. if one
656 - * of the file names is "/var/apache/logs/access_log"
657 - * the the -P will be attached to a line with that
654 + * name like "/var/apache2/2.2/logs/[a-z]*_log",
655 + * which expands to multiple file names. if one of
656 + * the file names is "/var/apache2/2.2/logs/access_log"
657 + * then the -P will be attached to a line with that
658 658 * logname in the conf file.
659 659 */
660 660 if (opts_count(opts, "P")) {
661 661 off_t last = opts_optarg_int(opts, "P");
662 662
663 663 /* return if not enough time has passed */
664 664 if (Now - last < when)
665 665 return (B_TRUE);
666 666 } else if ((cfopts = conf_opts(fname)) != NULL &&
667 667 opts_count(cfopts, "P")) {
668 668 off_t last = opts_optarg_int(cfopts, "P");
669 669
670 670 /*
671 671 * just checking this means this entry
672 672 * is now "done" if we're going through
673 673 * the entire conffile
674 674 */
675 675 Donenames = lut_add(Donenames, fname, "1");
676 676
677 677 /* return if not enough time has passed */
678 678 if (Now - last < when)
679 679 return (B_TRUE);
680 680 }
681 681 }
682 682 }
683 683
684 684 if (Debug)
685 685 (void) fprintf(stderr, "rotatelog: conditions met\n");
686 686 if (opts_count(opts, "l")) {
687 687 /* Change the time zone to local time zone */
688 688 if (putenv("TZ="))
689 689 err(EF_SYS, "putenv TZ");
690 690 tzset();
691 691 Now = time(0);
692 692
693 693 /* rename the log file */
694 694 rotateto(fnp, opts, 0, recentlog, B_FALSE);
695 695
696 696 /* Change the time zone to UTC */
697 697 if (putenv("TZ=UTC"))
698 698 err(EF_SYS, "putenv TZ");
699 699 tzset();
700 700 Now = time(0);
701 701 } else {
702 702 /* rename the log file */
703 703 rotateto(fnp, opts, 0, recentlog, B_FALSE);
704 704 }
705 705
706 706 /* determine owner, group, mode for empty log file */
707 707 if (opts_count(opts, "o"))
708 708 (void) strlcpy(ownerbuf, opts_optarg(opts, "o"), MAXATTR);
709 709 else {
710 710 (void) snprintf(ownerbuf, MAXATTR, "%ld", stbuf.st_uid);
711 711 }
712 712 owner = ownerbuf;
713 713 if (opts_count(opts, "g"))
714 714 group = opts_optarg(opts, "g");
715 715 else {
716 716 (void) snprintf(groupbuf, MAXATTR, "%ld", stbuf.st_gid);
717 717 group = groupbuf;
718 718 }
719 719 (void) strlcat(ownerbuf, ":", MAXATTR - strlen(ownerbuf));
720 720 (void) strlcat(ownerbuf, group, MAXATTR - strlen(ownerbuf));
721 721 if (opts_count(opts, "m"))
722 722 mode = opts_optarg(opts, "m");
723 723 else {
724 724 (void) snprintf(modebuf, MAXATTR,
725 725 "%03lo", stbuf.st_mode & 0777);
726 726 mode = modebuf;
727 727 }
728 728
729 729 /* create the empty log file */
730 730 docmd(opts, NULL, Touch, fname, NULL, NULL);
731 731 docmd(opts, NULL, Chown, owner, fname, NULL);
732 732 docmd(opts, NULL, Chmod, mode, fname, NULL);
733 733
734 734 /* execute post-rotation command */
735 735 if (opts_count(opts, "R")) {
736 736 struct fn *rawcmd = fn_new(opts_optarg(opts, "R"));
737 737 struct fn *cmd = fn_new(NULL);
738 738
739 739 kw_init(recentlog, NULL);
740 740 (void) kw_expand(rawcmd, cmd, 0, B_FALSE);
741 741 docmd(opts, "-R cmd", Sh, "-c", fn_s(cmd), NULL);
742 742 fn_free(rawcmd);
743 743 fn_free(cmd);
744 744 }
745 745 fn_free(recentlog);
746 746
747 747 /*
748 748 * add "after" command to list of after commands. we also record
749 749 * the email address, if any, where the error output of the after
750 750 * command should be sent. if the after command is already on
751 751 * our list, add the email addr to the list the email addrs for
752 752 * that command (the after command will only be executed once,
753 753 * so the error output gets mailed to every address we've come
754 754 * across associated with this command).
755 755 */
756 756 if (opts_count(opts, "a")) {
757 757 const char *cmd = opts_optarg(opts, "a");
758 758 struct lut *addrs = (struct lut *)lut_lookup(Aftercmds, cmd);
759 759 if (opts_count(opts, "e"))
760 760 addrs = lut_add(addrs, opts_optarg(opts, "e"), NULL);
761 761 Aftercmds = lut_add(Aftercmds, opts_optarg(opts, "a"), addrs);
762 762 }
763 763
764 764 /* record the rotation date */
765 765 (void) strftime(nowstr, sizeof (nowstr),
766 766 "%a %b %e %T %Y", gmtime(&Now));
767 767 if (opts_count(opts, "v") && fname != NULL)
768 768 (void) out("# recording rotation date %s for %s\n",
769 769 nowstr, fname);
770 770 conf_set(fname, "P", STRDUP(nowstr));
771 771 Donenames = lut_add(Donenames, fname, "1");
772 772 return (B_TRUE);
773 773 }
774 774
775 775 /* rotate files "up" according to current template */
776 776 static void
777 777 rotateto(struct fn *fnp, struct opts *opts, int n, struct fn *recentlog,
778 778 boolean_t isgz)
779 779 {
780 780 struct fn *template = fn_new(opts_optarg(opts, "t"));
781 781 struct fn *newfile = fn_new(NULL);
782 782 struct fn *dirname;
783 783 int hasn;
784 784 struct stat stbuf;
785 785 char *buf1;
786 786 char *buf2;
787 787
788 788 /* expand template to figure out new filename */
789 789 hasn = kw_expand(template, newfile, n, isgz);
790 790
791 791 buf1 = fn_s(fnp);
792 792 buf2 = fn_s(newfile);
793 793
794 794 if (Debug)
795 795 if (buf1 != NULL && buf2 != NULL) {
796 796 (void) fprintf(stderr, "rotateto: %s -> %s (%d)\n",
797 797 buf1, buf2, n);
798 798 }
799 799 /* if filename is there already, rotate "up" */
800 800 if (hasn && lstat(buf2, &stbuf) != -1)
801 801 rotateto(newfile, opts, n + 1, recentlog, isgz);
802 802 else if (hasn && opts_count(opts, "z")) {
803 803 struct fn *gzfnp = fn_dup(newfile);
804 804 /*
805 805 * since we're compressing old files, see if we
806 806 * about to rotate into one.
807 807 */
808 808 fn_puts(gzfnp, ".gz");
809 809 if (lstat(fn_s(gzfnp), &stbuf) != -1)
810 810 rotateto(gzfnp, opts, n + 1, recentlog, B_TRUE);
811 811 fn_free(gzfnp);
812 812 }
813 813
814 814 /* first time through run "before" cmd if not run already */
815 815 if (n == 0 && opts_count(opts, "b")) {
816 816 const char *cmd = opts_optarg(opts, "b");
817 817
818 818 if (lut_lookup(Beforecmds, cmd) == NULL) {
819 819 docmd(opts, "-b cmd", Sh, "-c", cmd, NULL);
820 820 Beforecmds = lut_add(Beforecmds, cmd, "1");
821 821 }
822 822 }
823 823
824 824 /* ensure destination directory exists */
825 825 dirname = fn_dirname(newfile);
826 826 docmd(opts, "verify directory exists", Mkdir, "-p",
827 827 fn_s(dirname), NULL);
828 828 fn_free(dirname);
829 829
830 830 /* do the rename */
831 831 if (opts_count(opts, "c") != NULL) {
832 832 docopytruncate(opts, fn_s(fnp), fn_s(newfile));
833 833 } else if (n == 0 && opts_count(opts, "M")) {
834 834 struct fn *rawcmd = fn_new(opts_optarg(opts, "M"));
835 835 struct fn *cmd = fn_new(NULL);
836 836
837 837 /* use specified command to mv the log file */
838 838 kw_init(fnp, newfile);
839 839 (void) kw_expand(rawcmd, cmd, 0, B_FALSE);
840 840 docmd(opts, "-M cmd", Sh, "-c", fn_s(cmd), NULL);
841 841 fn_free(rawcmd);
842 842 fn_free(cmd);
843 843 } else
844 844 /* common case: we call "mv" to handle the actual rename */
845 845 docmd(opts, "rotate log file", Mv, "-f",
846 846 fn_s(fnp), fn_s(newfile));
847 847
848 848 /* first time through, gather interesting info for caller */
849 849 if (n == 0)
850 850 fn_renew(recentlog, fn_s(newfile));
851 851 }
852 852
853 853 /* expire phase of logname processing */
854 854 static void
855 855 expirefiles(struct fn *fnp, struct opts *opts)
856 856 {
857 857 char *fname = fn_s(fnp);
858 858 struct fn *template;
859 859 struct fn *pattern;
860 860 struct fn_list *files;
861 861 struct fn *nextfnp;
862 862 off_t count;
863 863 off_t size;
864 864
865 865 if (Debug && fname != NULL)
866 866 (void) fprintf(stderr, "expirefiles: fname <%s>\n", fname);
867 867
868 868 /* return if no potential expire conditions */
869 869 if (opts_count(opts, "zAS") == 0 && opts_optarg_int(opts, "C") == 0)
870 870 return;
871 871
872 872 kw_init(fnp, NULL);
873 873 if (Debug > 1) {
874 874 (void) fprintf(stderr, "expirefiles keywords:\n");
875 875 kw_print(stderr);
876 876 }
877 877
878 878 /* see if pattern was supplied by user */
879 879 if (opts_count(opts, "T")) {
880 880 template = fn_new(opts_optarg(opts, "T"));
881 881 pattern = glob_to_reglob(template);
882 882 } else {
883 883 /* nope, generate pattern based on rotation template */
884 884 template = fn_new(opts_optarg(opts, "t"));
885 885 pattern = fn_new(NULL);
886 886 (void) kw_expand(template, pattern, -1,
887 887 opts_count(opts, "z") != 0);
888 888 }
889 889
890 890 /* match all old log files (hopefully not any others as well!) */
891 891 files = glob_reglob(pattern);
892 892
893 893 if (Debug) {
894 894 char *buf;
895 895
896 896 buf = fn_s(pattern);
897 897 if (buf != NULL) {
898 898 (void) fprintf(stderr, "expirefiles: pattern <%s>\n",
899 899 buf);
900 900 }
901 901 fn_list_rewind(files);
902 902 while ((nextfnp = fn_list_next(files)) != NULL)
903 903 buf = fn_s(nextfnp);
904 904 if (buf != NULL)
905 905 (void) fprintf(stderr, " <%s>\n", buf);
906 906 }
907 907
908 908 /* see if count causes expiration */
909 909 if ((count = opts_optarg_int(opts, "C")) > 0) {
910 910 int needexpire = fn_list_count(files) - count;
911 911
912 912 if (Debug)
913 913 (void) fprintf(stderr, "expirefiles: needexpire %d\n",
914 914 needexpire);
915 915
916 916 while (needexpire > 0 &&
917 917 ((nextfnp = fn_list_popoldest(files)) != NULL)) {
918 918 dorm(opts, "expire by count rule", nextfnp);
919 919 fn_free(nextfnp);
920 920 needexpire--;
921 921 }
922 922 }
923 923
924 924 /* see if total size causes expiration */
925 925 if (opts_count(opts, "S") && (size = opts_optarg_int(opts, "S")) > 0) {
926 926 while (fn_list_totalsize(files) > size &&
927 927 ((nextfnp = fn_list_popoldest(files)) != NULL)) {
928 928 dorm(opts, "expire by size rule", nextfnp);
929 929 fn_free(nextfnp);
930 930 }
931 931 }
932 932
933 933 /* see if age causes expiration */
934 934 if (opts_count(opts, "A")) {
935 935 int mtime = (int)time(0) - (int)opts_optarg_int(opts, "A");
936 936
937 937 while ((nextfnp = fn_list_popoldest(files)) != NULL) {
938 938 if (fn_getstat(nextfnp)->st_mtime < mtime) {
939 939 dorm(opts, "expire by age rule", nextfnp);
940 940 fn_free(nextfnp);
941 941 } else {
942 942 fn_list_addfn(files, nextfnp);
943 943 break;
944 944 }
945 945 }
946 946 }
947 947
948 948 /* record old log files to be gzip'ed according to -z count */
949 949 if (opts_count(opts, "z")) {
950 950 int zcount = (int)opts_optarg_int(opts, "z");
951 951 int fcount = fn_list_count(files);
952 952
953 953 while (fcount > zcount &&
954 954 (nextfnp = fn_list_popoldest(files)) != NULL) {
955 955 if (!fn_isgz(nextfnp)) {
956 956 /*
957 957 * Don't gzip the old log file yet -
958 958 * it takes too long. Just remember that we
959 959 * need to gzip.
960 960 */
961 961 if (Debug) {
962 962 (void) fprintf(stderr,
963 963 "will compress %s count %d\n",
964 964 fn_s(nextfnp), fcount);
965 965 }
966 966 Gzipnames = lut_add(Gzipnames,
967 967 fn_s(nextfnp), "1");
968 968 }
969 969 fn_free(nextfnp);
970 970 fcount--;
971 971 }
972 972 }
973 973
974 974 fn_free(template);
975 975 fn_list_free(files);
976 976 }
977 977
978 978 /* execute a command to remove an expired log file */
979 979 static void
980 980 dorm(struct opts *opts, const char *msg, struct fn *fnp)
981 981 {
982 982 if (opts_count(opts, "E")) {
983 983 struct fn *rawcmd = fn_new(opts_optarg(opts, "E"));
984 984 struct fn *cmd = fn_new(NULL);
985 985
986 986 /* user supplied cmd, expand $file */
987 987 kw_init(fnp, NULL);
988 988 (void) kw_expand(rawcmd, cmd, 0, B_FALSE);
989 989 docmd(opts, msg, Sh, "-c", fn_s(cmd), NULL);
990 990 fn_free(rawcmd);
991 991 fn_free(cmd);
992 992 } else
993 993 docmd(opts, msg, Rm, "-f", fn_s(fnp), NULL);
994 994 Gzipnames = lut_add(Gzipnames, fn_s(fnp), NULL);
995 995 }
996 996
997 997 /* execute a command, producing -n and -v output as necessary */
998 998 static void
999 999 docmd(struct opts *opts, const char *msg, const char *cmd,
1000 1000 const char *arg1, const char *arg2, const char *arg3)
1001 1001 {
1002 1002 int pid;
1003 1003 int errpipe[2];
1004 1004
1005 1005 /* print info about command if necessary */
1006 1006 if (opts_count(opts, "vn")) {
1007 1007 const char *simplecmd;
1008 1008
1009 1009 if ((simplecmd = strrchr(cmd, '/')) == NULL)
1010 1010 simplecmd = cmd;
1011 1011 else
1012 1012 simplecmd++;
1013 1013 (void) out("%s", simplecmd);
1014 1014 if (arg1)
1015 1015 (void) out(" %s", arg1);
1016 1016 if (arg2)
1017 1017 (void) out(" %s", arg2);
1018 1018 if (arg3)
1019 1019 (void) out(" %s", arg3);
1020 1020 if (msg)
1021 1021 (void) out(" # %s", msg);
1022 1022 (void) out("\n");
1023 1023 }
1024 1024
1025 1025 if (opts_count(opts, "n"))
1026 1026 return; /* -n means don't really do it */
1027 1027
1028 1028 /*
1029 1029 * run the cmd and see if it failed. this function is *not* a
1030 1030 * generic command runner -- we depend on some knowledge we
1031 1031 * have about the commands we run. first of all, we expect
1032 1032 * errors to spew something to stderr, and that something is
1033 1033 * typically short enough to fit into a pipe so we can wait()
1034 1034 * for the command to complete and then fetch the error text
1035 1035 * from the pipe. we also expect the exit codes to make sense.
1036 1036 * notice also that we only allow a command name which is an
1037 1037 * absolute pathname, and two args must be supplied (the
1038 1038 * second may be NULL, or they may both be NULL).
1039 1039 */
1040 1040 if (pipe(errpipe) < 0)
1041 1041 err(EF_SYS, "pipe");
1042 1042
1043 1043 if ((pid = fork()) < 0)
1044 1044 err(EF_SYS, "fork");
1045 1045 else if (pid) {
1046 1046 int wstat;
1047 1047 int count;
1048 1048
1049 1049 /* parent */
1050 1050 (void) close(errpipe[1]);
1051 1051 if (waitpid(pid, &wstat, 0) < 0)
1052 1052 err(EF_SYS, "waitpid");
1053 1053
1054 1054 /* check for stderr output */
1055 1055 if (ioctl(errpipe[0], FIONREAD, &count) >= 0 && count) {
1056 1056 err(EF_WARN, "command failed: %s%s%s%s%s%s%s",
1057 1057 cmd,
1058 1058 (arg1) ? " " : "",
1059 1059 (arg1) ? arg1 : "",
1060 1060 (arg2) ? " " : "",
1061 1061 (arg2) ? arg2 : "",
1062 1062 (arg3) ? " " : "",
1063 1063 (arg3) ? arg3 : "");
1064 1064 err_fromfd(errpipe[0]);
1065 1065 } else if (WIFSIGNALED(wstat))
1066 1066 err(EF_WARN,
1067 1067 "command died, signal %d: %s%s%s%s%s%s%s",
1068 1068 WTERMSIG(wstat),
1069 1069 cmd,
1070 1070 (arg1) ? " " : "",
1071 1071 (arg1) ? arg1 : "",
1072 1072 (arg2) ? " " : "",
1073 1073 (arg2) ? arg2 : "",
1074 1074 (arg3) ? " " : "",
1075 1075 (arg3) ? arg3 : "");
1076 1076 else if (WIFEXITED(wstat) && WEXITSTATUS(wstat))
1077 1077 err(EF_WARN,
1078 1078 "command error, exit %d: %s%s%s%s%s%s%s",
1079 1079 WEXITSTATUS(wstat),
1080 1080 cmd,
1081 1081 (arg1) ? " " : "",
1082 1082 (arg1) ? arg1 : "",
1083 1083 (arg2) ? " " : "",
1084 1084 (arg2) ? arg2 : "",
1085 1085 (arg3) ? " " : "",
1086 1086 (arg3) ? arg3 : "");
1087 1087
1088 1088 (void) close(errpipe[0]);
1089 1089 } else {
1090 1090 /* child */
1091 1091 (void) dup2(errpipe[1], fileno(stderr));
1092 1092 (void) close(errpipe[0]);
1093 1093 (void) execl(cmd, cmd, arg1, arg2, arg3, 0);
1094 1094 perror(cmd);
1095 1095 _exit(1);
1096 1096 }
1097 1097 }
1098 1098
1099 1099 /* do internal atomic file copy and truncation */
1100 1100 static void
1101 1101 docopytruncate(struct opts *opts, const char *file, const char *file_copy)
1102 1102 {
1103 1103 int fi, fo, len;
1104 1104 char buf[4096];
1105 1105 struct stat s;
1106 1106 struct utimbuf times;
1107 1107
1108 1108 /* print info if necessary */
1109 1109 if (opts_count(opts, "vn") != NULL) {
1110 1110 (void) out("# log rotation via atomic copy and truncation"
1111 1111 " (-c flag):\n");
1112 1112 (void) out("# copy %s to %s\n", file, file_copy);
1113 1113 (void) out("# truncate %s\n", file);
1114 1114 }
1115 1115
1116 1116 if (opts_count(opts, "n"))
1117 1117 return; /* -n means don't really do it */
1118 1118
1119 1119 /* open log file to be rotated and remember its chmod mask */
1120 1120 if ((fi = open(file, O_RDWR)) < 0) {
1121 1121 err(EF_SYS, "cannot open file %s", file);
1122 1122 return;
1123 1123 }
1124 1124
1125 1125 if (fstat(fi, &s) < 0) {
1126 1126 err(EF_SYS, "cannot access: %s", file);
1127 1127 (void) close(fi);
1128 1128 return;
1129 1129 }
1130 1130
1131 1131 /* create new file for copy destination with correct attributes */
1132 1132 if ((fo = open(file_copy, O_CREAT|O_APPEND|O_WRONLY, s.st_mode)) < 0) {
1133 1133 err(EF_SYS, "cannot create file: %s", file_copy);
1134 1134 (void) close(fi);
1135 1135 return;
1136 1136 }
1137 1137
1138 1138 (void) fchown(fo, s.st_uid, s.st_gid);
1139 1139
1140 1140 /* lock log file so that nobody can write into it before we are done */
1141 1141 if (fchmod(fi, s.st_mode|S_ISGID) < 0)
1142 1142 err(EF_SYS, "cannot set mandatory lock bit for: %s", file);
1143 1143
1144 1144 if (lockf(fi, F_LOCK, 0) == -1)
1145 1145 err(EF_SYS, "cannot lock file %s", file);
1146 1146
1147 1147 /* do atomic copy and truncation */
1148 1148 while ((len = read(fi, buf, sizeof (buf))) > 0)
1149 1149 if (write(fo, buf, len) != len) {
1150 1150 err(EF_SYS, "cannot write into file %s", file_copy);
1151 1151 (void) lockf(fi, F_ULOCK, 0);
1152 1152 (void) fchmod(fi, s.st_mode);
1153 1153 (void) close(fi);
1154 1154 (void) close(fo);
1155 1155 (void) remove(file_copy);
1156 1156 return;
1157 1157 }
1158 1158
1159 1159 (void) ftruncate(fi, 0);
1160 1160
1161 1161 /* unlock log file */
1162 1162 if (lockf(fi, F_ULOCK, 0) == -1)
1163 1163 err(EF_SYS, "cannot unlock file %s", file);
1164 1164
1165 1165 if (fchmod(fi, s.st_mode) < 0)
1166 1166 err(EF_SYS, "cannot reset mandatory lock bit for: %s", file);
1167 1167
1168 1168 (void) close(fi);
1169 1169 (void) close(fo);
1170 1170
1171 1171 /* keep times from original file */
1172 1172 times.actime = s.st_atime;
1173 1173 times.modtime = s.st_mtime;
1174 1174 (void) utime(file_copy, ×);
1175 1175 }
↓ open down ↓ |
508 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX