documented (slightly) the directory linking capability
[unix-history] / usr / src / usr.bin / at / atrun / atrun.c
CommitLineData
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
8char 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 14static 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
51char nowtime[11]; /* time it is right now (yy.ddd.hhmm) */
52char errfile[25]; /* file where we redirect errors to */
cf2bca11 53
cf2bca11
BJ
54
55main(argc, argv)
56char **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 */
105makenowtime(nowtime)
106char *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 */
140run(spoolfile)
141char *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 */
370sendmailto(user,jobname,exitstatus)
371char *user;
372char *jobname;
373int 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 */
463should_be_run(direntry)
464struct 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 */
494updatetime()
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 */
532char *
533getname(uid)
534int 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}