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