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