4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
25 * Copyright 2013 Joshua M. Clulow <josh@sysmgr.org>
27 * Copyright (c) 2014 Gary Mills
28 * Copyright (c) 2016 by Delphix. All rights reserved.
31 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
32 /* All Rights Reserved */
34 /* Copyright (c) 1987, 1988 Microsoft Corporation */
35 /* All Rights Reserved */
38 #include <sys/contract/process.h>
40 #include <sys/param.h>
41 #include <sys/resource.h>
45 #include <sys/types.h>
46 #include <sys/utsname.h>
49 #include <security/pam_appl.h>
58 #include <libcontract.h>
59 #include <libcontract_priv.h>
80 #define MAIL "/usr/bin/mail" /* mail program to use */
81 #define CONSOLE "/dev/console" /* where messages go when cron dies */
83 #define TMPINFILE "/tmp/crinXXXXXX" /* file to put stdin in for cmd */
86 #define TMPOUTFILE "/tmp/croutXXXXXX" /* file to place stdout, stderr */
88 #define INMODE 00400 /* mode for stdin file */
89 #define OUTMODE 00600 /* mode for stdout file */
90 #define ISUID S_ISUID /* mode for verifing at jobs */
92 #define INFINITY 2147483647L /* upper bound on time */
94 #define ZOMB 100 /* proc slot used for mailing output */
108 /* Defined actions for crabort() routine */
109 #define NO_ACTION 000
110 #define REMOVE_FIFO 001
111 #define CONSOLE_MSG 002
113 #define BADCD "can't change directory to the crontab directory."
114 #define NOREADDIR "can't read the crontab directory."
116 #define BADJOBOPEN "unable to read your at job."
117 #define BADSHELL "because your login shell \
118 isn't /usr/bin/sh, you can't use cron."
120 #define BADSTAT "can't access your crontab or at-job file. Resubmit it."
121 #define BADPROJID "can't set project id for your job."
122 #define CANTCDHOME "can't change directory to %s.\
123 \nYour commands will not be executed."
124 #define CANTEXECSH "unable to exec the shell, %s, for one of your \
126 #define CANT_STR_LEN (sizeof (CANTEXECSH) > sizeof (CANTCDHOME) ? \
127 sizeof (CANTEXECSH) : sizeof (CANTCDHOME))
128 #define NOREAD "can't read your crontab file. Resubmit it."
129 #define BADTYPE "crontab or at-job file is not a regular file.\n"
130 #define NOSTDIN "unable to create a standard input file for \
131 one of your crontab commands. \
132 \nThat command was not executed."
134 #define NOTALLOWED "you are not authorized to use cron. Sorry."
135 #define STDERRMSG "\n\n********************************************\
136 *****\nCron: The previous message is the \
137 standard output and standard error \
138 \nof one of your cron commands.\n"
140 #define STDOUTERR "one of your commands generated output or errors, \
141 but cron was unable to mail you this output.\
142 \nRemember to redirect standard output and standard \
143 error for each of your commands."
145 #define CLOCK_DRIFT "clock time drifted backwards after event!\n"
146 #define PIDERR "unexpected pid returned %d (ignored)"
147 #define CRONTABERR "Subject: Your crontab file has an error in it\n\n"
148 #define MALLOCERR "out of space, cannot create new string\n"
150 #define DIDFORK didfork
151 #define NOFORK !didfork
153 #define MAILBUFLEN (8*1024)
155 #define MAILBINITFREE (MAILBUFLEN - (sizeof (cte_intro) - 1) \
156 - (sizeof (cte_trail1) - 1) - (sizeof (cte_trail2) - 1) - 1)
158 #define ERR_CRONTABENT 0 /* error in crontab file entry */
159 #define ERR_UNIXERR 1 /* error in some system call */
160 #define ERR_CANTEXECCRON 2 /* error setting up "cron" job environment */
161 #define ERR_CANTEXECAT 3 /* error setting up "at" job environment */
162 #define ERR_NOTREG 4 /* error not a regular file */
164 #define PROJECT "project="
166 #define MAX_LOST_CONTRACTS 2048 /* reset if this many failed abandons */
168 #define FORMAT "%a %b %e %H:%M:%S %Y"
169 static char timebuf
[80];
171 static struct message msgbuf
;
174 int count
; /* usage count */
175 void (*free
)(void *obj
); /* routine that will free obj */
176 void *obj
; /* object */
180 time_t time
; /* time of the event */
181 short etype
; /* what type of event; 0=cron, 1=at */
182 char *cmd
; /* command for cron, job name for at */
183 struct usr
*u
; /* ptr to the owner (usr) of this event */
184 struct event
*link
; /* ptr to another event for this user */
186 struct { /* for crontab events */
187 char *minute
; /* (these */
188 char *hour
; /* fields */
189 char *daymon
; /* are */
190 char *month
; /* from */
191 char *dayweek
; /* crontab) */
192 char *input
; /* ptr to stdin */
193 struct shared
*tz
; /* timezone of this event */
194 struct shared
*home
; /* directory for this event */
195 struct shared
*shell
; /* shell for this event */
197 struct { /* for at events */
198 short exists
; /* for revising at events */
199 int eventid
; /* for el_remove-ing at events */
205 char *name
; /* name of user (e.g. "root") */
206 char *home
; /* home directory for user */
207 uid_t uid
; /* user id */
208 gid_t gid
; /* group id */
209 int aruncnt
; /* counter for running jobs per uid */
210 int cruncnt
; /* counter for running cron jobs per uid */
211 int ctid
; /* for el_remove-ing crontab events */
212 short ctexists
; /* for revising crontab events */
213 struct event
*ctevents
; /* list of this usr's crontab events */
214 struct event
*atevents
; /* list of this usr's at events */
216 }; /* ptr to next user */
220 int njob
; /* limit */
221 int nice
; /* nice for execution */
222 int nwait
; /* wait time to next execution attempt */
223 int nrun
; /* number running */
225 qd
= {100, 2, 60}, /* default values for queue defs */
227 static struct queue qq
;
229 static struct runinfo
233 struct usr
*rusr
; /* pointer to usr struct */
234 char *outfile
; /* file where stdout & stderr are trapped */
235 short jobtype
; /* what type of event: 0=cron, 1=at */
236 char *jobname
; /* command for "cron", jobname for "at" */
237 int mailwhendone
; /* 1 = send mail even if no ouptut */
238 struct runinfo
*next
;
241 static struct miscpid
{
243 struct miscpid
*next
;
246 static pid_t cron_pid
; /* own pid */
247 static char didfork
= 0; /* flag to see if I'm process group leader */
248 static int msgfd
; /* file descriptor for fifo queue */
249 static int ecid
= 1; /* event class id for el_remove(); MUST be set to 1 */
250 static int delayed
; /* is job being rescheduled or did it run first time */
251 static int cwd
; /* current working directory */
252 static struct event
*next_event
; /* the next event to execute */
253 static struct usr
*uhead
; /* ptr to the list of users */
255 /* Variables for error handling at reading crontabs. */
256 static char cte_intro
[] = "Line(s) with errors:\n\n";
257 static char cte_trail1
[] = "\nMax number of errors encountered.";
258 static char cte_trail2
[] = " Evaluation of crontab aborted.\n";
259 static int cte_free
= MAILBINITFREE
; /* Free buffer space */
260 static char *cte_text
= NULL
; /* Text buffer pointer */
261 static char *cte_lp
; /* Next free line in cte_text */
262 static int cte_nvalid
; /* Valid lines found */
264 /* user's default environment for the shell */
265 #define ROOTPATH "PATH=/usr/sbin:/usr/bin"
266 #define NONROOTPATH "PATH=/usr/bin:"
268 static char *Def_supath
= NULL
;
269 static char *Def_path
= NULL
;
270 static char path
[LINE_MAX
] = "PATH=";
271 static char supath
[LINE_MAX
] = "PATH=";
272 static char homedir
[LINE_MAX
] = ENV_HOME
;
273 static char logname
[LINE_MAX
] = "LOGNAME=";
274 static char tzone
[LINE_MAX
] = ENV_TZ
;
275 static char *envinit
[] = {
284 extern char **environ
;
288 static char hzname
[10];
290 static void cronend(int);
291 static void thaw_handler(int);
292 static void child_handler(int);
293 static void child_sigreset(void);
295 static void mod_ctab(char *, time_t);
296 static void mod_atjob(char *, time_t);
297 static void add_atevent(struct usr
*, char *, time_t, int);
298 static void rm_ctevents(struct usr
*);
299 static void cleanup(struct runinfo
*rn
, int r
);
300 static void crabort(char *, int);
301 static void msg(char *fmt
, ...);
302 static void ignore_msg(char *, char *, struct event
*);
303 static void logit(int, struct runinfo
*, int);
304 static void parsqdef(char *);
305 static void defaults();
306 static void initialize(int);
307 static void quedefs(int);
308 static int idle(long);
309 static struct usr
*find_usr(char *);
310 static int ex(struct event
*e
);
311 static void read_dirs(int);
312 static void mail(char *, char *, int);
313 static char *next_field(int, int);
314 static void readcron(struct usr
*, time_t);
315 static int next_ge(int, char *);
316 static void free_if_unused(struct usr
*);
317 static void del_atjob(char *, char *);
318 static void del_ctab(char *);
319 static void resched(int);
320 static int msg_wait(long);
321 static struct runinfo
*rinfo_get(pid_t
);
322 static void rinfo_free(struct runinfo
*rp
);
323 static void mail_result(struct usr
*p
, struct runinfo
*pr
, size_t filesize
);
324 static time_t next_time(struct event
*, time_t);
325 static time_t get_switching_time(int, time_t);
326 static time_t xmktime(struct tm
*);
327 static void process_msg(struct message
*, time_t);
328 static void reap_child(void);
329 static void miscpid_insert(pid_t
);
330 static int miscpid_delete(pid_t
);
331 static void contract_set_template(void);
332 static void contract_clear_template(void);
333 static void contract_abandon_latest(pid_t
);
335 static void cte_init(void);
336 static void cte_add(int, char *);
337 static void cte_valid(void);
338 static int cte_istoomany(void);
339 static void cte_sendmail(char *);
341 static int set_user_cred(const struct usr
*, struct project
*);
343 static struct shared
*create_shared_str(char *str
);
344 static struct shared
*dup_shared(struct shared
*obj
);
345 static void rel_shared(struct shared
*obj
);
346 static void *get_obj(struct shared
*obj
);
348 * last_time is set immediately prior to exection of an event (via ex())
349 * to indicate the last time an event was executed. This was (surely)
350 * it's original intended use.
352 static time_t last_time
, init_time
, t_old
;
353 static int reset_needed
; /* set to 1 when cron(8) needs to re-initialize */
356 static sigset_t defmask
, sigmask
;
358 static int cron_conv(int, struct pam_message
**,
359 struct pam_response
**, void *);
361 static struct pam_conv pam_conv
= {cron_conv
, NULL
};
362 static pam_handle_t
*pamh
; /* Authentication handle */
365 * Function to help check a user's credentials.
368 static int verify_user_cred(struct usr
*u
);
371 * Values returned by verify_user_cred and set_user_cred:
375 #define VUC_BADUSER 1
376 #define VUC_NOTINGROUP 2
377 #define VUC_EXPIRED 3
378 #define VUC_NEW_AUTH 4
381 * Functions to remove a user or job completely from the running database.
383 static void clean_out_atjobs(struct usr
*u
);
384 static void clean_out_ctab(struct usr
*u
);
385 static void clean_out_user(struct usr
*u
);
386 static void cron_unlink(char *name
);
391 extern void el_init(int, time_t, time_t, int);
392 extern int el_add(void *, time_t, int);
393 extern void el_remove(int, int);
394 extern int el_empty(void);
395 extern void *el_first(void);
396 extern void el_delete(void);
398 static int valid_entry(char *, int);
399 static struct usr
*create_ulist(char *, int);
400 static void init_cronevent(char *, int);
401 static void init_atevent(char *, time_t, int, int);
402 static void update_atevent(struct usr
*, char *, time_t, int);
405 main(int argc
, char *argv
[])
408 time_t ne_time
; /* amt of time until next event execution */
409 time_t newtime
, lastmtime
= 0L;
411 struct event
*e
, *e2
, *eprev
;
414 struct sigaction act
;
417 * reset_needed is set to 1 whenever el_add() finds out that a cron
418 * job is scheduled to be run before the time when cron(8) daemon
420 * Other cases where a reset is needed is when ex() finds that the
421 * event to be executed is being run at the wrong time, or when idle()
422 * determines that time was reset.
423 * We immediately return to the top of the while (TRUE) loop in
424 * main() where the event list is cleared and rebuilt, and reset_needed
430 * Only the privileged user can run this command.
433 crabort(NOTALLOWED
, 0);
436 (void) setlocale(LC_ALL
, "");
437 /* fork unless 'nofork' is specified */
438 if ((argc
<= 1) || (strcmp(argv
[1], "nofork"))) {
439 if (rfork
= fork()) {
440 if (rfork
== (pid_t
)-1) {
447 (void) setpgrp(); /* detach cron from console */
451 (void) signal(SIGHUP
, SIG_IGN
);
452 (void) signal(SIGINT
, SIG_IGN
);
453 (void) signal(SIGQUIT
, SIG_IGN
);
454 (void) signal(SIGTERM
, cronend
);
458 quedefs(DEFAULT
); /* load default queue definitions */
460 msg("*** cron started *** pid = %d", cron_pid
);
462 /* setup THAW handler */
463 act
.sa_handler
= thaw_handler
;
465 (void) sigemptyset(&act
.sa_mask
);
466 (void) sigaction(SIGTHAW
, &act
, NULL
);
468 /* setup CHLD handler */
469 act
.sa_handler
= child_handler
;
471 (void) sigemptyset(&act
.sa_mask
);
472 (void) sigaddset(&act
.sa_mask
, SIGCLD
);
473 (void) sigaction(SIGCLD
, &act
, NULL
);
475 (void) sigemptyset(&defmask
);
476 (void) sigemptyset(&sigmask
);
477 (void) sigaddset(&sigmask
, SIGCLD
);
478 (void) sigaddset(&sigmask
, SIGTHAW
);
479 (void) sigprocmask(SIG_BLOCK
, &sigmask
, NULL
);
483 for (;;) { /* MAIN LOOP */
485 if ((t_old
> t
) || (t
-last_time
> CUSHION
) || reset_needed
) {
488 * the time was set backwards or forward or
489 * refresh is requested.
492 msg("re-scheduling jobs");
494 msg("time was reset, re-initializing");
514 * reset_needed might have been set in the functions
515 * call path from initialize()
523 if (next_event
== NULL
&& !el_empty()) {
524 next_event
= (struct event
*)el_first();
526 if (next_event
== NULL
) {
529 ne_time
= next_event
->time
- t
;
531 cftime(timebuf
, "%+", &next_event
->time
);
532 (void) fprintf(stderr
, "next_time=%ld %s\n",
533 next_event
->time
, timebuf
);
538 * reset_needed may be set in the functions call path
541 if (idle(ne_time
) || reset_needed
) {
547 if (stat(QUEDEFS
, &buf
)) {
548 msg("cannot stat QUEDEFS file");
549 } else if (lastmtime
!= buf
.st_mtime
) {
551 lastmtime
= buf
.st_mtime
;
554 last_time
= next_event
->time
; /* save execution time */
557 * reset_needed may be set in the functions call path
560 if (ex(next_event
) || reset_needed
) {
565 switch (next_event
->etype
) {
567 /* add cronevent back into the main event list */
574 * check if time(0)< last_time. if so, then the
575 * system clock has gone backwards. to prevent this
576 * job from being started twice, we reschedule this
577 * job for the >>next time after last_time<<, and
578 * then set next_event->time to this. note that
579 * crontab's resolution is 1 minute.
582 if (last_time
> time(NULL
)) {
585 * bump up to next 30 second
589 newtime
= 30 - (last_time
% 30);
590 newtime
+= last_time
;
593 * get the next scheduled event,
594 * not the one that we just
598 next_time(next_event
, newtime
);
602 next_time(next_event
, (time_t)0);
605 cftime(timebuf
, "%+", &next_event
->time
);
606 (void) fprintf(stderr
,
607 "pushing back cron event %s at %ld (%s)\n",
608 next_event
->cmd
, next_event
->time
, timebuf
);
611 switch (el_add(next_event
, next_event
->time
,
612 (next_event
->u
)->ctid
)) {
614 ignore_msg("main", "cron", next_event
);
616 case -2: /* event time lower than init time */
622 /* remove at or batch job from system */
628 e
= (next_event
->u
)->atevents
;
630 if (e
== next_event
) {
632 (e
->u
)->atevents
= e
->link
;
634 eprev
->link
= e
->link
;
652 initialize(int firstpass
)
655 (void) fprintf(stderr
, "in initialize\n");
658 /* for mail(1), make sure messages come from root */
659 if (putenv("LOGNAME=root") != 0) {
660 crabort("cannot expand env variable",
661 REMOVE_FIFO
|CONSOLE_MSG
);
663 if (access(FIFO
, R_OK
) == -1) {
664 if (errno
== ENOENT
) {
665 if (mknod(FIFO
, S_IFIFO
|0600, 0) != 0)
666 crabort("cannot create fifo queue",
667 REMOVE_FIFO
|CONSOLE_MSG
);
670 /* didn't fork... init(8) is waiting */
674 crabort("cannot access fifo queue",
675 REMOVE_FIFO
|CONSOLE_MSG
);
679 /* didn't fork... init(8) is waiting */
682 * the wait is painful, but we don't want
683 * init respawning this quickly
686 crabort("cannot start cron; FIFO exists", CONSOLE_MSG
);
690 if ((msgfd
= open(FIFO
, O_RDWR
)) < 0) {
692 crabort("cannot open fifo queue", REMOVE_FIFO
|CONSOLE_MSG
);
695 init_time
= time(NULL
);
696 el_init(8, init_time
, (time_t)(60*60*24), 10);
698 init_time
= time(NULL
);
699 el_init(8, init_time
, (time_t)(60*60*24), 10);
702 * read directories, create users list, and add events to the
703 * main event list. Only zero user list on firstpass.
707 read_dirs(firstpass
);
713 /* stdout is log file */
714 if (freopen(ACCTFILE
, "a", stdout
) == NULL
)
715 (void) fprintf(stderr
, "cannot open %s\n", ACCTFILE
);
717 /* log should be root-only */
718 (void) fchmod(1, S_IRUSR
|S_IWUSR
);
720 /* stderr also goes to ACCTFILE */
721 (void) close(fileno(stderr
));
724 (void) freopen("/dev/null", "r", stdin
);
726 contract_set_template();
739 if (chdir(CRONDIR
) == -1)
740 crabort(BADCD
, REMOVE_FIFO
|CONSOLE_MSG
);
742 if ((dir
= opendir(".")) == NULL
)
743 crabort(NOREADDIR
, REMOVE_FIFO
|CONSOLE_MSG
);
744 while ((dp
= readdir(dir
)) != NULL
) {
745 if (!valid_entry(dp
->d_name
, CRONEVENT
))
747 init_cronevent(dp
->d_name
, first
);
749 (void) closedir(dir
);
751 if (chdir(ATDIR
) == -1) {
752 msg("cannot chdir to at directory");
755 if ((dir
= opendir(".")) == NULL
) {
756 msg("cannot read at at directory");
760 while ((dp
= readdir(dir
)) != NULL
) {
761 if (!valid_entry(dp
->d_name
, ATEVENT
))
764 if (((tim
= num(&ptr
)) == 0) || (*ptr
!= '.'))
769 jobtype
= *ptr
- 'a';
770 if (jobtype
>= NQUEUE
) {
771 cron_unlink(dp
->d_name
);
774 init_atevent(dp
->d_name
, tim
, jobtype
, first
);
776 (void) closedir(dir
);
780 valid_entry(char *name
, int type
)
784 if (strcmp(name
, ".") == 0 ||
785 strcmp(name
, "..") == 0)
788 if (stat(name
, &buf
)) {
789 mail(name
, BADSTAT
, ERR_UNIXERR
);
793 if (!S_ISREG(buf
.st_mode
)) {
794 mail(name
, BADTYPE
, ERR_NOTREG
);
798 if (type
== ATEVENT
) {
799 if (!(buf
.st_mode
& ISUID
)) {
808 create_ulist(char *name
, int type
)
812 u
= xcalloc(1, sizeof (struct usr
));
813 u
->name
= xstrdup(name
);
814 if (type
== CRONEVENT
) {
829 init_cronevent(char *name
, int first
)
834 u
= create_ulist(name
, CRONEVENT
);
837 if ((u
= find_usr(name
)) == NULL
) {
838 u
= create_ulist(name
, CRONEVENT
);
843 el_remove(u
->ctid
, 0);
850 init_atevent(char *name
, time_t tim
, int jobtype
, int first
)
855 u
= create_ulist(name
, ATEVENT
);
856 add_atevent(u
, name
, tim
, jobtype
);
858 if ((u
= find_usr(name
)) == NULL
) {
859 u
= create_ulist(name
, ATEVENT
);
860 add_atevent(u
, name
, tim
, jobtype
);
862 update_atevent(u
, name
, tim
, jobtype
);
868 mod_ctab(char *name
, time_t reftime
)
873 char namebuf
[LINE_MAX
];
876 if ((pw
= getpwnam(name
)) == NULL
) {
877 msg("No such user as %s - cron entries not created", name
);
881 if (snprintf(namebuf
, sizeof (namebuf
), "%s/%s",
882 CRONDIR
, name
) >= sizeof (namebuf
)) {
883 msg("Too long path name %s - cron entries not created",
892 * a warning message is given by the crontab command so there is
893 * no need to give one here...... use this code if you only want
894 * users with a login shell of /usr/bin/sh to use cron
896 #ifdef BOURNESHELLONLY
897 if ((strcmp(pw
->pw_shell
, "") != 0) &&
898 (strcmp(pw
->pw_shell
, SHELL
) != 0)) {
899 mail(name
, BADSHELL
, ERR_CANTEXECCRON
);
904 if (stat(pname
, &buf
)) {
905 mail(name
, BADSTAT
, ERR_UNIXERR
);
909 if (!S_ISREG(buf
.st_mode
)) {
910 mail(name
, BADTYPE
, ERR_CRONTABENT
);
913 if ((u
= find_usr(name
)) == NULL
) {
915 (void) fprintf(stderr
, "new user (%s) with a crontab\n", name
);
917 u
= create_ulist(name
, CRONEVENT
);
918 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
919 (void) strcpy(u
->home
, pw
->pw_dir
);
922 readcron(u
, reftime
);
926 if (u
->home
!= NULL
) {
927 if (strcmp(u
->home
, pw
->pw_dir
) != 0) {
929 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
930 (void) strcpy(u
->home
, pw
->pw_dir
);
933 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
934 (void) strcpy(u
->home
, pw
->pw_dir
);
939 (void) fprintf(stderr
, "%s now has a crontab\n",
942 /* user didnt have a crontab last time */
945 readcron(u
, reftime
);
949 (void) fprintf(stderr
, "%s has revised his crontab\n", u
->name
);
952 el_remove(u
->ctid
, 0);
953 readcron(u
, reftime
);
959 mod_atjob(char *name
, time_t reftime
)
966 char namebuf
[PATH_MAX
];
971 if (((tim
= num(&ptr
)) == 0) || (*ptr
!= '.'))
976 jobtype
= *ptr
- 'a';
979 if (snprintf(namebuf
, sizeof (namebuf
), "%s/%s", ATDIR
, name
)
980 >= sizeof (namebuf
)) {
987 if (stat(pname
, &buf
) || jobtype
>= NQUEUE
) {
991 if (!(buf
.st_mode
& ISUID
) || !S_ISREG(buf
.st_mode
)) {
995 if ((pw
= getpwuid(buf
.st_uid
)) == NULL
) {
1000 * a warning message is given by the at command so there is no
1001 * need to give one here......use this code if you only want
1002 * users with a login shell of /usr/bin/sh to use cron
1004 #ifdef BOURNESHELLONLY
1005 if ((strcmp(pw
->pw_shell
, "") != 0) &&
1006 (strcmp(pw
->pw_shell
, SHELL
) != 0)) {
1007 mail(pw
->pw_name
, BADSHELL
, ERR_CANTEXECAT
);
1012 if ((u
= find_usr(pw
->pw_name
)) == NULL
) {
1014 (void) fprintf(stderr
, "new user (%s) with an at job = %s\n",
1017 u
= create_ulist(pw
->pw_name
, ATEVENT
);
1018 u
->home
= xstrdup(pw
->pw_dir
);
1019 u
->uid
= pw
->pw_uid
;
1020 u
->gid
= pw
->pw_gid
;
1021 add_atevent(u
, name
, tim
, jobtype
);
1023 u
->uid
= pw
->pw_uid
;
1024 u
->gid
= pw
->pw_gid
;
1026 u
->home
= xstrdup(pw
->pw_dir
);
1027 update_atevent(u
, name
, tim
, jobtype
);
1032 add_atevent(struct usr
*u
, char *job
, time_t tim
, int jobtype
)
1036 e
= xmalloc(sizeof (struct event
));
1038 e
->cmd
= xmalloc(strlen(job
) + 1);
1039 (void) strcpy(e
->cmd
, job
);
1041 e
->link
= u
->atevents
;
1043 e
->of
.at
.exists
= TRUE
;
1044 e
->of
.at
.eventid
= ecid
++;
1045 if (tim
< init_time
) /* old job */
1046 e
->time
= init_time
;
1050 (void) fprintf(stderr
, "add_atevent: user=%s, job=%s, time=%ld\n",
1051 u
->name
, e
->cmd
, e
->time
);
1053 if (el_add(e
, e
->time
, e
->of
.at
.eventid
) < 0) {
1054 ignore_msg("add_atevent", "at", e
);
1059 update_atevent(struct usr
*u
, char *name
, time_t tim
, int jobtype
)
1065 if (strcmp(e
->cmd
, name
) == 0) {
1066 e
->of
.at
.exists
= TRUE
;
1074 (void) fprintf(stderr
, "%s has a new at job = %s\n",
1077 add_atevent(u
, name
, tim
, jobtype
);
1081 static char line
[CTLINESIZE
]; /* holds a line from a crontab file */
1082 static int cursor
; /* cursor for the above line */
1085 readcron(struct usr
*u
, time_t reftime
)
1088 * readcron reads in a crontab file for a user (u). The list of
1089 * events for user u is built, and u->events is made to point to
1090 * this list. Each event is also entered into the main event
1093 FILE *cf
; /* cf will be a user's crontab file */
1097 char namebuf
[PATH_MAX
];
1099 struct shared
*tz
= NULL
;
1100 struct shared
*home
= NULL
;
1101 struct shared
*shell
= NULL
;
1104 /* read the crontab file */
1105 cte_init(); /* Init error handling */
1107 if (snprintf(namebuf
, sizeof (namebuf
), "%s/%s",
1108 CRONDIR
, u
->name
) >= sizeof (namebuf
)) {
1115 if ((cf
= fopen(pname
, "r")) == NULL
) {
1116 mail(u
->name
, NOREAD
, ERR_UNIXERR
);
1119 while (fgets(line
, CTLINESIZE
, cf
) != NULL
) {
1121 /* process a line of a crontab file */
1123 if (cte_istoomany())
1126 while (line
[cursor
] == ' ' || line
[cursor
] == '\t')
1128 if (line
[cursor
] == '#' || line
[cursor
] == '\n')
1131 if (strncmp(&line
[cursor
], ENV_TZ
,
1132 strlen(ENV_TZ
)) == 0) {
1133 if ((tmp
= strchr(&line
[cursor
], '\n')) != NULL
) {
1137 if (tz
== NULL
|| strcmp(&line
[cursor
], get_obj(tz
))) {
1139 tz
= create_shared_str(&line
[cursor
]);
1144 if (strncmp(&line
[cursor
], ENV_HOME
,
1145 strlen(ENV_HOME
)) == 0) {
1146 if ((tmp
= strchr(&line
[cursor
], '\n')) != NULL
) {
1150 strcmp(&line
[cursor
], get_obj(home
))) {
1152 home
= create_shared_str(
1153 &line
[cursor
+ strlen(ENV_HOME
)]);
1158 if (strncmp(&line
[cursor
], ENV_SHELL
,
1159 strlen(ENV_SHELL
)) == 0) {
1160 if ((tmp
= strchr(&line
[cursor
], '\n')) != NULL
) {
1163 if (shell
== NULL
||
1164 strcmp(&line
[cursor
], get_obj(shell
))) {
1166 shell
= create_shared_str(&line
[cursor
]);
1171 e
= xmalloc(sizeof (struct event
));
1172 e
->etype
= CRONEVENT
;
1173 if (!(((e
->of
.ct
.minute
= next_field(0, 59)) != NULL
) &&
1174 ((e
->of
.ct
.hour
= next_field(0, 23)) != NULL
) &&
1175 ((e
->of
.ct
.daymon
= next_field(1, 31)) != NULL
) &&
1176 ((e
->of
.ct
.month
= next_field(1, 12)) != NULL
) &&
1177 ((e
->of
.ct
.dayweek
= next_field(0, 6)) != NULL
))) {
1179 cte_add(lineno
, line
);
1182 while (line
[cursor
] == ' ' || line
[cursor
] == '\t')
1184 if (line
[cursor
] == '\n' || line
[cursor
] == '\0')
1186 /* get the command to execute */
1189 while ((line
[cursor
] != '%') &&
1190 (line
[cursor
] != '\n') &&
1191 (line
[cursor
] != '\0') &&
1192 (line
[cursor
] != '\\'))
1194 if (line
[cursor
] == '\\') {
1198 e
->cmd
= xmalloc(cursor
-start
+ 1);
1199 (void) strncpy(e
->cmd
, line
+ start
, cursor
-start
);
1200 e
->cmd
[cursor
-start
] = '\0';
1201 /* see if there is any standard input */
1202 if (line
[cursor
] == '%') {
1203 e
->of
.ct
.input
= xmalloc(strlen(line
)-cursor
+ 1);
1204 (void) strcpy(e
->of
.ct
.input
, line
+ cursor
+ 1);
1205 for (i
= 0; i
< strlen(e
->of
.ct
.input
); i
++) {
1206 if (e
->of
.ct
.input
[i
] == '%')
1207 e
->of
.ct
.input
[i
] = '\n';
1210 e
->of
.ct
.input
= NULL
;
1212 /* set the timezone of this entry */
1213 e
->of
.ct
.tz
= dup_shared(tz
);
1214 /* set the shell of this entry */
1215 e
->of
.ct
.shell
= dup_shared(shell
);
1216 /* set the home of this entry */
1217 e
->of
.ct
.home
= dup_shared(home
);
1218 /* have the event point to it's owner */
1220 /* insert this event at the front of this user's event list */
1221 e
->link
= u
->ctevents
;
1223 /* set the time for the first occurance of this event */
1224 e
->time
= next_time(e
, reftime
);
1225 /* finally, add this event to the main event list */
1226 switch (el_add(e
, e
->time
, u
->ctid
)) {
1228 ignore_msg("readcron", "cron", e
);
1230 case -2: /* event time lower than init time */
1236 cftime(timebuf
, "%+", &e
->time
);
1237 (void) fprintf(stderr
, "inserting cron event %s at %ld (%s)\n",
1238 e
->cmd
, e
->time
, timebuf
);
1241 cte_sendmail(u
->name
); /* mail errors if any to user */
1249 * Below are the functions for handling of errors in crontabs. Concept is to
1250 * collect faulty lines and send one email at the end of the crontab
1251 * evaluation. If there are erroneous lines only ((cte_nvalid == 0), evaluation
1252 * of crontab is aborted. Otherwise reading of crontab is continued to the end
1253 * of the file but no further error logging appears.
1258 if (cte_text
== NULL
)
1259 cte_text
= xmalloc(MAILBUFLEN
);
1260 (void) strlcpy(cte_text
, cte_intro
, MAILBUFLEN
);
1261 cte_lp
= cte_text
+ sizeof (cte_intro
) - 1;
1262 cte_free
= MAILBINITFREE
;
1267 cte_add(int lineno
, char *ctline
)
1272 if (cte_free
>= LINELIMIT
) {
1273 (void) sprintf(cte_lp
, "%4d: ", lineno
);
1274 (void) strlcat(cte_lp
, ctline
, LINELIMIT
- 1);
1275 len
= strlen(cte_lp
);
1276 if (cte_lp
[len
- 1] != '\n') {
1277 cte_lp
[len
++] = '\n';
1280 for (p
= cte_lp
; *p
; p
++) {
1281 if (isprint(*p
) || *p
== '\n' || *p
== '\t')
1287 if (cte_free
< LINELIMIT
) {
1288 size_t buflen
= MAILBUFLEN
- (cte_lp
- cte_text
);
1289 (void) strlcpy(cte_lp
, cte_trail1
, buflen
);
1290 if (cte_nvalid
== 0)
1291 (void) strlcat(cte_lp
, cte_trail2
, buflen
);
1306 * Return TRUE only if all lines are faulty. So evaluation of
1307 * a crontab is not aborted if at least one valid line was found.
1309 return (cte_nvalid
== 0 && cte_free
< LINELIMIT
);
1313 cte_sendmail(char *username
)
1315 if (cte_free
< MAILBINITFREE
)
1316 mail(username
, cte_text
, ERR_CRONTABENT
);
1320 * Send mail with error message to a user
1323 mail(char *usrname
, char *mesg
, int format
)
1325 /* mail mails a user a message. */
1328 struct passwd
*ruser_ids
;
1330 int saveerrno
= errno
;
1331 struct utsname name
;
1336 (void) uname(&name
);
1337 if ((fork_val
= fork()) == (pid_t
)-1) {
1338 msg("cron cannot fork\n");
1341 if (fork_val
== 0) {
1343 contract_clear_template();
1344 if ((ruser_ids
= getpwnam(usrname
)) == NULL
)
1346 (void) setuid(ruser_ids
->pw_uid
);
1347 temp
= xmalloc(strlen(MAIL
) + strlen(usrname
) + 2);
1348 (void) sprintf(temp
, "%s %s", MAIL
, usrname
);
1349 pipe
= popen(temp
, "w");
1351 (void) fprintf(pipe
, "To: %s\n", usrname
);
1353 case ERR_CRONTABENT
:
1354 (void) fprintf(pipe
, CRONTABERR
);
1355 (void) fprintf(pipe
, "Your \"crontab\" on %s\n",
1357 (void) fprintf(pipe
, mesg
);
1358 (void) fprintf(pipe
,
1359 "\nEntries or crontab have been ignored\n");
1362 (void) fprintf(pipe
, "Subject: %s\n\n", mesg
);
1363 (void) fprintf(pipe
,
1364 "The error on %s was \"%s\"\n",
1365 name
.nodename
, errmsg(saveerrno
));
1368 case ERR_CANTEXECCRON
:
1369 (void) fprintf(pipe
,
1370 "Subject: Couldn't run your \"cron\" job\n\n");
1371 (void) fprintf(pipe
,
1372 "Your \"cron\" job on %s ", name
.nodename
);
1373 (void) fprintf(pipe
, "couldn't be run\n");
1374 (void) fprintf(pipe
, "%s\n", mesg
);
1375 (void) fprintf(pipe
,
1376 "The error was \"%s\"\n", errmsg(saveerrno
));
1379 case ERR_CANTEXECAT
:
1380 (void) fprintf(pipe
,
1381 "Subject: Couldn't run your \"at\" job\n\n");
1382 (void) fprintf(pipe
, "Your \"at\" job on %s ",
1384 (void) fprintf(pipe
, "couldn't be run\n");
1385 (void) fprintf(pipe
, "%s\n", mesg
);
1386 (void) fprintf(pipe
,
1387 "The error was \"%s\"\n", errmsg(saveerrno
));
1393 (void) pclose(pipe
);
1399 contract_abandon_latest(fork_val
);
1401 if (cron_pid
== getpid()) {
1402 miscpid_insert(fork_val
);
1407 next_field(int lower
, int upper
)
1410 * next_field returns a pointer to a string which holds the next
1411 * field of a line of a crontab file.
1412 * if (numbers in this field are out of range (lower..upper),
1413 * or there is a syntax error) then
1414 * NULL is returned, and a mail message is sent to the
1415 * user telling them which line the error was in.
1419 int num
, num2
, start
;
1421 while ((line
[cursor
] == ' ') || (line
[cursor
] == '\t'))
1424 if (line
[cursor
] == '\0') {
1427 if (line
[cursor
] == '*') {
1429 if ((line
[cursor
] != ' ') && (line
[cursor
] != '\t'))
1432 (void) strcpy(s
, "*");
1436 if (!isdigit(line
[cursor
]))
1440 num
= num
*10 + (line
[cursor
]-'0');
1441 } while (isdigit(line
[++cursor
]));
1442 if ((num
< lower
) || (num
> upper
))
1444 if (line
[cursor
] == '-') {
1445 if (!isdigit(line
[++cursor
]))
1449 num2
= num2
*10 + (line
[cursor
]-'0');
1450 } while (isdigit(line
[++cursor
]));
1451 if ((num2
< lower
) || (num2
> upper
))
1454 if ((line
[cursor
] == ' ') || (line
[cursor
] == '\t'))
1456 if (line
[cursor
] == '\0')
1458 if (line
[cursor
++] != ',')
1461 s
= xmalloc(cursor
-start
+ 1);
1462 (void) strncpy(s
, line
+ start
, cursor
-start
);
1463 s
[cursor
-start
] = '\0';
1467 #define tm_cmp(t1, t2) (\
1468 (t1)->tm_year == (t2)->tm_year && \
1469 (t1)->tm_mon == (t2)->tm_mon && \
1470 (t1)->tm_mday == (t2)->tm_mday && \
1471 (t1)->tm_hour == (t2)->tm_hour && \
1472 (t1)->tm_min == (t2)->tm_min)
1474 #define tm_setup(tp, yr, mon, dy, hr, min, dst) \
1475 (tp)->tm_year = yr; \
1476 (tp)->tm_mon = mon; \
1477 (tp)->tm_mday = dy; \
1478 (tp)->tm_hour = hr; \
1479 (tp)->tm_min = min; \
1480 (tp)->tm_isdst = dst; \
1482 (tp)->tm_wday = 0; \
1486 * modification for bugid 1104537. the second argument to next_time is
1487 * now the value of time(2) to be used. if this is 0, then use the
1488 * current time. otherwise, the second argument is the time from which to
1489 * calculate things. this is useful to correct situations where you've
1490 * gone backwards in time (I.e. the system's internal clock is correcting
1491 * itself backwards).
1497 tz_next_time(struct event
*e
, time_t tflag
)
1500 * returns the integer time for the next occurance of event e.
1501 * the following fields have ranges as indicated:
1502 * PRGM | min hour day of month mon day of week
1503 * ------|-------------------------------------------------------
1504 * cron | 0-59 0-23 1-31 1-12 0-6 (0=sunday)
1505 * time | 0-59 0-23 1-31 0-11 0-6 (0=sunday)
1506 * NOTE: this routine is hard to understand.
1509 struct tm
*tm
, ref_tm
, tmp
, tmp1
, tmp2
;
1510 int tm_mon
, tm_mday
, tm_wday
, wday
, m
, min
, h
, hr
, carry
, day
, days
;
1511 int d1
, day1
, carry1
, d2
, day2
, carry2
, daysahead
, mon
, yr
, db
, wd
;
1513 time_t t
, ref_t
, t1
, t2
, zone_start
;
1515 extern int days_btwn(int, int, int, int, int, int);
1518 t
= time(NULL
); /* original way of doing things */
1523 tm
= &ref_tm
; /* use a local variable and call localtime_r() */
1524 ref_t
= t
; /* keep a copy of the reference time */
1529 (void) localtime_r(&t
, tm
);
1533 tmp
.tm_isdst
= (tm
->tm_isdst
> 0 ? 0 : 1);
1536 * see if we will have timezone switch over, and clock will
1537 * fall back. zone_start will hold the time when it happens
1538 * (ie time of PST -> PDT switch over).
1540 if (tm
->tm_isdst
!= tmp
.tm_isdst
&&
1541 (t1
- t
) == (timezone
- altzone
) &&
1543 zone_start
= get_switching_time(tmp
.tm_isdst
, t
);
1548 tm_mon
= next_ge(tm
->tm_mon
+ 1, e
->of
.ct
.month
) - 1; /* 0-11 */
1549 tm_mday
= next_ge(tm
->tm_mday
, e
->of
.ct
.daymon
); /* 1-31 */
1550 tm_wday
= next_ge(tm
->tm_wday
, e
->of
.ct
.dayweek
); /* 0-6 */
1552 if ((strcmp(e
->of
.ct
.daymon
, "*") == 0 && tm
->tm_wday
!= tm_wday
) ||
1553 (strcmp(e
->of
.ct
.dayweek
, "*") == 0 && tm
->tm_mday
!= tm_mday
) ||
1554 (tm
->tm_mday
!= tm_mday
&& tm
->tm_wday
!= tm_wday
) ||
1555 (tm
->tm_mon
!= tm_mon
)) {
1558 m
= tm
->tm_min
+ (t
== ref_t
? 1 : 0);
1559 if ((tm
->tm_hour
+ 1) <= next_ge(tm
->tm_hour
, e
->of
.ct
.hour
)) {
1562 min
= next_ge(m
%60, e
->of
.ct
.minute
);
1563 carry
= (min
< m
) ? 1 : 0;
1564 h
= tm
->tm_hour
+ carry
;
1565 hr
= next_ge(h
%24, e
->of
.ct
.hour
);
1566 carry
= (hr
< h
) ? 1 : 0;
1568 if (carry
== 0 && today
) {
1569 /* this event must occur today */
1570 tm_setup(&tmp
, tm
->tm_year
, tm
->tm_mon
, tm
->tm_mday
,
1571 hr
, min
, tm
->tm_isdst
);
1573 if ((t1
= xmktime(&tmp1
)) == (time_t)-1) {
1576 if (daylight
&& tmp
.tm_isdst
!= tmp1
.tm_isdst
) {
1577 /* In case we are falling back */
1579 /* we may need to run the job once more. */
1585 * In case we are not in falling back period,
1586 * calculate the time assuming the DST. If the
1587 * date/time is not altered by mktime, it is the
1588 * time to execute the job.
1591 tmp2
.tm_isdst
= tmp1
.tm_isdst
;
1592 if ((t1
= xmktime(&tmp2
)) == (time_t)-1) {
1595 if (tmp1
.tm_isdst
== tmp2
.tm_isdst
&&
1596 tm_cmp(&tmp
, &tmp2
)) {
1598 * We got a valid time.
1603 * If the date does not match even if
1604 * we assume the alternate timezone, then
1605 * it must be the invalid time. eg
1606 * 2am while switching 1:59am to 3am.
1607 * t1 should point the time before the
1608 * switching over as we've calculate the
1609 * time with assuming alternate zone.
1611 if (tmp1
.tm_isdst
!= tmp2
.tm_isdst
) {
1612 t
= get_switching_time(tmp1
.tm_isdst
,
1615 /* does this really happen? */
1616 t
= get_switching_time(tmp1
.tm_isdst
,
1617 t1
- abs(timezone
- altzone
));
1619 if (t
== (time_t)-1) {
1625 if (tm_cmp(&tmp
, &tmp1
)) {
1626 /* got valid time */
1630 * This should never happen, but just in
1631 * case, we fall back to the old code.
1633 if (tm
->tm_min
> min
) {
1634 t
+= (time_t)(hr
-tm
->tm_hour
-1) * HOUR
+
1635 (time_t)(60-tm
->tm_min
+ min
) * MINUTE
;
1637 t
+= (time_t)(hr
-tm
->tm_hour
) * HOUR
+
1638 (time_t)(min
-tm
->tm_min
) * MINUTE
;
1641 t
-= (time_t)tm
->tm_sec
;
1642 (void) localtime_r(&t
, &tmp
);
1643 if ((tm
->tm_isdst
== 0) && (tmp
.tm_isdst
> 0))
1644 t
-= (timezone
- altzone
);
1645 return ((t
<= ref_t
) ? t1
: t
);
1650 * Job won't run today, however if we have a switch over within
1651 * one hour and we will have one hour time drifting back in this
1652 * period, we may need to run the job one more time if the job was
1653 * set to run on this hour of clock.
1660 min
= next_ge(0, e
->of
.ct
.minute
);
1661 hr
= next_ge(0, e
->of
.ct
.hour
);
1664 * calculate the date of the next occurance of this event, which
1665 * will be on a different day than the current
1668 /* check monthly day specification */
1669 d1
= tm
->tm_mday
+ 1;
1670 day1
= next_ge((d1
-1)%days_in_mon(tm
->tm_mon
, tm
->tm_year
) + 1,
1672 carry1
= (day1
< d1
) ? 1 : 0;
1674 /* check weekly day specification */
1675 d2
= tm
->tm_wday
+ 1;
1676 wday
= next_ge(d2
%7, e
->of
.ct
.dayweek
);
1678 daysahead
= 7 - d2
+ wday
;
1680 daysahead
= wday
- d2
;
1681 day2
= (d1
+ daysahead
-1)%days_in_mon(tm
->tm_mon
, tm
->tm_year
) + 1;
1682 carry2
= (day2
< d1
) ? 1 : 0;
1685 * based on their respective specifications, day1, and day2 give
1686 * the day of the month for the next occurance of this event.
1688 if ((strcmp(e
->of
.ct
.daymon
, "*") == 0) &&
1689 (strcmp(e
->of
.ct
.dayweek
, "*") != 0)) {
1693 if ((strcmp(e
->of
.ct
.daymon
, "*") != 0) &&
1694 (strcmp(e
->of
.ct
.dayweek
, "*") == 0)) {
1700 if ((carry1
&& carry2
) || (tm
->tm_mon
!= tm_mon
)) {
1701 /* event does not occur in this month */
1703 mon
= next_ge(m
%12 + 1, e
->of
.ct
.month
) - 1; /* 0..11 */
1704 carry
= (mon
< m
) ? 1 : 0;
1706 /* recompute day1 and day2 */
1707 day1
= next_ge(1, e
->of
.ct
.daymon
);
1708 db
= days_btwn(tm
->tm_mon
, tm
->tm_mday
, tm
->tm_year
, mon
,
1710 wd
= (tm
->tm_wday
+ db
)%7;
1711 /* wd is the day of the week of the first of month mon */
1712 wday
= next_ge(wd
, e
->of
.ct
.dayweek
);
1714 day2
= 1 + 7 - wd
+ wday
;
1716 day2
= 1 + wday
- wd
;
1717 if ((strcmp(e
->of
.ct
.daymon
, "*") != 0) &&
1718 (strcmp(e
->of
.ct
.dayweek
, "*") == 0))
1720 if ((strcmp(e
->of
.ct
.daymon
, "*") == 0) &&
1721 (strcmp(e
->of
.ct
.dayweek
, "*") != 0))
1723 day
= (day1
< day2
) ? day1
: day2
;
1724 } else { /* event occurs in this month */
1726 if (!carry1
&& !carry2
)
1727 day
= (day1
< day2
) ? day1
: day2
;
1735 * now that we have the min, hr, day, mon, yr of the next event,
1736 * figure out what time that turns out to be.
1738 tm_setup(&tmp
, yr
, mon
, day
, hr
, min
, -1);
1740 if ((t1
= xmktime(&tmp2
)) == (time_t)-1) {
1743 if (tm_cmp(&tmp
, &tmp2
)) {
1745 * mktime returns clock for the current time zone. If the
1746 * target date was in fallback period, it needs to be adjusted
1747 * to the time comes first.
1748 * Suppose, we are at Jan and scheduling job at 1:30am10/26/03.
1749 * mktime returns the time in PST, but 1:30am in PDT comes
1750 * first. So reverse the tm_isdst, and see if we have such
1754 int dst
= tmp2
.tm_isdst
;
1757 tmp2
.tm_isdst
= (dst
> 0 ? 0 : 1);
1758 if ((t2
= xmktime(&tmp2
)) == (time_t)-1) {
1761 if (tm_cmp(&tmp
, &tmp2
)) {
1763 * same time/date found in the opposite zone.
1764 * check the clock to see which comes early.
1766 if (t2
> ref_t
&& t2
< t1
) {
1774 * mktime has set different time/date for the given date.
1775 * This means that the next job is scheduled to be run on the
1776 * invalid time. There are three possible invalid date/time.
1777 * 1. Non existing day of the month. such as April 31th.
1778 * 2. Feb 29th in the non-leap year.
1779 * 3. Time gap during the DST switch over.
1781 d1
= days_in_mon(mon
, yr
);
1782 if ((mon
!= 1 && day
> d1
) || (mon
== 1 && day
> 29)) {
1784 * see if we have got a specific date which
1787 if (strcmp(e
->of
.ct
.dayweek
, "*") == 0 &&
1788 mon
== (next_ge((mon
+ 1)%12 + 1,
1789 e
->of
.ct
.month
) - 1) &&
1790 day
<= next_ge(1, e
->of
.ct
.daymon
)) {
1795 * Since the day has gone invalid, we need to go to
1796 * next month, and recalcuate the first occurrence.
1797 * eg the cron tab such as:
1798 * 0 0 1,15,31 1,2,3,4,5 * /usr/bin....
1799 * 2/31 is invalid, so the next job is 3/1.
1804 tmp2
.tm_mday
= 1; /* 1st day of the month */
1807 tmp2
.tm_year
= yr
+ 1;
1809 tmp2
.tm_mon
= mon
+ 1;
1811 if ((t
= xmktime(&tmp2
)) == (time_t)-1) {
1814 } else if (mon
== 1 && day
> d1
) {
1816 * ie 29th in the non-leap year. Forwarding the
1817 * clock to Feb 29th 00:00 (March 1st), and recalculate
1823 if ((t
= xmktime(&tmp2
)) == (time_t)-1) {
1826 } else if (daylight
) {
1828 * Non existing time, eg 2am PST during summer time
1830 * We need to get the correct isdst which we are
1831 * swithing to, by adding time difference to make sure
1832 * that t2 is in the zone being switched.
1835 t2
+= abs(timezone
- altzone
);
1836 (void) localtime_r(&t2
, &tmp2
);
1837 zone_start
= get_switching_time(tmp2
.tm_isdst
,
1838 t1
- abs(timezone
- altzone
));
1839 if (zone_start
== (time_t)-1) {
1845 * This should never happen, but fall back to the
1848 days
= days_btwn(tm
->tm_mon
,
1849 tm
->tm_mday
, tm
->tm_year
, mon
, day
, yr
);
1850 t
+= (time_t)(23-tm
->tm_hour
)*HOUR
1851 + (time_t)(60-tm
->tm_min
)*MINUTE
1852 + (time_t)hr
*HOUR
+ (time_t)min
*MINUTE
1855 t
-= (time_t)tm
->tm_sec
;
1856 (void) localtime_r(&t
, &tmp
);
1857 if ((tm
->tm_isdst
== 0) && (tmp
.tm_isdst
> 0))
1858 t
-= (timezone
- altzone
);
1859 return (t
<= ref_t
? t1
: t
);
1867 next_time(struct event
*e
, time_t tflag
)
1869 if (e
->of
.ct
.tz
!= NULL
) {
1872 (void) putenv((char *)get_obj(e
->of
.ct
.tz
));
1874 ret
= tz_next_time(e
, tflag
);
1875 (void) putenv(tzone
);
1879 return (tz_next_time(e
, tflag
));
1884 * This returns TOD in time_t that zone switch will happen, and this
1885 * will be called when clock fallback is about to happen.
1886 * (ie 30minutes before the time of PST -> PDT switch. 2:00 AM PST
1887 * will fall back to 1:00 PDT. So this function will be called only
1888 * for the time between 1:00 AM PST and 2:00 PST(1:00 PST)).
1889 * First goes through the common time differences to see if zone
1890 * switch happens at those minutes later. If not, check every minutes
1891 * until 6 hours ahead see if it happens(We might have 45minutes
1895 get_switching_time(int to_dst
, time_t t_ref
)
1898 struct tm tmp
, tmp1
;
1899 int hints
[] = { 60, 120, 30, 90, 0}; /* minutes */
1902 (void) localtime_r(&t_ref
, &tmp
);
1906 if ((t
= xmktime(&tmp1
)) == (time_t)-1)
1907 return ((time_t)-1);
1910 for (i
= 0; hints
[i
] != 0; i
++) {
1911 t1
= t
+ hints
[i
] * 60;
1912 (void) localtime_r(&t1
, &tmp1
);
1913 if (tmp1
.tm_isdst
== to_dst
) {
1915 (void) localtime_r(&t1
, &tmp1
);
1916 if (tmp1
.tm_isdst
!= to_dst
) {
1922 /* ugly, but don't know other than this. */
1925 if ((t
= xmktime(&tmp1
)) == (time_t)-1)
1926 return ((time_t)-1);
1927 while (t
< (t_ref
+ 6*60*60)) { /* 6 hours should be enough */
1928 t
+= 60; /* at least one minute, I assume */
1929 (void) localtime_r(&t
, &tmp
);
1930 if (tmp
.tm_isdst
== to_dst
)
1933 return ((time_t)-1);
1937 xmktime(struct tm
*tmp
)
1941 if ((ret
= mktime(tmp
)) == (time_t)-1) {
1942 if (errno
== EOVERFLOW
) {
1943 return ((time_t)-1);
1945 crabort("internal error: mktime failed",
1946 REMOVE_FIFO
|CONSOLE_MSG
);
1954 next_ge(int current
, char *list
)
1957 * list is a character field as in a crontab file;
1958 * for example: "40, 20, 50-10"
1959 * next_ge returns the next number in the list that is
1960 * greater than or equal to current. if no numbers of list
1961 * are >= current, the smallest element of list is returned.
1962 * NOTE: current must be in the appropriate range.
1966 int n
, n2
, min
, min_gt
;
1968 if (strcmp(list
, "*") == 0)
1974 if ((n
= (int)num(&ptr
)) == current
)
1978 if ((n
> current
) && (n
< min_gt
))
1982 if ((n2
= (int)num(&ptr
)) > n
) {
1983 if ((current
> n
) && (current
<= n2
))
1985 } else { /* range that wraps around */
1996 if (min_gt
!= DUMMY
)
2003 free_if_unused(struct usr
*u
)
2005 struct usr
*cur
, *prev
;
2007 * To make sure a usr structure is idle we must check that
2008 * there are no at jobs queued for the user; the user does
2009 * not have a crontab, and also that there are no running at
2010 * or cron jobs (since the runinfo structure also has a
2011 * pointer to the usr structure).
2013 if (!u
->ctexists
&& u
->atevents
== NULL
&&
2014 u
->cruncnt
== 0 && u
->aruncnt
== 0) {
2016 (void) fprintf(stderr
, "%s removed from usr list\n", u
->name
);
2018 for (cur
= uhead
, prev
= NULL
;
2020 prev
= cur
, cur
= cur
->nextusr
) {
2029 prev
->nextusr
= u
->nextusr
;
2037 del_atjob(char *name
, char *usrname
)
2040 struct event
*e
, *eprev
;
2043 if ((u
= find_usr(usrname
)) == NULL
)
2048 if (strcmp(name
, e
->cmd
) == 0) {
2049 if (next_event
== e
)
2052 u
->atevents
= e
->link
;
2054 eprev
->link
= e
->link
;
2055 el_remove(e
->of
.at
.eventid
, 1);
2069 del_ctab(char *name
)
2074 if ((u
= find_usr(name
)) == NULL
)
2077 el_remove(u
->ctid
, 0);
2085 rm_ctevents(struct usr
*u
)
2087 struct event
*e2
, *e3
;
2090 * see if the next event (to be run by cron) is a cronevent
2091 * owned by this user.
2094 if ((next_event
!= NULL
) &&
2095 (next_event
->etype
== CRONEVENT
) &&
2096 (next_event
->u
== u
)) {
2100 while (e2
!= NULL
) {
2102 rel_shared(e2
->of
.ct
.tz
);
2103 rel_shared(e2
->of
.ct
.shell
);
2104 rel_shared(e2
->of
.ct
.home
);
2105 free(e2
->of
.ct
.minute
);
2106 free(e2
->of
.ct
.hour
);
2107 free(e2
->of
.ct
.daymon
);
2108 free(e2
->of
.ct
.month
);
2109 free(e2
->of
.ct
.dayweek
);
2110 free(e2
->of
.ct
.input
);
2120 find_usr(char *uname
)
2126 if (strcmp(u
->name
, uname
) == 0)
2134 * Execute cron command or at/batch job.
2135 * If ever a premature return is added to this function pay attention to
2136 * free at_cmdfile and outfile plus jobname buffers of the runinfo structure.
2146 char *at_cmdfile
= NULL
;
2150 struct project proj
, *pproj
= NULL
;
2153 char buf
[PROJECT_BUFSZ
];
2154 char buf2
[PROJECT_BUFSZ
];
2156 char error
[CANT_STR_LEN
+ PATH_MAX
];
2166 qp
= &qt
[e
->etype
]; /* set pointer to queue defs */
2167 if (qp
->nrun
>= qp
->njob
) {
2168 msg("%c queue max run limit reached", e
->etype
+ 'a');
2173 rp
= rinfo_get(0); /* allocating a new runinfo struct */
2176 * the tempnam() function uses malloc(3C) to allocate space for the
2177 * constructed file name, and returns a pointer to this area, which
2178 * is assigned to rp->outfile. Here rp->outfile is not overwritten.
2181 rp
->outfile
= tempnam(TMPDIR
, PFX
);
2182 rp
->jobtype
= e
->etype
;
2183 if (e
->etype
== CRONEVENT
) {
2184 rp
->jobname
= xmalloc(strlen(e
->cmd
) + 1);
2185 (void) strcpy(rp
->jobname
, e
->cmd
);
2186 /* "cron" jobs only produce mail if there's output */
2187 rp
->mailwhendone
= 0;
2189 at_cmdfile
= xmalloc(strlen(ATDIR
) + strlen(e
->cmd
) + 2);
2190 (void) sprintf(at_cmdfile
, "%s/%s", ATDIR
, e
->cmd
);
2191 if ((atcmdfp
= fopen(at_cmdfile
, "r")) == NULL
) {
2192 if (errno
== ENAMETOOLONG
) {
2193 if (chdir(ATDIR
) == 0)
2194 cron_unlink(e
->cmd
);
2196 cron_unlink(at_cmdfile
);
2198 mail((e
->u
)->name
, BADJOBOPEN
, ERR_CANTEXECAT
);
2203 rp
->jobname
= xmalloc(strlen(at_cmdfile
) + 1);
2204 (void) strcpy(rp
->jobname
, at_cmdfile
);
2207 * Skip over the first two lines.
2209 (void) fscanf(atcmdfp
, "%*[^\n]\n");
2210 (void) fscanf(atcmdfp
, "%*[^\n]\n");
2211 if (fscanf(atcmdfp
, ": notify by mail: %3s%*[^\n]\n",
2214 * Check to see if we should always send mail
2217 rp
->mailwhendone
= (strcmp(mailvar
, "yes") == 0);
2219 rp
->mailwhendone
= 0;
2222 if (fscanf(atcmdfp
, "\n: project: %d\n", &projid
) == 1) {
2225 (void) fclose(atcmdfp
);
2229 * we make sure that the system time
2230 * hasn't drifted backwards. if it has, el_add() is now
2231 * called, to make sure that the event queue is back in order,
2232 * and we set the delayed flag. cron will pick up the request
2233 * later on at the proper time.
2235 dhltime
= time(NULL
);
2236 if ((dhltime
- e
->time
) < 0) {
2237 msg("clock time drifted backwards!\n");
2238 if (next_event
->etype
== CRONEVENT
) {
2239 msg("correcting cron event\n");
2240 next_event
->time
= next_time(next_event
, dhltime
);
2241 switch (el_add(next_event
, next_event
->time
,
2242 (next_event
->u
)->ctid
)) {
2244 ignore_msg("ex", "cron", next_event
);
2246 case -2: /* event time lower than init time */
2250 } else { /* etype == ATEVENT */
2251 msg("correcting batch event\n");
2252 if (el_add(next_event
, next_event
->time
,
2253 next_event
->of
.at
.eventid
) < 0) {
2254 ignore_msg("ex", "at", next_event
);
2264 if ((rfork
= fork()) == (pid_t
)-1) {
2266 if ((rfork
= fork()) == (pid_t
)-1) {
2275 if (rfork
) { /* parent process */
2276 contract_abandon_latest(rfork
);
2281 if (e
->etype
!= CRONEVENT
)
2286 logit(BCHAR
, rp
, 0);
2293 contract_clear_template();
2295 if (e
->etype
!= CRONEVENT
) {
2296 /* open jobfile as stdin to shell */
2297 if (stat(at_cmdfile
, &buf
)) {
2298 if (errno
== ENAMETOOLONG
) {
2299 if (chdir(ATDIR
) == 0)
2300 cron_unlink(e
->cmd
);
2302 cron_unlink(at_cmdfile
);
2303 mail((e
->u
)->name
, BADJOBOPEN
, ERR_CANTEXECCRON
);
2306 if (!(buf
.st_mode
&ISUID
)) {
2308 * if setuid bit off, original owner has
2309 * given this file to someone else
2311 cron_unlink(at_cmdfile
);
2314 if ((fd
= open(at_cmdfile
, O_RDONLY
)) == -1) {
2315 mail((e
->u
)->name
, BADJOBOPEN
, ERR_CANTEXECCRON
);
2316 cron_unlink(at_cmdfile
);
2324 * retrieve the project id of the at job and convert it
2325 * to a project name. fail if it's not a valid project
2326 * or if the user isn't a member of the project.
2328 if (projflag
== 1) {
2329 if ((pproj
= getprojbyid(projid
, &proj
,
2330 (void *)&bufs
.p
.buf
,
2331 sizeof (bufs
.p
.buf
))) == NULL
||
2332 !inproj(e
->u
->name
, pproj
->pj_name
,
2333 bufs
.p
.buf2
, sizeof (bufs
.p
.buf2
))) {
2334 cron_unlink(at_cmdfile
);
2335 mail((e
->u
)->name
, BADPROJID
, ERR_CANTEXECAT
);
2342 * Put process in a new session, and create a new task.
2345 msg("setsid failed with errno = %d. job failed (%s)"
2346 " for user %s", errno
, e
->cmd
, e
->u
->name
);
2347 if (e
->etype
!= CRONEVENT
)
2348 cron_unlink(at_cmdfile
);
2353 * set correct user identification and check their account
2355 r
= set_user_cred(e
->u
, pproj
);
2356 if (r
== VUC_EXPIRED
) {
2357 msg("user (%s) account is expired", e
->u
->name
);
2358 clean_out_user(e
->u
);
2361 if (r
== VUC_NEW_AUTH
) {
2362 msg("user (%s) password has expired", e
->u
->name
);
2363 clean_out_user(e
->u
);
2367 msg("bad user (%s)", e
->u
->name
);
2368 clean_out_user(e
->u
);
2372 * check user and initialize the supplementary group access list.
2373 * bugid 1230784: deleted from parent to avoid cron hang. Now
2374 * only child handles the call.
2377 if (verify_user_cred(e
->u
) != VUC_OK
||
2378 setgid(e
->u
->gid
) == -1 ||
2379 initgroups(e
->u
->name
, e
->u
->gid
) == -1) {
2380 msg("bad user (%s) or setgid failed (%s)",
2381 e
->u
->name
, e
->u
->name
);
2382 clean_out_user(e
->u
);
2386 if ((e
->u
)->uid
== 0) { /* set default path */
2387 /* path settable in defaults file */
2388 envinit
[2] = supath
;
2393 if (e
->etype
!= CRONEVENT
)
2394 cron_unlink(at_cmdfile
);
2396 if (setuid(e
->u
->uid
) == -1) {
2397 msg("setuid failed (%s)", e
->u
->name
);
2398 clean_out_user(e
->u
);
2402 if (e
->etype
== CRONEVENT
) {
2403 /* check for standard input to command */
2404 if (e
->of
.ct
.input
!= NULL
) {
2405 if ((tmpfile
= strdup(TMPINFILE
)) == NULL
) {
2406 mail((e
->u
)->name
, MALLOCERR
,
2410 if ((fd
= mkstemp(tmpfile
)) == -1 ||
2411 (fptr
= fdopen(fd
, "w")) == NULL
) {
2412 mail((e
->u
)->name
, NOSTDIN
,
2414 cron_unlink(tmpfile
);
2418 if ((fwrite(e
->of
.ct
.input
, sizeof (char),
2419 strlen(e
->of
.ct
.input
), fptr
)) !=
2420 strlen(e
->of
.ct
.input
)) {
2421 mail((e
->u
)->name
, NOSTDIN
, ERR_CANTEXECCRON
);
2422 cron_unlink(tmpfile
);
2425 (void) fclose(fptr
);
2428 if (fseek(fptr
, (off_t
)0, SEEK_SET
) != -1) {
2434 cron_unlink(tmpfile
);
2436 (void) fclose(fptr
);
2437 } else if ((fd
= open("/dev/null", O_RDONLY
)) > 0) {
2443 /* redirect stdout and stderr for the shell */
2444 if ((fd
= open(rp
->outfile
, O_WRONLY
|O_CREAT
|O_EXCL
, OUTMODE
)) == 1)
2445 fd
= open("/dev/null", O_WRONLY
);
2447 if (fd
>= 0 && fd
!= 1)
2450 if (fd
>= 0 && fd
!= 2) {
2456 if (e
->etype
== CRONEVENT
&& e
->of
.ct
.home
!= NULL
) {
2457 home
= (char *)get_obj(e
->of
.ct
.home
);
2459 home
= (e
->u
)->home
;
2461 (void) strlcat(homedir
, home
, sizeof (homedir
));
2462 (void) strlcat(logname
, (e
->u
)->name
, sizeof (logname
));
2464 if (chdir(home
) == -1) {
2465 snprintf(bufs
.error
, sizeof (bufs
.error
), CANTCDHOME
, home
);
2466 mail((e
->u
)->name
, bufs
.error
,
2467 e
->etype
== CRONEVENT
? ERR_CANTEXECCRON
:
2475 * make sure that all file descriptors EXCEPT 0, 1 and 2
2480 if ((e
->u
)->uid
!= 0)
2481 (void) nice(qp
->nice
);
2482 if (e
->etype
== CRONEVENT
) {
2484 (void) putenv((char *)get_obj(e
->of
.ct
.tz
));
2486 if (e
->of
.ct
.shell
) {
2489 sh
= (char *)get_obj(e
->of
.ct
.shell
);
2490 name
= strrchr(sh
, '/');
2497 sh
+= strlen(ENV_SHELL
);
2498 (void) execl(sh
, name
, "-c", e
->cmd
, 0);
2500 (void) execl(SHELL
, "sh", "-c", e
->cmd
, 0);
2503 } else { /* type == ATEVENT */
2504 (void) execl(SHELL
, "sh", 0);
2507 snprintf(bufs
.error
, sizeof (bufs
.error
), CANTEXECSH
, sh
);
2508 mail((e
->u
)->name
, bufs
.error
,
2509 e
->etype
== CRONEVENT
? ERR_CANTEXECCRON
: ERR_CANTEXECAT
);
2516 * When timed out to run the job, return 0.
2517 * If for some reasons we need to reschedule jobs, return 1.
2527 if (msg_wait(t
) != 0) {
2528 /* we need to run next job immediately */
2535 /* We got THAW or REFRESH message */
2540 if (last_time
> now
) {
2541 /* clock has been reset to backward */
2545 if (next_event
== NULL
&& !el_empty()) {
2546 next_event
= (struct event
*)el_first();
2549 if (next_event
== NULL
)
2552 t
= (long)next_event
->time
- now
;
2558 * This used to be in the idle(), but moved to the separate function.
2559 * This called from various place when cron needs to reap the
2560 * child. It includes the situation that cron hit maxrun, and needs
2561 * to reschedule the job.
2571 pid
= waitpid((pid_t
)-1, &prc
, WNOHANG
);
2576 "wait returned %x for process %d\n", prc
, pid
);
2578 if ((rp
= rinfo_get(pid
)) == NULL
) {
2579 if (miscpid_delete(pid
) == 0) {
2580 /* not found in anywhere */
2583 } else if (rp
->que
== ZOMB
) {
2584 (void) unlink(rp
->outfile
);
2593 cleanup(struct runinfo
*pr
, int rc
)
2599 logit(ECHAR
, pr
, rc
);
2602 if (pr
->que
!= CRONEVENT
)
2607 if (lstat(pr
->outfile
, &buf
) == 0) {
2608 if (!S_ISLNK(buf
.st_mode
) &&
2609 (buf
.st_size
> 0 || pr
->mailwhendone
)) {
2610 /* mail user stdout and stderr */
2612 if ((pr
->pid
= fork()) < 0) {
2614 * if fork fails try forever in doubling
2615 * retry times, up to 16 seconds
2617 (void) sleep(nextfork
);
2619 nextfork
+= nextfork
;
2621 } else if (pr
->pid
== 0) {
2623 contract_clear_template();
2625 mail_result(p
, pr
, buf
.st_size
);
2628 contract_abandon_latest(pr
->pid
);
2634 (void) unlink(pr
->outfile
);
2645 * Mail stdout and stderr of a job to user. Get uid for real user and become
2646 * that person. We do this so that mail won't come from root since this
2647 * could be a security hole. If failure, quit - don't send mail as root.
2650 mail_result(struct usr
*p
, struct runinfo
*pr
, size_t filesize
)
2652 struct passwd
*ruser_ids
;
2655 struct utsname name
;
2659 char *lowname
= (pr
->jobtype
== CRONEVENT
? "cron" : "at");
2661 (void) uname(&name
);
2662 if ((ruser_ids
= getpwnam(p
->name
)) == NULL
)
2664 (void) setuid(ruser_ids
->pw_uid
);
2666 cmd
= xmalloc(strlen(MAIL
) + strlen(p
->name
)+2);
2667 (void) sprintf(cmd
, "%s %s", MAIL
, p
->name
);
2668 mailpipe
= popen(cmd
, "w");
2670 if (mailpipe
== NULL
)
2672 (void) fprintf(mailpipe
, "To: %s\n", p
->name
);
2673 (void) fprintf(mailpipe
, "Subject: %s <%s@%s> %s\n",
2674 (pr
->jobtype
== CRONEVENT
? "Cron" : "At"),
2675 p
->name
, name
.nodename
, pr
->jobname
);
2678 * RFC3834 (Section 5) defines the Auto-Submitted header to prevent
2679 * vacation replies, et al, from being sent in response to
2680 * machine-generated mail.
2682 (void) fprintf(mailpipe
, "Auto-Submitted: auto-generated\n");
2685 * Additional headers for mail filtering and diagnostics:
2687 (void) fprintf(mailpipe
, "X-Mailer: cron (%s %s)\n", name
.sysname
,
2689 (void) fprintf(mailpipe
, "X-Cron-User: %s\n", p
->name
);
2690 (void) fprintf(mailpipe
, "X-Cron-Host: %s\n", name
.nodename
);
2691 (void) fprintf(mailpipe
, "X-Cron-Job-Name: %s\n", pr
->jobname
);
2692 (void) fprintf(mailpipe
, "X-Cron-Job-Type: %s\n", lowname
);
2697 * (Temporary file is fopen'ed with "r", secure open.)
2699 (void) fprintf(mailpipe
, "\n");
2701 (st
= fopen(pr
->outfile
, "r")) != NULL
) {
2702 while ((nbytes
= fread(iobuf
, sizeof (char), BUFSIZ
, st
)) != 0)
2703 (void) fwrite(iobuf
, sizeof (char), nbytes
, mailpipe
);
2706 (void) fprintf(mailpipe
, "Job completed with no output.\n");
2708 (void) pclose(mailpipe
);
2719 struct timespec tout
, *toutp
;
2720 static int pending_msg
;
2721 static time_t pending_reftime
;
2724 process_msg(&msgbuf
, pending_reftime
);
2730 FD_SET(msgfd
, &fds
);
2733 if (tim
!= INFINITY
) {
2734 #ifdef CRON_MAXSLEEP
2736 * CRON_MAXSLEEP can be defined to have cron periodically wake
2737 * up, so that cron can detect a change of TOD and adjust the
2738 * sleep time more frequently.
2740 tim
= (tim
> CRON_MAXSLEEP
) ? CRON_MAXSLEEP
: tim
;
2747 cnt
= pselect(msgfd
+ 1, &fds
, NULL
, NULL
, toutp
, &defmask
);
2748 if (cnt
== -1 && errno
!= EINTR
)
2749 perror("! pselect");
2751 /* pselect timeout or interrupted */
2756 if ((cnt
= read(msgfd
, &msg
, sizeof (msg
))) != sizeof (msg
)) {
2757 if (cnt
!= -1 || errno
!= EAGAIN
)
2761 reftime
= time(NULL
);
2762 if (next_event
!= NULL
&& reftime
>= next_event
->time
) {
2764 * we need to run the job before reloading crontab.
2766 (void) memcpy(&msgbuf
, &msg
, sizeof (msg
));
2768 pending_reftime
= reftime
;
2771 process_msg(&msg
, reftime
);
2776 * process the message supplied via pipe. This will be called either
2777 * immediately after cron read the message from pipe, or idle time
2778 * if the message was pending due to the job execution.
2781 process_msg(struct message
*pmsg
, time_t reftime
)
2783 if (pmsg
->etype
== 0)
2786 switch (pmsg
->etype
) {
2788 if (pmsg
->action
== DELETE
)
2789 del_atjob(pmsg
->fname
, pmsg
->logname
);
2791 mod_atjob(pmsg
->fname
, (time_t)0);
2794 if (pmsg
->action
== DELETE
)
2795 del_ctab(pmsg
->fname
);
2797 mod_ctab(pmsg
->fname
, reftime
);
2804 msg("message received - bad format");
2807 if (next_event
!= NULL
) {
2808 if (next_event
->etype
== CRONEVENT
) {
2809 switch (el_add(next_event
, next_event
->time
,
2810 (next_event
->u
)->ctid
)) {
2812 ignore_msg("process_msg", "cron", next_event
);
2814 case -2: /* event time lower than init time */
2818 } else { /* etype == ATEVENT */
2819 if (el_add(next_event
, next_event
->time
,
2820 next_event
->of
.at
.eventid
) < 0) {
2821 ignore_msg("process_msg", "at", next_event
);
2826 (void) fflush(stdout
);
2831 * Allocate a new or find an existing runinfo structure
2833 static struct runinfo
*
2834 rinfo_get(pid_t pid
)
2838 if (pid
== 0) { /* allocate a new entry */
2839 rp
= xcalloc(1, sizeof (struct runinfo
));
2840 rp
->next
= rthead
; /* link the entry into the list */
2844 /* search the list for an existing entry */
2845 for (rp
= rthead
; rp
!= NULL
; rp
= rp
->next
) {
2853 * Free a runinfo structure and its associated memory
2856 rinfo_free(struct runinfo
*entry
)
2858 struct runinfo
**rpp
;
2862 (void) fprintf(stderr
, "freeing job %s\n", entry
->jobname
);
2864 for (rpp
= &rthead
; (rp
= *rpp
) != NULL
; rpp
= &rp
->next
) {
2866 *rpp
= rp
->next
; /* unlink the entry */
2877 thaw_handler(int sig
)
2887 crabort("SIGTERM", REMOVE_FIFO
);
2892 child_handler(int sig
)
2898 child_sigreset(void)
2900 (void) signal(SIGCLD
, SIG_DFL
);
2901 (void) sigprocmask(SIG_SETMASK
, &defmask
, NULL
);
2905 * crabort() - handle exits out of cron
2908 crabort(char *mssg
, int action
)
2912 if (action
& REMOVE_FIFO
) {
2913 /* FIFO vanishes when cron finishes */
2914 if (unlink(FIFO
) < 0)
2915 perror("cron could not unlink FIFO");
2918 if (action
& CONSOLE_MSG
) {
2919 /* write error msg to console */
2920 if ((c
= open(CONSOLE
, O_WRONLY
)) >= 0) {
2921 (void) write(c
, "cron aborted: ", 14);
2922 (void) write(c
, mssg
, strlen(mssg
));
2923 (void) write(c
, "\n", 1);
2928 /* always log the message */
2930 msg("******* CRON ABORTED ********");
2935 * msg() - time-stamped error reporting function
2946 (void) fflush(stdout
);
2948 (void) fprintf(stderr
, "! ");
2950 va_start(args
, fmt
);
2951 (void) vfprintf(stderr
, fmt
, args
);
2954 (void) strftime(timebuf
, sizeof (timebuf
), FORMAT
, localtime(&t
));
2955 (void) fprintf(stderr
, " %s\n", timebuf
);
2957 (void) fflush(stderr
);
2961 ignore_msg(char *func_name
, char *job_type
, struct event
*event
)
2963 msg("%s: ignoring %s job (user: %s, cmd: %s, time: %ld)",
2964 func_name
, job_type
,
2965 event
->u
->name
? event
->u
->name
: "unknown",
2966 event
->cmd
? event
->cmd
: "unknown",
2971 logit(int cc
, struct runinfo
*rp
, int rc
)
2981 (void) printf("%c CMD: %s\n", cc
, next_event
->cmd
);
2982 (void) strftime(timebuf
, sizeof (timebuf
), FORMAT
, localtime(&t
));
2983 (void) printf("%c %s %u %c %s",
2984 cc
, (rp
->rusr
)->name
, rp
->pid
, QUE(rp
->que
), timebuf
);
2985 if ((ret
= TSTAT(rc
)) != 0)
2986 (void) printf(" ts=%d", ret
);
2987 if ((ret
= RCODE(rc
)) != 0)
2988 (void) printf(" rc=%d", ret
);
2989 (void) putchar('\n');
2990 (void) fflush(stdout
);
2998 /* run job at a later time */
2999 nt
= next_event
->time
+ delay
;
3000 if (next_event
->etype
== CRONEVENT
) {
3001 next_event
->time
= next_time(next_event
, (time_t)0);
3002 if (nt
< next_event
->time
)
3003 next_event
->time
= nt
;
3004 switch (el_add(next_event
, next_event
->time
,
3005 (next_event
->u
)->ctid
)) {
3007 ignore_msg("resched", "cron", next_event
);
3009 case -2: /* event time lower than init time */
3014 msg("rescheduling a cron job");
3017 add_atevent(next_event
->u
, next_event
->cmd
, nt
, next_event
->etype
);
3018 msg("rescheduling at job");
3029 /* set up default queue definitions */
3030 for (i
= 0; i
< NQUEUE
; i
++) {
3031 qt
[i
].njob
= qd
.njob
;
3032 qt
[i
].nice
= qd
.nice
;
3033 qt
[i
].nwait
= qd
.nwait
;
3035 if (action
== DEFAULT
)
3037 if ((fd
= fopen(QUEDEFS
, "r")) == NULL
) {
3038 msg("cannot open quedefs file");
3039 msg("using default queue definitions");
3042 while (fgets(qbuf
, QBUFSIZ
, fd
) != NULL
) {
3043 if ((j
= qbuf
[0]-'a') < 0 || j
>= NQUEUE
|| qbuf
[1] != '.')
3046 qt
[j
].njob
= qq
.njob
;
3047 qt
[j
].nice
= qq
.nice
;
3048 qt
[j
].nwait
= qq
.nwait
;
3054 parsqdef(char *name
)
3061 while (isdigit(*name
)) {
3080 * defaults - read defaults from /etc/default/cron
3090 * get HZ value for environment
3092 if ((hz
= getenv("HZ")) == NULL
)
3093 (void) sprintf(hzname
, "HZ=%d", HZ
);
3095 (void) snprintf(hzname
, sizeof (hzname
), "HZ=%s", hz
);
3097 * get TZ value for environment
3099 (void) snprintf(tzone
, sizeof (tzone
), "TZ=%s",
3100 ((tz
= getenv("TZ")) != NULL
) ? tz
: DEFTZ
);
3102 if (defopen(DEFFILE
) == 0) {
3104 flags
= defcntl(DC_GETFLAGS
, 0);
3105 TURNOFF(flags
, DC_CASE
);
3106 (void) defcntl(DC_SETFLAGS
, flags
);
3108 if (((deflog
= defread("CRONLOG=")) == NULL
) ||
3109 (*deflog
== 'N') || (*deflog
== 'n'))
3113 /* fix for 1087611 - allow paths to be set in defaults file */
3114 if ((Def_path
= defread("PATH=")) != NULL
) {
3115 (void) strlcat(path
, Def_path
, LINE_MAX
);
3117 (void) strlcpy(path
, NONROOTPATH
, LINE_MAX
);
3119 if ((Def_supath
= defread("SUPATH=")) != NULL
) {
3120 (void) strlcat(supath
, Def_supath
, LINE_MAX
);
3122 (void) strlcpy(supath
, ROOTPATH
, LINE_MAX
);
3124 (void) defopen(NULL
);
3129 * Determine if a user entry for a job is still ok. The method used here
3130 * is a lot (about 75x) faster than using setgrent() / getgrent()
3131 * endgrent(). It should be safe because we use the sysconf to determine
3132 * the max, and it tolerates the max being 0.
3136 verify_user_cred(struct usr
*u
)
3139 size_t numUsrGrps
= 0;
3140 size_t numOrigGrps
= 0;
3145 * Maximum number of groups a user may be in concurrently. This
3146 * is a value which we obtain at runtime through a sysconf()
3150 static size_t nGroupsMax
= (size_t)-1;
3153 * Arrays for cron user's group list, constructed at startup to
3154 * be nGroupsMax elements long, used for verifying user
3155 * credentials prior to execution.
3158 static gid_t
*UsrGrps
;
3159 static gid_t
*OrigGrps
;
3161 if ((pw
= getpwnam(u
->name
)) == NULL
)
3162 return (VUC_BADUSER
);
3163 if (u
->home
!= NULL
) {
3164 if (strcmp(u
->home
, pw
->pw_dir
) != 0) {
3166 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
3167 (void) strcpy(u
->home
, pw
->pw_dir
);
3170 u
->home
= xmalloc(strlen(pw
->pw_dir
) + 1);
3171 (void) strcpy(u
->home
, pw
->pw_dir
);
3173 if (u
->uid
!= pw
->pw_uid
)
3174 u
->uid
= pw
->pw_uid
;
3175 if (u
->gid
!= pw
->pw_gid
)
3176 u
->gid
= pw
->pw_gid
;
3179 * Create the group id lists needed for job credential
3183 if (nGroupsMax
== (size_t)-1) {
3184 if ((nGroupsMax
= sysconf(_SC_NGROUPS_MAX
)) > 0) {
3185 UsrGrps
= xcalloc(nGroupsMax
, sizeof (gid_t
));
3186 OrigGrps
= xcalloc(nGroupsMax
, sizeof (gid_t
));
3190 (void) fprintf(stderr
, "nGroupsMax = %ld\n", nGroupsMax
);
3195 (void) fprintf(stderr
, "verify_user_cred (%s-%d)\n", pw
->pw_name
,
3197 (void) fprintf(stderr
, "verify_user_cred: pw->pw_gid = %d, "
3198 "u->gid = %d\n", pw
->pw_gid
, u
->gid
);
3201 retval
= (u
->gid
== pw
->pw_gid
) ? VUC_OK
: VUC_NOTINGROUP
;
3203 if (nGroupsMax
> 0) {
3204 numOrigGrps
= getgroups(nGroupsMax
, OrigGrps
);
3206 (void) initgroups(pw
->pw_name
, pw
->pw_gid
);
3207 numUsrGrps
= getgroups(nGroupsMax
, UsrGrps
);
3209 for (i
= 0; i
< numUsrGrps
; i
++) {
3210 if (UsrGrps
[i
] == u
->gid
) {
3217 (void) setgroups(numOrigGrps
, OrigGrps
);
3222 (void) fprintf(stderr
, "verify_user_cred: VUC = %d\n", retval
);
3229 set_user_cred(const struct usr
*u
, struct project
*pproj
)
3231 static char *progname
= "cron";
3232 int r
= 0, rval
= 0;
3234 if ((r
= pam_start(progname
, u
->name
, &pam_conv
, &pamh
))
3237 msg("pam_start returns %d\n", r
);
3240 goto set_eser_cred_exit
;
3243 r
= pam_acct_mgmt(pamh
, 0);
3245 msg("pam_acc_mgmt returns %d\n", r
);
3247 if (r
== PAM_ACCT_EXPIRED
) {
3249 goto set_eser_cred_exit
;
3251 if (r
== PAM_NEW_AUTHTOK_REQD
) {
3252 rval
= VUC_NEW_AUTH
;
3253 goto set_eser_cred_exit
;
3255 if (r
!= PAM_SUCCESS
) {
3257 goto set_eser_cred_exit
;
3260 if (pproj
!= NULL
) {
3261 size_t sz
= sizeof (PROJECT
) + strlen(pproj
->pj_name
);
3262 char *buf
= alloca(sz
);
3264 (void) snprintf(buf
, sz
, PROJECT
"%s", pproj
->pj_name
);
3265 (void) pam_set_item(pamh
, PAM_RESOURCE
, buf
);
3268 r
= pam_setcred(pamh
, PAM_ESTABLISH_CRED
);
3269 if (r
!= PAM_SUCCESS
)
3273 (void) pam_end(pamh
, r
);
3278 clean_out_user(struct usr
*u
)
3280 if (next_event
->u
== u
) {
3285 clean_out_atjobs(u
);
3290 clean_out_atjobs(struct usr
*u
)
3292 struct event
*ev
, *pv
;
3294 for (pv
= NULL
, ev
= u
->atevents
;
3296 pv
= ev
, ev
= ev
->link
, free(pv
)) {
3297 el_remove(ev
->of
.at
.eventid
, 1);
3299 cron_unlink(ev
->cmd
);
3302 if (strlen(ATDIR
) + strlen(ev
->cmd
) + 2
3304 (void) sprintf(buf
, "%s/%s", ATDIR
, ev
->cmd
);
3315 clean_out_ctab(struct usr
*u
)
3318 el_remove(u
->ctid
, 0);
3324 cron_unlink(char *name
)
3331 cron_conv(int num_msg
, struct pam_message
**msgs
,
3332 struct pam_response
**response
, void *appdata_ptr
)
3334 struct pam_message
**m
= msgs
;
3337 for (i
= 0; i
< num_msg
; i
++) {
3338 switch (m
[i
]->msg_style
) {
3341 if (m
[i
]->msg
!= NULL
) {
3342 (void) msg("%s\n", m
[i
]->msg
);
3354 * Cron creates process for other than job. Mail process is the
3355 * one which rinfo does not cover. Therefore, miscpid will keep
3356 * track of the pids executed from cron. Otherwise, we will see
3357 * "unexpected pid returned.." messages appear in the log file.
3360 miscpid_insert(pid_t pid
)
3364 mp
= xmalloc(sizeof (*mp
));
3366 mp
->next
= miscpid_head
;
3371 miscpid_delete(pid_t pid
)
3373 struct miscpid
*mp
, *omp
;
3377 for (mp
= miscpid_head
; mp
!= NULL
; mp
= mp
->next
) {
3378 if (mp
->pid
== pid
) {
3386 omp
->next
= mp
->next
;
3388 miscpid_head
= NULL
;
3395 * Establish contract terms such that all children are in abandoned
3396 * process contracts.
3399 contract_set_template(void)
3403 if ((fd
= open64(CTFS_ROOT
"/process/template", O_RDWR
)) < 0)
3404 crabort("cannot open process contract template",
3405 REMOVE_FIFO
| CONSOLE_MSG
);
3407 if (ct_pr_tmpl_set_param(fd
, 0) ||
3408 ct_tmpl_set_informative(fd
, 0) ||
3409 ct_pr_tmpl_set_fatal(fd
, CT_PR_EV_HWERR
))
3410 crabort("cannot establish contract template terms",
3411 REMOVE_FIFO
| CONSOLE_MSG
);
3413 if (ct_tmpl_activate(fd
))
3414 crabort("cannot activate contract template",
3415 REMOVE_FIFO
| CONSOLE_MSG
);
3421 * Clear active process contract template.
3424 contract_clear_template(void)
3428 if ((fd
= open64(CTFS_ROOT
"/process/template", O_RDWR
)) < 0)
3429 crabort("cannot open process contract template",
3430 REMOVE_FIFO
| CONSOLE_MSG
);
3432 if (ct_tmpl_clear(fd
))
3433 crabort("cannot clear contract template",
3434 REMOVE_FIFO
| CONSOLE_MSG
);
3440 * Abandon latest process contract unconditionally. If we have leaked [some
3441 * critical amount], exit such that the kernel reaps our contracts.
3444 contract_abandon_latest(pid_t pid
)
3448 static uint_t cts_lost
;
3450 if (cts_lost
> MAX_LOST_CONTRACTS
)
3451 crabort("repeated failure to abandon contracts",
3452 REMOVE_FIFO
| CONSOLE_MSG
);
3454 if (r
= contract_latest(&id
)) {
3455 msg("could not obtain latest contract for "
3456 "PID %ld: %s", pid
, strerror(r
));
3461 if (r
= contract_abandon_id(id
)) {
3462 msg("could not abandon latest contract %ld: %s", id
,
3469 static struct shared
*
3470 create_shared(void *obj
, void * (*obj_alloc
)(void *obj
),
3471 void (*obj_free
)(void *))
3475 if ((out
= xmalloc(sizeof (struct shared
))) == NULL
) {
3478 if ((out
->obj
= obj_alloc(obj
)) == NULL
) {
3483 out
->free
= obj_free
;
3488 static struct shared
*
3489 create_shared_str(char *str
)
3491 return (create_shared(str
, (void *(*)(void *))strdup
, free
));
3494 static struct shared
*
3495 dup_shared(struct shared
*obj
)
3504 rel_shared(struct shared
*obj
)
3506 if (obj
&& (--obj
->count
) == 0) {
3507 obj
->free(obj
->obj
);
3513 get_obj(struct shared
*obj
)