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