backward compatible processing for "+/pattern"
[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
1a510e07 14static 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
50char nowtime[11]; /* time it is right now (yy.ddd.hhmm) */
51char errfile[25]; /* file where we redirect errors to */
cf2bca11 52
cf2bca11
BJ
53
54main(argc, argv)
55char **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 */
104makenowtime(nowtime)
105char *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 */
139run(spoolfile)
140char *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 */
369sendmailto(user,jobname,exitstatus)
370char *user;
371char *jobname;
372int 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 */
462should_be_run(direntry)
463struct 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 */
493updatetime()
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 */
531char *
532getname(uid)
533int 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}