Commit | Line | Data |
---|---|---|
d0aeaf5a DF |
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 | ||
840fc587 | 13 | #ifndef lint |
ca67e7b4 | 14 | static char sccsid[] = "@(#)at.c 5.5 (Berkeley) 1/18/87"; |
d6fff2c5 | 15 | #endif not lint |
255d293c | 16 | |
30488131 | 17 | /* |
d6fff2c5 KM |
18 | * Synopsis: at [-s] [-c] [-m] time [filename] |
19 | * | |
255d293c SW |
20 | * |
21 | * | |
d6fff2c5 | 22 | * Execute commands at a later date. |
255d293c SW |
23 | * |
24 | * | |
d6fff2c5 KM |
25 | * Modifications by: Steve Wall |
26 | * Computer Systems Research Group | |
27 | * University of California @ Berkeley | |
30488131 BJ |
28 | * |
29 | */ | |
1ce8fea1 KB |
30 | #include <stdio.h> |
31 | #include <ctype.h> | |
30488131 | 32 | #include <signal.h> |
1ce8fea1 | 33 | #include <pwd.h> |
89ab7eb3 | 34 | #include <sys/param.h> |
840fc587 | 35 | #include <sys/time.h> |
255d293c SW |
36 | #include <sys/file.h> |
37 | ||
d6fff2c5 KM |
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) */ | |
255d293c | 41 | |
d6fff2c5 KM |
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 */ | |
255d293c | 45 | |
e0799b82 KM |
46 | #define BOURNE "/bin/sh" /* run commands with Bourne shell*/ |
47 | #define CSHELL "/bin/csh" /* run commands with C shell */ | |
255d293c | 48 | |
d6fff2c5 | 49 | #define NODATEFOUND -1 /* no date was given on command line */ |
255d293c | 50 | |
d6fff2c5 | 51 | #define ATDIR "/usr/spool/at" /* spooling area */ |
255d293c | 52 | |
e0799b82 | 53 | #define LINSIZ 256 /* length of input buffer */ |
30488131 | 54 | |
255d293c SW |
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 { | |
d6fff2c5 KM |
63 | int type; |
64 | char *name; | |
255d293c | 65 | } dates_info[22] = { |
d6fff2c5 KM |
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, ""}, | |
30488131 BJ |
86 | }; |
87 | ||
255d293c SW |
88 | /* |
89 | * Months of the year. | |
90 | */ | |
91 | char *months[13] = { | |
d6fff2c5 KM |
92 | "jan", "feb", "mar", "apr", "may", "jun", |
93 | "jul", "aug", "sep", "oct", "nov", "dec", 0, | |
30488131 BJ |
94 | }; |
95 | ||
255d293c SW |
96 | /* |
97 | * A table of the number of days in each month of the year. | |
98 | * | |
d6fff2c5 KM |
99 | * yeartable[0] -- normal year |
100 | * yeartable[1] -- leap year | |
255d293c SW |
101 | */ |
102 | static int yeartable[2][13] = { | |
d6fff2c5 KM |
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}, | |
255d293c SW |
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 { | |
d6fff2c5 KM |
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 */ | |
255d293c SW |
121 | } attime, nowtime; |
122 | ||
d6fff2c5 KM |
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") */ | |
89ab7eb3 | 128 | char *getwd(); /* used to get current directory info */ |
255d293c | 129 | |
30488131 BJ |
130 | |
131 | main(argc, argv) | |
255d293c | 132 | int argc; |
30488131 BJ |
133 | char **argv; |
134 | { | |
d6fff2c5 KM |
135 | int c; /* scratch variable */ |
136 | int usage(); /* print usage info and exit */ | |
137 | int cleanup(); /* do cleanup on an interrupt signal */ | |
e0799b82 | 138 | int dateindex = NODATEFOUND; /* if a day is specified, what option |
d6fff2c5 | 139 | is it? (mon day, week, dayofweek) */ |
e0799b82 | 140 | char *shell = BOURNE; /* what shell do we use to run job? */ |
d6fff2c5 KM |
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 */ | |
e0799b82 | 148 | char line[LINSIZ]; /* a line from input file */ |
89ab7eb3 | 149 | char pwbuf[MAXPATHLEN]; /* the current working directory */ |
d6fff2c5 | 150 | char *jobfile = "stdin"; /* file containing job to be run */ |
344505d2 | 151 | char *getname(); /* get the login name of a user */ |
3ac9f399 | 152 | int pid; /* For forking for security reasons */ |
d6fff2c5 KM |
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); | |
255d293c SW |
194 | |
195 | #ifdef DEBUG | |
d6fff2c5 | 196 | printit(); |
255d293c SW |
197 | #endif |
198 | ||
e0799b82 | 199 | if (argc <= 0) |
d6fff2c5 | 200 | usage(); |
255d293c | 201 | |
d6fff2c5 KM |
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; | |
255d293c SW |
208 | |
209 | #ifdef DEBUG | |
d6fff2c5 KM |
210 | printf("\n\nAFTER MAKETIME\n"); |
211 | printit(); | |
255d293c SW |
212 | #endif |
213 | ||
d6fff2c5 | 214 | /* |
e0799b82 | 215 | * If argv[(2)] exists, this is a request to run a job on a certain |
d6fff2c5 KM |
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 | */ | |
e0799b82 | 225 | if (argc > 0) { |
d6fff2c5 KM |
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 | */ | |
e0799b82 | 234 | makedayofyear(dateindex, &argv, &argc); |
d6fff2c5 KM |
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) { | |
08e950fd | 248 | int daysinyear; |
e0799b82 | 249 | if ((argc > 0) && (strcmp(*argv,"week") == 0)) { |
d6fff2c5 | 250 | attime.yday += 7; |
e0799b82 KM |
251 | ++argv; --argc; |
252 | } else if (istomorrow()) | |
d6fff2c5 | 253 | ++attime.yday; |
08e950fd KD |
254 | |
255 | daysinyear = isleap(attime.year) ? 366 : 365; | |
256 | if (attime.yday >= daysinyear) { | |
257 | attime.yday -= daysinyear; | |
258 | ++attime.year; | |
259 | } | |
d6fff2c5 | 260 | } |
255d293c | 261 | |
e0799b82 KM |
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 | ||
255d293c SW |
270 | |
271 | #ifdef DEBUG | |
d6fff2c5 KM |
272 | printf("\n\nAFTER ADDDAYS\n"); |
273 | printit(); | |
255d293c SW |
274 | #endif |
275 | ||
d6fff2c5 KM |
276 | /* |
277 | * Start off assuming we're going to read from standard input, | |
3ac9f399 KM |
278 | * but if a filename has been given to read from, we will open it |
279 | * later. | |
d6fff2c5 KM |
280 | */ |
281 | inputfile = stdin; | |
d6fff2c5 KM |
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 | /* | |
3ac9f399 | 297 | * Make the file not world readable. |
d6fff2c5 | 298 | */ |
3ac9f399 KM |
299 | fchmod(fileno(spoolfile), 0400); |
300 | ||
301 | /* | |
302 | * The protection mechanism works like this: | |
6303198d MK |
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. | |
3ac9f399 KM |
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 | /* | |
6303198d | 340 | * We are the kid, give up special permissions. |
3ac9f399 KM |
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 | } | |
e0799b82 | 354 | |
d6fff2c5 KM |
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 | */ | |
e0799b82 KM |
360 | if ((!shflag) && (!cshflag) && (getenv("SHELL") != NULL)) |
361 | shell = "$SHELL"; | |
d6fff2c5 KM |
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 | */ | |
e0799b82 KM |
368 | fprintf(spoolfile, "# owner: %.127s\n",getname(getuid())); |
369 | fprintf(spoolfile, "# jobname: %.127s\n",jobfile); | |
370 | fprintf(spoolfile, "# shell: sh\n"); | |
d6fff2c5 KM |
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 | */ | |
89ab7eb3 EW |
385 | if (getwd(pwbuf) == NULL) { |
386 | fprintf(stderr, "at: can't get working directory\n"); | |
d6fff2c5 KM |
387 | exit(1); |
388 | } | |
89ab7eb3 | 389 | fprintf(spoolfile, "cd %s\n", pwbuf); |
d6fff2c5 KM |
390 | |
391 | /* | |
392 | * Copy the user's environment to the spoolfile. | |
393 | */ | |
394 | if (environ) { | |
e0799b82 | 395 | copyenvironment(&spoolfile); |
d6fff2c5 KM |
396 | } |
397 | ||
da35d128 | 398 | /* |
e0799b82 KM |
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 | |
6303198d MK |
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. | |
da35d128 | 404 | */ |
6303198d | 405 | fprintf(spoolfile, "%s << 'QAZWSXEDCRFVTGBYHNUJMIKOLP'\n", shell); |
da35d128 | 406 | |
d6fff2c5 KM |
407 | /* |
408 | * Now that we have all the files set up, we can start reading in | |
6303198d | 409 | * the job. |
d6fff2c5 | 410 | */ |
6303198d | 411 | while (fgets(line, LINSIZ, inputfile) != NULL) |
d6fff2c5 | 412 | fputs(line, spoolfile); |
d6fff2c5 KM |
413 | |
414 | /* | |
344505d2 | 415 | * Close all files and change the mode of the spoolfile. |
d6fff2c5 KM |
416 | */ |
417 | fclose(inputfile); | |
418 | fclose(spoolfile); | |
d6fff2c5 KM |
419 | |
420 | exit(0); | |
255d293c SW |
421 | |
422 | } | |
423 | ||
424 | /* | |
e0799b82 KM |
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. | |
255d293c | 428 | */ |
e0799b82 | 429 | copyenvironment(spoolfile) |
255d293c SW |
430 | FILE **spoolfile; |
431 | { | |
d6fff2c5 KM |
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 | */ | |
e0799b82 KM |
441 | if ((strncmp(tmp,"TERM=",5) == 0) || |
442 | (strncmp(tmp,"TERMCAP=",8) == 0)) { | |
d6fff2c5 KM |
443 | ++environptr; |
444 | continue; | |
445 | } | |
446 | ||
447 | /* | |
e0799b82 | 448 | * Set up the proper syntax. |
d6fff2c5 | 449 | */ |
d6fff2c5 KM |
450 | while (*tmp != '=') |
451 | fputc(*tmp++,*spoolfile); | |
e0799b82 KM |
452 | fputc('=', *spoolfile); |
453 | fputc('\'' , *spoolfile); | |
d6fff2c5 KM |
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 | } | |
e0799b82 | 468 | fputc('\'' , *spoolfile); |
d6fff2c5 KM |
469 | |
470 | /* | |
e0799b82 | 471 | * We need to "export" environment settings. |
d6fff2c5 | 472 | */ |
e0799b82 KM |
473 | fprintf(*spoolfile, "\nexport "); |
474 | tmp = *environptr; | |
475 | while (*tmp != '=') | |
476 | fputc(*tmp++,*spoolfile); | |
d6fff2c5 KM |
477 | fputc('\n',*spoolfile); |
478 | ++environptr; | |
479 | } | |
d6fff2c5 | 480 | return; |
255d293c SW |
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 | { | |
d6fff2c5 KM |
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 | } | |
30488131 BJ |
508 | } |
509 | ||
255d293c SW |
510 | /* |
511 | * Has the requested time already passed for the currrent day? If so, we | |
512 | * will run the job "tomorrow". | |
513 | */ | |
514 | istomorrow() | |
30488131 | 515 | { |
d6fff2c5 KM |
516 | if (attime.hour < nowtime.hour) |
517 | return(1); | |
518 | if ((attime.hour == nowtime.hour) && (attime.min < nowtime.min)) | |
519 | return(1); | |
255d293c | 520 | |
d6fff2c5 | 521 | return(0); |
30488131 BJ |
522 | } |
523 | ||
255d293c SW |
524 | /* |
525 | * Debugging wreckage. | |
526 | */ | |
527 | printit() | |
528 | { | |
d6fff2c5 KM |
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); | |
255d293c | 536 | } |
30488131 | 537 | |
255d293c SW |
538 | /* |
539 | * Calculate the day of year that the job will be executed. | |
e0799b82 | 540 | * The av,ac arguments are ptrs to argv,argc; updated as necessary. |
255d293c | 541 | */ |
e0799b82 | 542 | makedayofyear(dateindex, av, ac) |
255d293c | 543 | int dateindex; |
e0799b82 KM |
544 | char ***av; |
545 | int *ac; | |
255d293c | 546 | { |
e0799b82 KM |
547 | char **argv = *av; /* imitate argc,argv and update args at end */ |
548 | int argc = *ac; | |
d6fff2c5 KM |
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 | */ | |
e0799b82 | 579 | if (argc <= 0) { |
d6fff2c5 KM |
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()); | |
e0799b82 | 620 | ++argv; --argc; |
d6fff2c5 KM |
621 | } |
622 | ||
623 | /* | |
624 | * If 'week' is specified, add 7 to the day of year. | |
625 | */ | |
e0799b82 | 626 | if ((argc > 0) && (strcmp(*argv,"week") == 0)) { |
d6fff2c5 | 627 | attime.yday += 7; |
e0799b82 KM |
628 | ++argv; --argc; |
629 | } | |
d6fff2c5 KM |
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; | |
e0799b82 KM |
638 | |
639 | /* | |
640 | * Finally, reflect the updated argc,argv to the caller | |
641 | */ | |
642 | *av = argv; | |
643 | *ac = argc; | |
255d293c SW |
644 | } |
645 | ||
646 | /* | |
647 | * Should the job be run next year? We check for the following situations: | |
648 | * | |
d6fff2c5 KM |
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. | |
255d293c SW |
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() | |
08e950fd | 659 | { register daysinyear; |
d6fff2c5 KM |
660 | if (attime.yday < nowtime.yday) |
661 | return(1); | |
255d293c | 662 | |
d6fff2c5 KM |
663 | if ((attime.yday == nowtime.yday) && (attime.hour < nowtime.hour)) |
664 | return(1); | |
255d293c | 665 | |
08e950fd KD |
666 | daysinyear = isleap(attime.year) ? 366 : 365; |
667 | if (attime.yday >= daysinyear) { | |
668 | attime.yday -= daysinyear; | |
d6fff2c5 | 669 | return(1); |
08e950fd | 670 | } |
d6fff2c5 KM |
671 | if (attime.yday > (isleap(attime.year) ? 366 : 365)) { |
672 | attime.yday -= (isleap(attime.year) ? 366 : 365); | |
673 | return(1); | |
674 | } | |
255d293c | 675 | |
d6fff2c5 | 676 | return(0); |
255d293c SW |
677 | } |
678 | ||
679 | /* | |
680 | * Determine the day of year given a month and day of month value. | |
681 | */ | |
682 | countdays() | |
683 | { | |
d6fff2c5 KM |
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); | |
255d293c SW |
704 | } |
705 | ||
706 | /* | |
707 | * Is a year a leap year? | |
708 | */ | |
709 | isleap(year) | |
710 | int year; | |
711 | ||
30488131 | 712 | { |
d6fff2c5 | 713 | return((year%4 == 0 && year%100 != 0) || year%100 == 0); |
30488131 BJ |
714 | } |
715 | ||
255d293c SW |
716 | getdateindex(date) |
717 | char *date; | |
30488131 | 718 | { |
d6fff2c5 KM |
719 | int i = 0; |
720 | struct datetypes *ptr; | |
255d293c | 721 | |
d6fff2c5 | 722 | ptr = dates_info; |
255d293c | 723 | |
d6fff2c5 KM |
724 | for (ptr = dates_info; ptr->type != 0; ptr++, i++) { |
725 | if (isprefix(date, ptr->name)) | |
726 | return(i); | |
727 | } | |
e0799b82 | 728 | return(NODATEFOUND); |
30488131 BJ |
729 | } |
730 | ||
255d293c SW |
731 | isprefix(prefix, fullname) |
732 | char *prefix, *fullname; | |
30488131 | 733 | { |
789608a7 | 734 | char ch; |
d6fff2c5 KM |
735 | char *ptr; |
736 | char *ptr1; | |
255d293c | 737 | |
d6fff2c5 KM |
738 | ptr = prefix; |
739 | ptr1 = fullname; | |
255d293c | 740 | |
d6fff2c5 | 741 | while (*ptr) { |
789608a7 SW |
742 | ch = *ptr; |
743 | if (isupper(ch)) | |
744 | ch = tolower(ch); | |
255d293c | 745 | |
789608a7 | 746 | if (ch != *ptr1++) |
d6fff2c5 | 747 | return(0); |
789608a7 SW |
748 | |
749 | ++ptr; | |
d6fff2c5 KM |
750 | } |
751 | return(1); | |
30488131 BJ |
752 | } |
753 | ||
255d293c SW |
754 | getnowtime(nowtime, attime) |
755 | struct times *nowtime; | |
756 | struct times *attime; | |
30488131 | 757 | { |
d6fff2c5 KM |
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; | |
30488131 | 775 | } |
255d293c SW |
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 | { | |
d6fff2c5 KM |
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': | |
e0799b82 KM |
830 | if ((val == 0) || (val == HALFDAY)) |
831 | val = HALFDAY; | |
832 | else | |
833 | val = FULLDAY+1; /* illegal */ | |
d6fff2c5 KM |
834 | break; |
835 | ||
836 | case 'M': | |
837 | case 'm': | |
e0799b82 KM |
838 | if ((val == 0) || (val == HALFDAY)) |
839 | val = 0; | |
840 | else | |
841 | val = FULLDAY+1; /* illegal */ | |
d6fff2c5 KM |
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 | } | |
e0799b82 KM |
867 | attime->hour = val/HOUR; |
868 | attime->min = val%HOUR; | |
255d293c SW |
869 | } |
870 | ||
344505d2 SW |
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 */ | |
6303198d MK |
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); | |
344505d2 SW |
887 | exit(1); |
888 | } | |
889 | return(pwdinfo->pw_name); | |
890 | } | |
891 | ||
892 | /* | |
893 | * Do general cleanup. | |
894 | */ | |
255d293c SW |
895 | cleanup() |
896 | { | |
d6fff2c5 KM |
897 | if (unlink(atfile) == -1) |
898 | perror(atfile); | |
899 | exit(1); | |
255d293c SW |
900 | } |
901 | ||
344505d2 SW |
902 | /* |
903 | * Print usage info and exit. | |
904 | */ | |
255d293c SW |
905 | usage() |
906 | { | |
e0799b82 | 907 | fprintf(stderr,"usage: at [-csm] time [date] [filename]\n"); |
d6fff2c5 | 908 | exit(1); |
255d293c SW |
909 | } |
910 |