xref: /minix/minix/commands/cron/cron.c (revision 7f5f010b)
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 #if __minix && !__minix_vmd
25 #define initgroups(name, gid)	(0)
26 #endif
27 
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. */
32 
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.
38  */
39 {
40 	pid_t pid;
41 	int need_mailer;
42 	int mailfd[2], errfd[2];
43 	struct passwd *pw;
44 	crontab_t *tab= job->tab;
45 
46 	need_mailer= (tab->user != nil);
47 
48 	if (job->atjob) {
49 		struct stat st;
50 
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);
61 			}
62 			return;
63 		}
64 		/* Will need to determine the next AT job. */
65 		need_reload= 1;
66 
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;
72 		}
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;
78 		}
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;
85 		}
86 	}
87 
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]);
96 			}
97 			tab_reschedule(job);
98 			return;
99 		}
100 		(void) fcntl(errfd[1], F_SETFD,
101 				fcntl(errfd[1], F_GETFD) | FD_CLOEXEC);
102 
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;
112 		}
113 
114 		if (pid == 0) {
115 			/* Child that is to be the mailer. */
116 			char subject[70+20], *ps;
117 
118 			close(errfd[0]);
119 			close(mailfd[1]);
120 			if (mailfd[0] != 0) {
121 				dup2(mailfd[0], 0);
122 				close(mailfd[0]);
123 			}
124 
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, "...");
132 			}
133 			for (ps= subject; *ps != 0; ps++) {
134 				if (*ps == '\n') *ps= '%';
135 			}
136 
137 			execl("/usr/bin/mail", "mail", "-s", subject,
138 						pw->pw_name, (char *) nil);
139 			write(errfd[1], &errno, sizeof(errno));
140 			_exit(1);
141 		}
142 
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;
152 		}
153 		close(errfd[0]);
154 	}
155 
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;
161 	}
162 	(void) fcntl(errfd[1], F_SETFD, fcntl(errfd[1], F_GETFD) | FD_CLOEXEC);
163 
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;
171 	}
172 
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]);
180 			}
181 			dup2(1, 2);
182 		}
183 
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;
195 		}
196 
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);
201 		}
202 	    bad:
203 		write(errfd[1], &errno, sizeof(errno));
204 		_exit(1);
205 	}
206 
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;
214 	}
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);
219 }
220 
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.
224  */
225 {
226 	DIR *spool;
227 #if __minix_vmd
228 	FILE *pkgs;
229 #endif
230 
231 	tab_parse("/usr/lib/crontab", nil);
232 	tab_parse("/usr/local/lib/crontab", nil);
233 	tab_parse("/var/lib/crontab", nil);
234 
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];
241 
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);
247 			}
248 			*np= 0;
249 			while (c != EOF && c != '\n') c= fgetc(pkgs);
250 
251 			if (name[0] == 0) continue;	/* ? */
252 
253 			strcpy(tab, "/var/opt/");
254 			strcat(tab, name);
255 			strcat(tab, "/lib/crontab");
256 			tab_parse(tab, nil);
257 		}
258 		if (ferror(pkgs)) {
259 			log(LOG_CRIT, "/usr/lib/packages: %s\n",
260 							strerror(errno));
261 		}
262 		fclose(pkgs);
263 	} else {
264 		if (errno != ENOENT) {
265 			log(LOG_ERR, "/usr/lib/packages: %s\n",
266 							strerror(errno));
267 		}
268 	}
269 #endif /* Minix-vmd */
270 
271 	if ((spool= opendir("/usr/spool/crontabs")) != nil) {
272 		struct dirent *entry;
273 		char tab[sizeof("/usr/spool/crontabs/") + NAME_MAX];
274 
275 		while ((entry= readdir(spool)) != nil) {
276 			if (entry->d_name[0] == '.') continue;
277 
278 			strcpy(tab, "/usr/spool/crontabs/");
279 			strcat(tab, entry->d_name);
280 			tab_parse(tab, entry->d_name);
281 		}
282 		closedir(spool);
283 	}
284 
285 	/* Find the first to be executed AT job. */
286 	tab_find_atjob("/usr/spool/at");
287 
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);
293 	}
294 }
295 
296 static void handler(int sig)
297 {
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;
312 	}
313 	alarm(1);	/* A signal may come just before a blocking call. */
314 	busy= 1;
315 }
316 
317 static void usage(void)
318 {
319 	fprintf(stderr, "Usage: %s [-d[#]]\n", prog_name);
320 	exit(1);
321 }
322 
323 int main(int argc, char **argv)
324 {
325 	int i;
326 	struct sigaction sa, osa;
327 	FILE *pf;
328 	int r;
329 
330 	prog_name= strrchr(argv[0], '/');
331 	if (prog_name == nil) prog_name= argv[0]; else prog_name++;
332 
333 	i= 1;
334 	while (i < argc && argv[i][0] == '-') {
335 		char *opt= argv[i++] + 1;
336 
337 		if (opt[0] == '-' && opt[1] == 0) break;	/* -- */
338 
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();
346 			}
347 			break;
348 		default:
349 			usage();
350 		}
351 	}
352 	if (i != argc) usage();
353 
354 	selectlog(SYSLOG);
355 	openlog(prog_name, LOG_PID, LOG_DAEMON);
356 	setlogmask(LOG_UPTO(LOG_INFO));
357 
358 	/* Save process id. */
359 	if ((pf= fopen(PIDFILE, "w")) == NULL) {
360 		fprintf(stderr, "%s: %s\n", PIDFILE, strerror(errno));
361 		exit(1);
362 	}
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);
367 	}
368 
369 	sigemptyset(&sa.sa_mask);
370 	sa.sa_flags= 0;
371 	sa.sa_handler= handler;
372 
373 	/* Hangup: Reload crontab files. */
374 	sigaction(SIGHUP, &sa, nil);
375 
376 	/* User signal 1 & 2: Raise or reset debug level. */
377 	sigaction(SIGUSR1, &sa, nil);
378 	sigaction(SIGUSR2, &sa, nil);
379 
380 	/* Interrupt and Terminate: Cleanup and exit. */
381 	if (sigaction(SIGINT, nil, &osa) == 0 && osa.sa_handler != SIG_IGN) {
382 		sigaction(SIGINT, &sa, nil);
383 	}
384 	if (sigaction(SIGTERM, nil, &osa) == 0 && osa.sa_handler != SIG_IGN) {
385 		sigaction(SIGTERM, &sa, nil);
386 	}
387 
388 	/* Alarm: Wake up and run a job. */
389 	sigaction(SIGALRM, &sa, nil);
390 
391 	/* Initialize current time and time next to do something. */
392 	time(&now);
393 	next= NEVER;
394 
395 	/* Table load required first time. */
396 	need_reload= 1;
397 
398 	do {
399 		if (need_reload) {
400 			need_reload= 0;
401 			load_crontabs();
402 			busy= 1;
403 		}
404 
405 		/* Run jobs whose time has come. */
406 		if (next <= now) {
407 			cronjob_t *job;
408 
409 			if ((job= tab_nextjob()) != nil) run_job(job);
410 			busy= 1;
411 		}
412 
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;
424 
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
432 			}
433 			if (debug >= 1) fprintf(stderr, "%s: sleep until %s",
434 						prog_name, ctime(&next));
435 
436 			closelog();	/* Don't keep resources open. */
437 
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);
443 		}
444 
445 		if (r > 0) {
446 			/* A job has finished, reschedule it. */
447 			if (debug >= 1) fprintf(stderr, "pid %d has exited\n",
448 									r);
449 			tab_reap_job((pid_t) r);
450 			busy= 1;
451 		}
452 	} while (!need_quit);
453 
454 	/* Remove the pid file to signal that cron is gone. */
455 	unlink(PIDFILE);
456 
457 	return 0;
458 }
459 
460 /*
461  * $PchId: cron.c,v 1.4 2000/07/17 19:00:35 philip Exp $
462  */
463