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