* Copyright (c) 1983 Regents of the University of California.
* All rights reserved. The Berkeley software License Agreement
* specifies the terms and conditions for redistribution.
"@(#) Copyright (c) 1983 Regents of the University of California.\n\
static char sccsid
[] = "@(#)at.c 5.5 (Berkeley) 1/18/87";
* Synopsis: at [-s] [-c] [-m] time [filename]
* Execute commands at a later date.
* Modifications by: Steve Wall
* Computer Systems Research Group
* University of California @ Berkeley
#define HOUR 100 /* 1 hour (using military time) */
#define HALFDAY (12 * HOUR) /* half a day (12 hours) */
#define FULLDAY (24 * HOUR) /* a full day (24 hours) */
#define WEEK 1 /* day requested is 'week' */
#define DAY 2 /* day requested is a weekday */
#define MONTH 3 /* day requested is a month */
#define BOURNE "/bin/sh" /* run commands with Bourne shell*/
#define CSHELL "/bin/csh" /* run commands with C shell */
#define NODATEFOUND -1 /* no date was given on command line */
#define ATDIR "/usr/spool/at" /* spooling area */
#define LINSIZ 256 /* length of input buffer */
* A table to identify potential command line values for "time".
* We need this so that we can do some decent error checking on the
* command line arguments. (This was inspired by the old "at", which
* accepted "at 900 jan 55" as valid input and other small bugs.
"jan", "feb", "mar", "apr", "may", "jun",
"jul", "aug", "sep", "oct", "nov", "dec", 0,
* A table of the number of days in each month of the year.
* yeartable[0] -- normal year
* yeartable[1] -- leap year
static int yeartable
[2][13] = {
{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
* Structure holding the relevant values needed to create a spoolfile.
* "attime" will contain the info about when a job is to be run, and
* "nowtime" will contain info about what time the "at" command is in-
int year
; /* year that job is to be run */
int yday
; /* day of year that job is to be run */
int mon
; /* month of year that job is to be run*/
int mday
; /* day of month that job is to be run */
int wday
; /* day of week that job is to be run */
int hour
; /* hour of day that job is to be run */
int min
; /* min. of hour that job is to be run */
char atfile
[100]; /* name of spoolfile "yy.ddd.hhhh.??" */
char *getenv(); /* get info on user's environment */
char **environ
; /* user's environment */
FILE *spoolfile
; /* spool file */
FILE *inputfile
; /* input file ("stdin" or "filename") */
char *getwd(); /* used to get current directory info */
int c
; /* scratch variable */
int usage(); /* print usage info and exit */
int cleanup(); /* do cleanup on an interrupt signal */
int dateindex
= NODATEFOUND
; /* if a day is specified, what option
is it? (mon day, week, dayofweek) */
char *shell
= BOURNE
; /* what shell do we use to run job? */
int shflag
= 0; /* override the current shell and run
job using the Bourne Shell */
int cshflag
= 0; /* override the current shell and run
int mailflag
= 0; /* send mail after a job has been run?*/
int standardin
= 0; /* are we reading from stardard input */
char *tmp
; /* scratch pointer */
char line
[LINSIZ
]; /* a line from input file */
char pwbuf
[MAXPATHLEN
]; /* the current working directory */
char *jobfile
= "stdin"; /* file containing job to be run */
char *getname(); /* get the login name of a user */
int pid
; /* For forking for security reasons */
* Interpret command line flags if they exist.
while (argc
> 0 && **argv
== '-') {
while (**argv
) switch (*(*argv
)++) {
fprintf(stderr
,"ambiguous shell request.\n");
* Get the time it is when "at" is invoked. We set both nowtime and
* attime to this value so that as we interpret the time the job is to
* be run we can compare the two values to determine such things as
* whether of not the job should be run the same day the "at" command
* is given, whether a job is to be run next year, etc.
getnowtime(&nowtime
, &attime
);
* Interpret argv[1] and create the time of day that the job is to
* be run. This is the same function that was used in the old "at"
maketime(&attime
, *argv
);
printf("\n\nAFTER MAKETIME\n");
* If argv[(2)] exists, this is a request to run a job on a certain
* day of year or a certain day of week.
* We send argv to the function "getdateindex" which returns the
* index value of the requested day in the table "dates_info"
* (see line 50 for table). If 'getdateindex" returns a NODATEFOUND,
* then the requested day format was not found in the table (usually
* this means that the argument is a "filename"). If the requested
* day is found, we continue to process command line arguments.
if ((dateindex
= getdateindex(*argv
)) != NODATEFOUND
) {
* Determine the day of year that the job will be run
* depending on the value of argv.
makedayofyear(dateindex
, &argv
, &argc
);
* If we get to this point and "dateindex" is set to NODATEFOUND,
* then we are dealing with a request with only a "time" specified
* (i.e. at 400p) and perhaps 'week' specified (i.e. at 400p week).
* If 'week' is specified, we just set excecution for 7 days in the
* future. Otherwise, we need to check to see if the requested time
* has already passed for the current day. If it has, then we add
* one to the day of year that the job will be executed.
if (dateindex
== NODATEFOUND
) {
if ((argc
> 0) && (strcmp(*argv
,"week") == 0)) {
daysinyear
= isleap(attime
.year
) ? 366 : 365;
if (attime
.yday
>= daysinyear
) {
attime
.yday
-= daysinyear
;
* If no more arguments exist, then we are reading
* from standard input. Thus, we set the standard
* input flag (++standardin).
printf("\n\nAFTER ADDDAYS\n");
* Start off assuming we're going to read from standard input,
* but if a filename has been given to read from, we will open it
* Create the filename for the spoolfile.
makeatfile(atfile
,attime
.year
,attime
.yday
,attime
.hour
,attime
.min
);
* Open the spoolfile for writing.
if ((spoolfile
= fopen(atfile
, "w")) == NULL
){
* Make the file not world readable.
fchmod(fileno(spoolfile
), 0400);
* The protection mechanism works like this:
* We are running ruid=user, euid=spool owner. So far we have been
* messing around in the spool directory, so we needed to run
* as the owner of the spool directory.
* We now need to switch to the user's effective uid
* to simplify permission checking. However, we fork first,
* so that we can clean up if interrupted.
* We are the parent. If the kid has problems,
* cleanup the spool directory.
if (wpid
!= pid
|| status
) {
* The kid should have alread flushed the buffers.
* We are the kid, give up special permissions.
* Open the input file with the user's permissions.
if ((inputfile
= fopen(jobfile
, "r")) == NULL
) {
* Determine what shell we should use to run the job. If the user
* didn't explicitly request that his/her current shell be over-
* ridden (shflag of cshflag) then we use the current shell.
if ((!shflag
) && (!cshflag
) && (getenv("SHELL") != NULL
))
* Put some standard information at the top of the spoolfile.
* This info is used by the other "at"-oriented programs (atq,
fprintf(spoolfile
, "# owner: %.127s\n",getname(getuid()));
fprintf(spoolfile
, "# jobname: %.127s\n",jobfile
);
fprintf(spoolfile
, "# shell: sh\n");
fprintf(spoolfile
, "# notify by mail: %s\n",(mailflag
) ? "yes" : "no");
fprintf(spoolfile
, "\n");
* Set the modes for any files created by the job being run.
fprintf(spoolfile
, "umask %.1o\n", c
);
* Get the current working directory so we know what directory to
if (getwd(pwbuf
) == NULL
) {
fprintf(stderr
, "at: can't get working directory\n");
fprintf(spoolfile
, "cd %s\n", pwbuf
);
* Copy the user's environment to the spoolfile.
copyenvironment(&spoolfile
);
* Put in a line to run the proper shell using the rest of
* the file as input. Note that 'exec'ing the shell will
* cause sh() to leave a /tmp/sh### file around. This line
* depends on the shells allowing EOF to end tagged input. The
* quotes also guarantee a quoting of the lines before EOF.
fprintf(spoolfile
, "%s << 'QAZWSXEDCRFVTGBYHNUJMIKOLP'\n", shell
);
* Now that we have all the files set up, we can start reading in
while (fgets(line
, LINSIZ
, inputfile
) != NULL
)
* Close all files and change the mode of the spoolfile.
* Copy the user's environment to the spoolfile in the syntax of the
* Bourne shell. After the environment is set up, the proper shell
copyenvironment(spoolfile
)
char *tmp
; /* scratch pointer */
char **environptr
= environ
; /* pointer to an environment setting */
* We don't want the termcap or terminal entry so skip them.
if ((strncmp(tmp
,"TERM=",5) == 0) ||
(strncmp(tmp
,"TERMCAP=",8) == 0)) {
* Set up the proper syntax.
fputc(*tmp
++,*spoolfile
);
fputc('\'' , *spoolfile
);
fputs("'\\''", *spoolfile
);
fputc('\'' , *spoolfile
);
* We need to "export" environment settings.
fprintf(*spoolfile
, "\nexport ");
fputc(*tmp
++,*spoolfile
);
* Create the filename for the spoolfile. The format is "yy.ddd.mmmm.??"
* where "yy" is the year the job will be run, "ddd" the day of year,
* "mmmm" the hour and minute, and "??" a scratch value used to dis-
* tinguish between two files that are to be run at the same time.
makeatfile(atfile
,year
,dayofyear
,hour
,minute
)
int i
; /* scratch variable */
sprintf(atfile
, "%s/%02d.%03d.%02d%02d.%02d", ATDIR
, year
,
dayofyear
, hour
, minute
, (getpid() + i
) % 100);
* Make sure that the file name that we've created is unique.
if (access(atfile
, F_OK
) == -1)
* Has the requested time already passed for the currrent day? If so, we
* will run the job "tomorrow".
if (attime
.hour
< nowtime
.hour
)
if ((attime
.hour
== nowtime
.hour
) && (attime
.min
< nowtime
.min
))
printf("YEAR\tnowtime: %d\tattime: %d\n",nowtime
.year
,attime
.year
);
printf("YDAY\tnowtime: %d\tattime: %d\n",nowtime
.yday
,attime
.yday
);
printf("MON\tnowtime: %d\tattime: %d\n",nowtime
.mon
,attime
.mon
);
printf("MONDAY\tnowtime: %d\tattime: %d\n",nowtime
.mday
,attime
.mday
);
printf("WDAY\tnowtime: %d\tattime: %d\n",nowtime
.wday
,attime
.wday
);
printf("HOUR\tnowtime: %d\tattime: %d\n",nowtime
.hour
,attime
.hour
);
printf("MIN\tnowtime: %d\tattime: %d\n",nowtime
.min
,attime
.min
);
* Calculate the day of year that the job will be executed.
* The av,ac arguments are ptrs to argv,argc; updated as necessary.
makedayofyear(dateindex
, av
, ac
)
char **argv
= *av
; /* imitate argc,argv and update args at end */
char *ptr
; /* scratch pointer */
struct datetypes
*daterequested
; /* pointer to information about
daterequested
= &dates_info
[dateindex
];
* If we're dealing with a day of week, determine the number of days
* in the future the next day of this type will fall on. Add this
* value to "attime.yday".
if (daterequested
->type
== DAY
) {
if (attime
.wday
< dateindex
)
attime
.yday
+= dateindex
- attime
.wday
;
else if(attime
.wday
> dateindex
)
attime
.yday
+= (7 - attime
.wday
) + dateindex
;
* If we're dealing with a month and day of month, determine the
* day of year that this date will fall on.
if (daterequested
->type
== MONTH
) {
* If a day of month isn't specified, print a message
fprintf(stderr
,"day of month not specified.\n");
* Scan the day of month value and make sure that it
* has no characters in it. If characters are found or
* the day requested is zero, print a message and exit.
if ((*ptr
!= '\0') || (atoi(*argv
) == 0)) {
fprintf(stderr
,"\"%s\": illegal day of month\n",*argv
);
* Set the month of year and day of month values. Since
* the first 7 values in our dateinfo table do not deal
* with month names, we subtract 7 from the month of year
attime
.mon
= (dateindex
- 7);
attime
.mday
= (atoi(*argv
) - 1);
* Test the day of month value to make sure that the
yeartable
[isleap(attime
.year
)][attime
.mon
+ 1]) {
fprintf(stderr
,"\"%s\": illegal day of month\n",*argv
);
* Finally, we determine the day of year.
attime
.yday
= (countdays());
* If 'week' is specified, add 7 to the day of year.
if ((argc
> 0) && (strcmp(*argv
,"week") == 0)) {
* Now that all that is done, see if the requested execution time
* has already passed for this year, and if it has, set execution
* Finally, reflect the updated argc,argv to the caller
* Should the job be run next year? We check for the following situations:
* 1) the requested time has already passed for the current year.
* 2) the day of year is greater than the number of days in the year.
* If either of these tests succeed, we increment "attime.year" by 1.
* If #2 is true, we also subtract the number of days in the current year
* from "attime.yday". #2 can only occur if someone specifies a job to
* be run "tomorrow" on Dec. 31 or if they specify a job to be run a
* 'week' later and the date is at least Dec. 24. (I think so anyway)
if (attime
.yday
< nowtime
.yday
)
if ((attime
.yday
== nowtime
.yday
) && (attime
.hour
< nowtime
.hour
))
daysinyear
= isleap(attime
.year
) ? 366 : 365;
if (attime
.yday
>= daysinyear
) {
attime
.yday
-= daysinyear
;
if (attime
.yday
> (isleap(attime
.year
) ? 366 : 365)) {
attime
.yday
-= (isleap(attime
.year
) ? 366 : 365);
* Determine the day of year given a month and day of month value.
int leap
; /* are we dealing with a leap year? */
int dayofyear
; /* the day of year after conversion */
int monthofyear
; /* the month of year that we are
* Are we dealing with a leap year?
leap
= isleap(attime
.year
);
monthofyear
= attime
.mon
;
* Determine the day of year.
dayofyear
+= yeartable
[leap
][monthofyear
--];
return((year
%4 == 0 && year
%100 != 0) || year
%100 == 0);
for (ptr
= dates_info
; ptr
->type
!= 0; ptr
++, i
++) {
if (isprefix(date
, ptr
->name
))
isprefix(prefix
, fullname
)
getnowtime(nowtime
, attime
)
if (gettimeofday(&time
,&zone
) < 0) {
now
= localtime(&time
.tv_sec
);
attime
->year
= nowtime
->year
= now
->tm_year
;
attime
->yday
= nowtime
->yday
= now
->tm_yday
;
attime
->mon
= nowtime
->mon
= now
->tm_mon
;
attime
->mday
= nowtime
->mday
= now
->tm_mday
;
attime
->wday
= nowtime
->wday
= now
->tm_wday
;
attime
->hour
= nowtime
->hour
= now
->tm_hour
;
attime
->min
= nowtime
->min
= now
->tm_min
;
* This is the same routine used in the old "at", so I won't bother
* commenting it. It'll give you an idea of what the code looked
val
= val
*10+(*p
++ -'0');
val
+=(10* *p
+ p
[1] - 11*'0');
fprintf(stderr
, "bad time format:\n");
val
= FULLDAY
+1; /* illegal */
if (val
>= HALFDAY
&& val
<(HALFDAY
+HOUR
))
val
= FULLDAY
+1; /* illegal */
if ((val
== 0) || (val
== HALFDAY
))
val
= FULLDAY
+1; /* illegal */
if ((val
== 0) || (val
== HALFDAY
))
val
= FULLDAY
+1; /* illegal */
fprintf(stderr
, "bad time format\n");
if (val
< 0 || val
>= FULLDAY
) {
fprintf(stderr
, "time out of range\n");
fprintf(stderr
, "illegal minute field\n");
* Get the full login name of a person using his/her user id.
struct passwd
*pwdinfo
; /* password info structure */
char *logname
, *getlogin();
if (logname
== NULL
|| (pwdinfo
= getpwnam(logname
)) == NULL
||
fprintf(stderr
, "no name for uid %d?\n", uid
);
return(pwdinfo
->pw_name
);
if (unlink(atfile
) == -1)
* Print usage info and exit.
fprintf(stderr
,"usage: at [-csm] time [date] [filename]\n");