1 /*- 2 * ------+---------+---------+-------- + --------+---------+---------+---------* 3 * This file includes significant modifications done by: 4 * Copyright (c) 2003, 2004 - Garance Alistair Drosehn <gad@FreeBSD.org>. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 * 28 * ------+---------+---------+-------- + --------+---------+---------+---------* 29 */ 30 31 /* 32 * This file contains changes from the Open Software Foundation. 33 */ 34 35 /* 36 * Copyright 1988, 1989 by the Massachusetts Institute of Technology 37 * 38 * Permission to use, copy, modify, and distribute this software and its 39 * documentation for any purpose and without fee is hereby granted, provided 40 * that the above copyright notice appear in all copies and that both that 41 * copyright notice and this permission notice appear in supporting 42 * documentation, and that the names of M.I.T. and the M.I.T. S.I.P.B. not be 43 * used in advertising or publicity pertaining to distribution of the 44 * software without specific, written prior permission. M.I.T. and the M.I.T. 45 * S.I.P.B. make no representations about the suitability of this software 46 * for any purpose. It is provided "as is" without express or implied 47 * warranty. 48 * 49 */ 50 51 /* 52 * newsyslog - roll over selected logs at the appropriate time, keeping the a 53 * specified number of backup files around. 54 */ 55 56 #include <sys/cdefs.h> 57 #define OSF 58 59 #include <sys/param.h> 60 #include <sys/queue.h> 61 #include <sys/sbuf.h> 62 #include <sys/stat.h> 63 #include <sys/wait.h> 64 65 #include <assert.h> 66 #include <ctype.h> 67 #include <err.h> 68 #include <errno.h> 69 #include <dirent.h> 70 #include <fcntl.h> 71 #include <fnmatch.h> 72 #include <glob.h> 73 #include <grp.h> 74 #include <paths.h> 75 #include <pwd.h> 76 #include <signal.h> 77 #include <stdio.h> 78 #include <libgen.h> 79 #include <stdlib.h> 80 #include <string.h> 81 #include <syslog.h> 82 #include <time.h> 83 #include <unistd.h> 84 85 #include "pathnames.h" 86 #include "extern.h" 87 88 /* 89 * Compression types 90 */ 91 #define COMPRESS_TYPES 5 /* Number of supported compression types */ 92 93 #define COMPRESS_NONE 0 94 #define COMPRESS_GZIP 1 95 #define COMPRESS_BZIP2 2 96 #define COMPRESS_XZ 3 97 #define COMPRESS_ZSTD 4 98 99 /* 100 * Bit-values for the 'flags' parsed from a config-file entry. 101 */ 102 #define CE_BINARY 0x0008 /* Logfile is in binary, do not add status */ 103 /* messages to logfile(s) when rotating. */ 104 #define CE_NOSIGNAL 0x0010 /* There is no process to signal when */ 105 /* trimming this file. */ 106 #define CE_TRIMAT 0x0020 /* trim file at a specific time. */ 107 #define CE_GLOB 0x0040 /* name of the log is file name pattern. */ 108 #define CE_SIGNALGROUP 0x0080 /* Signal a process-group instead of a single */ 109 /* process when trimming this file. */ 110 #define CE_CREATE 0x0100 /* Create the log file if it does not exist. */ 111 #define CE_NODUMP 0x0200 /* Set 'nodump' on newly created log file. */ 112 #define CE_PID2CMD 0x0400 /* Replace PID file with a shell command.*/ 113 #define CE_PLAIN0 0x0800 /* Do not compress zero'th history file */ 114 #define CE_RFC5424 0x1000 /* Use RFC5424 format rotation message */ 115 #define CE_NOEMPTY 0x2000 /* Do not rotate the file when its size */ 116 /* is zero */ 117 #define MIN_PID 5 /* Don't touch pids lower than this */ 118 #define MAX_PID 99999 /* was lower, see /usr/include/sys/proc.h */ 119 120 #define kbytes(size) (((size) + 1023) >> 10) 121 122 #define DEFAULT_MARKER "<default>" 123 #define DEBUG_MARKER "<debug>" 124 #define INCLUDE_MARKER "<include>" 125 #define DEFAULT_TIMEFNAME_FMT "%Y%m%dT%H%M%S" 126 127 #define MAX_OLDLOGS 65536 /* Default maximum number of old logfiles */ 128 129 struct compress_types { 130 const char *flag; /* Flag in configuration file */ 131 const char *suffix; /* Compression suffix */ 132 const char *path; /* Path to compression program */ 133 const char **flags; /* Compression program flags */ 134 int nflags; /* Program flags count */ 135 }; 136 137 static const char *gzip_flags[] = { "-f" }; 138 #define bzip2_flags gzip_flags 139 #define xz_flags gzip_flags 140 static const char *zstd_flags[] = { "-q", "--rm" }; 141 142 static const struct compress_types compress_type[COMPRESS_TYPES] = { 143 { "", "", "", NULL, 0 }, 144 { "Z", ".gz", _PATH_GZIP, gzip_flags, nitems(gzip_flags) }, 145 { "J", ".bz2", _PATH_BZIP2, bzip2_flags, nitems(bzip2_flags) }, 146 { "X", ".xz", _PATH_XZ, xz_flags, nitems(xz_flags) }, 147 { "Y", ".zst", _PATH_ZSTD, zstd_flags, nitems(zstd_flags) } 148 }; 149 150 struct conf_entry { 151 STAILQ_ENTRY(conf_entry) cf_nextp; 152 char *log; /* Name of the log */ 153 char *pid_cmd_file; /* PID or command file */ 154 char *r_reason; /* The reason this file is being rotated */ 155 int firstcreate; /* Creating log for the first time (-C). */ 156 int rotate; /* Non-zero if this file should be rotated */ 157 int fsize; /* size found for the log file */ 158 uid_t uid; /* Owner of log */ 159 gid_t gid; /* Group of log */ 160 int numlogs; /* Number of logs to keep */ 161 int trsize; /* Size cutoff to trigger trimming the log */ 162 int hours; /* Hours between log trimming */ 163 struct ptime_data *trim_at; /* Specific time to do trimming */ 164 unsigned int permissions; /* File permissions on the log */ 165 int flags; /* CE_BINARY */ 166 int compress; /* Compression */ 167 int sig; /* Signal to send */ 168 int def_cfg; /* Using the <default> rule for this file */ 169 }; 170 171 struct sigwork_entry { 172 SLIST_ENTRY(sigwork_entry) sw_nextp; 173 int sw_signum; /* the signal to send */ 174 int sw_pidok; /* true if pid value is valid */ 175 pid_t sw_pid; /* the process id from the PID file */ 176 const char *sw_pidtype; /* "daemon" or "process group" */ 177 int sw_runcmd; /* run command or send PID to signal */ 178 char sw_fname[1]; /* file the PID was read from or shell cmd */ 179 }; 180 181 struct zipwork_entry { 182 SLIST_ENTRY(zipwork_entry) zw_nextp; 183 const struct conf_entry *zw_conf; /* for chown/perm/flag info */ 184 const struct sigwork_entry *zw_swork; /* to know success of signal */ 185 int zw_fsize; /* size of the file to compress */ 186 char zw_fname[1]; /* the file to compress */ 187 }; 188 189 struct include_entry { 190 STAILQ_ENTRY(include_entry) inc_nextp; 191 const char *file; /* Name of file to process */ 192 }; 193 194 struct oldlog_entry { 195 char *fname; /* Filename of the log file */ 196 time_t t; /* Parsed timestamp of the logfile */ 197 }; 198 199 typedef enum { 200 FREE_ENT, KEEP_ENT 201 } fk_entry; 202 203 STAILQ_HEAD(cflist, conf_entry); 204 static SLIST_HEAD(swlisthead, sigwork_entry) swhead = 205 SLIST_HEAD_INITIALIZER(swhead); 206 static SLIST_HEAD(zwlisthead, zipwork_entry) zwhead = 207 SLIST_HEAD_INITIALIZER(zwhead); 208 STAILQ_HEAD(ilist, include_entry); 209 210 int dbg_at_times; /* -D Show details of 'trim_at' code */ 211 212 static int archtodir = 0; /* Archive old logfiles to other directory */ 213 static int createlogs; /* Create (non-GLOB) logfiles which do not */ 214 /* already exist. 1=='for entries with */ 215 /* C flag', 2=='for all entries'. */ 216 int verbose = 0; /* Print out what's going on */ 217 static int needroot = 1; /* Root privs are necessary */ 218 int noaction = 0; /* Don't do anything, just show it */ 219 static int norotate = 0; /* Don't rotate */ 220 static int nosignal; /* Do not send any signals */ 221 static int enforcepid = 0; /* If PID file does not exist or empty, do nothing */ 222 static int force = 0; /* Force the trim no matter what */ 223 static int rotatereq = 0; /* -R = Always rotate the file(s) as given */ 224 /* on the command (this also requires */ 225 /* that a list of files *are* given on */ 226 /* the run command). */ 227 static char *requestor; /* The name given on a -R request */ 228 static char *timefnamefmt = NULL;/* Use time based filenames instead of .0 */ 229 static char *archdirname; /* Directory path to old logfiles archive */ 230 static char *destdir = NULL; /* Directory to treat at root for logs */ 231 static const char *conf; /* Configuration file to use */ 232 233 struct ptime_data *dbg_timenow; /* A "timenow" value set via -D option */ 234 static struct ptime_data *timenow; /* The time to use for checking at-fields */ 235 236 #define DAYTIME_LEN 16 237 static char daytime[DAYTIME_LEN];/* The current time in human readable form, 238 * used for rotation-tracking messages. */ 239 240 /* Another buffer to hold the current time in RFC5424 format. Fractional 241 * seconds are allowed by the RFC, but are not included in the 242 * rotation-tracking messages written by newsyslog and so are not accounted for 243 * in the length below. 244 */ 245 #define DAYTIME_RFC5424_LEN sizeof("YYYY-MM-DDTHH:MM:SS+00:00") 246 static char daytime_rfc5424[DAYTIME_RFC5424_LEN]; 247 248 static char hostname[MAXHOSTNAMELEN]; /* hostname */ 249 static size_t hostname_shortlen; 250 251 static const char *path_syslogpid = _PATH_SYSLOGPID; 252 253 static struct cflist *get_worklist(char **files); 254 static void parse_file(FILE *cf, struct cflist *work_p, struct cflist *glob_p, 255 struct conf_entry **defconf, struct ilist *inclist); 256 static void add_to_queue(const char *fname, struct ilist *inclist); 257 static char *sob(char *p); 258 static char *son(char *p); 259 static int isnumberstr(const char *); 260 static int isglobstr(const char *); 261 static char *missing_field(char *p, char *errline); 262 static void change_attrs(const char *, const struct conf_entry *); 263 static const char *get_logfile_suffix(const char *logfile); 264 static fk_entry do_entry(struct conf_entry *); 265 static fk_entry do_rotate(const struct conf_entry *); 266 static void do_sigwork(struct sigwork_entry *); 267 static void do_zipwork(struct zipwork_entry *); 268 static struct sigwork_entry * 269 save_sigwork(const struct conf_entry *); 270 static struct zipwork_entry * 271 save_zipwork(const struct conf_entry *, const struct 272 sigwork_entry *, int, const char *); 273 static void set_swpid(struct sigwork_entry *, const struct conf_entry *); 274 static int sizefile(const char *); 275 static void expand_globs(struct cflist *work_p, struct cflist *glob_p); 276 static void free_clist(struct cflist *list); 277 static void free_entry(struct conf_entry *ent); 278 static struct conf_entry *init_entry(const char *fname, 279 struct conf_entry *src_entry); 280 static void parse_args(int argc, char **argv); 281 static int parse_doption(const char *doption); 282 static void usage(void) __dead2; 283 static int log_trim(const char *logname, const struct conf_entry *log_ent); 284 static int age_old_log(const char *file); 285 static void savelog(char *from, char *to); 286 static void createdir(const struct conf_entry *ent, char *dirpart); 287 static void createlog(const struct conf_entry *ent); 288 static int parse_signal(const char *str); 289 290 /* 291 * All the following take a parameter of 'int', but expect values in the 292 * range of unsigned char. Define wrappers which take values of type 'char', 293 * whether signed or unsigned, and ensure they end up in the right range. 294 */ 295 #define isdigitch(Anychar) isdigit((u_char)(Anychar)) 296 #define isprintch(Anychar) isprint((u_char)(Anychar)) 297 #define isspacech(Anychar) isspace((u_char)(Anychar)) 298 #define tolowerch(Anychar) tolower((u_char)(Anychar)) 299 300 int 301 main(int argc, char **argv) 302 { 303 struct cflist *worklist; 304 struct conf_entry *p; 305 struct sigwork_entry *stmp; 306 struct zipwork_entry *ztmp; 307 308 SLIST_INIT(&swhead); 309 SLIST_INIT(&zwhead); 310 311 parse_args(argc, argv); 312 argc -= optind; 313 argv += optind; 314 315 if (needroot && getuid() && geteuid()) 316 errx(1, "must have root privs"); 317 worklist = get_worklist(argv); 318 319 /* 320 * Rotate all the files which need to be rotated. Note that 321 * some users have *hundreds* of entries in newsyslog.conf! 322 */ 323 while (!STAILQ_EMPTY(worklist)) { 324 p = STAILQ_FIRST(worklist); 325 STAILQ_REMOVE_HEAD(worklist, cf_nextp); 326 if (do_entry(p) == FREE_ENT) 327 free_entry(p); 328 } 329 330 /* 331 * Send signals to any processes which need a signal to tell 332 * them to close and re-open the log file(s) we have rotated. 333 * Note that zipwork_entries include pointers to these 334 * sigwork_entry's, so we can not free the entries here. 335 */ 336 if (!SLIST_EMPTY(&swhead)) { 337 if (noaction || verbose) 338 printf("Signal all daemon process(es)...\n"); 339 SLIST_FOREACH(stmp, &swhead, sw_nextp) 340 do_sigwork(stmp); 341 if (!(rotatereq && nosignal)) { 342 if (noaction) 343 printf("\tsleep 10\n"); 344 else { 345 if (verbose) 346 printf("Pause 10 seconds to allow " 347 "daemon(s) to close log file(s)\n"); 348 sleep(10); 349 } 350 } 351 } 352 /* 353 * Compress all files that we're expected to compress, now 354 * that all processes should have closed the files which 355 * have been rotated. 356 */ 357 if (!SLIST_EMPTY(&zwhead)) { 358 if (noaction || verbose) 359 printf("Compress all rotated log file(s)...\n"); 360 while (!SLIST_EMPTY(&zwhead)) { 361 ztmp = SLIST_FIRST(&zwhead); 362 do_zipwork(ztmp); 363 SLIST_REMOVE_HEAD(&zwhead, zw_nextp); 364 free(ztmp); 365 } 366 } 367 /* Now free all the sigwork entries. */ 368 while (!SLIST_EMPTY(&swhead)) { 369 stmp = SLIST_FIRST(&swhead); 370 SLIST_REMOVE_HEAD(&swhead, sw_nextp); 371 free(stmp); 372 } 373 374 while (wait(NULL) > 0 || errno == EINTR) 375 ; 376 return (0); 377 } 378 379 static struct conf_entry * 380 init_entry(const char *fname, struct conf_entry *src_entry) 381 { 382 struct conf_entry *tempwork; 383 384 if (verbose > 4) 385 printf("\t--> [creating entry for %s]\n", fname); 386 387 tempwork = malloc(sizeof(struct conf_entry)); 388 if (tempwork == NULL) 389 err(1, "malloc of conf_entry for %s", fname); 390 391 if (destdir == NULL || fname[0] != '/') 392 tempwork->log = strdup(fname); 393 else 394 asprintf(&tempwork->log, "%s%s", destdir, fname); 395 if (tempwork->log == NULL) 396 err(1, "strdup for %s", fname); 397 398 if (src_entry != NULL) { 399 tempwork->pid_cmd_file = NULL; 400 if (src_entry->pid_cmd_file) 401 tempwork->pid_cmd_file = strdup(src_entry->pid_cmd_file); 402 tempwork->r_reason = NULL; 403 tempwork->firstcreate = 0; 404 tempwork->rotate = 0; 405 tempwork->fsize = -1; 406 tempwork->uid = src_entry->uid; 407 tempwork->gid = src_entry->gid; 408 tempwork->numlogs = src_entry->numlogs; 409 tempwork->trsize = src_entry->trsize; 410 tempwork->hours = src_entry->hours; 411 tempwork->trim_at = NULL; 412 if (src_entry->trim_at != NULL) 413 tempwork->trim_at = ptime_init(src_entry->trim_at); 414 tempwork->permissions = src_entry->permissions; 415 tempwork->flags = src_entry->flags; 416 tempwork->compress = src_entry->compress; 417 tempwork->sig = src_entry->sig; 418 tempwork->def_cfg = src_entry->def_cfg; 419 } else { 420 /* Initialize as a "do-nothing" entry */ 421 tempwork->pid_cmd_file = NULL; 422 tempwork->r_reason = NULL; 423 tempwork->firstcreate = 0; 424 tempwork->rotate = 0; 425 tempwork->fsize = -1; 426 tempwork->uid = (uid_t)-1; 427 tempwork->gid = (gid_t)-1; 428 tempwork->numlogs = 1; 429 tempwork->trsize = -1; 430 tempwork->hours = -1; 431 tempwork->trim_at = NULL; 432 tempwork->permissions = 0; 433 tempwork->flags = 0; 434 tempwork->compress = COMPRESS_NONE; 435 tempwork->sig = SIGHUP; 436 tempwork->def_cfg = 0; 437 } 438 439 return (tempwork); 440 } 441 442 static void 443 free_entry(struct conf_entry *ent) 444 { 445 446 if (ent == NULL) 447 return; 448 449 if (ent->log != NULL) { 450 if (verbose > 4) 451 printf("\t--> [freeing entry for %s]\n", ent->log); 452 free(ent->log); 453 ent->log = NULL; 454 } 455 456 if (ent->pid_cmd_file != NULL) { 457 free(ent->pid_cmd_file); 458 ent->pid_cmd_file = NULL; 459 } 460 461 if (ent->r_reason != NULL) { 462 free(ent->r_reason); 463 ent->r_reason = NULL; 464 } 465 466 if (ent->trim_at != NULL) { 467 ptime_free(ent->trim_at); 468 ent->trim_at = NULL; 469 } 470 471 free(ent); 472 } 473 474 static void 475 free_clist(struct cflist *list) 476 { 477 struct conf_entry *ent; 478 479 while (!STAILQ_EMPTY(list)) { 480 ent = STAILQ_FIRST(list); 481 STAILQ_REMOVE_HEAD(list, cf_nextp); 482 free_entry(ent); 483 } 484 485 free(list); 486 list = NULL; 487 } 488 489 static fk_entry 490 do_entry(struct conf_entry * ent) 491 { 492 #define REASON_MAX 80 493 int modtime; 494 fk_entry free_or_keep; 495 double diffsecs; 496 char temp_reason[REASON_MAX]; 497 int oversized; 498 499 free_or_keep = FREE_ENT; 500 if (verbose) 501 printf("%s <%d%s>: ", ent->log, ent->numlogs, 502 compress_type[ent->compress].flag); 503 ent->fsize = sizefile(ent->log); 504 oversized = ((ent->trsize > 0) && (ent->fsize >= ent->trsize)); 505 modtime = age_old_log(ent->log); 506 ent->rotate = 0; 507 ent->firstcreate = 0; 508 if (ent->fsize < 0) { 509 /* 510 * If either the C flag or the -C option was specified, 511 * and if we won't be creating the file, then have the 512 * verbose message include a hint as to why the file 513 * will not be created. 514 */ 515 temp_reason[0] = '\0'; 516 if (createlogs > 1) 517 ent->firstcreate = 1; 518 else if ((ent->flags & CE_CREATE) && createlogs) 519 ent->firstcreate = 1; 520 else if (ent->flags & CE_CREATE) 521 strlcpy(temp_reason, " (no -C option)", REASON_MAX); 522 else if (createlogs) 523 strlcpy(temp_reason, " (no C flag)", REASON_MAX); 524 525 if (ent->firstcreate) { 526 if (verbose) 527 printf("does not exist -> will create.\n"); 528 createlog(ent); 529 } else if (verbose) { 530 printf("does not exist, skipped%s.\n", temp_reason); 531 } 532 } else { 533 if (ent->flags & CE_NOEMPTY && ent->fsize == 0) { 534 if (verbose) 535 printf("--> Not rotating empty file\n"); 536 return (free_or_keep); 537 } 538 if (ent->flags & CE_TRIMAT && !force && !rotatereq && 539 !oversized) { 540 diffsecs = ptimeget_diff(timenow, ent->trim_at); 541 if (diffsecs < 0.0) { 542 /* trim_at is some time in the future. */ 543 if (verbose) { 544 ptime_adjust4dst(ent->trim_at, 545 timenow); 546 printf("--> will trim at %s", 547 ptimeget_ctime(ent->trim_at)); 548 } 549 return (free_or_keep); 550 } else if (diffsecs >= 3600.0) { 551 /* 552 * trim_at is more than an hour in the past, 553 * so find the next valid trim_at time, and 554 * tell the user what that will be. 555 */ 556 if (verbose && dbg_at_times) 557 printf("\n\t--> prev trim at %s\t", 558 ptimeget_ctime(ent->trim_at)); 559 if (verbose) { 560 ptimeset_nxtime(ent->trim_at); 561 printf("--> will trim at %s", 562 ptimeget_ctime(ent->trim_at)); 563 } 564 return (free_or_keep); 565 } else if (verbose && noaction && dbg_at_times) { 566 /* 567 * If we are just debugging at-times, then 568 * a detailed message is helpful. Also 569 * skip "doing" any commands, since they 570 * would all be turned off by no-action. 571 */ 572 printf("\n\t--> timematch at %s", 573 ptimeget_ctime(ent->trim_at)); 574 return (free_or_keep); 575 } else if (verbose && ent->hours <= 0) { 576 printf("--> time is up\n"); 577 } 578 } 579 if (verbose && (ent->trsize > 0)) 580 printf("size (Kb): %d [%d] ", ent->fsize, ent->trsize); 581 if (verbose && (ent->hours > 0)) 582 printf(" age (hr): %d [%d] ", modtime, ent->hours); 583 584 /* 585 * Figure out if this logfile needs to be rotated. 586 */ 587 temp_reason[0] = '\0'; 588 if (rotatereq) { 589 ent->rotate = 1; 590 snprintf(temp_reason, REASON_MAX, " due to -R from %s", 591 requestor); 592 } else if (force) { 593 ent->rotate = 1; 594 snprintf(temp_reason, REASON_MAX, " due to -F request"); 595 } else if (oversized) { 596 ent->rotate = 1; 597 snprintf(temp_reason, REASON_MAX, " due to size>%dK", 598 ent->trsize); 599 } else if (ent->hours <= 0 && (ent->flags & CE_TRIMAT)) { 600 ent->rotate = 1; 601 } else if ((ent->hours > 0) && ((modtime >= ent->hours) || 602 (modtime < 0))) { 603 ent->rotate = 1; 604 } 605 606 /* 607 * If the file needs to be rotated, then rotate it. 608 */ 609 if (ent->rotate && !norotate) { 610 if (temp_reason[0] != '\0') 611 ent->r_reason = strdup(temp_reason); 612 if (verbose) 613 printf("--> trimming log....\n"); 614 if (noaction && !verbose) 615 printf("%s <%d%s>: trimming\n", ent->log, 616 ent->numlogs, 617 compress_type[ent->compress].flag); 618 free_or_keep = do_rotate(ent); 619 } else { 620 if (verbose) 621 printf("--> skipping\n"); 622 } 623 } 624 return (free_or_keep); 625 #undef REASON_MAX 626 } 627 628 static void 629 parse_args(int argc, char **argv) 630 { 631 int ch; 632 char *p; 633 634 timenow = ptime_init(NULL); 635 ptimeset_time(timenow, time(NULL)); 636 strlcpy(daytime, ptimeget_ctime(timenow) + 4, DAYTIME_LEN); 637 ptimeget_ctime_rfc5424(timenow, daytime_rfc5424, DAYTIME_RFC5424_LEN); 638 639 /* Let's get our hostname */ 640 (void)gethostname(hostname, sizeof(hostname)); 641 hostname_shortlen = strcspn(hostname, "."); 642 643 /* Parse command line options. */ 644 while ((ch = getopt(argc, argv, "a:d:f:nrst:vCD:FNPR:S:")) != -1) 645 switch (ch) { 646 case 'a': 647 archtodir++; 648 archdirname = optarg; 649 break; 650 case 'd': 651 destdir = optarg; 652 break; 653 case 'f': 654 conf = optarg; 655 break; 656 case 'n': 657 noaction++; 658 /* FALLTHROUGH */ 659 case 'r': 660 needroot = 0; 661 break; 662 case 's': 663 nosignal = 1; 664 break; 665 case 't': 666 if (optarg[0] == '\0' || 667 strcmp(optarg, "DEFAULT") == 0) 668 timefnamefmt = strdup(DEFAULT_TIMEFNAME_FMT); 669 else 670 timefnamefmt = strdup(optarg); 671 break; 672 case 'v': 673 verbose++; 674 break; 675 case 'C': 676 /* Useful for things like rc.diskless... */ 677 createlogs++; 678 break; 679 case 'D': 680 /* 681 * Set some debugging option. The specific option 682 * depends on the value of optarg. These options 683 * may come and go without notice or documentation. 684 */ 685 if (parse_doption(optarg)) 686 break; 687 usage(); 688 /* NOTREACHED */ 689 case 'F': 690 force++; 691 break; 692 case 'N': 693 norotate++; 694 break; 695 case 'P': 696 enforcepid++; 697 break; 698 case 'R': 699 rotatereq++; 700 requestor = strdup(optarg); 701 break; 702 case 'S': 703 path_syslogpid = optarg; 704 break; 705 case 'm': /* Used by OpenBSD for "monitor mode" */ 706 default: 707 usage(); 708 /* NOTREACHED */ 709 } 710 711 if (force && norotate) { 712 warnx("Only one of -F and -N may be specified."); 713 usage(); 714 /* NOTREACHED */ 715 } 716 717 if (rotatereq) { 718 if (optind == argc) { 719 warnx("At least one filename must be given when -R is specified."); 720 usage(); 721 /* NOTREACHED */ 722 } 723 /* Make sure "requestor" value is safe for a syslog message. */ 724 for (p = requestor; *p != '\0'; p++) { 725 if (!isprintch(*p) && (*p != '\t')) 726 *p = '.'; 727 } 728 } 729 730 if (dbg_timenow) { 731 /* 732 * Note that the 'daytime' variable is not changed. 733 * That is only used in messages that track when a 734 * logfile is rotated, and if a file *is* rotated, 735 * then it will still rotated at the "real now" time. 736 */ 737 ptime_free(timenow); 738 timenow = dbg_timenow; 739 fprintf(stderr, "Debug: Running as if TimeNow is %s", 740 ptimeget_ctime(dbg_timenow)); 741 } 742 743 } 744 745 /* 746 * These debugging options are mainly meant for developer use, such 747 * as writing regression-tests. They would not be needed by users 748 * during normal operation of newsyslog... 749 */ 750 static int 751 parse_doption(const char *doption) 752 { 753 const char TN[] = "TN="; 754 int res; 755 756 if (strncmp(doption, TN, sizeof(TN) - 1) == 0) { 757 /* 758 * The "TimeNow" debugging option. This might be off 759 * by an hour when crossing a timezone change. 760 */ 761 dbg_timenow = ptime_init(NULL); 762 res = ptime_relparse(dbg_timenow, PTM_PARSE_ISO8601, 763 time(NULL), doption + sizeof(TN) - 1); 764 if (res == -2) { 765 warnx("Non-existent time specified on -D %s", doption); 766 return (0); /* failure */ 767 } else if (res < 0) { 768 warnx("Malformed time given on -D %s", doption); 769 return (0); /* failure */ 770 } 771 return (1); /* successfully parsed */ 772 773 } 774 775 if (strcmp(doption, "ats") == 0) { 776 dbg_at_times++; 777 return (1); /* successfully parsed */ 778 } 779 780 /* XXX - This check could probably be dropped. */ 781 if ((strcmp(doption, "neworder") == 0) || (strcmp(doption, "oldorder") 782 == 0)) { 783 warnx("NOTE: newsyslog always uses 'neworder'."); 784 return (1); /* successfully parsed */ 785 } 786 787 warnx("Unknown -D (debug) option: '%s'", doption); 788 return (0); /* failure */ 789 } 790 791 static void 792 usage(void) 793 { 794 795 fprintf(stderr, 796 "usage: newsyslog [-CFNPnrsv] [-a directory] [-d directory] [-f config_file]\n" 797 " [-S pidfile] [-t timefmt] [[-R tagname] file ...]\n"); 798 exit(1); 799 } 800 801 /* 802 * Parse a configuration file and return a linked list of all the logs 803 * which should be processed. 804 */ 805 static struct cflist * 806 get_worklist(char **files) 807 { 808 FILE *f; 809 char **given; 810 struct cflist *cmdlist, *filelist, *globlist; 811 struct conf_entry *defconf, *dupent, *ent; 812 struct ilist inclist; 813 struct include_entry *inc; 814 int gmatch, fnres; 815 816 defconf = NULL; 817 STAILQ_INIT(&inclist); 818 819 filelist = malloc(sizeof(struct cflist)); 820 if (filelist == NULL) 821 err(1, "malloc of filelist"); 822 STAILQ_INIT(filelist); 823 globlist = malloc(sizeof(struct cflist)); 824 if (globlist == NULL) 825 err(1, "malloc of globlist"); 826 STAILQ_INIT(globlist); 827 828 inc = malloc(sizeof(struct include_entry)); 829 if (inc == NULL) 830 err(1, "malloc of inc"); 831 inc->file = conf; 832 if (inc->file == NULL) 833 inc->file = _PATH_CONF; 834 STAILQ_INSERT_TAIL(&inclist, inc, inc_nextp); 835 836 STAILQ_FOREACH(inc, &inclist, inc_nextp) { 837 if (strcmp(inc->file, "-") != 0) 838 f = fopen(inc->file, "r"); 839 else { 840 f = stdin; 841 inc->file = "<stdin>"; 842 } 843 if (!f) 844 err(1, "%s", inc->file); 845 846 if (verbose) 847 printf("Processing %s\n", inc->file); 848 parse_file(f, filelist, globlist, &defconf, &inclist); 849 (void) fclose(f); 850 } 851 852 /* 853 * All config-file information has been read in and turned into 854 * a filelist and a globlist. If there were no specific files 855 * given on the run command, then the only thing left to do is to 856 * call a routine which finds all files matched by the globlist 857 * and adds them to the filelist. Then return the worklist. 858 */ 859 if (*files == NULL) { 860 expand_globs(filelist, globlist); 861 free_clist(globlist); 862 if (defconf != NULL) 863 free_entry(defconf); 864 return (filelist); 865 } 866 867 /* 868 * If newsyslog was given a specific list of files to process, 869 * it may be that some of those files were not listed in any 870 * config file. Those unlisted files should get the default 871 * rotation action. First, create the default-rotation action 872 * if none was found in a system config file. 873 */ 874 if (defconf == NULL) { 875 defconf = init_entry(DEFAULT_MARKER, NULL); 876 defconf->numlogs = 3; 877 defconf->trsize = 50; 878 defconf->permissions = S_IRUSR|S_IWUSR; 879 } 880 881 /* 882 * If newsyslog was run with a list of specific filenames, 883 * then create a new worklist which has only those files in 884 * it, picking up the rotation-rules for those files from 885 * the original filelist. 886 * 887 * XXX - Note that this will copy multiple rules for a single 888 * logfile, if multiple entries are an exact match for 889 * that file. That matches the historic behavior, but do 890 * we want to continue to allow it? If so, it should 891 * probably be handled more intelligently. 892 */ 893 cmdlist = malloc(sizeof(struct cflist)); 894 if (cmdlist == NULL) 895 err(1, "malloc of cmdlist"); 896 STAILQ_INIT(cmdlist); 897 898 for (given = files; *given; ++given) { 899 /* 900 * First try to find exact-matches for this given file. 901 */ 902 gmatch = 0; 903 STAILQ_FOREACH(ent, filelist, cf_nextp) { 904 if (strcmp(ent->log, *given) == 0) { 905 gmatch++; 906 dupent = init_entry(*given, ent); 907 STAILQ_INSERT_TAIL(cmdlist, dupent, cf_nextp); 908 } 909 } 910 if (gmatch) { 911 if (verbose > 2) 912 printf("\t+ Matched entry %s\n", *given); 913 continue; 914 } 915 916 /* 917 * There was no exact-match for this given file, so look 918 * for a "glob" entry which does match. 919 */ 920 gmatch = 0; 921 if (verbose > 2) 922 printf("\t+ Checking globs for %s\n", *given); 923 STAILQ_FOREACH(ent, globlist, cf_nextp) { 924 fnres = fnmatch(ent->log, *given, FNM_PATHNAME); 925 if (verbose > 2) 926 printf("\t+ = %d for pattern %s\n", fnres, 927 ent->log); 928 if (fnres == 0) { 929 gmatch++; 930 dupent = init_entry(*given, ent); 931 /* This new entry is not a glob! */ 932 dupent->flags &= ~CE_GLOB; 933 STAILQ_INSERT_TAIL(cmdlist, dupent, cf_nextp); 934 /* Only allow a match to one glob-entry */ 935 break; 936 } 937 } 938 if (gmatch) { 939 if (verbose > 2) 940 printf("\t+ Matched %s via %s\n", *given, 941 ent->log); 942 continue; 943 } 944 945 /* 946 * This given file was not found in any config file, so 947 * add a worklist item based on the default entry. 948 */ 949 if (verbose > 2) 950 printf("\t+ No entry matched %s (will use %s)\n", 951 *given, DEFAULT_MARKER); 952 dupent = init_entry(*given, defconf); 953 /* Mark that it was *not* found in a config file */ 954 dupent->def_cfg = 1; 955 STAILQ_INSERT_TAIL(cmdlist, dupent, cf_nextp); 956 } 957 958 /* 959 * Free all the entries in the original work list, the list of 960 * glob entries, and the default entry. 961 */ 962 free_clist(filelist); 963 free_clist(globlist); 964 free_entry(defconf); 965 966 /* And finally, return a worklist which matches the given files. */ 967 return (cmdlist); 968 } 969 970 /* 971 * Expand the list of entries with filename patterns, and add all files 972 * which match those glob-entries onto the worklist. 973 */ 974 static void 975 expand_globs(struct cflist *work_p, struct cflist *glob_p) 976 { 977 int gmatch, gres; 978 size_t i; 979 char *mfname; 980 struct conf_entry *dupent, *ent, *globent; 981 glob_t pglob; 982 struct stat st_fm; 983 984 /* 985 * The worklist contains all fully-specified (non-GLOB) names. 986 * 987 * Now expand the list of filename-pattern (GLOB) entries into 988 * a second list, which (by definition) will only match files 989 * that already exist. Do not add a glob-related entry for any 990 * file which already exists in the fully-specified list. 991 */ 992 STAILQ_FOREACH(globent, glob_p, cf_nextp) { 993 gres = glob(globent->log, GLOB_NOCHECK, NULL, &pglob); 994 if (gres != 0) { 995 warn("cannot expand pattern (%d): %s", gres, 996 globent->log); 997 continue; 998 } 999 1000 if (verbose > 2) 1001 printf("\t+ Expanding pattern %s\n", globent->log); 1002 for (i = 0; i < pglob.gl_matchc; i++) { 1003 mfname = pglob.gl_pathv[i]; 1004 1005 /* See if this file already has a specific entry. */ 1006 gmatch = 0; 1007 STAILQ_FOREACH(ent, work_p, cf_nextp) { 1008 if (strcmp(mfname, ent->log) == 0) { 1009 gmatch++; 1010 break; 1011 } 1012 } 1013 if (gmatch) 1014 continue; 1015 1016 /* Make sure the named matched is a file. */ 1017 gres = lstat(mfname, &st_fm); 1018 if (gres != 0) { 1019 /* Error on a file that glob() matched?!? */ 1020 warn("Skipping %s - lstat() error", mfname); 1021 continue; 1022 } 1023 if (!S_ISREG(st_fm.st_mode)) { 1024 /* We only rotate files! */ 1025 if (verbose > 2) 1026 printf("\t+ . skipping %s (!file)\n", 1027 mfname); 1028 continue; 1029 } 1030 1031 if (verbose > 2) 1032 printf("\t+ . add file %s\n", mfname); 1033 dupent = init_entry(mfname, globent); 1034 /* This new entry is not a glob! */ 1035 dupent->flags &= ~CE_GLOB; 1036 1037 /* Add to the worklist. */ 1038 STAILQ_INSERT_TAIL(work_p, dupent, cf_nextp); 1039 } 1040 globfree(&pglob); 1041 if (verbose > 2) 1042 printf("\t+ Done with pattern %s\n", globent->log); 1043 } 1044 } 1045 1046 /* 1047 * Parse a configuration file and update a linked list of all the logs to 1048 * process. 1049 */ 1050 static void 1051 parse_file(FILE *cf, struct cflist *work_p, struct cflist *glob_p, 1052 struct conf_entry **defconf_p, struct ilist *inclist) 1053 { 1054 char line[BUFSIZ], *parse, *q; 1055 char *cp, *errline, *group; 1056 struct conf_entry *working; 1057 struct passwd *pwd; 1058 struct group *grp; 1059 glob_t pglob; 1060 int eol, ptm_opts, res, special; 1061 size_t i; 1062 1063 errline = NULL; 1064 while (fgets(line, BUFSIZ, cf)) { 1065 if ((line[0] == '\n') || (line[0] == '#') || 1066 (strlen(line) == 0)) 1067 continue; 1068 if (errline != NULL) 1069 free(errline); 1070 errline = strdup(line); 1071 for (cp = line + 1; *cp != '\0'; cp++) { 1072 if (*cp != '#') 1073 continue; 1074 if (*(cp - 1) == '\\') { 1075 strcpy(cp - 1, cp); 1076 cp--; 1077 continue; 1078 } 1079 *cp = '\0'; 1080 break; 1081 } 1082 1083 q = parse = missing_field(sob(line), errline); 1084 parse = son(line); 1085 if (!*parse) { 1086 warnx("malformed line (missing fields):\n%s", 1087 errline); 1088 continue; 1089 } 1090 *parse = '\0'; 1091 1092 /* 1093 * Allow people to set debug options via the config file. 1094 * (NOTE: debug options are undocumented, and may disappear 1095 * at any time, etc). 1096 */ 1097 if (strcasecmp(DEBUG_MARKER, q) == 0) { 1098 q = parse = missing_field(sob(parse + 1), errline); 1099 parse = son(parse); 1100 if (!*parse) 1101 warnx("debug line specifies no option:\n%s", 1102 errline); 1103 else { 1104 *parse = '\0'; 1105 parse_doption(q); 1106 } 1107 continue; 1108 } else if (strcasecmp(INCLUDE_MARKER, q) == 0) { 1109 if (verbose) 1110 printf("Found: %s", errline); 1111 q = parse = missing_field(sob(parse + 1), errline); 1112 parse = son(parse); 1113 if (!*parse) { 1114 warnx("include line missing argument:\n%s", 1115 errline); 1116 continue; 1117 } 1118 1119 *parse = '\0'; 1120 1121 if (isglobstr(q)) { 1122 res = glob(q, GLOB_NOCHECK, NULL, &pglob); 1123 if (res != 0) { 1124 warn("cannot expand pattern (%d): %s", 1125 res, q); 1126 continue; 1127 } 1128 1129 if (verbose > 2) 1130 printf("\t+ Expanding pattern %s\n", q); 1131 1132 for (i = 0; i < pglob.gl_matchc; i++) 1133 add_to_queue(pglob.gl_pathv[i], 1134 inclist); 1135 globfree(&pglob); 1136 } else 1137 add_to_queue(q, inclist); 1138 continue; 1139 } 1140 1141 #define badline(msg, ...) do { \ 1142 warnx(msg, __VA_ARGS__); \ 1143 goto cleanup; \ 1144 } while (0) 1145 1146 special = 0; 1147 working = init_entry(q, NULL); 1148 if (strcasecmp(DEFAULT_MARKER, q) == 0) { 1149 special = 1; 1150 if (*defconf_p != NULL) 1151 badline("Ignoring duplicate entry for %s!", q); 1152 *defconf_p = working; 1153 } 1154 1155 q = parse = missing_field(sob(parse + 1), errline); 1156 parse = son(parse); 1157 if (!*parse) 1158 badline("malformed line (missing fields):\n%s", 1159 errline); 1160 *parse = '\0'; 1161 if ((group = strchr(q, ':')) != NULL || 1162 (group = strrchr(q, '.')) != NULL) { 1163 *group++ = '\0'; 1164 if (*q) { 1165 if (!(isnumberstr(q))) { 1166 if ((pwd = getpwnam(q)) == NULL) 1167 badline( 1168 "error in config file; unknown user:\n%s", 1169 errline); 1170 working->uid = pwd->pw_uid; 1171 } else 1172 working->uid = atoi(q); 1173 } else 1174 working->uid = (uid_t)-1; 1175 1176 q = group; 1177 if (*q) { 1178 if (!(isnumberstr(q))) { 1179 if ((grp = getgrnam(q)) == NULL) 1180 badline( 1181 "error in config file; unknown group:\n%s", 1182 errline); 1183 working->gid = grp->gr_gid; 1184 } else 1185 working->gid = atoi(q); 1186 } else 1187 working->gid = (gid_t)-1; 1188 1189 q = parse = missing_field(sob(parse + 1), errline); 1190 parse = son(parse); 1191 if (!*parse) 1192 badline("malformed line (missing fields):\n%s", 1193 errline); 1194 *parse = '\0'; 1195 } else { 1196 working->uid = (uid_t)-1; 1197 working->gid = (gid_t)-1; 1198 } 1199 1200 if (!sscanf(q, "%o", &working->permissions)) 1201 badline("error in config file; bad permissions:\n%s", 1202 errline); 1203 if ((working->permissions & ~DEFFILEMODE) != 0) { 1204 warnx("File mode bits 0%o changed to 0%o in line:\n%s", 1205 working->permissions, 1206 working->permissions & DEFFILEMODE, errline); 1207 working->permissions &= DEFFILEMODE; 1208 } 1209 1210 q = parse = missing_field(sob(parse + 1), errline); 1211 parse = son(parse); 1212 if (!*parse) 1213 badline("malformed line (missing fields):\n%s", 1214 errline); 1215 *parse = '\0'; 1216 if (!sscanf(q, "%d", &working->numlogs) || working->numlogs < 0) 1217 badline("error in config file; bad value for count of logs to save:\n%s", 1218 errline); 1219 1220 q = parse = missing_field(sob(parse + 1), errline); 1221 parse = son(parse); 1222 if (!*parse) 1223 badline("malformed line (missing fields):\n%s", 1224 errline); 1225 *parse = '\0'; 1226 if (isdigitch(*q)) 1227 working->trsize = atoi(q); 1228 else if (strcmp(q, "*") == 0) 1229 working->trsize = -1; 1230 else { 1231 warnx("Invalid value of '%s' for 'size' in line:\n%s", 1232 q, errline); 1233 working->trsize = -1; 1234 } 1235 1236 working->flags = 0; 1237 working->compress = COMPRESS_NONE; 1238 q = parse = missing_field(sob(parse + 1), errline); 1239 parse = son(parse); 1240 eol = !*parse; 1241 *parse = '\0'; 1242 { 1243 char *ep; 1244 u_long ul; 1245 1246 ul = strtoul(q, &ep, 10); 1247 if (ep == q) 1248 working->hours = 0; 1249 else if (*ep == '*') 1250 working->hours = -1; 1251 else if (ul > INT_MAX) 1252 badline("interval is too large:\n%s", errline); 1253 else 1254 working->hours = ul; 1255 1256 if (*ep == '\0' || strcmp(ep, "*") == 0) 1257 goto no_trimat; 1258 if (*ep != '@' && *ep != '$') 1259 badline("malformed interval/at:\n%s", errline); 1260 1261 working->flags |= CE_TRIMAT; 1262 working->trim_at = ptime_init(NULL); 1263 ptm_opts = PTM_PARSE_ISO8601; 1264 if (*ep == '$') 1265 ptm_opts = PTM_PARSE_DWM; 1266 ptm_opts |= PTM_PARSE_MATCHDOM; 1267 res = ptime_relparse(working->trim_at, ptm_opts, 1268 ptimeget_secs(timenow), ep + 1); 1269 if (res == -2) 1270 badline("nonexistent time for 'at' value:\n%s", 1271 errline); 1272 else if (res < 0) 1273 badline("malformed 'at' value:\n%s", errline); 1274 } 1275 no_trimat: 1276 1277 if (eol) 1278 q = NULL; 1279 else { 1280 q = parse = sob(parse + 1); /* Optional field */ 1281 parse = son(parse); 1282 if (!*parse) 1283 eol = 1; 1284 *parse = '\0'; 1285 } 1286 1287 for (; q && *q && !isspacech(*q); q++) { 1288 switch (tolowerch(*q)) { 1289 case 'b': 1290 working->flags |= CE_BINARY; 1291 break; 1292 case 'c': 1293 working->flags |= CE_CREATE; 1294 break; 1295 case 'd': 1296 working->flags |= CE_NODUMP; 1297 break; 1298 case 'e': 1299 working->flags |= CE_NOEMPTY; 1300 break; 1301 case 'g': 1302 working->flags |= CE_GLOB; 1303 break; 1304 case 'j': 1305 working->compress = COMPRESS_BZIP2; 1306 break; 1307 case 'n': 1308 working->flags |= CE_NOSIGNAL; 1309 break; 1310 case 'p': 1311 working->flags |= CE_PLAIN0; 1312 break; 1313 case 'r': 1314 working->flags |= CE_PID2CMD; 1315 break; 1316 case 't': 1317 working->flags |= CE_RFC5424; 1318 break; 1319 case 'u': 1320 working->flags |= CE_SIGNALGROUP; 1321 break; 1322 case 'w': 1323 /* Deprecated flag - keep for compatibility purposes */ 1324 break; 1325 case 'x': 1326 working->compress = COMPRESS_XZ; 1327 break; 1328 case 'y': 1329 working->compress = COMPRESS_ZSTD; 1330 break; 1331 case 'z': 1332 working->compress = COMPRESS_GZIP; 1333 break; 1334 case '-': 1335 break; 1336 case 'f': /* Used by OpenBSD for "CE_FOLLOW" */ 1337 case 'm': /* Used by OpenBSD for "CE_MONITOR" */ 1338 default: 1339 badline("illegal flag in config file -- %c", 1340 *q); 1341 } 1342 } 1343 1344 if (eol) 1345 q = NULL; 1346 else { 1347 q = parse = sob(parse + 1); /* Optional field */ 1348 parse = son(parse); 1349 if (!*parse) 1350 eol = 1; 1351 *parse = '\0'; 1352 } 1353 1354 working->pid_cmd_file = NULL; 1355 if (q && *q) { 1356 if (*q == '/') 1357 working->pid_cmd_file = strdup(q); 1358 else if (isalnum(*q)) 1359 goto got_sig; 1360 else { 1361 badline( 1362 "illegal pid file or signal in config file:\n%s", 1363 errline); 1364 } 1365 } 1366 if (eol) 1367 q = NULL; 1368 else { 1369 q = parse = sob(parse + 1); /* Optional field */ 1370 parse = son(parse); 1371 *parse = '\0'; 1372 } 1373 1374 working->sig = SIGHUP; 1375 if (q && *q) { 1376 got_sig: 1377 working->sig = parse_signal(q); 1378 if (working->sig < 1 || working->sig >= sys_nsig) { 1379 badline( 1380 "illegal signal in config file:\n%s", 1381 errline); 1382 } 1383 } 1384 1385 /* 1386 * Finish figuring out what pid-file to use (if any) in 1387 * later processing if this logfile needs to be rotated. 1388 */ 1389 if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) { 1390 /* 1391 * This config-entry specified 'n' for nosignal, 1392 * see if it also specified an explicit pid_cmd_file. 1393 * This would be a pretty pointless combination. 1394 */ 1395 if (working->pid_cmd_file != NULL) { 1396 warnx("Ignoring '%s' because flag 'n' was specified in line:\n%s", 1397 working->pid_cmd_file, errline); 1398 free(working->pid_cmd_file); 1399 working->pid_cmd_file = NULL; 1400 } 1401 } else if (working->pid_cmd_file == NULL) { 1402 /* 1403 * This entry did not specify the 'n' flag, which 1404 * means it should signal syslogd unless it had 1405 * specified some other pid-file (and obviously the 1406 * syslog pid-file will not be for a process-group). 1407 * Also, we should only try to notify syslog if we 1408 * are root. 1409 */ 1410 if (working->flags & CE_SIGNALGROUP) { 1411 warnx("Ignoring flag 'U' in line:\n%s", 1412 errline); 1413 working->flags &= ~CE_SIGNALGROUP; 1414 } 1415 if (needroot) 1416 working->pid_cmd_file = strdup(path_syslogpid); 1417 } 1418 1419 /* 1420 * Add this entry to the appropriate list of entries, unless 1421 * it was some kind of special entry (eg: <default>). 1422 */ 1423 if (special) { 1424 ; /* Do not add to any list */ 1425 } else if (working->flags & CE_GLOB) { 1426 STAILQ_INSERT_TAIL(glob_p, working, cf_nextp); 1427 } else { 1428 STAILQ_INSERT_TAIL(work_p, working, cf_nextp); 1429 } 1430 continue; 1431 cleanup: 1432 free_entry(working); 1433 #undef badline 1434 } /* while (fgets(line, BUFSIZ, cf)) */ 1435 if (errline != NULL) 1436 free(errline); 1437 } 1438 1439 static char * 1440 missing_field(char *p, char *errline) 1441 { 1442 1443 if (!p || !*p) 1444 errx(1, "missing field in config file:\n%s", errline); 1445 return (p); 1446 } 1447 1448 /* 1449 * In our sort we return it in the reverse of what qsort normally 1450 * would do, as we want the newest files first. If we have two 1451 * entries with the same time we don't really care about order. 1452 * 1453 * Support function for qsort() in delete_oldest_timelog(). 1454 */ 1455 static int 1456 oldlog_entry_compare(const void *a, const void *b) 1457 { 1458 const struct oldlog_entry *ola = a, *olb = b; 1459 1460 if (ola->t > olb->t) 1461 return (-1); 1462 else if (ola->t < olb->t) 1463 return (1); 1464 else 1465 return (0); 1466 } 1467 1468 /* 1469 * Check whether the file corresponding to dp is an archive of the logfile 1470 * logfname, based on the timefnamefmt format string. Return true and fill out 1471 * tm if this is the case; otherwise return false. 1472 */ 1473 static int 1474 validate_old_timelog(int fd, const struct dirent *dp, const char *logfname, 1475 struct tm *tm) 1476 { 1477 struct stat sb; 1478 size_t logfname_len; 1479 char *s; 1480 int c; 1481 1482 logfname_len = strlen(logfname); 1483 1484 if (dp->d_type != DT_REG) { 1485 /* 1486 * Some filesystems (e.g. NFS) don't fill out the d_type field 1487 * and leave it set to DT_UNKNOWN; in this case we must obtain 1488 * the file type ourselves. 1489 */ 1490 if (dp->d_type != DT_UNKNOWN || 1491 fstatat(fd, dp->d_name, &sb, AT_SYMLINK_NOFOLLOW) != 0 || 1492 !S_ISREG(sb.st_mode)) 1493 return (0); 1494 } 1495 /* Ignore everything but files with our logfile prefix. */ 1496 if (strncmp(dp->d_name, logfname, logfname_len) != 0) 1497 return (0); 1498 /* Ignore the actual non-rotated logfile. */ 1499 if (dp->d_namlen == logfname_len) 1500 return (0); 1501 1502 /* 1503 * Make sure we created have found a logfile, so the 1504 * postfix is valid, IE format is: '.<time>(.[bgx]z)?'. 1505 */ 1506 if (dp->d_name[logfname_len] != '.') { 1507 if (verbose) 1508 printf("Ignoring %s which has unexpected " 1509 "extension '%s'\n", dp->d_name, 1510 &dp->d_name[logfname_len]); 1511 return (0); 1512 } 1513 memset(tm, 0, sizeof(*tm)); 1514 if ((s = strptime(&dp->d_name[logfname_len + 1], 1515 timefnamefmt, tm)) == NULL) { 1516 /* 1517 * We could special case "old" sequentially named logfiles here, 1518 * but we do not as that would require special handling to 1519 * decide which one was the oldest compared to "new" time based 1520 * logfiles. 1521 */ 1522 if (verbose) 1523 printf("Ignoring %s which does not " 1524 "match time format\n", dp->d_name); 1525 return (0); 1526 } 1527 1528 for (c = 0; c < COMPRESS_TYPES; c++) 1529 if (strcmp(s, compress_type[c].suffix) == 0) 1530 /* We're done. */ 1531 return (1); 1532 1533 if (verbose) 1534 printf("Ignoring %s which has unexpected extension '%s'\n", 1535 dp->d_name, s); 1536 1537 return (0); 1538 } 1539 1540 /* 1541 * Delete the oldest logfiles, when using time based filenames. 1542 */ 1543 static void 1544 delete_oldest_timelog(const struct conf_entry *ent, const char *archive_dir) 1545 { 1546 char *basebuf, *dirbuf, errbuf[80]; 1547 const char *base, *dir; 1548 int dir_fd, i, logcnt, max_logcnt; 1549 struct oldlog_entry *oldlogs; 1550 struct dirent *dp; 1551 struct tm tm; 1552 DIR *dirp; 1553 1554 oldlogs = malloc(MAX_OLDLOGS * sizeof(struct oldlog_entry)); 1555 max_logcnt = MAX_OLDLOGS; 1556 logcnt = 0; 1557 1558 if (archive_dir != NULL && archive_dir[0] != '\0') { 1559 dirbuf = NULL; 1560 dir = archive_dir; 1561 } else { 1562 if ((dirbuf = strdup(ent->log)) == NULL) 1563 err(1, "strdup()"); 1564 dir = dirname(dirbuf); 1565 } 1566 1567 if ((basebuf = strdup(ent->log)) == NULL) 1568 err(1, "strdup()"); 1569 base = basename(basebuf); 1570 if (strcmp(base, "/") == 0) 1571 errx(1, "Invalid log filename - became '/'"); 1572 1573 if (verbose > 2) 1574 printf("Searching for old logs in %s\n", dir); 1575 1576 /* First we create a 'list' of all archived logfiles */ 1577 if ((dirp = opendir(dir)) == NULL) 1578 err(1, "Cannot open log directory '%s'", dir); 1579 dir_fd = dirfd(dirp); 1580 while ((dp = readdir(dirp)) != NULL) { 1581 if (validate_old_timelog(dir_fd, dp, base, &tm) == 0) 1582 continue; 1583 1584 /* 1585 * We should now have old an old rotated logfile, so 1586 * add it to the 'list'. 1587 */ 1588 if ((oldlogs[logcnt].t = timegm(&tm)) == -1) 1589 err(1, "Could not convert time string to time value"); 1590 if ((oldlogs[logcnt].fname = strdup(dp->d_name)) == NULL) 1591 err(1, "strdup()"); 1592 logcnt++; 1593 1594 /* 1595 * It is very unlikely we ever run out of space in the 1596 * logfile array from the default size, but lets 1597 * handle it anyway... 1598 */ 1599 if (logcnt >= max_logcnt) { 1600 max_logcnt *= 4; 1601 /* Detect integer overflow */ 1602 if (max_logcnt < logcnt) 1603 errx(1, "Too many old logfiles found"); 1604 oldlogs = realloc(oldlogs, 1605 max_logcnt * sizeof(struct oldlog_entry)); 1606 if (oldlogs == NULL) 1607 err(1, "realloc()"); 1608 } 1609 } 1610 1611 /* Second, if needed we delete oldest archived logfiles */ 1612 if (logcnt > 0 && logcnt >= ent->numlogs && ent->numlogs > 1) { 1613 oldlogs = realloc(oldlogs, logcnt * 1614 sizeof(struct oldlog_entry)); 1615 if (oldlogs == NULL) 1616 err(1, "realloc()"); 1617 1618 /* 1619 * We now sort the logs in the order of newest to 1620 * oldest. That way we can simply skip over the 1621 * number of records we want to keep. 1622 */ 1623 qsort(oldlogs, logcnt, sizeof(struct oldlog_entry), 1624 oldlog_entry_compare); 1625 for (i = ent->numlogs - 1; i < logcnt; i++) { 1626 if (noaction) 1627 printf("\trm -f %s/%s\n", dir, 1628 oldlogs[i].fname); 1629 else if (unlinkat(dir_fd, oldlogs[i].fname, 0) != 0) { 1630 snprintf(errbuf, sizeof(errbuf), 1631 "Could not delete old logfile '%s'", 1632 oldlogs[i].fname); 1633 perror(errbuf); 1634 } 1635 } 1636 } else if (verbose > 1) 1637 printf("No old logs to delete for logfile %s\n", ent->log); 1638 1639 /* Third, cleanup */ 1640 closedir(dirp); 1641 for (i = 0; i < logcnt; i++) { 1642 assert(oldlogs[i].fname != NULL); 1643 free(oldlogs[i].fname); 1644 } 1645 free(oldlogs); 1646 free(dirbuf); 1647 free(basebuf); 1648 } 1649 1650 /* 1651 * Generate a log filename, when using classic filenames. 1652 */ 1653 static void 1654 gen_classiclog_fname(char *fname, size_t fname_sz, const char *archive_dir, 1655 const char *namepart, int numlogs_c) 1656 { 1657 1658 if (archive_dir[0] != '\0') 1659 (void) snprintf(fname, fname_sz, "%s/%s.%d", archive_dir, 1660 namepart, numlogs_c); 1661 else 1662 (void) snprintf(fname, fname_sz, "%s.%d", namepart, numlogs_c); 1663 } 1664 1665 /* 1666 * Delete a rotated logfile, when using classic filenames. 1667 */ 1668 static void 1669 delete_classiclog(const char *archive_dir, const char *namepart, int numlog_c) 1670 { 1671 char file1[MAXPATHLEN], zfile1[MAXPATHLEN]; 1672 int c; 1673 1674 gen_classiclog_fname(file1, sizeof(file1), archive_dir, namepart, 1675 numlog_c); 1676 1677 for (c = 0; c < COMPRESS_TYPES; c++) { 1678 (void) snprintf(zfile1, sizeof(zfile1), "%s%s", file1, 1679 compress_type[c].suffix); 1680 if (noaction) 1681 printf("\trm -f %s\n", zfile1); 1682 else 1683 (void) unlink(zfile1); 1684 } 1685 } 1686 1687 /* 1688 * Only add to the queue if the file hasn't already been added. This is 1689 * done to prevent circular include loops. 1690 */ 1691 static void 1692 add_to_queue(const char *fname, struct ilist *inclist) 1693 { 1694 struct include_entry *inc; 1695 1696 STAILQ_FOREACH(inc, inclist, inc_nextp) { 1697 if (strcmp(fname, inc->file) == 0) { 1698 warnx("duplicate include detected: %s", fname); 1699 return; 1700 } 1701 } 1702 1703 inc = malloc(sizeof(struct include_entry)); 1704 if (inc == NULL) 1705 err(1, "malloc of inc"); 1706 inc->file = strdup(fname); 1707 1708 if (verbose > 2) 1709 printf("\t+ Adding %s to the processing queue.\n", fname); 1710 1711 STAILQ_INSERT_TAIL(inclist, inc, inc_nextp); 1712 } 1713 1714 /* 1715 * Search for logfile and return its compression suffix (if supported) 1716 * The suffix detection is first-match in the order of compress_types 1717 * 1718 * Note: if logfile without suffix exists (uncompressed, COMPRESS_NONE) 1719 * a zero-length string is returned 1720 */ 1721 static const char * 1722 get_logfile_suffix(const char *logfile) 1723 { 1724 struct stat st; 1725 char zfile[MAXPATHLEN]; 1726 int c; 1727 1728 for (c = 0; c < COMPRESS_TYPES; c++) { 1729 (void) strlcpy(zfile, logfile, MAXPATHLEN); 1730 (void) strlcat(zfile, compress_type[c].suffix, MAXPATHLEN); 1731 if (lstat(zfile, &st) == 0) 1732 return (compress_type[c].suffix); 1733 } 1734 return (NULL); 1735 } 1736 1737 static fk_entry 1738 do_rotate(const struct conf_entry *ent) 1739 { 1740 char dirpart[MAXPATHLEN], namepart[MAXPATHLEN]; 1741 char file1[MAXPATHLEN], file2[MAXPATHLEN]; 1742 char zfile1[MAXPATHLEN], zfile2[MAXPATHLEN]; 1743 const char *logfile_suffix; 1744 char datetimestr[30]; 1745 int flags, numlogs_c; 1746 fk_entry free_or_keep; 1747 struct sigwork_entry *swork; 1748 struct stat st; 1749 struct tm tm; 1750 time_t now; 1751 1752 flags = ent->flags; 1753 free_or_keep = FREE_ENT; 1754 1755 if (archtodir) { 1756 char *p; 1757 1758 /* build complete name of archive directory into dirpart */ 1759 if (*archdirname == '/') { /* absolute */ 1760 strlcpy(dirpart, archdirname, sizeof(dirpart)); 1761 } else { /* relative */ 1762 /* get directory part of logfile */ 1763 strlcpy(dirpart, ent->log, sizeof(dirpart)); 1764 if ((p = strrchr(dirpart, '/')) == NULL) 1765 dirpart[0] = '\0'; 1766 else 1767 *(p + 1) = '\0'; 1768 strlcat(dirpart, archdirname, sizeof(dirpart)); 1769 } 1770 1771 /* check if archive directory exists, if not, create it */ 1772 if (lstat(dirpart, &st)) 1773 createdir(ent, dirpart); 1774 1775 /* get filename part of logfile */ 1776 if ((p = strrchr(ent->log, '/')) == NULL) 1777 strlcpy(namepart, ent->log, sizeof(namepart)); 1778 else 1779 strlcpy(namepart, p + 1, sizeof(namepart)); 1780 } else { 1781 /* 1782 * Tell utility functions we are not using an archive 1783 * dir. 1784 */ 1785 dirpart[0] = '\0'; 1786 strlcpy(namepart, ent->log, sizeof(namepart)); 1787 } 1788 1789 /* Delete old logs */ 1790 if (timefnamefmt != NULL) 1791 delete_oldest_timelog(ent, dirpart); 1792 else { 1793 /* 1794 * Handle cleaning up after legacy newsyslog where we 1795 * kept ent->numlogs + 1 files. This code can go away 1796 * at some point in the future. 1797 */ 1798 delete_classiclog(dirpart, namepart, ent->numlogs); 1799 1800 if (ent->numlogs > 0) 1801 delete_classiclog(dirpart, namepart, ent->numlogs - 1); 1802 1803 } 1804 1805 if (timefnamefmt != NULL) { 1806 /* If time functions fails we can't really do any sensible */ 1807 if (time(&now) == (time_t)-1 || 1808 localtime_r(&now, &tm) == NULL) 1809 bzero(&tm, sizeof(tm)); 1810 1811 strftime(datetimestr, sizeof(datetimestr), timefnamefmt, &tm); 1812 if (archtodir) 1813 (void) snprintf(file1, sizeof(file1), "%s/%s.%s", 1814 dirpart, namepart, datetimestr); 1815 else 1816 (void) snprintf(file1, sizeof(file1), "%s.%s", 1817 ent->log, datetimestr); 1818 1819 /* Don't run the code to move down logs */ 1820 numlogs_c = -1; 1821 } else { 1822 gen_classiclog_fname(file1, sizeof(file1), dirpart, namepart, 1823 ent->numlogs - 1); 1824 numlogs_c = ent->numlogs - 2; /* copy for countdown */ 1825 } 1826 1827 /* Move down log files */ 1828 for (; numlogs_c >= 0; numlogs_c--) { 1829 (void) strlcpy(file2, file1, sizeof(file2)); 1830 1831 gen_classiclog_fname(file1, sizeof(file1), dirpart, namepart, 1832 numlogs_c); 1833 1834 logfile_suffix = get_logfile_suffix(file1); 1835 if (logfile_suffix == NULL) 1836 continue; 1837 (void) strlcpy(zfile1, file1, MAXPATHLEN); 1838 (void) strlcpy(zfile2, file2, MAXPATHLEN); 1839 (void) strlcat(zfile1, logfile_suffix, MAXPATHLEN); 1840 (void) strlcat(zfile2, logfile_suffix, MAXPATHLEN); 1841 1842 if (noaction) 1843 printf("\tmv %s %s\n", zfile1, zfile2); 1844 else { 1845 /* XXX - Ought to be checking for failure! */ 1846 (void)rename(zfile1, zfile2); 1847 } 1848 change_attrs(zfile2, ent); 1849 if (ent->compress && strlen(logfile_suffix) == 0) { 1850 /* compress old rotation */ 1851 struct zipwork_entry *zwork; 1852 size_t sz; 1853 1854 sz = sizeof(*zwork) + strlen(zfile2) + 1; 1855 zwork = calloc(1, sz); 1856 if (zwork == NULL) 1857 err(1, "calloc"); 1858 1859 zwork->zw_conf = ent; 1860 zwork->zw_fsize = sizefile(zfile2); 1861 strcpy(zwork->zw_fname, zfile2); 1862 do_zipwork(zwork); 1863 free(zwork); 1864 } 1865 } 1866 1867 if (ent->numlogs > 0) { 1868 if (noaction) { 1869 /* 1870 * Note that savelog() may succeed with using link() 1871 * for the archtodir case, but there is no good way 1872 * of knowing if it will when doing "noaction", so 1873 * here we claim that it will have to do a copy... 1874 */ 1875 if (archtodir) 1876 printf("\tcp %s %s\n", ent->log, file1); 1877 else 1878 printf("\tln %s %s\n", ent->log, file1); 1879 printf("\ttouch %s\t\t" 1880 "# Update mtime for 'when'-interval processing\n", 1881 file1); 1882 } else { 1883 if (!(flags & CE_BINARY)) { 1884 /* Report the trimming to the old log */ 1885 log_trim(ent->log, ent); 1886 } 1887 savelog(ent->log, file1); 1888 /* 1889 * Interval-based rotations are done using the mtime of 1890 * the most recently archived log, so make sure it gets 1891 * updated during a rotation. 1892 */ 1893 utimes(file1, NULL); 1894 } 1895 change_attrs(file1, ent); 1896 } 1897 1898 /* Create the new log file and move it into place */ 1899 if (noaction) 1900 printf("Start new log...\n"); 1901 createlog(ent); 1902 1903 /* 1904 * Save all signalling and file-compression to be done after log 1905 * files from all entries have been rotated. This way any one 1906 * process will not be sent the same signal multiple times when 1907 * multiple log files had to be rotated. 1908 */ 1909 swork = NULL; 1910 if (ent->pid_cmd_file != NULL) 1911 swork = save_sigwork(ent); 1912 if (ent->numlogs > 0 && ent->compress > COMPRESS_NONE) { 1913 if (!(ent->flags & CE_PLAIN0) || 1914 strcmp(&file1[strlen(file1) - 2], ".0") != 0) { 1915 /* 1916 * The zipwork_entry will include a pointer to this 1917 * conf_entry, so the conf_entry should not be freed. 1918 */ 1919 free_or_keep = KEEP_ENT; 1920 save_zipwork(ent, swork, ent->fsize, file1); 1921 } 1922 } 1923 1924 return (free_or_keep); 1925 } 1926 1927 static void 1928 do_sigwork(struct sigwork_entry *swork) 1929 { 1930 struct sigwork_entry *nextsig; 1931 int kres, secs; 1932 char *tmp; 1933 1934 if (swork->sw_runcmd == 0 && (!(swork->sw_pidok) || swork->sw_pid == 0)) 1935 return; /* no work to do... */ 1936 1937 /* 1938 * If nosignal (-s) was specified, then do not signal any process. 1939 * Note that a nosignal request triggers a warning message if the 1940 * rotated logfile needs to be compressed, *unless* -R was also 1941 * specified. We assume that an `-sR' request came from a process 1942 * which writes to the logfile, and as such, we assume that process 1943 * has already made sure the logfile is not presently in use. This 1944 * just sets swork->sw_pidok to a special value, and do_zipwork 1945 * will print any necessary warning(s). 1946 */ 1947 if (nosignal) { 1948 if (!rotatereq) 1949 swork->sw_pidok = -1; 1950 return; 1951 } 1952 1953 /* 1954 * Compute the pause between consecutive signals. Use a longer 1955 * sleep time if we will be sending two signals to the same 1956 * daemon or process-group. 1957 */ 1958 secs = 0; 1959 nextsig = SLIST_NEXT(swork, sw_nextp); 1960 if (nextsig != NULL) { 1961 if (swork->sw_pid == nextsig->sw_pid) 1962 secs = 10; 1963 else 1964 secs = 1; 1965 } 1966 1967 if (noaction) { 1968 if (swork->sw_runcmd) 1969 printf("\tsh -c '%s %d'\n", swork->sw_fname, 1970 swork->sw_signum); 1971 else { 1972 printf("\tkill -%d %d \t\t# %s\n", swork->sw_signum, 1973 (int)swork->sw_pid, swork->sw_fname); 1974 if (secs > 0) 1975 printf("\tsleep %d\n", secs); 1976 } 1977 return; 1978 } 1979 1980 if (swork->sw_runcmd) { 1981 asprintf(&tmp, "%s %d", swork->sw_fname, swork->sw_signum); 1982 if (tmp == NULL) { 1983 warn("can't allocate memory to run %s", 1984 swork->sw_fname); 1985 return; 1986 } 1987 if (verbose) 1988 printf("Run command: %s\n", tmp); 1989 kres = system(tmp); 1990 if (kres) { 1991 warnx("%s: returned non-zero exit code: %d", 1992 tmp, kres); 1993 } 1994 free(tmp); 1995 return; 1996 } 1997 1998 kres = kill(swork->sw_pid, swork->sw_signum); 1999 if (kres != 0) { 2000 /* 2001 * Assume that "no such process" (ESRCH) is something 2002 * to warn about, but is not an error. Presumably the 2003 * process which writes to the rotated log file(s) is 2004 * gone, in which case we should have no problem with 2005 * compressing the rotated log file(s). 2006 */ 2007 if (errno != ESRCH) 2008 swork->sw_pidok = 0; 2009 warn("can't notify %s, pid %d = %s", swork->sw_pidtype, 2010 (int)swork->sw_pid, swork->sw_fname); 2011 } else { 2012 if (verbose) 2013 printf("Notified %s pid %d = %s\n", swork->sw_pidtype, 2014 (int)swork->sw_pid, swork->sw_fname); 2015 if (secs > 0) { 2016 if (verbose) 2017 printf("Pause %d second(s) between signals\n", 2018 secs); 2019 sleep(secs); 2020 } 2021 } 2022 } 2023 2024 static void 2025 do_zipwork(struct zipwork_entry *zwork) 2026 { 2027 const struct compress_types *ct; 2028 struct sbuf *command; 2029 pid_t pidzip, wpid; 2030 int c, errsav, fcount, zstatus; 2031 const char **args, *pgm_name, *pgm_path; 2032 char *zresult; 2033 2034 assert(zwork != NULL); 2035 assert(zwork->zw_conf != NULL); 2036 assert(zwork->zw_conf->compress > COMPRESS_NONE); 2037 assert(zwork->zw_conf->compress < COMPRESS_TYPES); 2038 2039 if (zwork->zw_swork != NULL && zwork->zw_swork->sw_runcmd == 0 && 2040 zwork->zw_swork->sw_pidok <= 0) { 2041 warnx( 2042 "log %s not compressed because daemon(s) not notified", 2043 zwork->zw_fname); 2044 change_attrs(zwork->zw_fname, zwork->zw_conf); 2045 return; 2046 } 2047 2048 ct = &compress_type[zwork->zw_conf->compress]; 2049 2050 /* 2051 * execv will be called with the array [ program, flags ... , 2052 * filename, NULL ] so allocate nflags+3 elements for the array. 2053 */ 2054 args = calloc(ct->nflags + 3, sizeof(*args)); 2055 if (args == NULL) 2056 err(1, "calloc"); 2057 2058 pgm_path = ct->path; 2059 pgm_name = strrchr(pgm_path, '/'); 2060 if (pgm_name == NULL) 2061 pgm_name = pgm_path; 2062 else 2063 pgm_name++; 2064 2065 /* Build the argument array. */ 2066 args[0] = pgm_name; 2067 for (c = 0; c < ct->nflags; c++) 2068 args[c + 1] = ct->flags[c]; 2069 args[c + 1] = zwork->zw_fname; 2070 2071 /* Also create a space-delimited version if we need to print it. */ 2072 if ((command = sbuf_new_auto()) == NULL) 2073 errx(1, "sbuf_new"); 2074 sbuf_cpy(command, pgm_path); 2075 for (c = 1; args[c] != NULL; c++) { 2076 sbuf_putc(command, ' '); 2077 sbuf_cat(command, args[c]); 2078 } 2079 if (sbuf_finish(command) == -1) 2080 err(1, "sbuf_finish"); 2081 2082 /* Determine the filename of the compressed file. */ 2083 asprintf(&zresult, "%s%s", zwork->zw_fname, ct->suffix); 2084 if (zresult == NULL) 2085 errx(1, "asprintf"); 2086 2087 if (verbose) 2088 printf("Executing: %s\n", sbuf_data(command)); 2089 2090 if (noaction) { 2091 printf("\t%s %s\n", pgm_name, zwork->zw_fname); 2092 change_attrs(zresult, zwork->zw_conf); 2093 goto out; 2094 } 2095 2096 fcount = 1; 2097 pidzip = fork(); 2098 while (pidzip < 0) { 2099 /* 2100 * The fork failed. If the failure was due to a temporary 2101 * problem, then wait a short time and try it again. 2102 */ 2103 errsav = errno; 2104 warn("fork() for `%s %s'", pgm_name, zwork->zw_fname); 2105 if (errsav != EAGAIN || fcount > 5) 2106 errx(1, "Exiting..."); 2107 sleep(fcount * 12); 2108 fcount++; 2109 pidzip = fork(); 2110 } 2111 if (!pidzip) { 2112 /* The child process executes the compression command */ 2113 execv(pgm_path, __DECONST(char *const*, args)); 2114 err(1, "execv(`%s')", sbuf_data(command)); 2115 } 2116 2117 wpid = waitpid(pidzip, &zstatus, 0); 2118 if (wpid == -1) { 2119 /* XXX - should this be a fatal error? */ 2120 warn("%s: waitpid(%d)", pgm_path, pidzip); 2121 goto out; 2122 } 2123 if (!WIFEXITED(zstatus)) { 2124 warnx("`%s' did not terminate normally", sbuf_data(command)); 2125 goto out; 2126 } 2127 if (WEXITSTATUS(zstatus)) { 2128 warnx("`%s' terminated with a non-zero status (%d)", 2129 sbuf_data(command), WEXITSTATUS(zstatus)); 2130 goto out; 2131 } 2132 2133 /* Compression was successful, set file attributes on the result. */ 2134 change_attrs(zresult, zwork->zw_conf); 2135 2136 out: 2137 sbuf_delete(command); 2138 free(args); 2139 free(zresult); 2140 } 2141 2142 /* 2143 * Save information on any process we need to signal. Any single 2144 * process may need to be sent different signal-values for different 2145 * log files, but usually a single signal-value will cause the process 2146 * to close and re-open all of its log files. 2147 */ 2148 static struct sigwork_entry * 2149 save_sigwork(const struct conf_entry *ent) 2150 { 2151 struct sigwork_entry *sprev, *stmp; 2152 int ndiff; 2153 size_t tmpsiz; 2154 2155 sprev = NULL; 2156 ndiff = 1; 2157 SLIST_FOREACH(stmp, &swhead, sw_nextp) { 2158 ndiff = strcmp(ent->pid_cmd_file, stmp->sw_fname); 2159 if (ndiff > 0) 2160 break; 2161 if (ndiff == 0) { 2162 if (ent->sig == stmp->sw_signum) 2163 break; 2164 if (ent->sig > stmp->sw_signum) { 2165 ndiff = 1; 2166 break; 2167 } 2168 } 2169 sprev = stmp; 2170 } 2171 if (stmp != NULL && ndiff == 0) 2172 return (stmp); 2173 2174 tmpsiz = sizeof(struct sigwork_entry) + strlen(ent->pid_cmd_file) + 1; 2175 stmp = malloc(tmpsiz); 2176 2177 stmp->sw_runcmd = 0; 2178 /* If this is a command to run we just set the flag and run command */ 2179 if (ent->flags & CE_PID2CMD) { 2180 stmp->sw_pid = -1; 2181 stmp->sw_pidok = 0; 2182 stmp->sw_runcmd = 1; 2183 } else { 2184 set_swpid(stmp, ent); 2185 } 2186 stmp->sw_signum = ent->sig; 2187 strcpy(stmp->sw_fname, ent->pid_cmd_file); 2188 if (sprev == NULL) 2189 SLIST_INSERT_HEAD(&swhead, stmp, sw_nextp); 2190 else 2191 SLIST_INSERT_AFTER(sprev, stmp, sw_nextp); 2192 return (stmp); 2193 } 2194 2195 /* 2196 * Save information on any file we need to compress. We may see the same 2197 * file multiple times, so check the full list to avoid duplicates. The 2198 * list itself is sorted smallest-to-largest, because that's the order we 2199 * want to compress the files. If the partition is very low on disk space, 2200 * then the smallest files are the most likely to compress, and compressing 2201 * them first will free up more space for the larger files. 2202 */ 2203 static struct zipwork_entry * 2204 save_zipwork(const struct conf_entry *ent, const struct sigwork_entry *swork, 2205 int zsize, const char *zipfname) 2206 { 2207 struct zipwork_entry *zprev, *ztmp; 2208 int ndiff; 2209 size_t tmpsiz; 2210 2211 /* Compute the size if the caller did not know it. */ 2212 if (zsize < 0) 2213 zsize = sizefile(zipfname); 2214 2215 zprev = NULL; 2216 ndiff = 1; 2217 SLIST_FOREACH(ztmp, &zwhead, zw_nextp) { 2218 ndiff = strcmp(zipfname, ztmp->zw_fname); 2219 if (ndiff == 0) 2220 break; 2221 if (zsize > ztmp->zw_fsize) 2222 zprev = ztmp; 2223 } 2224 if (ztmp != NULL && ndiff == 0) 2225 return (ztmp); 2226 2227 tmpsiz = sizeof(struct zipwork_entry) + strlen(zipfname) + 1; 2228 ztmp = malloc(tmpsiz); 2229 ztmp->zw_conf = ent; 2230 ztmp->zw_swork = swork; 2231 ztmp->zw_fsize = zsize; 2232 strcpy(ztmp->zw_fname, zipfname); 2233 if (zprev == NULL) 2234 SLIST_INSERT_HEAD(&zwhead, ztmp, zw_nextp); 2235 else 2236 SLIST_INSERT_AFTER(zprev, ztmp, zw_nextp); 2237 return (ztmp); 2238 } 2239 2240 /* Send a signal to the pid specified by pidfile */ 2241 static void 2242 set_swpid(struct sigwork_entry *swork, const struct conf_entry *ent) 2243 { 2244 FILE *f; 2245 long minok, maxok, rval; 2246 char *endp, *linep, line[BUFSIZ]; 2247 2248 minok = MIN_PID; 2249 maxok = MAX_PID; 2250 swork->sw_pidok = 0; 2251 swork->sw_pid = 0; 2252 swork->sw_pidtype = "daemon"; 2253 if (ent->flags & CE_SIGNALGROUP) { 2254 /* 2255 * If we are expected to signal a process-group when 2256 * rotating this logfile, then the value read in should 2257 * be the negative of a valid process ID. 2258 */ 2259 minok = -MAX_PID; 2260 maxok = -MIN_PID; 2261 swork->sw_pidtype = "process-group"; 2262 } 2263 2264 f = fopen(ent->pid_cmd_file, "r"); 2265 if (f == NULL) { 2266 if (errno == ENOENT && enforcepid == 0) { 2267 /* 2268 * Warn if the PID file doesn't exist, but do 2269 * not consider it an error. Most likely it 2270 * means the process has been terminated, 2271 * so it should be safe to rotate any log 2272 * files that the process would have been using. 2273 */ 2274 swork->sw_pidok = 1; 2275 warnx("pid file doesn't exist: %s", ent->pid_cmd_file); 2276 } else 2277 warn("can't open pid file: %s", ent->pid_cmd_file); 2278 return; 2279 } 2280 2281 if (fgets(line, BUFSIZ, f) == NULL) { 2282 /* 2283 * Warn if the PID file is empty, but do not consider 2284 * it an error. Most likely it means the process has 2285 * has terminated, so it should be safe to rotate any 2286 * log files that the process would have been using. 2287 */ 2288 if (feof(f) && enforcepid == 0) { 2289 swork->sw_pidok = 1; 2290 warnx("pid/cmd file is empty: %s", ent->pid_cmd_file); 2291 } else 2292 warn("can't read from pid file: %s", ent->pid_cmd_file); 2293 (void)fclose(f); 2294 return; 2295 } 2296 (void)fclose(f); 2297 2298 errno = 0; 2299 linep = line; 2300 while (*linep == ' ') 2301 linep++; 2302 rval = strtol(linep, &endp, 10); 2303 if (*endp != '\0' && !isspacech(*endp)) { 2304 warnx("pid file does not start with a valid number: %s", 2305 ent->pid_cmd_file); 2306 } else if (rval < minok || rval > maxok) { 2307 warnx("bad value '%ld' for process number in %s", 2308 rval, ent->pid_cmd_file); 2309 if (verbose) 2310 warnx("\t(expecting value between %ld and %ld)", 2311 minok, maxok); 2312 } else { 2313 swork->sw_pidok = 1; 2314 swork->sw_pid = rval; 2315 } 2316 2317 return; 2318 } 2319 2320 /* Log the fact that the logs were turned over */ 2321 static int 2322 log_trim(const char *logname, const struct conf_entry *log_ent) 2323 { 2324 FILE *f; 2325 const char *xtra; 2326 2327 if ((f = fopen(logname, "a")) == NULL) 2328 return (-1); 2329 xtra = ""; 2330 if (log_ent->def_cfg) 2331 xtra = " using <default> rule"; 2332 if (log_ent->flags & CE_RFC5424) { 2333 if (log_ent->firstcreate) { 2334 fprintf(f, "<%d>1 %s %s newsyslog %d - - %s%s\n", 2335 LOG_MAKEPRI(LOG_USER, LOG_INFO), 2336 daytime_rfc5424, hostname, getpid(), 2337 "logfile first created", xtra); 2338 } else if (log_ent->r_reason != NULL) { 2339 fprintf(f, "<%d>1 %s %s newsyslog %d - - %s%s%s\n", 2340 LOG_MAKEPRI(LOG_USER, LOG_INFO), 2341 daytime_rfc5424, hostname, getpid(), 2342 "logfile turned over", log_ent->r_reason, xtra); 2343 } else { 2344 fprintf(f, "<%d>1 %s %s newsyslog %d - - %s%s\n", 2345 LOG_MAKEPRI(LOG_USER, LOG_INFO), 2346 daytime_rfc5424, hostname, getpid(), 2347 "logfile turned over", xtra); 2348 } 2349 } else { 2350 if (log_ent->firstcreate) 2351 fprintf(f, 2352 "%s %.*s newsyslog[%d]: logfile first created%s\n", 2353 daytime, (int)hostname_shortlen, hostname, getpid(), 2354 xtra); 2355 else if (log_ent->r_reason != NULL) 2356 fprintf(f, 2357 "%s %.*s newsyslog[%d]: logfile turned over%s%s\n", 2358 daytime, (int)hostname_shortlen, hostname, getpid(), 2359 log_ent->r_reason, xtra); 2360 else 2361 fprintf(f, 2362 "%s %.*s newsyslog[%d]: logfile turned over%s\n", 2363 daytime, (int)hostname_shortlen, hostname, getpid(), 2364 xtra); 2365 } 2366 if (fclose(f) == EOF) 2367 err(1, "log_trim: fclose"); 2368 return (0); 2369 } 2370 2371 /* Return size in kilobytes of a file */ 2372 static int 2373 sizefile(const char *file) 2374 { 2375 struct stat sb; 2376 2377 if (stat(file, &sb) < 0) 2378 return (-1); 2379 return (kbytes(sb.st_size)); 2380 } 2381 2382 /* 2383 * Return the mtime of the most recent archive of the logfile, using timestamp 2384 * based filenames. 2385 */ 2386 static time_t 2387 mtime_old_timelog(const char *file) 2388 { 2389 struct stat sb; 2390 struct tm tm; 2391 int dir_fd; 2392 time_t t; 2393 struct dirent *dp; 2394 DIR *dirp; 2395 char *logfname, *logfnamebuf, *dir, *dirbuf; 2396 2397 t = -1; 2398 2399 if ((dirbuf = strdup(file)) == NULL) { 2400 warn("strdup() of '%s'", file); 2401 return (t); 2402 } 2403 dir = dirname(dirbuf); 2404 if ((logfnamebuf = strdup(file)) == NULL) { 2405 warn("strdup() of '%s'", file); 2406 free(dirbuf); 2407 return (t); 2408 } 2409 logfname = basename(logfnamebuf); 2410 if (logfname[0] == '/') { 2411 warnx("Invalid log filename '%s'", logfname); 2412 goto out; 2413 } 2414 2415 if ((dirp = opendir(dir)) == NULL) { 2416 warn("Cannot open log directory '%s'", dir); 2417 goto out; 2418 } 2419 dir_fd = dirfd(dirp); 2420 /* Open the archive dir and find the most recent archive of logfname. */ 2421 while ((dp = readdir(dirp)) != NULL) { 2422 if (validate_old_timelog(dir_fd, dp, logfname, &tm) == 0) 2423 continue; 2424 2425 if (fstatat(dir_fd, dp->d_name, &sb, AT_SYMLINK_NOFOLLOW) == -1) { 2426 warn("Cannot stat '%s'", file); 2427 continue; 2428 } 2429 if (t < sb.st_mtime) 2430 t = sb.st_mtime; 2431 } 2432 closedir(dirp); 2433 2434 out: 2435 free(dirbuf); 2436 free(logfnamebuf); 2437 return (t); 2438 } 2439 2440 /* Return the age in hours of the most recent archive of the logfile. */ 2441 static int 2442 age_old_log(const char *file) 2443 { 2444 struct stat sb; 2445 const char *logfile_suffix; 2446 static unsigned int suffix_maxlen = 0; 2447 char *tmp; 2448 size_t tmpsiz; 2449 time_t mtime; 2450 int c; 2451 2452 if (suffix_maxlen == 0) { 2453 for (c = 0; c < COMPRESS_TYPES; c++) 2454 suffix_maxlen = MAX(suffix_maxlen, 2455 strlen(compress_type[c].suffix)); 2456 } 2457 2458 tmpsiz = MAXPATHLEN + sizeof(".0") + suffix_maxlen + 1; 2459 tmp = alloca(tmpsiz); 2460 2461 if (archtodir) { 2462 char *p; 2463 2464 /* build name of archive directory into tmp */ 2465 if (*archdirname == '/') { /* absolute */ 2466 strlcpy(tmp, archdirname, tmpsiz); 2467 } else { /* relative */ 2468 /* get directory part of logfile */ 2469 strlcpy(tmp, file, tmpsiz); 2470 if ((p = strrchr(tmp, '/')) == NULL) 2471 tmp[0] = '\0'; 2472 else 2473 *(p + 1) = '\0'; 2474 strlcat(tmp, archdirname, tmpsiz); 2475 } 2476 2477 strlcat(tmp, "/", tmpsiz); 2478 2479 /* get filename part of logfile */ 2480 if ((p = strrchr(file, '/')) == NULL) 2481 strlcat(tmp, file, tmpsiz); 2482 else 2483 strlcat(tmp, p + 1, tmpsiz); 2484 } else { 2485 (void) strlcpy(tmp, file, tmpsiz); 2486 } 2487 2488 if (timefnamefmt != NULL) { 2489 mtime = mtime_old_timelog(tmp); 2490 if (mtime == -1) 2491 return (-1); 2492 } else { 2493 strlcat(tmp, ".0", tmpsiz); 2494 logfile_suffix = get_logfile_suffix(tmp); 2495 if (logfile_suffix == NULL) 2496 return (-1); 2497 (void) strlcat(tmp, logfile_suffix, tmpsiz); 2498 if (stat(tmp, &sb) < 0) 2499 return (-1); 2500 mtime = sb.st_mtime; 2501 } 2502 2503 return ((int)(ptimeget_secs(timenow) - mtime + 1800) / 3600); 2504 } 2505 2506 /* Skip Over Blanks */ 2507 static char * 2508 sob(char *p) 2509 { 2510 while (p && *p && isspace(*p)) 2511 p++; 2512 return (p); 2513 } 2514 2515 /* Skip Over Non-Blanks */ 2516 static char * 2517 son(char *p) 2518 { 2519 while (p && *p && !isspace(*p)) 2520 p++; 2521 return (p); 2522 } 2523 2524 /* Check if string is actually a number */ 2525 static int 2526 isnumberstr(const char *string) 2527 { 2528 while (*string) { 2529 if (!isdigitch(*string++)) 2530 return (0); 2531 } 2532 return (1); 2533 } 2534 2535 /* Check if string contains a glob */ 2536 static int 2537 isglobstr(const char *string) 2538 { 2539 char chr; 2540 2541 while ((chr = *string++)) { 2542 if (chr == '*' || chr == '?' || chr == '[') 2543 return (1); 2544 } 2545 return (0); 2546 } 2547 2548 /* 2549 * Save the active log file under a new name. A link to the new name 2550 * is the quick-and-easy way to do this. If that fails (which it will 2551 * if the destination is on another partition), then make a copy of 2552 * the file to the new location. 2553 */ 2554 static void 2555 savelog(char *from, char *to) 2556 { 2557 FILE *src, *dst; 2558 int c, res; 2559 2560 res = link(from, to); 2561 if (res == 0) 2562 return; 2563 2564 if ((src = fopen(from, "r")) == NULL) 2565 err(1, "can't fopen %s for reading", from); 2566 if ((dst = fopen(to, "w")) == NULL) 2567 err(1, "can't fopen %s for writing", to); 2568 2569 while ((c = getc(src)) != EOF) { 2570 if ((putc(c, dst)) == EOF) 2571 err(1, "error writing to %s", to); 2572 } 2573 2574 if (ferror(src)) 2575 err(1, "error reading from %s", from); 2576 if ((fclose(src)) != 0) 2577 err(1, "can't fclose %s", to); 2578 if ((fclose(dst)) != 0) 2579 err(1, "can't fclose %s", from); 2580 } 2581 2582 /* create one or more directory components of a path */ 2583 static void 2584 createdir(const struct conf_entry *ent, char *dirpart) 2585 { 2586 int res; 2587 char *s, *d; 2588 char mkdirpath[MAXPATHLEN]; 2589 struct stat st; 2590 2591 s = dirpart; 2592 d = mkdirpath; 2593 2594 for (;;) { 2595 *d++ = *s++; 2596 if (*s != '/' && *s != '\0') 2597 continue; 2598 *d = '\0'; 2599 res = lstat(mkdirpath, &st); 2600 if (res != 0) { 2601 if (noaction) { 2602 printf("\tmkdir %s\n", mkdirpath); 2603 } else { 2604 res = mkdir(mkdirpath, 0755); 2605 if (res != 0) 2606 err(1, "Error on mkdir(\"%s\") for -a", 2607 mkdirpath); 2608 } 2609 } 2610 if (*s == '\0') 2611 break; 2612 } 2613 if (verbose) { 2614 if (ent->firstcreate) 2615 printf("Created directory '%s' for new %s\n", 2616 dirpart, ent->log); 2617 else 2618 printf("Created directory '%s' for -a\n", dirpart); 2619 } 2620 } 2621 2622 /* 2623 * Create a new log file, destroying any currently-existing version 2624 * of the log file in the process. If the caller wants a backup copy 2625 * of the file to exist, they should call 'link(logfile,logbackup)' 2626 * before calling this routine. 2627 */ 2628 void 2629 createlog(const struct conf_entry *ent) 2630 { 2631 int fd, failed; 2632 struct stat st; 2633 char *realfile, *slash, tempfile[MAXPATHLEN]; 2634 2635 fd = -1; 2636 realfile = ent->log; 2637 2638 /* 2639 * If this log file is being created for the first time (-C option), 2640 * then it may also be true that the parent directory does not exist 2641 * yet. Check, and create that directory if it is missing. 2642 */ 2643 if (ent->firstcreate) { 2644 strlcpy(tempfile, realfile, sizeof(tempfile)); 2645 slash = strrchr(tempfile, '/'); 2646 if (slash != NULL) { 2647 *slash = '\0'; 2648 failed = stat(tempfile, &st); 2649 if (failed && errno != ENOENT) 2650 err(1, "Error on stat(%s)", tempfile); 2651 if (failed) 2652 createdir(ent, tempfile); 2653 else if (!S_ISDIR(st.st_mode)) 2654 errx(1, "%s exists but is not a directory", 2655 tempfile); 2656 } 2657 } 2658 2659 /* 2660 * First create an unused filename, so it can be chown'ed and 2661 * chmod'ed before it is moved into the real location. mkstemp 2662 * will create the file mode=600 & owned by us. Note that all 2663 * temp files will have a suffix of '.z<something>'. 2664 */ 2665 strlcpy(tempfile, realfile, sizeof(tempfile)); 2666 strlcat(tempfile, ".zXXXXXX", sizeof(tempfile)); 2667 if (noaction) 2668 printf("\tmktemp %s\n", tempfile); 2669 else { 2670 fd = mkstemp(tempfile); 2671 if (fd < 0) 2672 err(1, "can't mkstemp logfile %s", tempfile); 2673 2674 /* 2675 * Add status message to what will become the new log file. 2676 */ 2677 if (!(ent->flags & CE_BINARY)) { 2678 if (log_trim(tempfile, ent)) 2679 err(1, "can't add status message to log"); 2680 } 2681 } 2682 2683 /* Change the owner/group, if we are supposed to */ 2684 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) { 2685 if (noaction) 2686 printf("\tchown %u:%u %s\n", ent->uid, ent->gid, 2687 tempfile); 2688 else { 2689 failed = fchown(fd, ent->uid, ent->gid); 2690 if (failed) 2691 err(1, "can't fchown temp file %s", tempfile); 2692 } 2693 } 2694 2695 /* Turn on NODUMP if it was requested in the config-file. */ 2696 if (ent->flags & CE_NODUMP) { 2697 if (noaction) 2698 printf("\tchflags nodump %s\n", tempfile); 2699 else { 2700 failed = fchflags(fd, UF_NODUMP); 2701 if (failed) { 2702 warn("log_trim: fchflags(NODUMP)"); 2703 } 2704 } 2705 } 2706 2707 /* 2708 * Note that if the real logfile still exists, and if the call 2709 * to rename() fails, then "neither the old file nor the new 2710 * file shall be changed or created" (to quote the standard). 2711 * If the call succeeds, then the file will be replaced without 2712 * any window where some other process might find that the file 2713 * did not exist. 2714 * XXX - ? It may be that for some error conditions, we could 2715 * retry by first removing the realfile and then renaming. 2716 */ 2717 if (noaction) { 2718 printf("\tchmod %o %s\n", ent->permissions, tempfile); 2719 printf("\tmv %s %s\n", tempfile, realfile); 2720 } else { 2721 failed = fchmod(fd, ent->permissions); 2722 if (failed) 2723 err(1, "can't fchmod temp file '%s'", tempfile); 2724 failed = rename(tempfile, realfile); 2725 if (failed) 2726 err(1, "can't mv %s to %s", tempfile, realfile); 2727 } 2728 2729 if (fd >= 0) 2730 close(fd); 2731 } 2732 2733 /* 2734 * Change the attributes of a given filename to what was specified in 2735 * the newsyslog.conf entry. This routine is only called for files 2736 * that newsyslog expects that it has created, and thus it is a fatal 2737 * error if this routine finds that the file does not exist. 2738 */ 2739 static void 2740 change_attrs(const char *fname, const struct conf_entry *ent) 2741 { 2742 int failed; 2743 2744 if (noaction) { 2745 printf("\tchmod %o %s\n", ent->permissions, fname); 2746 2747 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) 2748 printf("\tchown %u:%u %s\n", 2749 ent->uid, ent->gid, fname); 2750 2751 if (ent->flags & CE_NODUMP) 2752 printf("\tchflags nodump %s\n", fname); 2753 return; 2754 } 2755 2756 failed = chmod(fname, ent->permissions); 2757 if (failed) { 2758 if (errno != EPERM) 2759 err(1, "chmod(%s) in change_attrs", fname); 2760 warn("change_attrs couldn't chmod(%s)", fname); 2761 } 2762 2763 if (ent->uid != (uid_t)-1 || ent->gid != (gid_t)-1) { 2764 failed = chown(fname, ent->uid, ent->gid); 2765 if (failed) 2766 warn("can't chown %s", fname); 2767 } 2768 2769 if (ent->flags & CE_NODUMP) { 2770 failed = chflags(fname, UF_NODUMP); 2771 if (failed) 2772 warn("can't chflags %s NODUMP", fname); 2773 } 2774 } 2775 2776 /* 2777 * Parse a signal number or signal name. Returns the signal number parsed or -1 2778 * on failure. 2779 */ 2780 static int 2781 parse_signal(const char *str) 2782 { 2783 int sig, i; 2784 const char *errstr; 2785 2786 sig = strtonum(str, 1, sys_nsig - 1, &errstr); 2787 2788 if (errstr == NULL) 2789 return (sig); 2790 if (strncasecmp(str, "SIG", 3) == 0) 2791 str += 3; 2792 2793 for (i = 1; i < sys_nsig; i++) { 2794 if (strcasecmp(str, sys_signame[i]) == 0) 2795 return (i); 2796 } 2797 2798 return (-1); 2799 } 2800