148235Sbostic /*-
2*64038Sbostic * Copyright (c) 1983, 1993
3*64038Sbostic * The Regents of the University of California. All rights reserved.
448235Sbostic *
548235Sbostic * %sccs.include.proprietary.c%
622449Sdist */
722449Sdist
813708Ssam #ifndef lint
9*64038Sbostic static char copyright[] =
10*64038Sbostic "@(#) Copyright (c) 1983, 1993\n\
11*64038Sbostic The Regents of the University of California. All rights reserved.\n";
1248235Sbostic #endif /* not lint */
1322449Sdist
1422449Sdist #ifndef lint
15*64038Sbostic static char sccsid[] = "@(#)atrun.c 8.1 (Berkeley) 07/26/93";
1648235Sbostic #endif /* not lint */
1722449Sdist
181187Sbill /*
1916827Smckusick * Synopsis: atrun
2016823Swall *
2116823Swall *
2216827Smckusick * Run jobs created by at(1)
2316823Swall *
2416823Swall *
2516827Smckusick * Modifications by: Steve Wall
2616827Smckusick * Computer Systems Research Group
2716827Smckusick * University of California @ Berkeley
2816823Swall *
291187Sbill */
3016823Swall # include <stdio.h>
3130197Sbostic # include <sys/param.h>
3216823Swall # include <sys/dir.h>
3316823Swall # include <sys/file.h>
3416823Swall # include <sys/time.h>
3528871Smckusick #ifdef notdef
3622447Skjd # include <sys/quota.h>
3728871Smckusick #endif
3816823Swall # include <sys/stat.h>
3916823Swall # include <pwd.h>
4037962Sbostic # include "pathnames.h"
411187Sbill
4216827Smckusick # define NORMAL 0 /* job exited normally */
4316827Smckusick # define ABNORMAL 1 /* job exited abnormally */
441187Sbill
4516827Smckusick char nowtime[11]; /* time it is right now (yy.ddd.hhmm) */
4616827Smckusick char errfile[25]; /* file where we redirect errors to */
4716823Swall
4816823Swall
main(argc,argv)491187Sbill main(argc, argv)
501187Sbill char **argv;
511187Sbill {
521187Sbill
5316827Smckusick int i; /* for loop index */
5416827Smckusick int numjobs; /* number of jobs to be run */
5516827Smckusick int should_be_run(); /* should a job be run? */
5616827Smckusick struct direct **jobqueue; /* queue of jobs to be run */
5716823Swall
5816823Swall
5916827Smckusick /*
6016827Smckusick * Move to the spooling area.
6116827Smckusick */
6237962Sbostic chdir(_PATH_ATDIR);
6316823Swall
6416827Smckusick /*
6516827Smckusick * Create a filename that represents the time it is now. This is used
6616827Smckusick * to determine if the execution time for a job has arrived.
6716827Smckusick */
6816827Smckusick makenowtime(nowtime);
6916823Swall
7016827Smckusick /*
7116827Smckusick * Create a queue of the jobs that should be run.
7216827Smckusick */
7316827Smckusick if ((numjobs = scandir(".",&jobqueue,should_be_run, 0)) < 0) {
7437962Sbostic perror(_PATH_ATDIR);
7516827Smckusick exit(1);
7616827Smckusick }
7716823Swall
7816827Smckusick /*
7916827Smckusick * If there are jobs to be run, run them.
8016827Smckusick */
8116827Smckusick if (numjobs > 0) {
8216827Smckusick for (i = 0; i < numjobs; ++i) {
8316827Smckusick run(jobqueue[i]->d_name);
8416827Smckusick }
8516827Smckusick }
8616823Swall
8716827Smckusick /*
8816827Smckusick * Record the last update time.
8916827Smckusick */
9016827Smckusick updatetime();
9116823Swall
921187Sbill }
931187Sbill
9416823Swall /*
9516823Swall * Create a string with the syntax yy.ddd.hhmm that represents the
9616823Swall * time it is right now. This string is used to determine whether a
9716823Swall * job should be run.
9816823Swall */
makenowtime(nowtime)9916823Swall makenowtime(nowtime)
10016823Swall char *nowtime;
1011187Sbill {
10216827Smckusick struct tm *now; /* broken down representation of the
10316827Smckusick time it is right now */
10416827Smckusick struct timeval time; /* number of seconds since 1/1/70 */
10516827Smckusick struct timezone zone; /* time zone we're in (NOT USED) */
1061187Sbill
10716827Smckusick /*
10816827Smckusick * Get the time of day.
10916827Smckusick */
11016827Smckusick if (gettimeofday(&time,&zone) < 0) {
11116827Smckusick perror("gettimeofday");
11216827Smckusick exit(1);
11316827Smckusick }
11416823Swall
11516827Smckusick /*
11616827Smckusick * Get a broken down representation of the time it is right now.
11716827Smckusick */
11816827Smckusick now = localtime(&time.tv_sec);
11916823Swall
12016827Smckusick /*
12116827Smckusick * Create a string to be used in determining whether or not a job
12216827Smckusick * should be run. The syntax is yy.ddd.hhmm .
12316827Smckusick */
12416827Smckusick sprintf(nowtime,"%d.%03d.%02d%02d",now->tm_year,
12516827Smckusick now->tm_yday,
12616827Smckusick now->tm_hour,
12716827Smckusick now->tm_min);
12816827Smckusick return;
1291187Sbill }
1301187Sbill
13116823Swall /*
13216823Swall * Run a job.
13316823Swall */
run(spoolfile)13416823Swall run(spoolfile)
13516823Swall char *spoolfile;
1361187Sbill {
13716827Smckusick int i; /* scratch variable */
13816827Smckusick int pid; /* process id of forked shell */
13916827Smckusick int exitstatus; /* exit status of the job */
14016827Smckusick int notifybymail; /* should we notify the owner of the
14116827Smckusick job after the job is run? */
14216827Smckusick char shell[4]; /* shell to run the job under */
14316827Smckusick char *getname(); /* get a uname from using a uid */
14416827Smckusick char mailvar[4]; /* send mail variable ("yes" or "no") */
14516827Smckusick char runfile[100]; /* file sent to forked shell for exec-
14616827Smckusick ution */
14728871Smckusick char owner[128]; /* owner of job we're going to run */
14828871Smckusick char jobname[128]; /* name of job we're going to run */
14916827Smckusick char whichshell[100]; /* which shell should we fork off? */
15016867Swall struct passwd *pwdbuf; /* password info of the owner of job */
15116827Smckusick struct stat errbuf; /* stats on error file */
15216827Smckusick struct stat jobbuf; /* stats on job file */
15316827Smckusick FILE *infile; /* I/O stream to spoolfile */
1541187Sbill
15516823Swall
15616827Smckusick /*
15716827Smckusick * First we fork a child so that the main can run other jobs.
15816827Smckusick */
15916827Smckusick if (pid = fork())
16016827Smckusick return;
16116823Swall
16216827Smckusick /*
16316827Smckusick * Open the spoolfile.
16416827Smckusick */
16516827Smckusick if ((infile = fopen(spoolfile,"r")) == NULL) {
16616827Smckusick perror(spoolfile);
16737349Sedward (void) unlink(spoolfile);
16816827Smckusick exit(1);
16916827Smckusick }
17016823Swall
17116827Smckusick /*
17228871Smckusick * Grab the 4-line header out of the spoolfile.
17316827Smckusick */
17428871Smckusick if (
17564037Sbostic (fscanf(infile,"# owner: %127s\n",owner) != 1) ||
17664037Sbostic (fscanf(infile,"# jobname: %127s\n",jobname) != 1) ||
17764037Sbostic (fscanf(infile,"# shell: %3s\n",shell) != 1) ||
17864037Sbostic (fscanf(infile,"# notify by mail: %3s\n",mailvar) != 1)
17928871Smckusick ) {
18028871Smckusick fprintf(stderr, "%s: bad spool header\n", spoolfile);
18137349Sedward (void) unlink(spoolfile);
18228871Smckusick exit(1);
18328871Smckusick }
18416823Swall
18516827Smckusick /*
18616827Smckusick * Check to see if we should send mail to the owner.
18716827Smckusick */
18816827Smckusick notifybymail = (strcmp(mailvar, "yes") == 0);
18916827Smckusick fclose(infile);
19016823Swall
19116827Smckusick /*
19216867Swall * Change the ownership of the spoolfile from "daemon" to the owner
19316867Swall * of the job.
19416867Swall */
19516867Swall pwdbuf = getpwnam(owner);
19628871Smckusick if (pwdbuf == NULL) {
19728871Smckusick fprintf(stderr, "%s: could not find owner in passwd file\n",
19828871Smckusick spoolfile);
19937349Sedward (void) unlink(spoolfile);
20028871Smckusick exit(1);
20128871Smckusick }
20216867Swall if (chown(spoolfile,pwdbuf->pw_uid,pwdbuf->pw_gid) == -1) {
20316867Swall perror(spoolfile);
20437349Sedward (void) unlink(spoolfile);
20516867Swall exit(1);
20616867Swall }
20716867Swall
20816867Swall /*
20916827Smckusick * Move the spoolfile to the directory where jobs are run from and
21016827Smckusick * then move into that directory.
21116827Smckusick */
21237962Sbostic sprintf(runfile,"%s/%s",_PATH_PAST,spoolfile);
21316827Smckusick rename(spoolfile, runfile);
21437962Sbostic chdir(_PATH_PAST);
21516823Swall
21616827Smckusick /*
21716827Smckusick * Create a temporary file where we will redirect errors to.
21816827Smckusick * Just to make sure we've got a unique file, we'll run an "access"
21916827Smckusick * check on the file.
22016827Smckusick */
22116827Smckusick for (i = 0; i <= 1000; i += 2) {
22237962Sbostic sprintf(errfile,"%s/at.err%d",_PATH_TMP,(getpid() + i));
22316823Swall
22416827Smckusick if (access(errfile, F_OK))
22516827Smckusick break;
22616823Swall
22716827Smckusick if (i == 1000) {
22816827Smckusick fprintf(stderr, "couldn't create errorfile.\n");
22916827Smckusick exit(1);
23016827Smckusick }
23116827Smckusick }
23216823Swall
23316827Smckusick /*
23416827Smckusick * Get the stats of the job being run.
23516827Smckusick */
23616827Smckusick if (stat(runfile, &jobbuf) == -1) {
23716827Smckusick perror(runfile);
23816827Smckusick exit(1);
23916827Smckusick }
24016823Swall
24116827Smckusick /*
24216827Smckusick * Fork another child that will run the job.
24316827Smckusick */
24416827Smckusick if (pid = fork()) {
24516823Swall
24616827Smckusick /*
24716827Smckusick * If the child fails, save the job so that it gets
24816827Smckusick * rerun the next time "atrun" is executed and then exit.
24916827Smckusick */
25016827Smckusick if (pid == -1) {
25137962Sbostic chdir(_PATH_ATDIR);
25216827Smckusick rename(runfile, spoolfile);
25316827Smckusick exit(1);
25416827Smckusick }
25516823Swall
25616827Smckusick /*
25716827Smckusick * Wait for the child to terminate.
25816827Smckusick */
25916827Smckusick wait((int *)0);
26016823Swall
26116827Smckusick /*
26216827Smckusick * Get the stats of the error file and determine the exit
26316827Smckusick * status of the child. We assume that if there is anything
26416827Smckusick * in the error file then the job ran into some errors.
26516827Smckusick */
26616827Smckusick if (stat(errfile,&errbuf) != 0) {
26716827Smckusick perror(errfile);
26816827Smckusick exit(1);
26916827Smckusick }
27016827Smckusick exitstatus = ((errbuf.st_size == 0) ? NORMAL : ABNORMAL);
27116823Swall
27228868Sbloom /* If errors occurred, then we send mail to the owner
27316827Smckusick * telling him/her that we ran into trouble.
27416827Smckusick *
27516827Smckusick * (NOTE: this could easily be modified so that if any
27628868Sbloom * errors occurred while running a job, mail is sent regard-
27716827Smckusick * less of whether the -m flag was set or not.
27816827Smckusick *
27916827Smckusick * i.e. rather than:
28016827Smckusick *
28116827Smckusick * "if (notifybymail)" use
28216827Smckusick * use:
28316827Smckusick *
28416827Smckusick * "if ((exitstatus == ABNORMAL) || (notifybymail))"
28516827Smckusick *
28616827Smckusick * It's up to you if you want to implement this.
28716827Smckusick *
28816827Smckusick */
28924912Sserge if (exitstatus == ABNORMAL || notifybymail)
29016827Smckusick sendmailto(getname(jobbuf.st_uid),jobname,exitstatus);
29116823Swall
29216827Smckusick /*
29316827Smckusick * Remove the errorfile and the jobfile.
29416827Smckusick */
29516827Smckusick if (unlink(errfile) == -1)
29616827Smckusick perror(errfile);
29716827Smckusick if (unlink(runfile) == -1)
29816827Smckusick perror(runfile);
29916823Swall
30016827Smckusick exit(0);
30116827Smckusick }
30216823Swall
30316827Smckusick /*
30416827Smckusick * HERE'S WHERE WE SET UP AND FORK THE SHELL.
30516827Smckusick */
30616823Swall
30716827Smckusick /*
30816827Smckusick * Run the job as the owner of the jobfile
30916827Smckusick */
31028871Smckusick #ifdef notdef
31128871Smckusick /* This is no longer needed with the new, stripped-down quota system */
31222447Skjd quota(Q_SETUID,jobbuf.st_uid,0,0);
31328871Smckusick #endif
31416827Smckusick setgid(jobbuf.st_gid);
31522447Skjd initgroups(getname(jobbuf.st_uid),jobbuf.st_gid);
31616827Smckusick setuid(jobbuf.st_uid);
31716823Swall
31816827Smckusick /*
31916827Smckusick * Close all open files so that we can reopen a temporary file
32016827Smckusick * for stdout and sterr.
32116827Smckusick */
32222447Skjd for (i = getdtablesize(); --i >= 0;)
32316827Smckusick close(i);
32416823Swall
32516827Smckusick /*
32616827Smckusick * Reposition stdin, stdout, and stderr.
32716827Smckusick *
32816827Smckusick * stdin = /dev/null
32916827Smckusick * stout = /dev/null
33016827Smckusick * stderr = /tmp/at.err{pid}
33116827Smckusick *
33216827Smckusick */
33337962Sbostic open(_PATH_DEVNULL, 0);
33437962Sbostic open(_PATH_DEVNULL, 1);
33516827Smckusick open(errfile,O_CREAT|O_WRONLY,00644);
33616823Swall
33716827Smckusick /*
33816827Smckusick * Now we fork the shell.
33916827Smckusick *
34016827Smckusick * See if the shell is in /bin
34116827Smckusick */
34216827Smckusick sprintf(whichshell,"/bin/%s",shell);
34316827Smckusick execl(whichshell,shell,runfile, 0);
34416823Swall
34516827Smckusick /*
34616827Smckusick * If we don't succeed by now, we're really having troubles,
34716827Smckusick * so we'll send the owner some mail.
34816827Smckusick */
34916827Smckusick fprintf(stderr, "%s: Can't execl shell\n",shell);
35022447Skjd exit(1);
3511187Sbill }
3521187Sbill
35316823Swall /*
35416823Swall * Send mail to the owner of the job.
35516823Swall */
sendmailto(user,jobname,exitstatus)35616823Swall sendmailto(user,jobname,exitstatus)
35716823Swall char *user;
35816823Swall char *jobname;
35916823Swall int exitstatus;
3601187Sbill {
36130197Sbostic int ch; /* scratch variable */
36216827Smckusick char mailtouser[100]; /* the process we use to send mail */
36316827Smckusick FILE *mailptr; /* I/O stream to the mail process */
36416827Smckusick FILE *errptr; /* I/O stream to file containing error
36516827Smckusick messages */
36616827Smckusick FILE *popen(); /* initiate I/O to a process */
3671187Sbill
36816823Swall
36916827Smckusick /*
37016827Smckusick * Create the full name for the mail process.
37116827Smckusick */
37237962Sbostic sprintf(mailtouser,"%s %s", _PATH_MAIL, user);
37316823Swall
37416827Smckusick /*
37516827Smckusick * Open a stream to the mail process.
37616827Smckusick */
37716827Smckusick if ((mailptr = popen(mailtouser,"w")) == NULL) {
37837962Sbostic perror(_PATH_MAIL);
37916827Smckusick exit(1);
38016827Smckusick }
38116823Swall
38216827Smckusick /*
38316827Smckusick * Send the letter. If the job exited normally, just send a
38416827Smckusick * quick letter notifying the owner that everthing went ok.
38516827Smckusick */
38616827Smckusick if (exitstatus == NORMAL) {
38716827Smckusick fprintf(mailptr,"Your job \"%s\" was run without ",jobname);
38816827Smckusick fprintf(mailptr,"any errors.\n");
38916827Smckusick }
39016823Swall
39116827Smckusick /*
39216827Smckusick * If the job exited abnormally, send a letter notifying the user
39316827Smckusick * that the job didn't run proberly. Also, send a copy of the errors
39428868Sbloom * that occurred to the user.
39516827Smckusick */
39616827Smckusick else {
39716827Smckusick if (exitstatus == ABNORMAL) {
39816823Swall
39916827Smckusick /*
40016827Smckusick * Write the intro to the letter.
40116827Smckusick */
40216827Smckusick fprintf(mailptr,"\n\nThe job you submitted to at, ");
40316827Smckusick fprintf(mailptr,"\"%s\", ",jobname);
40416827Smckusick fprintf(mailptr,"exited abnormally.\nA list of the ");
40528868Sbloom fprintf(mailptr," errors that occurred follows:\n\n\n");
40616823Swall
40716827Smckusick /*
40816827Smckusick * Open the file containing a log of the errors that
40928868Sbloom * occurred.
41016827Smckusick */
41116827Smckusick if ((errptr = fopen(errfile,"r")) == NULL) {
41216827Smckusick perror(errfile);
41316827Smckusick exit(1);
41416827Smckusick }
41516823Swall
41616827Smckusick /*
41716827Smckusick * Send the copy of the errors to the owner.
41816827Smckusick */
41916827Smckusick fputc('\t',mailptr);
42016827Smckusick while ((ch = fgetc(errptr)) != EOF) {
42116827Smckusick fputc(ch,mailptr);
42230197Sbostic if (ch == (int)'\n')
42316827Smckusick fputc('\t',mailptr);
42416827Smckusick }
42516827Smckusick fclose(errptr);
42616827Smckusick }
42716827Smckusick }
42816823Swall
42916827Smckusick /*
43016827Smckusick * Sign the letter.
43116827Smckusick */
43216827Smckusick fprintf(mailptr,"\n\n-----------------\n");
43316827Smckusick fprintf(mailptr,"The Atrun Program\n");
43416823Swall
43516827Smckusick /*
43616827Smckusick * Close the stream to the mail process.
43716827Smckusick */
43816827Smckusick pclose(mailptr);
43916827Smckusick return;
4401187Sbill }
44116823Swall
44216823Swall /*
44316823Swall * Do we want to include a file in the job queue? (used by "scandir")
44416823Swall * We are looking for files whose "value" (its name) is less than or
44516823Swall * equal to the time it is right now (represented by "nowtime").
44616823Swall * We'll only consider files with three dots in their name since these
44716823Swall * are the only files that represent jobs to be run.
44816823Swall */
44916823Swall should_be_run(direntry)
45016823Swall struct direct *direntry;
45116823Swall {
45216827Smckusick int numdot = 0; /* number of dots found in a filename */
45316827Smckusick char *filename; /* pointer for scanning a filename */
45416823Swall
45516823Swall
45616827Smckusick filename = direntry->d_name;
45716823Swall
45816827Smckusick /*
45916827Smckusick * Count the number of dots found in the directory entry.
46016827Smckusick */
46116827Smckusick while (*filename)
46216827Smckusick numdot += (*(filename++) == '.');
46316823Swall
46416827Smckusick /*
46516827Smckusick * If the directory entry doesn't represent a job, just return a 0.
46616827Smckusick */
46716827Smckusick if (numdot != 3)
46816827Smckusick return(0);
46916823Swall
47016827Smckusick /*
47116827Smckusick * If a directory entry represents a job, determine if it's time to
47216827Smckusick * run it.
47316827Smckusick */
47416827Smckusick return(strncmp(direntry->d_name, nowtime,11) <= 0);
47516823Swall }
47616823Swall
47716823Swall /*
47816823Swall * Record the last time that "atrun" was run.
47916823Swall */
updatetime()48016823Swall updatetime()
48116823Swall {
48216823Swall
48316827Smckusick struct timeval time; /* number of seconds since 1/1/70 */
48416827Smckusick struct timezone zone; /* time zone we're in (NOT USED) */
48516827Smckusick FILE *lastimefile; /* file where recored is kept */
48616823Swall
48716827Smckusick /*
48816827Smckusick * Get the time of day.
48916827Smckusick */
49016827Smckusick if (gettimeofday(&time,&zone) < 0) {
49116827Smckusick perror("gettimeofday");
49216827Smckusick exit(1);
49316827Smckusick }
49416823Swall
49516827Smckusick /*
49616827Smckusick * Open the record file.
49716827Smckusick */
49837962Sbostic if ((lastimefile = fopen(_PATH_LASTFILE, "w")) == NULL) {
49916827Smckusick fprintf(stderr, "can't update lastfile: ");
50037962Sbostic perror(_PATH_LASTFILE);
50116827Smckusick exit(1);
50216827Smckusick }
50316823Swall
50416827Smckusick /*
50516827Smckusick * Record the last update time (in seconds since 1/1/70).
50616827Smckusick */
50716827Smckusick fprintf(lastimefile, "%d\n", (u_long) time.tv_sec);
50816823Swall
50916827Smckusick /*
51016827Smckusick * Close the record file.
51116827Smckusick */
51216827Smckusick fclose(lastimefile);
51316823Swall }
51416823Swall
51516823Swall /*
51616823Swall * Get the full login name of a person using his/her user id.
51716823Swall */
51816823Swall char *
getname(uid)51916823Swall getname(uid)
52046818Sbostic int uid;
52116823Swall {
52216827Smckusick struct passwd *pwdinfo; /* password info structure */
52316827Smckusick
52416827Smckusick if ((pwdinfo = getpwuid(uid)) == 0) {
52546818Sbostic (void)fprintf(stderr, "atrun: %d: no such user uid\n");
52616827Smckusick exit(1);
52716827Smckusick }
52816827Smckusick return(pwdinfo->pw_name);
52916823Swall }
530