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