xref: /original-bsd/usr.bin/at/at/at.c (revision cd18b70b)
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 
13 #ifndef lint
14 static char sccsid[] = "@(#)at.c	5.5 (Berkeley) 01/18/87";
15 #endif not lint
16 
17 /*
18  *	Synopsis:	at [-s] [-c] [-m] time [filename]
19  *
20  *
21  *
22  *	Execute commands at a later date.
23  *
24  *
25  *	Modifications by:	Steve Wall
26  *				Computer Systems Research Group
27  *				University of California @ Berkeley
28  *
29  */
30 #include <stdio.h>
31 #include <ctype.h>
32 #include <signal.h>
33 #include <pwd.h>
34 #include <sys/param.h>
35 #include <sys/time.h>
36 #include <sys/file.h>
37 
38 #define HOUR		100		/* 1 hour (using military time) */
39 #define HALFDAY		(12 * HOUR)	/* half a day (12 hours) */
40 #define FULLDAY		(24 * HOUR)	/* a full day (24 hours) */
41 
42 #define WEEK		1		/* day requested is 'week' */
43 #define DAY		2		/* day requested is a weekday */
44 #define MONTH		3		/* day requested is a month */
45 
46 #define BOURNE		"/bin/sh"	/* run commands with Bourne shell*/
47 #define CSHELL		"/bin/csh"	/* run commands with C shell */
48 
49 #define NODATEFOUND	-1		/* no date was given on command line */
50 
51 #define ATDIR		"/usr/spool/at"		/* spooling area */
52 
53 #define LINSIZ		256		/* length of input buffer */
54 
55 /*
56  * A table to identify potential command line values for "time".
57  *
58  * We need this so that we can do some decent error checking on the
59  * command line arguments. (This was inspired by the old "at", which
60  * accepted "at 900 jan 55" as valid input and other small bugs.
61  */
62 struct datetypes {
63 	int type;
64 	char *name;
65 } dates_info[22] = {
66 	{ DAY,	 "sunday"    },
67 	{ DAY,	 "monday"    },
68 	{ DAY,	 "tuesday"   },
69 	{ DAY,	 "wednesday" },
70 	{ DAY,	 "thursday"  },
71 	{ DAY,	 "friday"    },
72 	{ DAY,	 "saturday"  },
73 	{ MONTH, "january"   },
74 	{ MONTH, "february"  },
75 	{ MONTH, "march"     },
76 	{ MONTH, "april"     },
77 	{ MONTH, "may"	     },
78 	{ MONTH, "june"	     },
79 	{ MONTH, "july"	     },
80 	{ MONTH, "august"    },
81 	{ MONTH, "september" },
82 	{ MONTH, "october"   },
83 	{ MONTH, "november"  },
84 	{ MONTH, "december"  },
85 	{ 0, ""},
86 };
87 
88 /*
89  * Months of the year.
90  */
91 char *months[13] = {
92 	"jan", "feb", "mar", "apr", "may", "jun",
93 	"jul", "aug", "sep", "oct", "nov", "dec", 0,
94 };
95 
96 /*
97  * A table of the number of days in each month of the year.
98  *
99  *	yeartable[0] -- normal year
100  *	yeartable[1] -- leap year
101  */
102 static int yeartable[2][13] = {
103 	{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
104 	{ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
105 };
106 
107 /*
108  * Structure holding the relevant values needed to create a spoolfile.
109  * "attime" will contain the info about when a job is to be run, and
110  * "nowtime" will contain info about what time the "at" command is in-
111  * voked.
112  */
113 struct times {
114 	int year;			/* year that job is to be run */
115 	int yday;			/* day of year that job is to be run */
116 	int mon;			/* month of year that job is to be run*/
117 	int mday;			/* day of month that job is to be run */
118 	int wday;			/* day of week that job is to be run */
119 	int hour;			/* hour of day that job is to be run */
120 	int min;			/* min. of hour that job is to be run */
121 } attime, nowtime;
122 
123 char	atfile[100];			/* name of spoolfile "yy.ddd.hhhh.??" */
124 char	*getenv();			/* get info on user's environment */
125 char	**environ;			/* user's environment */
126 FILE	*spoolfile;			/* spool file */
127 FILE	*inputfile;			/* input file ("stdin" or "filename") */
128 char	*getwd();			/* used to get current directory info */
129 
130 
131 main(argc, argv)
132 int argc;
133 char **argv;
134 {
135 	int c;				/* scratch variable */
136 	int usage();			/* print usage info and exit */
137 	int cleanup();			/* do cleanup on an interrupt signal */
138 	int dateindex = NODATEFOUND;	/* if a day is specified, what option
139 					   is it? (mon day, week, dayofweek) */
140 	char *shell = BOURNE;		/* what shell do we use to run job? */
141 	int shflag = 0;			/* override the current shell and run
142 					   job using the Bourne Shell */
143 	int cshflag = 0;		/* override the current shell and run
144 					   job using the Cshell */
145 	int mailflag = 0;		/* send mail after a job has been run?*/
146 	int standardin = 0;		/* are we reading from stardard input */
147 	char *tmp;			/* scratch pointer */
148 	char line[LINSIZ];		/* a line from input file */
149 	char pwbuf[MAXPATHLEN];		/* the current working directory */
150 	char *jobfile = "stdin";	/* file containing job to be run */
151 	char *getname();		/* get the login name of a user */
152 	int pid;			/* For forking for security reasons */
153 
154 
155 
156 	argv++; argc--;
157 
158 	/*
159 	 * Interpret command line flags if they exist.
160 	 */
161 	while (argc > 0 && **argv == '-') {
162 		(*argv)++;
163 		while (**argv) switch (*(*argv)++) {
164 
165 			case 'c' :	cshflag++;
166 					shell = CSHELL;
167 					break;
168 
169 			case 's' :	shflag++;
170 					shell = BOURNE;
171 					break;
172 
173 			case 'm' :	mailflag++;
174 					break;
175 
176 			default	 :	usage();
177 
178 		}
179 		--argc, ++argv;
180 	}
181 	if (shflag && cshflag) {
182 		fprintf(stderr,"ambiguous shell request.\n");
183 		exit(1);
184 	}
185 
186 	/*
187 	 * Get the time it is when "at" is invoked. We set both nowtime and
188 	 * attime to this value so that as we interpret the time the job is to
189 	 * be run we can compare the two values to determine such things as
190 	 * whether of not the job should be run the same day the "at" command
191 	 * is given, whether a job is to be run next year, etc.
192 	 */
193 	getnowtime(&nowtime, &attime);
194 
195 #ifdef DEBUG
196 	printit();
197 #endif
198 
199 	if (argc <= 0)
200 		usage();
201 
202 	/*
203 	 * Interpret argv[1] and create the time of day that the job is to
204 	 * be run. This is the same function that was used in the old "at"
205 	 */
206 	maketime(&attime, *argv);
207 	--argc; ++argv;
208 
209 #ifdef DEBUG
210 	printf("\n\nAFTER MAKETIME\n");
211 	printit();
212 #endif
213 
214 	/*
215 	 * If argv[(2)] exists, this is a request to run a job on a certain
216 	 * day of year or a certain day of week.
217 	 *
218 	 * We send  argv to the function "getdateindex" which returns the
219 	 * index value of the requested day in the table "dates_info"
220 	 * (see line 50 for table). If 'getdateindex" returns a NODATEFOUND,
221 	 * then the requested day format was not found in the table (usually
222 	 * this means that the argument is a "filename"). If the requested
223 	 * day is found, we continue to process command line arguments.
224 	 */
225 	if (argc > 0) {
226 		if ((dateindex = getdateindex(*argv)) != NODATEFOUND) {
227 
228 			++argv; --argc;
229 
230 			/*
231 			 * Determine the day of year that the job will be run
232 			 * depending on the value of argv.
233 			 */
234 			makedayofyear(dateindex, &argv, &argc);
235 		}
236 	}
237 
238 	/*
239 	 * If we get to this point and "dateindex" is set to NODATEFOUND,
240 	 * then we are dealing with a request with only a "time" specified
241 	 * (i.e. at 400p) and perhaps 'week' specified (i.e. at 400p week).
242 	 * If 'week' is specified, we just set excecution for 7 days in the
243 	 * future. Otherwise, we need to check to see if the requested time
244 	 * has already passed for the current day. If it has, then we add
245 	 * one to the day of year that the job will be executed.
246 	 */
247 	if (dateindex == NODATEFOUND) {
248 		int daysinyear;
249 		if ((argc > 0) && (strcmp(*argv,"week") == 0)) {
250 			attime.yday += 7;
251 			++argv; --argc;
252 		} else if (istomorrow())
253 			++attime.yday;
254 
255 		daysinyear = isleap(attime.year) ? 366 : 365;
256 		if (attime.yday >= daysinyear) {
257 			attime.yday -= daysinyear;
258 			++attime.year;
259 		}
260 	}
261 
262 	/*
263 	 * If no more arguments exist, then we are reading
264 	 * from standard input. Thus, we set the standard
265 	 * input flag (++standardin).
266 	 */
267 	if (argc <= 0)
268 		++standardin;
269 
270 
271 #ifdef DEBUG
272 	printf("\n\nAFTER ADDDAYS\n");
273 	printit();
274 #endif
275 
276 	/*
277 	 * Start off assuming we're going to read from standard input,
278 	 * but if a filename has been given to read from, we will open it
279 	 * later.
280 	 */
281 	inputfile = stdin;
282 
283 	/*
284 	 * Create the filename for the spoolfile.
285 	 */
286 	makeatfile(atfile,attime.year,attime.yday,attime.hour,attime.min);
287 
288 	/*
289 	 * Open the spoolfile for writing.
290 	 */
291 	if ((spoolfile = fopen(atfile, "w")) == NULL){
292 		perror(atfile);
293 		exit(1);
294 	}
295 
296 	/*
297 	 * Make the file not world readable.
298 	 */
299 	fchmod(fileno(spoolfile), 0400);
300 
301 	/*
302 	 * The protection mechanism works like this:
303 	 * We are running ruid=user, euid=spool owner.  So far we have been
304 	 * messing around in the spool directory, so we needed to run
305 	 * as the owner of the spool directory.
306 	 * We now need to switch to the user's effective uid
307 	 * to simplify permission checking.  However, we fork first,
308 	 * so that we can clean up if interrupted.
309 	 */
310 	signal(SIGINT, SIG_IGN);
311 	pid = fork();
312 	if (pid == -1) {
313 		perror("fork");
314 		exit(1);
315 	}
316 	if (pid) {
317 		int wpid, status;
318 
319 		/*
320 		 * We are the parent. If the kid has problems,
321 		 * cleanup the spool directory.
322 		 */
323 		wpid = wait(&status);
324 		if (wpid != pid || status) {
325 			cleanup();
326 			exit(1);
327 		}
328 		/*
329 		 * The kid should have alread flushed the buffers.
330 		 */
331 		_exit(0);
332 	}
333 
334 	/*
335 	 * Exit on interrupt.
336 	 */
337 	signal(SIGINT, SIG_DFL);
338 
339 	/*
340 	 * We are the kid, give up special permissions.
341 	 */
342 	setuid(getuid());
343 
344 	/*
345 	 * Open the input file with the user's permissions.
346 	 */
347 	if (!standardin) {
348 		jobfile = *argv;
349 		if ((inputfile = fopen(jobfile, "r")) == NULL) {
350 			perror(jobfile);
351 			exit(1);
352 		}
353 	}
354 
355 	/*
356 	 * Determine what shell we should use to run the job. If the user
357 	 * didn't explicitly request that his/her current shell be over-
358 	 * ridden (shflag of cshflag) then we use the current shell.
359 	 */
360 	if ((!shflag) && (!cshflag) && (getenv("SHELL") != NULL))
361 		shell = "$SHELL";
362 
363 	/*
364 	 * Put some standard information at the top of the spoolfile.
365 	 * This info is used by the other "at"-oriented programs (atq,
366 	 * atrm, atrun).
367 	 */
368 	fprintf(spoolfile, "# owner: %.127s\n",getname(getuid()));
369 	fprintf(spoolfile, "# jobname: %.127s\n",jobfile);
370 	fprintf(spoolfile, "# shell: sh\n");
371 	fprintf(spoolfile, "# notify by mail: %s\n",(mailflag) ? "yes" : "no");
372 	fprintf(spoolfile, "\n");
373 
374 	/*
375 	 * Set the modes for any files created by the job being run.
376 	 */
377 	c = umask(0);
378 	umask(c);
379 	fprintf(spoolfile, "umask %.1o\n", c);
380 
381 	/*
382 	 * Get the current working directory so we know what directory to
383 	 * run the job from.
384 	 */
385 	if (getwd(pwbuf) == NULL) {
386 		fprintf(stderr, "at: can't get working directory\n");
387 		exit(1);
388 	}
389 	fprintf(spoolfile, "cd %s\n", pwbuf);
390 
391 	/*
392 	 * Copy the user's environment to the spoolfile.
393 	 */
394 	if (environ) {
395 		copyenvironment(&spoolfile);
396 	}
397 
398 	/*
399 	 * Put in a line to run the proper shell using the rest of
400 	 * the file as input.  Note that 'exec'ing the shell will
401 	 * cause sh() to leave a /tmp/sh### file around.  This line
402 	 * depends on the shells allowing EOF to end tagged input.  The
403 	 * quotes also guarantee a quoting of the lines before EOF.
404 	 */
405 	fprintf(spoolfile, "%s << 'QAZWSXEDCRFVTGBYHNUJMIKOLP'\n", shell);
406 
407 	/*
408 	 * Now that we have all the files set up, we can start reading in
409 	 * the job.
410 	 */
411 	while (fgets(line, LINSIZ, inputfile) != NULL)
412 		fputs(line, spoolfile);
413 
414 	/*
415 	 * Close all files and change the mode of the spoolfile.
416 	 */
417 	fclose(inputfile);
418 	fclose(spoolfile);
419 
420 	exit(0);
421 
422 }
423 
424 /*
425  * Copy the user's environment to the spoolfile in the syntax of the
426  * Bourne shell.  After the environment is set up, the proper shell
427  * will be invoked.
428  */
429 copyenvironment(spoolfile)
430 FILE **spoolfile;
431 {
432 	char *tmp;			/* scratch pointer */
433 	char **environptr = environ;	/* pointer to an environment setting */
434 
435 	while(*environptr) {
436 		tmp = *environptr;
437 
438 		/*
439 		 * We don't want the termcap or terminal entry so skip them.
440 		 */
441 		if ((strncmp(tmp,"TERM=",5) == 0) ||
442 		    (strncmp(tmp,"TERMCAP=",8) == 0)) {
443 			++environptr;
444 			continue;
445 		}
446 
447 		/*
448 		 * Set up the proper syntax.
449 		 */
450 		while (*tmp != '=')
451 			fputc(*tmp++,*spoolfile);
452 		fputc('=', *spoolfile);
453 		fputc('\'' , *spoolfile);
454 		++tmp;
455 
456 		/*
457 		 * Now copy the entry.
458 		 */
459 		while (*tmp) {
460 			if (*tmp == '\'')
461 				fputs("'\\''", *spoolfile);
462 			else if (*tmp == '\n')
463 				fputs("\\",*spoolfile);
464 			else
465 				fputc(*tmp, *spoolfile);
466 			++tmp;
467 		}
468 		fputc('\'' , *spoolfile);
469 
470 		/*
471 		 * We need to "export" environment settings.
472 		 */
473 		fprintf(*spoolfile, "\nexport ");
474 		tmp = *environptr;
475 		while (*tmp != '=')
476 			fputc(*tmp++,*spoolfile);
477 		fputc('\n',*spoolfile);
478 		++environptr;
479 	}
480 	return;
481 }
482 
483 /*
484  * Create the filename for the spoolfile. The format is "yy.ddd.mmmm.??"
485  * where "yy" is the year the job will be run, "ddd" the day of year,
486  * "mmmm" the hour and minute, and "??" a scratch value used to dis-
487  * tinguish between two files that are to be run at the same time.
488  */
489 makeatfile(atfile,year,dayofyear,hour,minute)
490 int year;
491 int hour;
492 int minute;
493 int dayofyear;
494 char *atfile;
495 {
496 	int i;				/* scratch variable */
497 
498 	for (i=0; ; i += 53) {
499 		sprintf(atfile, "%s/%02d.%03d.%02d%02d.%02d", ATDIR, year,
500 			dayofyear, hour, minute, (getpid() + i) % 100);
501 
502 		/*
503 		 * Make sure that the file name that we've created is unique.
504 		 */
505 		if (access(atfile, F_OK) == -1)
506 			return;
507 	}
508 }
509 
510 /*
511  * Has the requested time already passed for the currrent day? If so, we
512  * will run the job "tomorrow".
513  */
514 istomorrow()
515 {
516 	if (attime.hour < nowtime.hour)
517 		return(1);
518 	if ((attime.hour == nowtime.hour) && (attime.min < nowtime.min))
519 		return(1);
520 
521 	return(0);
522 }
523 
524 /*
525  * Debugging wreckage.
526  */
527 printit()
528 {
529 	printf("YEAR\tnowtime: %d\tattime: %d\n",nowtime.year,attime.year);
530 	printf("YDAY\tnowtime: %d\tattime: %d\n",nowtime.yday,attime.yday);
531 	printf("MON\tnowtime: %d\tattime: %d\n",nowtime.mon,attime.mon);
532 	printf("MONDAY\tnowtime: %d\tattime: %d\n",nowtime.mday,attime.mday);
533 	printf("WDAY\tnowtime: %d\tattime: %d\n",nowtime.wday,attime.wday);
534 	printf("HOUR\tnowtime: %d\tattime: %d\n",nowtime.hour,attime.hour);
535 	printf("MIN\tnowtime: %d\tattime: %d\n",nowtime.min,attime.min);
536 }
537 
538 /*
539  * Calculate the day of year that the job will be executed.
540  * The av,ac arguments are ptrs to argv,argc; updated as necessary.
541  */
542 makedayofyear(dateindex, av, ac)
543 int dateindex;
544 char ***av;
545 int *ac;
546 {
547 	char **argv = *av;	/* imitate argc,argv and update args at end */
548 	int argc = *ac;
549 	char *ptr;				/* scratch pointer */
550 	struct datetypes *daterequested;	/* pointer to information about
551 						   the type of date option
552 						   we're dealing with */
553 
554 	daterequested = &dates_info[dateindex];
555 
556 	/*
557 	 * If we're dealing with a day of week, determine the number of days
558 	 * in the future the next day of this type will fall on. Add this
559 	 * value to "attime.yday".
560 	 */
561 	if (daterequested->type == DAY) {
562 		if (attime.wday < dateindex)
563 			attime.yday += dateindex - attime.wday;
564 		else if(attime.wday > dateindex)
565 			attime.yday += (7 - attime.wday) + dateindex;
566 		else attime.yday += 7;
567 	}
568 
569 	/*
570 	 * If we're dealing with a month and day of month, determine the
571 	 * day of year that this date will fall on.
572 	 */
573 	if (daterequested->type == MONTH) {
574 
575 		/*
576 		 * If a day of month isn't specified, print a message
577 		 * and exit.
578 		 */
579 		if (argc <= 0) {
580 			fprintf(stderr,"day of month not specified.\n");
581 			exit(1);
582 		}
583 
584 		/*
585 		 * Scan the day of month value and make sure that it
586 		 * has no characters in it. If characters are found or
587 		 * the day requested is zero, print a message and exit.
588 		 */
589 		ptr = *argv;
590 		while (isdigit(*ptr))
591 			++ptr;
592 		if ((*ptr != '\0') || (atoi(*argv) == 0)) {
593 			fprintf(stderr,"\"%s\": illegal day of month\n",*argv);
594 			exit(1);
595 		}
596 
597 		/*
598 		 * Set the month of year and day of month values. Since
599 		 * the first 7 values in our dateinfo table do not deal
600 		 * with month names, we subtract 7 from the month of year
601 		 * value.
602 		 */
603 		attime.mon = (dateindex - 7);
604 		attime.mday = (atoi(*argv) - 1);
605 
606 		/*
607 		 * Test the day of month value to make sure that the
608 		 * value is legal.
609 		 */
610 		if ((attime.mday + 1) >
611 		    yeartable[isleap(attime.year)][attime.mon + 1]) {
612 			fprintf(stderr,"\"%s\": illegal day of month\n",*argv);
613 			exit(1);
614 		}
615 
616 		/*
617 		 * Finally, we determine the day of year.
618 		 */
619 		attime.yday = (countdays());
620 		++argv; --argc;
621 	}
622 
623 	/*
624 	 * If 'week' is specified, add 7 to the day of year.
625 	 */
626 	if ((argc > 0) && (strcmp(*argv,"week") == 0)) {
627 		attime.yday += 7;
628 		++argv; --argc;
629 	}
630 
631 	/*
632 	 * Now that all that is done, see if the requested execution time
633 	 * has already passed for this year, and if it has, set execution
634 	 * for next year.
635 	 */
636 	if (isnextyear())
637 		++attime.year;
638 
639 	/*
640 	 * Finally, reflect the updated argc,argv to the caller
641 	 */
642 	*av = argv;
643 	*ac = argc;
644 }
645 
646 /*
647  * Should the job be run next year? We check for the following situations:
648  *
649  *	1) the requested time has already passed for the current year.
650  *	2) the day of year is greater than the number of days in the year.
651  *
652  * If either of these tests succeed, we increment "attime.year" by 1.
653  * If #2 is true, we also subtract the number of days in the current year
654  * from "attime.yday". #2 can only occur if someone specifies a job to
655  * be run "tomorrow" on Dec. 31 or if they specify a job to be run a
656  * 'week' later and the date is at least Dec. 24. (I think so anyway)
657  */
658 isnextyear()
659 {	register daysinyear;
660 	if (attime.yday < nowtime.yday)
661 		return(1);
662 
663 	if ((attime.yday == nowtime.yday) && (attime.hour < nowtime.hour))
664 		return(1);
665 
666 	daysinyear = isleap(attime.year) ? 366 : 365;
667 	if (attime.yday >= daysinyear) {
668 		attime.yday -= daysinyear;
669 		return(1);
670 	}
671 	if (attime.yday > (isleap(attime.year) ? 366 : 365)) {
672 		attime.yday -= (isleap(attime.year) ? 366 : 365);
673 		return(1);
674 	}
675 
676 	return(0);
677 }
678 
679 /*
680  * Determine the day of year given a month and day of month value.
681  */
682 countdays()
683 {
684 	int leap;			/* are we dealing with a leap year? */
685 	int dayofyear;			/* the day of year after conversion */
686 	int monthofyear;		/* the month of year that we are
687 					   dealing with */
688 
689 	/*
690 	 * Are we dealing with a leap year?
691 	 */
692 	leap = isleap(attime.year);
693 
694 	monthofyear = attime.mon;
695 	dayofyear = attime.mday;
696 
697 	/*
698 	 * Determine the day of year.
699 	 */
700 	while (monthofyear > 0)
701 		dayofyear += yeartable[leap][monthofyear--];
702 
703 	return(dayofyear);
704 }
705 
706 /*
707  * Is a year a leap year?
708  */
709 isleap(year)
710 int year;
711 
712 {
713 	return((year%4 == 0 && year%100 != 0) || year%100 == 0);
714 }
715 
716 getdateindex(date)
717 char *date;
718 {
719 	int i = 0;
720 	struct datetypes *ptr;
721 
722 	ptr = dates_info;
723 
724 	for (ptr = dates_info; ptr->type != 0; ptr++, i++) {
725 		if (isprefix(date, ptr->name))
726 			return(i);
727 	}
728 	return(NODATEFOUND);
729 }
730 
731 isprefix(prefix, fullname)
732 char *prefix, *fullname;
733 {
734 	char ch;
735 	char *ptr;
736 	char *ptr1;
737 
738 	ptr = prefix;
739 	ptr1 = fullname;
740 
741 	while (*ptr) {
742 		ch = *ptr;
743 		if (isupper(ch))
744 			ch = tolower(ch);
745 
746 		if (ch != *ptr1++)
747 			return(0);
748 
749 		++ptr;
750 	}
751 	return(1);
752 }
753 
754 getnowtime(nowtime, attime)
755 struct times *nowtime;
756 struct times *attime;
757 {
758 	struct tm *now;
759 	struct timeval time;
760 	struct timezone zone;
761 
762 	if (gettimeofday(&time,&zone) < 0) {
763 		perror("gettimeofday");
764 		exit(1);
765 	}
766 	now = localtime(&time.tv_sec);
767 
768 	attime->year = nowtime->year = now->tm_year;
769 	attime->yday = nowtime->yday = now->tm_yday;
770 	attime->mon = nowtime->mon = now->tm_mon;
771 	attime->mday = nowtime->mday = now->tm_mday;
772 	attime->wday = nowtime->wday = now->tm_wday;
773 	attime->hour = nowtime->hour = now->tm_hour;
774 	attime->min = nowtime->min = now->tm_min;
775 }
776 
777 /*
778  * This is the same routine used in the old "at", so I won't bother
779  * commenting it. It'll give you an idea of what the code looked
780  * like when I got it.
781  */
782 maketime(attime,ptr)
783 char *ptr;
784 struct times *attime;
785 {
786 	int val;
787 	char *p;
788 
789 	p = ptr;
790 	val = 0;
791 	while(isdigit(*p)) {
792 		val = val*10+(*p++ -'0');
793 	}
794 	if (p-ptr < 3)
795 		val *= HOUR;
796 
797 	for (;;) {
798 		switch(*p) {
799 
800 		case ':':
801 			++p;
802 			if (isdigit(*p)) {
803 				if (isdigit(p[1])) {
804 					val +=(10* *p + p[1] - 11*'0');
805 					p += 2;
806 					continue;
807 				}
808 			}
809 			fprintf(stderr, "bad time format:\n");
810 			exit(1);
811 
812 		case 'A':
813 		case 'a':
814 			if (val >= HALFDAY+HOUR)
815 				val = FULLDAY+1;  /* illegal */
816 			if (val >= HALFDAY && val <(HALFDAY+HOUR))
817 				val -= HALFDAY;
818 			break;
819 
820 		case 'P':
821 		case 'p':
822 			if (val >= HALFDAY+HOUR)
823 				val = FULLDAY+1;  /* illegal */
824 			if (val < HALFDAY)
825 				val += HALFDAY;
826 			break;
827 
828 		case 'n':
829 		case 'N':
830 			if ((val == 0) || (val == HALFDAY))
831 				val = HALFDAY;
832 			else
833 				val = FULLDAY+1;  /* illegal */
834 			break;
835 
836 		case 'M':
837 		case 'm':
838 			if ((val == 0) || (val == HALFDAY))
839 				val = 0;
840 			else
841 				val = FULLDAY+1;  /* illegal */
842 			break;
843 
844 
845 		case '\0':
846 		case ' ':
847 			/* 24 hour time */
848 			if (val == FULLDAY)
849 				val -= FULLDAY;
850 			break;
851 
852 		default:
853 			fprintf(stderr, "bad time format\n");
854 			exit(1);
855 
856 		}
857 		break;
858 	}
859 	if (val < 0 || val >= FULLDAY) {
860 		fprintf(stderr, "time out of range\n");
861 		exit(1);
862 	}
863 	if (val%HOUR >= 60) {
864 		fprintf(stderr, "illegal minute field\n");
865 		exit(1);
866 	}
867 	attime->hour = val/HOUR;
868 	attime->min = val%HOUR;
869 }
870 
871 /*
872  * Get the full login name of a person using his/her user id.
873  */
874 char *
875 getname(uid)
876 int uid;
877 {
878 	struct passwd *pwdinfo;			/* password info structure */
879 	char *logname, *getlogin();
880 
881 	logname = getlogin();
882 	if (logname == NULL || (pwdinfo = getpwnam(logname)) == NULL ||
883 	    pwdinfo->pw_uid != uid)
884 		pwdinfo = getpwuid(uid);
885 	if (pwdinfo == 0) {
886 		fprintf(stderr, "no name for uid %d?\n", uid);
887 		exit(1);
888 	}
889 	return(pwdinfo->pw_name);
890 }
891 
892 /*
893  * Do general cleanup.
894  */
895 cleanup()
896 {
897 	if (unlink(atfile) == -1)
898 		perror(atfile);
899 	exit(1);
900 }
901 
902 /*
903  * Print usage info and exit.
904  */
905 usage()
906 {
907 	fprintf(stderr,"usage: at [-csm] time [date] [filename]\n");
908 	exit(1);
909 }
910 
911