1 /* $NetBSD: at.c,v 1.26 2008/04/05 16:26:57 christos Exp $ */
4 * at.c : Put file into atrun queue
5 * Copyright (C) 1993, 1994 Thomas Koenig
7 * Atrun & Atq modifications
8 * Copyright (C) 1993 David Parsons
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. The name of the author(s) may not be used to endorse or promote
16 * products derived from this software without specific prior written
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 #include <sys/types.h>
33 #include <sys/param.h>
56 #include "parsetime.h"
58 #include "pathnames.h"
64 #define ALARMC 10 /* Number of seconds to wait for timeout */
68 enum { ATQ
, ATRM
, AT
, BATCH
, CAT
}; /* what program we want to run */
70 /* File scope variables */
73 static char rcsid
[] = "$OpenBSD: at.c,v 1.15 1998/06/03 16:20:26 deraadt Exp $";
75 __RCSID("$NetBSD: at.c,v 1.26 2008/04/05 16:26:57 christos Exp $");
79 const char *no_export
[] = {"TERM", "TERMCAP", "DISPLAY", "_"};
80 static int send_mail
= 0;
82 /* External variables */
84 extern char **environ
;
85 bool fcreated
= false;
86 char atfile
[FILENAME_MAX
];
88 char *atinput
= NULL
; /* where to get input from */
89 unsigned char atqueue
= 0; /* which queue to examine for jobs (atq) */
90 char atverify
= 0; /* verify time instead of queuing job */
92 /* Function declarations */
94 static void sigc (int);
95 static void alarmc (int);
96 static char *cwdname (void);
97 static int nextjob (void);
98 static void writefile (time_t, unsigned char);
99 static void list_jobs (void);
100 static void process_jobs (int, char **, int);
102 /* Signal catching functions */
109 /* If a signal interrupts us, remove the spool file and exit. */
112 (void)unlink(atfile
);
115 (void)raise_default_signal(signo
);
124 /* Time out after some seconds. */
125 warnx("File locking timed out");
129 /* Local functions */
136 * Read in the current directory; the name will be overwritten on
139 static char path
[MAXPATHLEN
];
141 return getcwd(path
, sizeof(path
));
150 if ((fid
= fopen(_PATH_SEQFILE
, "r+")) != NULL
) {
151 if (fscanf(fid
, "%5x", &jobno
) == 1) {
153 jobno
= (1+jobno
) % 0xfffff; /* 2^20 jobs enough? */
154 (void)fprintf(fid
, "%05x\n", jobno
);
159 } else if ((fid
= fopen(_PATH_SEQFILE
, "w")) != NULL
) {
160 (void)fprintf(fid
, "%05x\n", jobno
= 1);
168 writefile(time_t runtimer
, unsigned char queue
)
171 * This does most of the work if at or batch are invoked for
176 const char *mailname
;
177 struct passwd
*pass_entry
;
179 int fdes
, lockdes
, fd2
;
181 struct sigaction act
;
187 (void)setlocale(LC_TIME
, "");
190 * Install the signal handler for SIGINT; terminate after removing the
191 * spool file if necessary
193 (void)memset(&act
, 0, sizeof(act
));
194 act
.sa_handler
= sigc
;
195 (void)sigemptyset(&act
.sa_mask
);
198 sigaction(SIGINT
, &act
, NULL
);
200 (void)strlcpy(atfile
, _PATH_ATJOBS
, sizeof(atfile
));
201 ppos
= atfile
+ strlen(atfile
);
204 * Loop over all possible file names for running something at this
205 * particular time, see if a file is there; the first empty slot at
206 * any particular time is used. Lock the file _PATH_LOCKFILE first
207 * to make sure we're alone when doing this.
212 if ((lockdes
= open(_PATH_LOCKFILE
, O_WRONLY
| O_CREAT
, S_IWUSR
| S_IRUSR
)) < 0)
213 perr("Cannot open lockfile " _PATH_LOCKFILE
);
215 lock
.l_type
= F_WRLCK
;
216 lock
.l_whence
= SEEK_SET
;
220 act
.sa_handler
= alarmc
;
221 (void)sigemptyset(&act
.sa_mask
);
225 * Set an alarm so a timeout occurs after ALARMC seconds, in case
226 * something is seriously broken.
228 sigaction(SIGALRM
, &act
, NULL
);
230 (void)fcntl(lockdes
, F_SETLKW
, &lock
);
233 if ((jobno
= nextjob()) == EOF
)
234 perr("Cannot generate job number");
236 (void)snprintf(ppos
, sizeof(atfile
) - (ppos
- atfile
),
237 "%c%5x%8lx", queue
, jobno
, (unsigned long) (runtimer
/60));
239 for (ap
= ppos
; *ap
!= '\0'; ap
++)
243 if (stat(atfile
, &statbuf
) == -1)
245 perr("Cannot access " _PATH_ATJOBS
);
248 * Create the file. The x bit is only going to be set after it has
249 * been completely written out, to make sure it is not executed in
250 * the meantime. To make sure they do not get deleted, turn off
251 * their r bit. Yes, this is a kluge.
253 cmask
= umask(S_IRUSR
| S_IWUSR
| S_IXUSR
);
254 if ((fdes
= open(atfile
, O_CREAT
| O_TRUNC
| O_WRONLY
, S_IRUSR
)) == -1)
255 perr("Cannot create atjob file");
257 if ((fd2
= dup(fdes
)) == -1)
258 perr("Error in dup() of job file");
260 if (fchown(fd2
, real_uid
, real_gid
) == -1)
261 perr("Cannot give away file");
266 * We've successfully created the file; let's set the flag so it
267 * gets removed in case of an interrupt or error.
271 /* Now we can release the lock, so other people can access it */
272 lock
.l_type
= F_UNLCK
;
273 lock
.l_whence
= SEEK_SET
;
276 (void)fcntl(lockdes
, F_SETLKW
, &lock
);
277 (void)close(lockdes
);
279 if ((fp
= fdopen(fdes
, "w")) == NULL
)
280 panic("Cannot reopen atjob file");
283 * Get the userid to mail to, first by trying getlogin(), which reads
284 * /etc/utmp, then from $LOGNAME or $USER, finally from getpwuid().
286 mailname
= getlogin();
287 if (mailname
== NULL
&& (mailname
= getenv("LOGNAME")) == NULL
)
288 mailname
= getenv("USER");
290 if (mailname
== NULL
|| mailname
[0] == '\0' ||
291 strlen(mailname
) > LOGIN_NAME_MAX
|| getpwnam(mailname
) == NULL
) {
292 pass_entry
= getpwuid(real_uid
);
293 if (pass_entry
!= NULL
)
294 mailname
= pass_entry
->pw_name
;
297 if (atinput
!= NULL
) {
298 fpin
= freopen(atinput
, "r", stdin
);
300 perr("Cannot open input file");
304 "# atrun uid=%u gid=%u\n"
306 real_uid
, real_gid
, mailname
, send_mail
);
308 /* Write out the umask at the time of invocation */
309 (void)fprintf(fp
, "umask %o\n", cmask
);
312 * Write out the environment. Anything that may look like a special
313 * character to the shell is quoted, except for \n, which is done
314 * with a pair of "'s. Dont't export the no_export list (such as
315 * TERM or DISPLAY) because we don't want these.
317 for (atenv
= environ
; *atenv
!= NULL
; atenv
++) {
321 eqp
= strchr(*atenv
, '=');
327 for (i
= 0; i
< __arraycount(no_export
); i
++) {
329 strncmp(*atenv
, no_export
[i
],
330 (size_t)(eqp
- *atenv
)) != 0;
336 (void)fwrite(*atenv
, sizeof(char),
337 (size_t)(eqp
- *atenv
), fp
);
338 for (ap
= eqp
; *ap
!= '\0'; ap
++) {
340 (void)fprintf(fp
, "\"\n\"");
342 if (!isalnum((unsigned char)*ap
)) {
344 case '%': case '/': case '{':
345 case '[': case ']': case '=':
346 case '}': case '@': case '+':
347 case '#': case ',': case '.':
348 case ':': case '-': case '_':
351 (void)fputc('\\', fp
);
355 (void)fputc(*ap
, fp
);
358 (void)fputs("; export ", fp
);
359 (void)fwrite(*atenv
, sizeof(char),
360 (size_t)(eqp
- *atenv
- 1), fp
);
361 (void)fputc('\n', fp
);
365 * Cd to the directory at the time and write out all the
366 * commands the user supplies from stdin.
368 (void)fputs("cd ", fp
);
369 for (ap
= cwdname(); *ap
!= '\0'; ap
++) {
371 (void)fprintf(fp
, "\"\n\"");
373 if (*ap
!= '/' && !isalnum((unsigned char)*ap
))
374 (void)fputc('\\', fp
);
376 (void)fputc(*ap
, fp
);
380 * Test cd's exit status: die if the original directory has been
381 * removed, become unreadable or whatever.
385 "\t echo 'Execution directory inaccessible' >&2\n"
389 if ((ch
= getchar()) == EOF
)
390 panic("Input error");
394 } while ((ch
= getchar()) != EOF
);
396 (void)fprintf(fp
, "\n");
398 panic("Output error");
401 panic("Input error");
408 * Set the x bit so that we're ready to start executing
410 if (fchmod(fd2
, S_IRUSR
| S_IWUSR
| S_IXUSR
) == -1)
411 perr("Cannot give away file");
416 (void)fprintf(stderr
,
417 "Job %d will be executed using /bin/sh\n", jobno
);
424 * List all a user's jobs in the queue, by looping through
425 * _PATH_ATJOBS, or everybody's if we are root
429 struct dirent
*dirent
;
436 char timestr
[TIMESIZE
];
441 if (chdir(_PATH_ATJOBS
) == -1)
442 perr("Cannot change to " _PATH_ATJOBS
);
444 if ((spool
= opendir(".")) == NULL
)
445 perr("Cannot open " _PATH_ATJOBS
);
447 /* Loop over every file in the directory */
448 while ((dirent
= readdir(spool
)) != NULL
) {
449 if (stat(dirent
->d_name
, &buf
) == -1)
450 perr("Cannot stat in " _PATH_ATJOBS
);
453 * See it's a regular file and has its x bit turned on and
456 if (!S_ISREG(buf
.st_mode
)
457 || (buf
.st_uid
!= real_uid
&& real_uid
!= 0)
458 || !(S_IXUSR
& buf
.st_mode
|| atverify
))
461 if (sscanf(dirent
->d_name
, "%c%5x%8lx", &queue
, &jobno
, &ctm
) != 3)
464 if (atqueue
&& queue
!= atqueue
)
467 runtimer
= 60 * (time_t)ctm
;
468 runtime
= *localtime(&runtimer
);
469 (void)strftime(timestr
, TIMESIZE
, "%X %x", &runtime
);
471 (void)printf("%-*s %-*s %-*s %s\n",
472 (int)strlen(timestr
), "Date",
473 LOGIN_NAME_MAX
, "Owner",
478 pw
= getpwuid(buf
.st_uid
);
480 (void)printf("%s %-*s %c%-*s %d\n",
482 LOGIN_NAME_MAX
, pw
? pw
->pw_name
: "???",
484 6, (S_IXUSR
& buf
.st_mode
) ? "" : "(done)",
491 process_jobs(int argc
, char **argv
, int what
)
493 /* Delete every argument (job - ID) given */
497 struct dirent
*dirent
;
504 if (chdir(_PATH_ATJOBS
) == -1)
505 perr("Cannot change to " _PATH_ATJOBS
);
507 if ((spool
= opendir(".")) == NULL
)
508 perr("Cannot open " _PATH_ATJOBS
);
512 /* Loop over every file in the directory */
513 while((dirent
= readdir(spool
)) != NULL
) {
516 if (stat(dirent
->d_name
, &buf
) == -1)
517 perr("Cannot stat in " _PATH_ATJOBS
);
520 if (sscanf(dirent
->d_name
, "%c%5x%8lx", &queue
, &jobno
, &ctm
) !=3)
523 for (i
= optind
; i
< argc
; i
++) {
524 if (atoi(argv
[i
]) == jobno
) {
525 if (buf
.st_uid
!= real_uid
&& real_uid
!= 0)
527 "%s: Not owner", argv
[i
]);
533 if (unlink(dirent
->d_name
) == -1)
534 perr(dirent
->d_name
);
545 fp
= fopen(dirent
->d_name
, "r");
550 perr("Cannot open file");
552 while((ch
= getc(fp
)) != EOF
)
561 "Internal error, process_jobs = %d",
570 /* Global functions */
573 main(int argc
, char **argv
)
576 unsigned char queue
= DEFAULT_AT_QUEUE
;
581 int program
= AT
; /* our default program */
582 const char *options
= "q:f:t:mvldbrVc"; /* default options for at */
583 int disp_version
= 0;
588 /* Eat any leading paths */
589 if ((pgm
= strrchr(argv
[0], '/')) == NULL
)
594 /* find out what this program is supposed to do */
595 if (strcmp(pgm
, "atq") == 0) {
598 } else if (strcmp(pgm
, "atrm") == 0) {
601 } else if (strcmp(pgm
, "batch") == 0) {
603 options
= "f:q:t:mvV";
606 /* process whatever options we can process */
608 while ((c
= getopt(argc
, argv
, options
)) != -1) {
610 case 'v': /* verify time settings */
614 case 'm': /* send mail when job is complete */
622 case 'q': /* specify queue */
623 if (strlen(optarg
) > 1)
626 atqueue
= queue
= *optarg
;
627 if (!(islower(queue
) || isupper(queue
)))
632 case 't': /* touch(1) date format */
633 timer
= stime(optarg
);
675 } /* end of options eating */
678 (void)fprintf(stderr
, "%s version %.1f\n", pgm
, AT_VERSION
);
680 if (!check_permission())
682 "You do not have permission to use %s.", pgm
);
684 /* select our program */
696 process_jobs(argc
, argv
, program
);
701 /* -t and timespec argument are mutually exclusive */
706 timer
= parsetime(argc
, argv
);
712 struct tm
*tm
= localtime(&timer
);
713 (void)fprintf(stderr
, "%s\n", asctime(tm
));
715 writefile(timer
, queue
);
720 queue
= toupper(queue
);
722 queue
= DEFAULT_BATCH_QUEUE
;
725 /* -t and timespec argument are mutually exclusive */
730 timer
= parsetime(argc
, argv
);
733 } else if (!time_set
)
737 struct tm
*tm
= localtime(&timer
);
738 (void)fprintf(stderr
, "%s\n", asctime(tm
));
741 writefile(timer
, queue
);
745 panic("Internal error");