1 /* $NetBSD: newsyslog.c,v 1.58 2009/06/20 19:34:19 christos Exp $ */
4 * Copyright (c) 1999, 2000 Andrew Doran <ad@NetBSD.org>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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.
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
31 * This file contains changes from the Open Software Foundation.
35 * Copyright 1988, 1989 by the Massachusetts Institute of Technology
37 * Permission to use, copy, modify, and distribute this software
38 * and its documentation for any purpose and without fee is
39 * hereby granted, provided that the above copyright notice
40 * appear in all copies and that both that copyright notice and
41 * this permission notice appear in supporting documentation,
42 * 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
44 * of the software without specific, written prior permission.
45 * M.I.T. and the M.I.T. S.I.P.B. make no representations about
46 * the suitability of this software for any purpose. It is
47 * provided "as is" without express or implied warranty.
52 * newsyslog(8) - a program to roll over log files provided that specified
53 * critera are met, optionally preserving a number of historical log files.
56 #include <sys/cdefs.h>
58 __RCSID("$NetBSD: newsyslog.c,v 1.58 2009/06/20 19:34:19 christos Exp $");
61 #include <sys/types.h>
64 #include <sys/param.h>
82 #define PRHDRINFO(x) \
83 (/*LINTED*/(void)(verbose ? printf x : 0))
85 (/*LINTED*/(void)(verbose ? printf(" ") + printf x : 0))
88 #define __arraycount(a) (sizeof(a) / sizeof(a[0]))
91 #define CE_BINARY 0x02 /* Logfile is a binary file/non-syslog */
92 #define CE_NOSIGNAL 0x04 /* Don't send a signal when trimmed */
93 #define CE_CREATE 0x08 /* Create log file if none exists */
94 #define CE_PLAIN0 0x10 /* Do not compress zero'th history file */
95 #define CE_SYSLPROTOCOL 0x20 /* log in syslog-protocol format,
96 not configurable but detected at runtime */
99 uid_t uid
; /* Owner of log */
100 gid_t gid
; /* Group of log */
101 mode_t mode
; /* File permissions */
102 int numhist
; /* Number of historical logs to keep */
103 size_t maxsize
; /* Maximum log size */
104 int maxage
; /* Hours between log trimming */
105 time_t trimat
; /* Specific trim time */
106 int flags
; /* Flags (CE_*) */
107 int signum
; /* Signal to send */
108 char pidfile
[MAXPATHLEN
]; /* File containing PID to signal */
109 char logfile
[MAXPATHLEN
]; /* Path to log file */
116 const char *flag
; /* newsyslog.conf flag */
119 static struct compressor compress
[] =
121 {NULL
, "", "", ""}, /* 0th compressor is "no compression" */
122 {"/usr/bin/gzip", "-f", ".gz", "Z"},
123 {"/usr/bin/bzip2", "-9f", ".bz2", "J"},
126 #define _PATH_NEWSYSLOGCONF "/etc/newsyslog.conf"
127 #define _PATH_SYSLOGDPID _PATH_VARRUN"syslogd.pid"
129 static int verbose
; /* Be verbose */
130 static int noaction
; /* Take no action */
131 static int nosignal
; /* Do not send signals */
132 static char hostname
[MAXHOSTNAMELEN
+ 1]; /* Hostname, with domain */
133 static uid_t myeuid
; /* EUID we are running with */
134 static int ziptype
; /* compression type, if any */
136 static int getsig(const char *);
137 static int isnumber(const char *);
138 static int parse_cfgline(struct conf_entry
*, FILE *, size_t *);
139 static time_t parse_iso8601(char *);
140 static time_t parse_dwm(char *);
141 static int parse_userspec(const char *, struct passwd
**, struct group
**);
142 static pid_t
readpidfile(const char *);
143 static void usage(void) __dead
;
145 static void log_compress(struct conf_entry
*, const char *);
146 static void log_create(struct conf_entry
*);
147 static void log_examine(struct conf_entry
*, int);
148 static void log_trim(struct conf_entry
*);
149 static void log_trimmed(struct conf_entry
*);
150 static void log_get_format(struct conf_entry
*);
153 * Program entry point.
156 main(int argc
, char **argv
)
158 struct conf_entry log
;
161 int c
, needroot
, i
, force
;
167 cfile
= _PATH_NEWSYSLOGCONF
;
169 (void)gethostname(hostname
, sizeof(hostname
));
170 hostname
[sizeof(hostname
) - 1] = '\0';
172 /* Parse command line options. */
173 while ((c
= getopt(argc
, argv
, "f:nrsvF")) != -1) {
201 if (needroot
&& myeuid
!= 0)
202 errx(EXIT_FAILURE
, "must be run as root");
207 if (strcmp(cfile
, "-") == 0)
209 else if ((fd
= fopen(cfile
, "rt")) == NULL
)
210 err(EXIT_FAILURE
, "%s", cfile
);
212 for (lineno
= 0; !parse_cfgline(&log
, fd
, &lineno
);) {
214 * If specific log files were specified, touch only
218 for (i
= 0; i
< argc
; i
++)
219 if (strcmp(log
.logfile
, argv
[i
]) == 0)
224 log_examine(&log
, force
);
235 * Parse a single line from the configuration file.
238 parse_cfgline(struct conf_entry
*log
, FILE *fd
, size_t *_lineno
)
240 char *line
, *q
, **ap
, *argv
[10];
243 int nf
, lineno
, i
, rv
;
249 /* Place the white-space separated fields into an array. */
253 if ((line
= fparseln(fd
, NULL
, _lineno
, NULL
, 0)) == NULL
)
255 lineno
= (int)*_lineno
;
257 for (ap
= argv
, nf
= 0; (*ap
= strsep(&line
, " \t")) != NULL
;)
259 if (++nf
== sizeof(argv
) / sizeof(argv
[0])) {
260 warnx("config line %d: "
261 "too many fields", lineno
);
269 errx(EXIT_FAILURE
, "config line %d: too few fields", lineno
);
271 (void)memset(log
, 0, sizeof(*log
));
275 (void)strlcpy(log
->logfile
, *ap
++, sizeof(log
->logfile
));
276 if (log
->logfile
[0] != '/')
278 "config line %d: logfile must have a full path", lineno
);
281 if (strchr(*ap
, ':') != NULL
|| strchr(*ap
, '.') != NULL
) {
282 if (parse_userspec(*ap
++, &pw
, &gr
)) {
283 warnx("config line %d: unknown user/group", lineno
);
288 * We may only change the file's owner as non-root.
291 if (pw
->pw_uid
!= myeuid
)
292 errx(EXIT_FAILURE
, "config line %d: user:group "
293 "as non-root must match current user",
295 log
->uid
= (uid_t
)-1;
297 log
->uid
= pw
->pw_uid
;
298 log
->gid
= gr
->gr_gid
;
300 errx(EXIT_FAILURE
, "config line %d: too few fields",
302 } else if (myeuid
!= 0) {
303 log
->uid
= (uid_t
)-1;
304 log
->gid
= getegid();
308 if (sscanf(*ap
++, "%o", &i
) != 1) {
309 warnx("config line %d: bad permissions", lineno
);
312 log
->mode
= (mode_t
)i
;
315 if (sscanf(*ap
++, "%d", &log
->numhist
) != 1) {
316 warnx("config line %d: bad log count", lineno
);
322 log
->maxsize
= (size_t)-1;
324 log
->maxsize
= (int)strtol(*ap
, &q
, 0);
326 warnx("config line %d: bad log size", lineno
);
334 log
->trimat
= (time_t)-1;
337 if (strcmp(q
, "*") != 0) {
338 if (isdigit((unsigned char)*q
))
339 log
->maxage
= (int)strtol(q
, &q
, 10);
342 * One class of periodic interval specification can follow a
343 * maximum age specification. Handle it.
346 log
->trimat
= parse_iso8601(q
+ 1);
347 if (log
->trimat
== (time_t)-1) {
348 warnx("config line %d: bad trim time", lineno
);
351 } else if (*q
== '$') {
352 if ((log
->trimat
= parse_dwm(q
+ 1)) == (time_t)-1) {
353 warnx("config line %d: bad trim time", lineno
);
356 } else if (log
->maxage
== -1) {
357 warnx("config line %d: bad log age", lineno
);
363 log
->flags
= (nosignal
? CE_NOSIGNAL
: 0);
365 for (q
= *ap
++; q
!= NULL
&& *q
!= '\0'; q
++) {
366 char qq
= toupper((unsigned char)*q
);
369 log
->flags
|= CE_BINARY
;
372 log
->flags
|= CE_CREATE
;
375 log
->flags
|= CE_NOSIGNAL
;
378 log
->flags
|= CE_PLAIN0
;
381 for (ziptype
= __arraycount(compress
); --ziptype
; ) {
382 if (*compress
[ziptype
].flag
== qq
)
389 warnx("config line %d: bad flags", lineno
);
394 /* path_to_pidfile */
395 if (*ap
!= NULL
&& **ap
== '/')
396 (void)strlcpy(log
->pidfile
, *ap
++, sizeof(log
->pidfile
));
398 log
->pidfile
[0] = '\0';
402 if ((log
->signum
= getsig(*ap
++)) < 0) {
403 warnx("config line %d: bad signal type", lineno
);
407 log
->signum
= SIGHUP
;
417 * Examine a log file. If the trim conditions are met, call log_trim() to
421 log_examine(struct conf_entry
*log
, int force
)
427 char tmp
[MAXPATHLEN
];
433 PRHDRINFO(("\n%s <%d%s>: ", log
->logfile
, log
->numhist
,
434 compress
[ziptype
].flag
));
437 * stat() the logfile. If it doesn't exist and the `c' flag has
438 * been specified, create it. If it doesn't exist and the `c' flag
439 * hasn't been specified, give up.
441 if (stat(log
->logfile
, &sb
) < 0) {
442 if (errno
== ENOENT
&& (log
->flags
& CE_CREATE
) != 0) {
443 PRHDRINFO(("creating; "));
447 PRHDRINFO(("can't proceed with `-n'\n"));
450 if (stat(log
->logfile
, &sb
))
451 err(EXIT_FAILURE
, "%s", log
->logfile
);
452 } else if (errno
== ENOENT
) {
453 PRHDRINFO(("does not exist --> skip log\n"));
455 } else if (errno
!= 0)
456 err(EXIT_FAILURE
, "%s", log
->logfile
);
459 if (!S_ISREG(sb
.st_mode
)) {
460 PRHDRINFO(("not a regular file --> skip log\n"));
464 /* Size of the log file in kB. */
465 size
= ((size_t)sb
.st_blocks
* S_BLKSIZE
) >> 10;
468 * Get the age (expressed in hours) of the current log file with
469 * respect to the newest historical log file.
472 for (j
= 0; j
< __arraycount(compress
); j
++) {
473 (void)strlcpy(tmp
, log
->logfile
, sizeof(tmp
));
474 (void)strlcat(tmp
, ".0", sizeof(tmp
));
475 (void)strlcat(tmp
, compress
[j
].suffix
, sizeof(tmp
));
476 if (!stat(tmp
, &sb
)) {
477 age
= (int)(now
- sb
.st_mtime
+ 1800) / 3600;
483 * Examine the set of given trim conditions and if any one is met,
486 * Note: if `maxage' or `trimat' is used as a trim condition, we
487 * need at least one historical log file to determine the `age' of
488 * the active log file. WRT `trimat', we will trim up to one hour
489 * after the specific trim time has passed - we need to know if
490 * we've trimmed to meet that condition with a previous invocation
493 if (log
->maxage
>= 0 && (age
>= log
->maxage
|| age
< 0)) {
495 reason
= "log age > interval";
496 } else if (size
>= log
->maxsize
) {
498 reason
= "log size > size";
499 } else if (log
->trimat
!= (time_t)-1 && now
>= log
->trimat
&&
500 (age
== -1 || age
> 1) &&
501 difftime(now
, log
->trimat
) < 60 * 60) {
503 reason
= "specific trim time";
506 reason
= "trim forced";
510 PRHDRINFO(("--> trim log (%s)\n", reason
));
513 PRHDRINFO(("--> skip log (trim conditions not met)\n"));
517 * Trim the specified log file.
520 log_trim(struct conf_entry
*log
)
522 char file1
[MAXPATHLEN
], file2
[MAXPATHLEN
];
527 if (log
->numhist
!= 0) {
528 /* Remove oldest historical log. */
529 for (j
= 0; j
< (int)__arraycount(compress
); j
++) {
530 (void)snprintf(file1
, sizeof(file1
), "%s.%d",
531 log
->logfile
, log
->numhist
- 1);
532 (void)strlcat(file1
, compress
[j
].suffix
,
534 PRINFO(("rm -f %s\n", file1
));
541 * If a historical log file isn't compressed, and 'z' has been
542 * specified, compress it. (This is convenient, but is also needed
543 * if 'p' has been specified.) It should be noted that gzip(1)
544 * preserves file ownership and file mode.
547 for (i
= 0; i
< log
->numhist
; i
++) {
548 snprintf(file1
, sizeof(file1
), "%s.%d", log
->logfile
, i
);
549 if (lstat(file1
, &st
) != 0)
551 snprintf(file2
, sizeof(file2
), "%s%s", file1
,
552 compress
[ziptype
].suffix
);
553 if (lstat(file2
, &st
) == 0)
555 log_compress(log
, file1
);
559 /* Move down log files. */
560 for (i
= log
->numhist
- 1; i
> 0; i
--) {
561 for (j
= 0; j
< (int)__arraycount(compress
); j
++) {
562 snprintf(file1
, sizeof(file1
), "%s.%d%s", log
->logfile
,
563 i
- 1, compress
[j
].suffix
);
564 snprintf(file2
, sizeof(file2
), "%s.%d%s", log
->logfile
,
565 i
, compress
[j
].suffix
);
566 k
= lstat(file1
, &st
);
571 PRINFO(("mv %s %s\n", file1
, file2
));
573 if (rename(file1
, file2
))
574 err(EXIT_FAILURE
, "%s", file1
);
575 PRINFO(("chmod %o %s\n", log
->mode
, file2
));
577 if (chmod(file2
, log
->mode
))
578 err(EXIT_FAILURE
, "%s", file2
);
579 PRINFO(("chown %d:%d %s\n", log
->uid
, log
->gid
,
582 if (chown(file2
, log
->uid
, log
->gid
))
583 err(EXIT_FAILURE
, "%s", file2
);
589 /* Create the historical log file if we're maintaining history. */
590 if (log
->numhist
== 0) {
591 PRINFO(("rm -f %s\n", log
->logfile
));
593 if (unlink(log
->logfile
))
594 err(EXIT_FAILURE
, "%s", log
->logfile
);
596 (void)snprintf(file1
, sizeof(file1
), "%s.0", log
->logfile
);
597 PRINFO(("mv %s %s\n", log
->logfile
, file1
));
599 if (rename(log
->logfile
, file1
))
600 err(EXIT_FAILURE
, "%s", log
->logfile
);
603 PRINFO(("(create new log)\n"));
607 /* Set the correct permissions on the log. */
608 PRINFO(("chmod %o %s\n", log
->mode
, log
->logfile
));
610 if (chmod(log
->logfile
, log
->mode
))
611 err(EXIT_FAILURE
, "%s", log
->logfile
);
613 /* Do we need to signal a daemon? */
614 if ((log
->flags
& CE_NOSIGNAL
) == 0) {
615 if (log
->pidfile
[0] != '\0')
616 pid
= readpidfile(log
->pidfile
);
618 pid
= readpidfile(_PATH_SYSLOGDPID
);
620 if (pid
!= (pid_t
)-1) {
621 PRINFO(("kill -%s %lu\n",
622 sys_signame
[log
->signum
], (u_long
)pid
));
624 if (kill(pid
, log
->signum
))
629 /* If the newest historical log is to be compressed, do it here. */
630 if (ziptype
&& !(log
->flags
& CE_PLAIN0
) && log
->numhist
!= 0) {
631 snprintf(file1
, sizeof(file1
), "%s.0", log
->logfile
);
632 if ((log
->flags
& CE_NOSIGNAL
) == 0) {
633 PRINFO(("sleep for 10 seconds before compressing...\n"));
636 log_compress(log
, file1
);
641 log_get_format(struct conf_entry
*log
)
647 if ((log
->flags
& CE_BINARY
) != 0)
649 PRINFO(("(read line format of %s)\n", log
->logfile
));
653 if ((fd
= fopen(log
->logfile
, "r")) == NULL
)
657 line
= fgetln(fd
, &linelen
);
658 if ((line
= fgetln(fd
, &linelen
)) != NULL
660 log
->flags
|= CE_SYSLPROTOCOL
;
665 * Write an entry to the log file recording the fact that it was trimmed.
668 log_trimmed(struct conf_entry
*log
)
673 const char trim_message
[] = "log file turned over";
675 if ((log
->flags
& CE_BINARY
) != 0)
677 PRINFO(("(append rotation notice to %s)\n", log
->logfile
));
681 if ((fd
= fopen(log
->logfile
, "at")) == NULL
)
682 err(EXIT_FAILURE
, "%s", log
->logfile
);
684 if ((log
->flags
& CE_SYSLPROTOCOL
) == 0) {
685 char shorthostname
[MAXHOSTNAMELEN
];
688 /* Truncate domain. */
689 (void)strlcpy(shorthostname
, hostname
, sizeof(shorthostname
));
690 if ((p
= strchr(shorthostname
, '.')) != NULL
)
694 daytime
= p
= ctime(&now
) + 4;
697 (void)fprintf(fd
, "%s %s newsyslog[%lu]: %s\n",
698 daytime
, hostname
, (u_long
)getpid(), trim_message
);
705 if (gettimeofday(&tv
, NULL
) == -1) {
709 now
= (time_t) tv
.tv_sec
;
710 tmnow
= localtime(&now
);
712 i
= strftime(timestamp
, sizeof(timestamp
),
714 i
+= snprintf(timestamp
+i
, sizeof(timestamp
)-i
,
715 ".%06ld", (long)tv
.tv_usec
);
716 i
+= j
= strftime(timestamp
+i
, sizeof(timestamp
)-i
-1,
718 /* strftime gives eg. "+0200", but we need "+02:00" */
720 timestamp
[i
+1] = timestamp
[i
];
721 timestamp
[i
] = timestamp
[i
-1];
722 timestamp
[i
-1] = timestamp
[i
-2];
723 timestamp
[i
-2] = ':';
728 (void)fprintf(fd
, "%s %s newsyslog %lu - - %s\n",
729 daytime
, hostname
, (u_long
)getpid(), trim_message
);
736 * Create a new log file.
739 log_create(struct conf_entry
*log
)
746 if ((fd
= creat(log
->logfile
, log
->mode
)) < 0)
747 err(EXIT_FAILURE
, "%s", log
->logfile
);
748 if (fchown(fd
, log
->uid
, log
->gid
) < 0)
749 err(EXIT_FAILURE
, "%s", log
->logfile
);
754 * Fork off gzip(1) to compress a log file. This routine takes an
755 * additional string argument (the name of the file to compress): it is also
756 * used to compress historical log files other than the newest.
759 log_compress(struct conf_entry
*log
, const char *fn
)
761 char tmp
[MAXPATHLEN
];
763 PRINFO(("%s %s %s\n", compress
[ziptype
].path
, compress
[ziptype
].args
,
769 if ((pid
= vfork()) < 0)
770 err(EXIT_FAILURE
, "vfork");
772 (void)execl(compress
[ziptype
].path
,
773 compress
[ziptype
].path
, compress
[ziptype
].args
, fn
,
777 while (waitpid(pid
, &status
, 0) != pid
);
779 if (!WIFEXITED(status
) || (WEXITSTATUS(status
) != 0))
780 errx(EXIT_FAILURE
, "%s failed", compress
[ziptype
].path
);
783 (void)snprintf(tmp
, sizeof(tmp
), "%s%s", fn
, compress
[ziptype
].suffix
);
784 PRINFO(("chown %d:%d %s\n", log
->uid
, log
->gid
, tmp
));
786 if (chown(tmp
, log
->uid
, log
->gid
))
787 err(EXIT_FAILURE
, "%s", tmp
);
791 * Display program usage information.
797 (void)fprintf(stderr
,
798 "Usage: %s [-nrsvF] [-f config-file] [file ...]\n", getprogname());
803 * Return non-zero if a string represents a decimal value.
806 isnumber(const char *string
)
809 while (isdigit((unsigned char)*string
))
812 return *string
== '\0';
816 * Given a signal name, attempt to find the corresponding signal number.
819 getsig(const char *sig
)
825 n
= (int)strtol(sig
, &p
, 0);
826 if (p
!= '\0' || n
< 0 || n
>= NSIG
)
831 if (strncasecmp(sig
, "SIG", 3) == 0)
833 for (n
= 1; n
< NSIG
; n
++)
834 if (strcasecmp(sys_signame
[n
], sig
) == 0)
840 * Given a path to a PID file, return the PID contained within.
843 readpidfile(const char *file
)
851 (void)snprintf(tmp
, sizeof(tmp
), "%s%s", _PATH_VARRUN
, file
);
853 (void)strlcpy(tmp
, file
, sizeof(tmp
));
856 if ((fd
= fopen(file
, "r")) == NULL
) {
861 if (fgets(line
, sizeof(line
) - 1, fd
) != NULL
) {
862 line
[sizeof(line
) - 1] = '\0';
863 pid
= (pid_t
)strtol(line
, NULL
, 0);
865 warnx("unable to read %s", file
);
874 * Parse a user:group specification.
876 * XXX This is over the top for newsyslog(8). It should be moved to libutil.
879 parse_userspec(const char *name
, struct passwd
**pw
, struct group
**gr
)
881 char buf
[MAXLOGNAME
* 2 + 2], *group
;
883 (void)strlcpy(buf
, name
, sizeof(buf
));
887 * Before attempting to use '.' as a separator, see if the whole
888 * string resolves as a user name.
890 if ((*pw
= getpwnam(buf
)) != NULL
) {
891 *gr
= getgrgid((*pw
)->pw_gid
);
895 /* Split the user and group name. */
896 if ((group
= strchr(buf
, ':')) != NULL
||
897 (group
= strchr(buf
, '.')) != NULL
)
901 *pw
= getpwuid((uid_t
)atoi(buf
));
906 * Find the group. If a group wasn't specified, use the user's
907 * `natural' group. We get to this point even if no user was found.
908 * This is to allow the caller to get a better idea of what went
909 * wrong, if anything.
911 if (group
== NULL
|| *group
== '\0') {
914 *gr
= getgrgid((*pw
)->pw_gid
);
915 } else if (isnumber(group
))
916 *gr
= getgrgid((gid_t
)atoi(group
));
918 *gr
= getgrnam(group
);
920 return *pw
!= NULL
&& *gr
!= NULL
? 0 : -1;
924 * Parse a cyclic time specification, the format is as follows:
926 * [Dhh] or [Wd[Dhh]] or [Mdd[Dhh]]
928 * to rotate a log file cyclic at
930 * - every day (D) within a specific hour (hh) (hh = 0...23)
931 * - once a week (W) at a specific day (d) OR (d = 0..6, 0 = Sunday)
932 * - once a month (M) at a specific day (d) (d = 1..31,l|L)
934 * We don't accept a timezone specification; missing fields are defaulted to
935 * the current date but time zero.
944 static int mtab
[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
945 int wmseen
, dseen
, nd
, save
;
951 tmp
= localtime(&now
);
954 /* Set no. of days per month */
955 nd
= mtab
[tm
.tm_mon
];
957 if (tm
.tm_mon
== 1 &&
958 ((tm
.tm_year
+ 1900) % 4 == 0) &&
959 ((tm
.tm_year
+ 1900) % 100 != 0) &&
960 ((tm
.tm_year
+ 1900) % 400 == 0))
961 nd
++; /* leap year, 29 days in february */
962 tm
.tm_hour
= tm
.tm_min
= tm
.tm_sec
= 0;
971 ul
= strtol(s
, &t
, 10);
972 if (ul
> 23 || ul
< 0)
982 ul
= strtol(s
, &t
, 10);
983 if (ul
> 6 || ul
< 0)
985 if (ul
!= tm
.tm_wday
) {
986 if (ul
< tm
.tm_wday
) {
987 save
= 6 - tm
.tm_wday
;
990 save
= ul
- tm
.tm_wday
;
993 if (tm
.tm_mday
> nd
) {
995 tm
.tm_mday
= tm
.tm_mday
- nd
;
1005 if (tolower((unsigned char)*s
) == 'l') {
1010 ul
= strtol(s
, &t
, 10);
1011 if (ul
< 1 || ul
> 31)
1024 if (*t
== '\0' || isspace((unsigned char)*t
))
1034 * Parse a limited subset of ISO 8601. The specific format is as follows:
1036 * [CC[YY[MM[DD]]]][THH[MM[SS]]] (where `T' is the literal letter)
1038 * We don't accept a timezone specification; missing fields (including
1039 * timezone) are defaulted to the current date but time zero.
1042 parse_iso8601(char *s
)
1050 tmp
= localtime(&now
);
1053 tm
.tm_hour
= tm
.tm_min
= tm
.tm_sec
= 0;
1055 ul
= strtoul(s
, &t
, 10);
1056 if (*t
!= '\0' && *t
!= 'T')
1060 * Now t points either to the end of the string (if no time was
1061 * provided) or to the letter `T' which separates date and time in
1062 * ISO 8601. The pointer arithmetic is the same for either case.
1066 tm
.tm_year
= ((ul
/ 1000000) - 19) * 100;
1070 tm
.tm_year
= tm
.tm_year
- (tm
.tm_year
% 100);
1071 tm
.tm_year
+= ul
/ 10000;
1075 tm
.tm_mon
= (ul
/ 100) - 1;
1088 if (tm
.tm_year
< 70 || tm
.tm_mon
< 0 || tm
.tm_mon
> 12 ||
1089 tm
.tm_mday
< 1 || tm
.tm_mday
> 31)
1094 ul
= strtoul(s
, &t
, 10);
1095 if (*t
!= '\0' && !isspace((unsigned char)*t
))
1100 tm
.tm_sec
= ul
% 100;
1104 tm
.tm_min
= ul
% 100;
1117 if (tm
.tm_sec
< 0 || tm
.tm_sec
> 60 || tm
.tm_min
< 0 ||
1118 tm
.tm_min
> 59 || tm
.tm_hour
< 0 || tm
.tm_hour
> 23)