xref: /original-bsd/usr.bin/at/atrun/atrun.c (revision 87febec0)
1 /*
2  * Copyright (c) 1983 Regents of the University of California.
3  * All rights reserved.  The Berkeley software License Agreement
4  * specifies the terms and conditions for redistribution.
5  */
6 
7 #ifndef lint
8 char copyright[] =
9 "@(#) Copyright (c) 1983 Regents of the University of California.\n\
10  All rights reserved.\n";
11 #endif not lint
12 
13 #ifndef lint
14 static char sccsid[] = "@(#)atrun.c	5.6 (Berkeley) 04/09/89";
15 #endif not lint
16 
17 /*
18  *	Synopsis: atrun
19  *
20  *
21  *	Run jobs created by at(1)
22  *
23  *
24  *	Modifications by:	Steve Wall
25  *				Computer Systems Research Group
26  *				University of California @ Berkeley
27  *
28  */
29 # include <stdio.h>
30 # include <sys/param.h>
31 # include <sys/dir.h>
32 # include <sys/file.h>
33 # include <sys/time.h>
34 #ifdef notdef
35 # include <sys/quota.h>
36 #endif
37 # include <sys/stat.h>
38 # include <pwd.h>
39 
40 # define ATDIR		"/usr/spool/at"		/* spooling area */
41 # define TMPDIR		"/tmp"			/* area for temporary files */
42 # define MAILER		"/bin/mail"		/* program to use for sending
43 						   mail */
44 # define NORMAL		0			/* job exited normally */
45 # define ABNORMAL	1			/* job exited abnormally */
46 # define PASTDIR	"/usr/spool/at/past"	/* area to run jobs from */
47 # define LASTFILE	"/usr/spool/at/lasttimedone"	/* update time file */
48 
49 
50 char nowtime[11];			/* time it is right now (yy.ddd.hhmm) */
51 char errfile[25];			/* file where we redirect errors to */
52 
53 
54 main(argc, argv)
55 char **argv;
56 {
57 
58 	int i;				/* for loop index */
59 	int numjobs;			/* number of jobs to be run */
60 	int should_be_run();		/* should a job be run? */
61 	struct direct **jobqueue;	/* queue of jobs to be run */
62 
63 
64 	/*
65 	 * Move to the spooling area.
66 	 */
67 	chdir(ATDIR);
68 
69 	/*
70 	 * Create a filename that represents the time it is now. This is used
71 	 * to determine if the execution time for a job has arrived.
72 	 */
73 	makenowtime(nowtime);
74 
75 	/*
76 	 * Create a queue of the jobs that should be run.
77 	 */
78 	if ((numjobs = scandir(".",&jobqueue,should_be_run, 0)) < 0) {
79 		perror(ATDIR);
80 		exit(1);
81 	}
82 
83 	/*
84 	 * If there are jobs to be run, run them.
85 	 */
86 	if (numjobs > 0) {
87 		for (i = 0; i < numjobs; ++i) {
88 			run(jobqueue[i]->d_name);
89 		}
90 	}
91 
92 	/*
93 	 * Record the last update time.
94 	 */
95 	updatetime();
96 
97 }
98 
99 /*
100  * Create a string with the syntax yy.ddd.hhmm that represents the
101  * time it is right now. This string is used to determine whether a
102  * job should be run.
103  */
104 makenowtime(nowtime)
105 char *nowtime;
106 {
107 	struct tm *now;			/* broken down representation of the
108 					   time it is right now */
109 	struct timeval time;		/* number of seconds since 1/1/70 */
110 	struct timezone zone;		/* time zone we're in (NOT USED) */
111 
112 	/*
113 	 * Get the time of day.
114 	 */
115 	if (gettimeofday(&time,&zone) < 0) {
116 		perror("gettimeofday");
117 		exit(1);
118 	}
119 
120 	/*
121 	 * Get a broken down representation of the time it is right now.
122 	 */
123 	now = localtime(&time.tv_sec);
124 
125 	/*
126 	 * Create a string to be used in determining whether or not a job
127 	 * should be run. The syntax is yy.ddd.hhmm .
128 	 */
129 	sprintf(nowtime,"%d.%03d.%02d%02d",now->tm_year,
130 					   now->tm_yday,
131 					   now->tm_hour,
132 					   now->tm_min);
133 	return;
134 }
135 
136 /*
137  * Run a job.
138  */
139 run(spoolfile)
140 char *spoolfile;
141 {
142 	int i;				/* scratch variable */
143 	int pid;			/* process id of forked shell */
144 	int exitstatus;			/* exit status of the job */
145 	int notifybymail;		/* should we notify the owner of the
146 					   job after the job is run? */
147 	char shell[4];			/* shell to run the job under */
148 	char *getname();		/* get a uname from using a uid */
149 	char mailvar[4];		/* send mail variable ("yes" or "no") */
150 	char runfile[100];		/* file sent to forked shell for exec-
151 					   ution */
152 	char owner[128];		/* owner of job we're going to run */
153 	char jobname[128];		/* name of job we're going to run */
154 	char whichshell[100];		/* which shell should we fork off? */
155 	struct passwd *pwdbuf;		/* password info of the owner of job */
156 	struct stat errbuf;		/* stats on error file */
157 	struct stat jobbuf;		/* stats on job file */
158 	FILE *infile;			/* I/O stream to spoolfile */
159 
160 
161 	/*
162 	 * First we fork a child so that the main can run other jobs.
163 	 */
164 	if (pid = fork())
165 		return;
166 
167 	/*
168 	 * Open the spoolfile.
169 	 */
170 	if ((infile = fopen(spoolfile,"r")) == NULL) {
171 		perror(spoolfile);
172 		(void) unlink(spoolfile);
173 		exit(1);
174 	}
175 
176 	/*
177 	 * Grab the 4-line header out of the spoolfile.
178 	 */
179 	if (
180 	    (fscanf(infile,"# owner: %127s%*[^\n]\n",owner) != 1) ||
181 	    (fscanf(infile,"# jobname: %127s%*[^\n]\n",jobname) != 1) ||
182 	    (fscanf(infile,"# shell: %3s%*[^\n]\n",shell) != 1) ||
183 	    (fscanf(infile,"# notify by mail: %3s%*[^\n]\n",mailvar) != 1)
184 	    ) {
185 		fprintf(stderr, "%s: bad spool header\n", spoolfile);
186 		(void) unlink(spoolfile);
187 		exit(1);
188 	}
189 
190 	/*
191 	 * Check to see if we should send mail to the owner.
192 	 */
193 	notifybymail = (strcmp(mailvar, "yes") == 0);
194 	fclose(infile);
195 
196 	/*
197 	 * Change the ownership of the spoolfile from "daemon" to the owner
198 	 * of the job.
199 	 */
200 	pwdbuf = getpwnam(owner);
201 	if (pwdbuf == NULL) {
202 		fprintf(stderr, "%s: could not find owner in passwd file\n",
203 		    spoolfile);
204 		(void) unlink(spoolfile);
205 		exit(1);
206 	}
207 	if (chown(spoolfile,pwdbuf->pw_uid,pwdbuf->pw_gid) == -1) {
208 		perror(spoolfile);
209 		(void) unlink(spoolfile);
210 		exit(1);
211 	}
212 
213 	/*
214 	 * Move the spoolfile to the directory where jobs are run from and
215 	 * then move into that directory.
216 	 */
217 	sprintf(runfile,"%s/%s",PASTDIR,spoolfile);
218 	rename(spoolfile, runfile);
219 	chdir(PASTDIR);
220 
221 	/*
222 	 * Create a temporary file where we will redirect errors to.
223 	 * Just to make sure we've got a unique file, we'll run an "access"
224 	 * check on the file.
225 	 */
226 	for (i = 0; i <= 1000; i += 2) {
227 		sprintf(errfile,"%s/at.err%d",TMPDIR,(getpid() + i));
228 
229 		if (access(errfile, F_OK))
230 			break;
231 
232 		if (i == 1000) {
233 			fprintf(stderr, "couldn't create errorfile.\n");
234 			exit(1);
235 		}
236 	}
237 
238 	/*
239 	 * Get the stats of the job being run.
240 	 */
241 	if (stat(runfile, &jobbuf) == -1) {
242 		perror(runfile);
243 		exit(1);
244 	}
245 
246 	/*
247 	 * Fork another child that will run the job.
248 	 */
249 	if (pid = fork()) {
250 
251 		/*
252 		 * If the child fails, save the job so that it gets
253 		 * rerun the next time "atrun" is executed and then exit.
254 		 */
255 		if (pid == -1) {
256 			chdir(ATDIR);
257 			rename(runfile, spoolfile);
258 			exit(1);
259 		}
260 
261 		/*
262 		 * Wait for the child to terminate.
263 		 */
264 		wait((int *)0);
265 
266 		/*
267 		 * Get the stats of the error file and determine the exit
268 		 * status of the child. We assume that if there is anything
269 		 * in the error file then the job ran into some errors.
270 		 */
271 		if (stat(errfile,&errbuf) != 0) {
272 			perror(errfile);
273 			exit(1);
274 		}
275 		exitstatus = ((errbuf.st_size == 0) ? NORMAL : ABNORMAL);
276 
277 		/* If errors occurred, then we send mail to the owner
278 		 * telling him/her that we ran into trouble.
279 		 *
280 		 * (NOTE: this could easily be modified so that if any
281 		 * errors occurred while running a job, mail is sent regard-
282 		 * less of whether the -m flag was set or not.
283 		 *
284 		 * i.e. rather than:
285 		 *
286 		 *	"if (notifybymail)" use
287 		 * use:
288 		 *
289 		 *	"if ((exitstatus == ABNORMAL) || (notifybymail))"
290 		 *
291 		 * It's up to you if you want to implement this.
292 		 *
293 		 */
294 		if (exitstatus == ABNORMAL || notifybymail)
295 			sendmailto(getname(jobbuf.st_uid),jobname,exitstatus);
296 
297 		/*
298 		 * Remove the errorfile and the jobfile.
299 		 */
300 		if (unlink(errfile) == -1)
301 			perror(errfile);
302 		if (unlink(runfile) == -1)
303 			perror(runfile);
304 
305 		exit(0);
306 	}
307 
308 	/*
309 	 * HERE'S WHERE WE SET UP AND FORK THE SHELL.
310 	 */
311 
312 	/*
313 	 * Run the job as the owner of the jobfile
314 	 */
315 #ifdef notdef
316 	/* This is no longer needed with the new, stripped-down quota system */
317 	quota(Q_SETUID,jobbuf.st_uid,0,0);
318 #endif
319 	setgid(jobbuf.st_gid);
320 	initgroups(getname(jobbuf.st_uid),jobbuf.st_gid);
321 	setuid(jobbuf.st_uid);
322 
323 	/*
324 	 * Close all open files so that we can reopen a temporary file
325 	 * for stdout and sterr.
326 	 */
327 	for (i = getdtablesize(); --i >= 0;)
328 		close(i);
329 
330 	/*
331 	 * Reposition stdin, stdout, and stderr.
332 	 *
333 	 *	stdin  = /dev/null
334 	 *	stout  = /dev/null
335 	 *	stderr = /tmp/at.err{pid}
336 	 *
337 	 */
338 	open("/dev/null", 0);
339 	open("/dev/null", 1);
340 	open(errfile,O_CREAT|O_WRONLY,00644);
341 
342 	/*
343 	 * Now we fork the shell.
344 	 *
345 	 * See if the shell is in /bin
346 	 */
347 	sprintf(whichshell,"/bin/%s",shell);
348 	execl(whichshell,shell,runfile, 0);
349 
350 	/*
351 	 * If not in /bin, look for the shell in /usr/bin.
352 	 */
353 	sprintf(whichshell,"/usr/bin/%s",shell);
354 	execl(whichshell,shell,runfile, 0);
355 
356 	/*
357 	 * If not in /bin, look for the shell in /usr/new.
358 	 */
359 	sprintf(whichshell,"/usr/new/%s",shell);
360 	execl(whichshell,shell,runfile, 0);
361 
362 	/*
363 	 * If we don't succeed by now, we're really having troubles,
364 	 * so we'll send the owner some mail.
365 	 */
366 	fprintf(stderr, "%s: Can't execl shell\n",shell);
367 	exit(1);
368 }
369 
370 /*
371  * Send mail to the owner of the job.
372  */
373 sendmailto(user,jobname,exitstatus)
374 char *user;
375 char *jobname;
376 int exitstatus;
377 {
378 	int ch;				/* scratch variable */
379 	char mailtouser[100];		/* the process we use to send mail */
380 	FILE *mailptr;			/* I/O stream to the mail process */
381 	FILE *errptr;			/* I/O stream to file containing error
382 					   messages */
383 	FILE *popen();			/* initiate I/O to a process */
384 
385 
386 	/*
387 	 * Create the full name for the mail process.
388 	 */
389 	sprintf(mailtouser,"%s %s",MAILER, user);
390 
391 	/*
392 	 * Open a stream to the mail process.
393 	 */
394 	if ((mailptr = popen(mailtouser,"w")) == NULL) {
395 		perror(MAILER);
396 		exit(1);
397 	}
398 
399 	/*
400 	 * Send the letter. If the job exited normally, just send a
401 	 * quick letter notifying the owner that everthing went ok.
402 	 */
403 	if (exitstatus == NORMAL) {
404 		fprintf(mailptr,"Your job \"%s\" was run without ",jobname);
405 		fprintf(mailptr,"any errors.\n");
406 	}
407 
408 	/*
409 	 * If the job exited abnormally, send a letter notifying the user
410 	 * that the job didn't run proberly. Also, send a copy of the errors
411 	 * that occurred to the user.
412 	 */
413 	else {
414 		if (exitstatus == ABNORMAL) {
415 
416 			/*
417 			 * Write the intro to the letter.
418 			 */
419 			fprintf(mailptr,"\n\nThe job you submitted to at, ");
420 			fprintf(mailptr,"\"%s\", ",jobname);
421 			fprintf(mailptr,"exited abnormally.\nA list of the ");
422 			fprintf(mailptr," errors that occurred follows:\n\n\n");
423 
424 			/*
425 			 * Open the file containing a log of the errors that
426 			 * occurred.
427 			 */
428 			if ((errptr = fopen(errfile,"r")) == NULL) {
429 				perror(errfile);
430 				exit(1);
431 			}
432 
433 			/*
434 			 * Send the copy of the errors to the owner.
435 			 */
436 			fputc('\t',mailptr);
437 			while ((ch = fgetc(errptr)) != EOF) {
438 				fputc(ch,mailptr);
439 				if (ch == (int)'\n')
440 					fputc('\t',mailptr);
441 			}
442 			fclose(errptr);
443 		}
444 	}
445 
446 	/*
447 	 * Sign the letter.
448 	 */
449 	fprintf(mailptr,"\n\n-----------------\n");
450 	fprintf(mailptr,"The Atrun Program\n");
451 
452 	/*
453 	 * Close the stream to the mail process.
454 	 */
455 	pclose(mailptr);
456 	return;
457 }
458 
459 /*
460  * Do we want to include a file in the job queue? (used by "scandir")
461  * We are looking for files whose "value" (its name) is less than or
462  * equal to the time it is right now (represented by "nowtime").
463  * We'll only consider files with three dots in their name since these
464  * are the only files that represent jobs to be run.
465  */
466 should_be_run(direntry)
467 struct direct *direntry;
468 {
469 	int numdot = 0;			/* number of dots found in a filename */
470 	char *filename;			/* pointer for scanning a filename */
471 
472 
473 	filename = direntry->d_name;
474 
475 	/*
476 	 * Count the number of dots found in the directory entry.
477 	 */
478 	while (*filename)
479 		numdot += (*(filename++) == '.');
480 
481 	/*
482 	 * If the directory entry doesn't represent a job, just return a 0.
483 	 */
484 	if (numdot != 3)
485 		return(0);
486 
487 	/*
488 	 * If a directory entry represents a job, determine if it's time to
489 	 * run it.
490 	 */
491 	return(strncmp(direntry->d_name, nowtime,11) <= 0);
492 }
493 
494 /*
495  * Record the last time that "atrun" was run.
496  */
497 updatetime()
498 {
499 
500 	struct timeval time;		/* number of seconds since 1/1/70 */
501 	struct timezone zone;		/* time zone we're in (NOT USED) */
502 	FILE *lastimefile;		/* file where recored is kept */
503 
504 	/*
505 	 * Get the time of day.
506 	 */
507 	if (gettimeofday(&time,&zone) < 0) {
508 		perror("gettimeofday");
509 		exit(1);
510 	}
511 
512 	/*
513 	 * Open the record file.
514 	 */
515 	if ((lastimefile = fopen(LASTFILE, "w")) == NULL) {
516 		fprintf(stderr, "can't update lastfile: ");
517 		perror(LASTFILE);
518 		exit(1);
519 	}
520 
521 	/*
522 	 * Record the last update time (in seconds since 1/1/70).
523 	 */
524 	fprintf(lastimefile, "%d\n", (u_long) time.tv_sec);
525 
526 	/*
527 	 * Close the record file.
528 	 */
529 	fclose(lastimefile);
530 }
531 
532 /*
533  * Get the full login name of a person using his/her user id.
534  */
535 char *
536 getname(uid)
537 int uid;
538 {
539 	struct passwd *pwdinfo;			/* password info structure */
540 
541 
542 	if ((pwdinfo = getpwuid(uid)) == 0) {
543 		perror(uid);
544 		exit(1);
545 	}
546 	return(pwdinfo->pw_name);
547 }
548