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