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