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
main(argc,argv)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 */
makenowtime(nowtime)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 */
run(spoolfile)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 */
sendmailto(user,jobname,exitstatus)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 */
updatetime()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 *
getname(uid)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