xref: /original-bsd/usr.bin/at/atrun/atrun.c (revision 7211505a)
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.5 (Berkeley) 11/26/86";
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 		exit(1);
173 	}
174 
175 	/*
176 	 * Grab the 4-line header out of the spoolfile.
177 	 */
178 	if (
179 	    (fscanf(infile,"# owner: %127s%*[^\n]\n",owner) != 1) ||
180 	    (fscanf(infile,"# jobname: %127s%*[^\n]\n",jobname) != 1) ||
181 	    (fscanf(infile,"# shell: %3s%*[^\n]\n",shell) != 1) ||
182 	    (fscanf(infile,"# notify by mail: %3s%*[^\n]\n",mailvar) != 1)
183 	    ) {
184 		fprintf(stderr, "%s: bad spool header\n", spoolfile);
185 		exit(1);
186 	}
187 
188 	/*
189 	 * Check to see if we should send mail to the owner.
190 	 */
191 	notifybymail = (strcmp(mailvar, "yes") == 0);
192 	fclose(infile);
193 
194 	/*
195 	 * Change the ownership of the spoolfile from "daemon" to the owner
196 	 * of the job.
197 	 */
198 	pwdbuf = getpwnam(owner);
199 	if (pwdbuf == NULL) {
200 		fprintf(stderr, "%s: could not find owner in passwd file\n",
201 		    spoolfile);
202 		exit(1);
203 	}
204 	if (chown(spoolfile,pwdbuf->pw_uid,pwdbuf->pw_gid) == -1) {
205 		perror(spoolfile);
206 		exit(1);
207 	}
208 
209 	/*
210 	 * Move the spoolfile to the directory where jobs are run from and
211 	 * then move into that directory.
212 	 */
213 	sprintf(runfile,"%s/%s",PASTDIR,spoolfile);
214 	rename(spoolfile, runfile);
215 	chdir(PASTDIR);
216 
217 	/*
218 	 * Create a temporary file where we will redirect errors to.
219 	 * Just to make sure we've got a unique file, we'll run an "access"
220 	 * check on the file.
221 	 */
222 	for (i = 0; i <= 1000; i += 2) {
223 		sprintf(errfile,"%s/at.err%d",TMPDIR,(getpid() + i));
224 
225 		if (access(errfile, F_OK))
226 			break;
227 
228 		if (i == 1000) {
229 			fprintf(stderr, "couldn't create errorfile.\n");
230 			exit(1);
231 		}
232 	}
233 
234 	/*
235 	 * Get the stats of the job being run.
236 	 */
237 	if (stat(runfile, &jobbuf) == -1) {
238 		perror(runfile);
239 		exit(1);
240 	}
241 
242 	/*
243 	 * Fork another child that will run the job.
244 	 */
245 	if (pid = fork()) {
246 
247 		/*
248 		 * If the child fails, save the job so that it gets
249 		 * rerun the next time "atrun" is executed and then exit.
250 		 */
251 		if (pid == -1) {
252 			chdir(ATDIR);
253 			rename(runfile, spoolfile);
254 			exit(1);
255 		}
256 
257 		/*
258 		 * Wait for the child to terminate.
259 		 */
260 		wait((int *)0);
261 
262 		/*
263 		 * Get the stats of the error file and determine the exit
264 		 * status of the child. We assume that if there is anything
265 		 * in the error file then the job ran into some errors.
266 		 */
267 		if (stat(errfile,&errbuf) != 0) {
268 			perror(errfile);
269 			exit(1);
270 		}
271 		exitstatus = ((errbuf.st_size == 0) ? NORMAL : ABNORMAL);
272 
273 		/* If errors occurred, then we send mail to the owner
274 		 * telling him/her that we ran into trouble.
275 		 *
276 		 * (NOTE: this could easily be modified so that if any
277 		 * errors occurred while running a job, mail is sent regard-
278 		 * less of whether the -m flag was set or not.
279 		 *
280 		 * i.e. rather than:
281 		 *
282 		 *	"if (notifybymail)" use
283 		 * use:
284 		 *
285 		 *	"if ((exitstatus == ABNORMAL) || (notifybymail))"
286 		 *
287 		 * It's up to you if you want to implement this.
288 		 *
289 		 */
290 		if (exitstatus == ABNORMAL || notifybymail)
291 			sendmailto(getname(jobbuf.st_uid),jobname,exitstatus);
292 
293 		/*
294 		 * Remove the errorfile and the jobfile.
295 		 */
296 		if (unlink(errfile) == -1)
297 			perror(errfile);
298 		if (unlink(runfile) == -1)
299 			perror(runfile);
300 
301 		exit(0);
302 	}
303 
304 	/*
305 	 * HERE'S WHERE WE SET UP AND FORK THE SHELL.
306 	 */
307 
308 	/*
309 	 * Run the job as the owner of the jobfile
310 	 */
311 #ifdef notdef
312 	/* This is no longer needed with the new, stripped-down quota system */
313 	quota(Q_SETUID,jobbuf.st_uid,0,0);
314 #endif
315 	setgid(jobbuf.st_gid);
316 	initgroups(getname(jobbuf.st_uid),jobbuf.st_gid);
317 	setuid(jobbuf.st_uid);
318 
319 	/*
320 	 * Close all open files so that we can reopen a temporary file
321 	 * for stdout and sterr.
322 	 */
323 	for (i = getdtablesize(); --i >= 0;)
324 		close(i);
325 
326 	/*
327 	 * Reposition stdin, stdout, and stderr.
328 	 *
329 	 *	stdin  = /dev/null
330 	 *	stout  = /dev/null
331 	 *	stderr = /tmp/at.err{pid}
332 	 *
333 	 */
334 	open("/dev/null", 0);
335 	open("/dev/null", 1);
336 	open(errfile,O_CREAT|O_WRONLY,00644);
337 
338 	/*
339 	 * Now we fork the shell.
340 	 *
341 	 * See if the shell is in /bin
342 	 */
343 	sprintf(whichshell,"/bin/%s",shell);
344 	execl(whichshell,shell,runfile, 0);
345 
346 	/*
347 	 * If not in /bin, look for the shell in /usr/bin.
348 	 */
349 	sprintf(whichshell,"/usr/bin/%s",shell);
350 	execl(whichshell,shell,runfile, 0);
351 
352 	/*
353 	 * If not in /bin, look for the shell in /usr/new.
354 	 */
355 	sprintf(whichshell,"/usr/new/%s",shell);
356 	execl(whichshell,shell,runfile, 0);
357 
358 	/*
359 	 * If we don't succeed by now, we're really having troubles,
360 	 * so we'll send the owner some mail.
361 	 */
362 	fprintf(stderr, "%s: Can't execl shell\n",shell);
363 	exit(1);
364 }
365 
366 /*
367  * Send mail to the owner of the job.
368  */
369 sendmailto(user,jobname,exitstatus)
370 char *user;
371 char *jobname;
372 int exitstatus;
373 {
374 	int ch;				/* scratch variable */
375 	char mailtouser[100];		/* the process we use to send mail */
376 	FILE *mailptr;			/* I/O stream to the mail process */
377 	FILE *errptr;			/* I/O stream to file containing error
378 					   messages */
379 	FILE *popen();			/* initiate I/O to a process */
380 
381 
382 	/*
383 	 * Create the full name for the mail process.
384 	 */
385 	sprintf(mailtouser,"%s %s",MAILER, user);
386 
387 	/*
388 	 * Open a stream to the mail process.
389 	 */
390 	if ((mailptr = popen(mailtouser,"w")) == NULL) {
391 		perror(MAILER);
392 		exit(1);
393 	}
394 
395 	/*
396 	 * Send the letter. If the job exited normally, just send a
397 	 * quick letter notifying the owner that everthing went ok.
398 	 */
399 	if (exitstatus == NORMAL) {
400 		fprintf(mailptr,"Your job \"%s\" was run without ",jobname);
401 		fprintf(mailptr,"any errors.\n");
402 	}
403 
404 	/*
405 	 * If the job exited abnormally, send a letter notifying the user
406 	 * that the job didn't run proberly. Also, send a copy of the errors
407 	 * that occurred to the user.
408 	 */
409 	else {
410 		if (exitstatus == ABNORMAL) {
411 
412 			/*
413 			 * Write the intro to the letter.
414 			 */
415 			fprintf(mailptr,"\n\nThe job you submitted to at, ");
416 			fprintf(mailptr,"\"%s\", ",jobname);
417 			fprintf(mailptr,"exited abnormally.\nA list of the ");
418 			fprintf(mailptr," errors that occurred follows:\n\n\n");
419 
420 			/*
421 			 * Open the file containing a log of the errors that
422 			 * occurred.
423 			 */
424 			if ((errptr = fopen(errfile,"r")) == NULL) {
425 				perror(errfile);
426 				exit(1);
427 			}
428 
429 			/*
430 			 * Send the copy of the errors to the owner.
431 			 */
432 			fputc('\t',mailptr);
433 			while ((ch = fgetc(errptr)) != EOF) {
434 				fputc(ch,mailptr);
435 				if (ch == (int)'\n')
436 					fputc('\t',mailptr);
437 			}
438 			fclose(errptr);
439 		}
440 	}
441 
442 	/*
443 	 * Sign the letter.
444 	 */
445 	fprintf(mailptr,"\n\n-----------------\n");
446 	fprintf(mailptr,"The Atrun Program\n");
447 
448 	/*
449 	 * Close the stream to the mail process.
450 	 */
451 	pclose(mailptr);
452 	return;
453 }
454 
455 /*
456  * Do we want to include a file in the job queue? (used by "scandir")
457  * We are looking for files whose "value" (its name) is less than or
458  * equal to the time it is right now (represented by "nowtime").
459  * We'll only consider files with three dots in their name since these
460  * are the only files that represent jobs to be run.
461  */
462 should_be_run(direntry)
463 struct direct *direntry;
464 {
465 	int numdot = 0;			/* number of dots found in a filename */
466 	char *filename;			/* pointer for scanning a filename */
467 
468 
469 	filename = direntry->d_name;
470 
471 	/*
472 	 * Count the number of dots found in the directory entry.
473 	 */
474 	while (*filename)
475 		numdot += (*(filename++) == '.');
476 
477 	/*
478 	 * If the directory entry doesn't represent a job, just return a 0.
479 	 */
480 	if (numdot != 3)
481 		return(0);
482 
483 	/*
484 	 * If a directory entry represents a job, determine if it's time to
485 	 * run it.
486 	 */
487 	return(strncmp(direntry->d_name, nowtime,11) <= 0);
488 }
489 
490 /*
491  * Record the last time that "atrun" was run.
492  */
493 updatetime()
494 {
495 
496 	struct timeval time;		/* number of seconds since 1/1/70 */
497 	struct timezone zone;		/* time zone we're in (NOT USED) */
498 	FILE *lastimefile;		/* file where recored is kept */
499 
500 	/*
501 	 * Get the time of day.
502 	 */
503 	if (gettimeofday(&time,&zone) < 0) {
504 		perror("gettimeofday");
505 		exit(1);
506 	}
507 
508 	/*
509 	 * Open the record file.
510 	 */
511 	if ((lastimefile = fopen(LASTFILE, "w")) == NULL) {
512 		fprintf(stderr, "can't update lastfile: ");
513 		perror(LASTFILE);
514 		exit(1);
515 	}
516 
517 	/*
518 	 * Record the last update time (in seconds since 1/1/70).
519 	 */
520 	fprintf(lastimefile, "%d\n", (u_long) time.tv_sec);
521 
522 	/*
523 	 * Close the record file.
524 	 */
525 	fclose(lastimefile);
526 }
527 
528 /*
529  * Get the full login name of a person using his/her user id.
530  */
531 char *
532 getname(uid)
533 int uid;
534 {
535 	struct passwd *pwdinfo;			/* password info structure */
536 
537 
538 	if ((pwdinfo = getpwuid(uid)) == 0) {
539 		perror(uid);
540 		exit(1);
541 	}
542 	return(pwdinfo->pw_name);
543 }
544