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