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