custom message type for SEMOP
[minix3.git] / commands / cron / cron.c
blob53ff880102d69438ff73085dcad9398fcfe1da6c
1 /* cron 1.4 - clock daemon Author: Kees J. Bot
2 * 7 Dec 1996
3 */
5 #define nil ((void*)0)
6 #include <sys/types.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <signal.h>
11 #include <limits.h>
12 #include <dirent.h>
13 #include <time.h>
14 #include <errno.h>
15 #include <unistd.h>
16 #include <fcntl.h>
17 #include <pwd.h>
18 #include <grp.h>
19 #include <sys/stat.h>
20 #include <sys/wait.h>
21 #include "misc.h"
22 #include "tab.h"
24 #if __minix && !__minix_vmd
25 #define initgroups(name, gid) (0)
26 #endif
28 static volatile int busy; /* Set when something is afoot, don't sleep! */
29 static volatile int need_reload;/* Set if table reload required. */
30 static volatile int need_quit; /* Set if cron must exit. */
31 static volatile int debug; /* Debug level. */
33 static void run_job(cronjob_t *job)
34 /* Execute a cron job. Register its pid in the job structure. If a job's
35 * crontab has an owner then its output is mailed to that owner, otherwise
36 * no special provisions are made, so the output will go where cron's output
37 * goes. This keeps root's mailbox from filling up.
40 pid_t pid;
41 int need_mailer;
42 int mailfd[2], errfd[2];
43 struct passwd *pw;
44 crontab_t *tab= job->tab;
46 need_mailer= (tab->user != nil);
48 if (job->atjob) {
49 struct stat st;
51 need_mailer= 1;
52 if (rename(tab->file, tab->data) < 0) {
53 if (errno == ENOENT) {
54 /* Normal error, job deleted. */
55 need_reload= 1;
56 } else {
57 /* Bad error, halt processing AT jobs. */
58 log(LOG_CRIT, "Can't rename %s: %s\n",
59 tab->file, strerror(errno));
60 tab_reschedule(job);
62 return;
64 /* Will need to determine the next AT job. */
65 need_reload= 1;
67 if (stat(tab->data, &st) < 0) {
68 log(LOG_ERR, "Can't stat %s: %s\n",
69 tab->data, strerror(errno));
70 tab_reschedule(job);
71 return;
73 if ((pw= getpwuid(st.st_uid)) == nil) {
74 log(LOG_ERR, "Unknown owner for uid %lu of AT job %s\n",
75 (unsigned long) st.st_uid, job->cmd);
76 tab_reschedule(job);
77 return;
79 } else {
80 pw= nil;
81 if (job->user != nil && (pw= getpwnam(job->user)) == nil) {
82 log(LOG_ERR, "%s: Unknown user\n", job->user);
83 tab_reschedule(job);
84 return;
88 if (need_mailer) {
89 errfd[0]= -1;
90 if (pipe(errfd) < 0 || pipe(mailfd) < 0) {
91 log(LOG_ERR, "pipe() call failed: %s\n",
92 strerror(errno));
93 if (errfd[0] != -1) {
94 close(errfd[0]);
95 close(errfd[1]);
97 tab_reschedule(job);
98 return;
100 (void) fcntl(errfd[1], F_SETFD,
101 fcntl(errfd[1], F_GETFD) | FD_CLOEXEC);
103 if ((pid= fork()) == -1) {
104 log(LOG_ERR, "fork() call failed: %s\n",
105 strerror(errno));
106 close(errfd[0]);
107 close(errfd[1]);
108 close(mailfd[0]);
109 close(mailfd[1]);
110 tab_reschedule(job);
111 return;
114 if (pid == 0) {
115 /* Child that is to be the mailer. */
116 char subject[70+20], *ps;
118 close(errfd[0]);
119 close(mailfd[1]);
120 if (mailfd[0] != 0) {
121 dup2(mailfd[0], 0);
122 close(mailfd[0]);
125 memset(subject, 0, sizeof(subject));
126 sprintf(subject,
127 "Output from your %s job: %.50s",
128 job->atjob ? "AT" : "cron",
129 job->cmd);
130 if (subject[70] != 0) {
131 strcpy(subject+70-3, "...");
133 for (ps= subject; *ps != 0; ps++) {
134 if (*ps == '\n') *ps= '%';
137 execl("/usr/bin/mail", "mail", "-s", subject,
138 pw->pw_name, (char *) nil);
139 write(errfd[1], &errno, sizeof(errno));
140 _exit(1);
143 close(mailfd[0]);
144 close(errfd[1]);
145 if (read(errfd[0], &errno, sizeof(errno)) > 0) {
146 log(LOG_ERR, "can't execute /usr/bin/mail: %s\n",
147 strerror(errno));
148 close(errfd[0]);
149 close(mailfd[1]);
150 tab_reschedule(job);
151 return;
153 close(errfd[0]);
156 if (pipe(errfd) < 0) {
157 log(LOG_ERR, "pipe() call failed: %s\n", strerror(errno));
158 if (need_mailer) close(mailfd[1]);
159 tab_reschedule(job);
160 return;
162 (void) fcntl(errfd[1], F_SETFD, fcntl(errfd[1], F_GETFD) | FD_CLOEXEC);
164 if ((pid= fork()) == -1) {
165 log(LOG_ERR, "fork() call failed: %s\n", strerror(errno));
166 close(errfd[0]);
167 close(errfd[1]);
168 if (need_mailer) close(mailfd[1]);
169 tab_reschedule(job);
170 return;
173 if (pid == 0) {
174 /* Child that is to be the cron job. */
175 close(errfd[0]);
176 if (need_mailer) {
177 if (mailfd[1] != 1) {
178 dup2(mailfd[1], 1);
179 close(mailfd[1]);
181 dup2(1, 2);
184 if (pw != nil) {
185 /* Change id to the owner of the job. */
186 initgroups(pw->pw_name, pw->pw_gid);
187 setgid(pw->pw_gid);
188 setuid(pw->pw_uid);
189 chdir(pw->pw_dir);
190 if (setenv("USER", pw->pw_name, 1) < 0) goto bad;
191 if (setenv("LOGNAME", pw->pw_name, 1) < 0) goto bad;
192 if (setenv("HOME", pw->pw_dir, 1) < 0) goto bad;
193 if (setenv("SHELL", pw->pw_shell[0] == 0 ? "/bin/sh"
194 : pw->pw_shell, 1) < 0) goto bad;
197 if (job->atjob) {
198 execl("/bin/sh", "sh", tab->data, (char *) nil);
199 } else {
200 execl("/bin/sh", "sh", "-c", job->cmd, (char *) nil);
202 bad:
203 write(errfd[1], &errno, sizeof(errno));
204 _exit(1);
207 if (need_mailer) close(mailfd[1]);
208 close(errfd[1]);
209 if (read(errfd[0], &errno, sizeof(errno)) > 0) {
210 log(LOG_ERR, "can't execute /bin/sh: %s\n", strerror(errno));
211 close(errfd[0]);
212 tab_reschedule(job);
213 return;
215 close(errfd[0]);
216 job->pid= pid;
217 if (debug >= 1) fprintf(stderr, "executing >%s<, pid = %ld\n",
218 job->cmd, (long) job->pid);
221 static void load_crontabs(void)
222 /* Load all the crontabs we like to run. We didn't bother to make a list in
223 * an array or something, this is too system specific to make nice.
226 DIR *spool;
227 #if __minix_vmd
228 FILE *pkgs;
229 #endif
231 tab_parse("/usr/lib/crontab", nil);
232 tab_parse("/usr/local/lib/crontab", nil);
233 tab_parse("/var/lib/crontab", nil);
235 #if __minix_vmd
236 if ((pkgs= fopen("/usr/lib/packages", "r")) != nil) {
237 char name[NAME_MAX+1];
238 char *np;
239 int c;
240 char tab[sizeof("/var/opt//lib/crontab") + NAME_MAX];
242 while ((c= fgetc(pkgs)) != EOF) {
243 np= name;
244 while (c != EOF && c != '/' && c != '\n') {
245 if (np < name+NAME_MAX) *np++ = c;
246 c= fgetc(pkgs);
248 *np= 0;
249 while (c != EOF && c != '\n') c= fgetc(pkgs);
251 if (name[0] == 0) continue; /* ? */
253 strcpy(tab, "/var/opt/");
254 strcat(tab, name);
255 strcat(tab, "/lib/crontab");
256 tab_parse(tab, nil);
258 if (ferror(pkgs)) {
259 log(LOG_CRIT, "/usr/lib/packages: %s\n",
260 strerror(errno));
262 fclose(pkgs);
263 } else {
264 if (errno != ENOENT) {
265 log(LOG_ERR, "/usr/lib/packages: %s\n",
266 strerror(errno));
269 #endif /* Minix-vmd */
271 if ((spool= opendir("/usr/spool/crontabs")) != nil) {
272 struct dirent *entry;
273 char tab[sizeof("/usr/spool/crontabs/") + NAME_MAX];
275 while ((entry= readdir(spool)) != nil) {
276 if (entry->d_name[0] == '.') continue;
278 strcpy(tab, "/usr/spool/crontabs/");
279 strcat(tab, entry->d_name);
280 tab_parse(tab, entry->d_name);
282 closedir(spool);
285 /* Find the first to be executed AT job. */
286 tab_find_atjob("/usr/spool/at");
288 tab_purge();
289 if (debug >= 2) {
290 tab_print(stderr);
291 fprintf(stderr, "%lu memory chunks in use\n",
292 (unsigned long) alloc_count);
296 static void handler(int sig)
298 switch (sig) {
299 case SIGHUP:
300 need_reload= 1;
301 break;
302 case SIGINT:
303 case SIGTERM:
304 need_quit= 1;
305 break;
306 case SIGUSR1:
307 debug++;
308 break;
309 case SIGUSR2:
310 debug= 0;
311 break;
313 alarm(1); /* A signal may come just before a blocking call. */
314 busy= 1;
317 static void usage(void)
319 fprintf(stderr, "Usage: %s [-d[#]]\n", prog_name);
320 exit(1);
323 int main(int argc, char **argv)
325 int i;
326 struct sigaction sa, osa;
327 FILE *pf;
328 int r;
330 prog_name= strrchr(argv[0], '/');
331 if (prog_name == nil) prog_name= argv[0]; else prog_name++;
333 i= 1;
334 while (i < argc && argv[i][0] == '-') {
335 char *opt= argv[i++] + 1;
337 if (opt[0] == '-' && opt[1] == 0) break; /* -- */
339 while (*opt != 0) switch (*opt++) {
340 case 'd':
341 if (*opt == 0) {
342 debug= 1;
343 } else {
344 debug= strtoul(opt, &opt, 10);
345 if (*opt != 0) usage();
347 break;
348 default:
349 usage();
352 if (i != argc) usage();
354 selectlog(SYSLOG);
355 openlog(prog_name, LOG_PID, LOG_DAEMON);
356 setlogmask(LOG_UPTO(LOG_INFO));
358 /* Save process id. */
359 if ((pf= fopen(PIDFILE, "w")) == NULL) {
360 fprintf(stderr, "%s: %s\n", PIDFILE, strerror(errno));
361 exit(1);
363 fprintf(pf, "%d\n", getpid());
364 if (ferror(pf) || fclose(pf) == EOF) {
365 fprintf(stderr, "%s: %s\n", PIDFILE, strerror(errno));
366 exit(1);
369 sigemptyset(&sa.sa_mask);
370 sa.sa_flags= 0;
371 sa.sa_handler= handler;
373 /* Hangup: Reload crontab files. */
374 sigaction(SIGHUP, &sa, nil);
376 /* User signal 1 & 2: Raise or reset debug level. */
377 sigaction(SIGUSR1, &sa, nil);
378 sigaction(SIGUSR2, &sa, nil);
380 /* Interrupt and Terminate: Cleanup and exit. */
381 if (sigaction(SIGINT, nil, &osa) == 0 && osa.sa_handler != SIG_IGN) {
382 sigaction(SIGINT, &sa, nil);
384 if (sigaction(SIGTERM, nil, &osa) == 0 && osa.sa_handler != SIG_IGN) {
385 sigaction(SIGTERM, &sa, nil);
388 /* Alarm: Wake up and run a job. */
389 sigaction(SIGALRM, &sa, nil);
391 /* Initialize current time and time next to do something. */
392 time(&now);
393 next= NEVER;
395 /* Table load required first time. */
396 need_reload= 1;
398 do {
399 if (need_reload) {
400 need_reload= 0;
401 load_crontabs();
402 busy= 1;
405 /* Run jobs whose time has come. */
406 if (next <= now) {
407 cronjob_t *job;
409 if ((job= tab_nextjob()) != nil) run_job(job);
410 busy= 1;
413 if (busy) {
414 /* Did a job finish? */
415 r= waitpid(-1, nil, WNOHANG);
416 busy= 0;
417 } else {
418 /* Sleep until the next job must be started. */
419 if (next == NEVER) {
420 alarm(0);
421 } else {
422 #if __minix_vmd
423 struct timeval tvnext;
425 tvnext.tv_sec= next;
426 tvnext.tv_usec= 0;
427 sysutime(UTIME_SETALARM, &tvnext);
428 #else
429 alarm((next - now) > INT_MAX
430 ? INT_MAX : (next - now));
431 #endif
433 if (debug >= 1) fprintf(stderr, "%s: sleep until %s",
434 prog_name, ctime(&next));
436 closelog(); /* Don't keep resources open. */
438 /* Wait for a job to exit or a timeout. */
439 r= waitpid(-1, nil, 0);
440 if (r == -1 && errno == ECHILD) pause();
441 alarm(0);
442 time(&now);
445 if (r > 0) {
446 /* A job has finished, reschedule it. */
447 if (debug >= 1) fprintf(stderr, "pid %d has exited\n",
449 tab_reap_job((pid_t) r);
450 busy= 1;
452 } while (!need_quit);
454 /* Remove the pid file to signal that cron is gone. */
455 unlink(PIDFILE);
457 return 0;
461 * $PchId: cron.c,v 1.4 2000/07/17 19:00:35 philip Exp $