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