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