2 * at.c : Put file into atrun queue
3 * Copyright (C) 1993, 1994 Thomas Koenig
5 * Atrun & Atq modifications
6 * Copyright (C) 1993 David Parsons
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. The name of the author(s) may not be used to endorse or promote
14 * products derived from this software without specific prior written
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 * $FreeBSD: src/usr.bin/at/at.c,v 1.18.2.1 2001/08/02 00:55:58 obrien Exp $
29 * $DragonFly: src/usr.bin/at/at.c,v 1.6 2006/03/29 21:37:43 swildner Exp $
36 #include <sys/types.h>
39 #include <sys/param.h>
56 #if (MAXLOGNAME-1) > UT_NAMESIZE
57 #define LOGNAMESIZE UT_NAMESIZE
59 #define LOGNAMESIZE (MAXLOGNAME-1)
66 #include "parsetime.h"
75 #define ATJOB_DIR "/usr/spool/atjobs/"
79 #define LFILE ATJOB_DIR ".lockfile"
86 #define ALARMC 10 /* Number of seconds to wait for timeout */
91 enum { ATQ
, ATRM
, AT
, BATCH
, CAT
}; /* what program we want to run */
93 /* File scope variables */
95 const char *no_export
[] =
97 "TERM", "TERMCAP", "DISPLAY", "_"
99 static int send_mail
= 0;
101 /* External variables */
102 uid_t real_uid
, effective_uid
;
103 gid_t real_gid
, effective_gid
;
105 extern char **environ
;
107 char atfile
[sizeof(ATJOB_DIR
) + 14] = ATJOB_DIR
;
109 char *atinput
= NULL
; /* where to get input from */
110 char atqueue
= 0; /* which queue to examine for jobs (atq) */
111 char atverify
= 0; /* verify time instead of queuing job */
113 /* Function declarations */
115 static void sigc(int signo
);
116 static void alarmc(int signo
);
117 static char *cwdname(void);
118 static void writefile(time_t runtimer
, char queue
);
119 static void list_jobs(void);
121 /* Signal catching functions */
124 void sigc(int signo __unused
)
126 /* If the user presses ^C, remove the spool file and exit
139 void alarmc(int sign __unused
)
141 /* Time out after some seconds
143 panic("file locking timed out");
146 /* Local functions */
148 static char *cwdname(void)
150 /* Read in the current directory; the name will be overwritten on
153 static char *ptr
= NULL
;
154 static size_t size
= SIZE
;
157 if ((ptr
= malloc(size
)) == NULL
)
158 errx(EXIT_FAILURE
, "virtual memory exhausted");
163 panic("out of memory");
165 if (getcwd(ptr
, size
-1) != NULL
)
169 perr("cannot get directory");
173 if ((ptr
= malloc(size
)) == NULL
)
174 errx(EXIT_FAILURE
, "virtual memory exhausted");
184 if ((fid
= fopen(ATJOB_DIR
".SEQ", "r+")) != (FILE*)0) {
185 if (fscanf(fid
, "%5lx", &jobno
) == 1) {
187 jobno
= (1+jobno
) % 0xfffff; /* 2^20 jobs enough? */
188 fprintf(fid
, "%05lx\n", jobno
);
195 else if ((fid
= fopen(ATJOB_DIR
".SEQ", "w")) != (FILE*)0) {
196 fprintf(fid
, "%05lx\n", jobno
= 1);
204 writefile(time_t runtimer
, char queue
)
206 /* This does most of the work if at or batch are invoked for writing a job.
209 char *ap
, *ppos
, *mailname
;
210 struct passwd
*pass_entry
;
212 int fdes
, lockdes
, fd2
;
214 struct sigaction act
;
220 setlocale(LC_TIME
, "");
222 /* Install the signal handler for SIGINT; terminate after removing the
223 * spool file if necessary
225 act
.sa_handler
= sigc
;
226 sigemptyset(&(act
.sa_mask
));
229 sigaction(SIGINT
, &act
, NULL
);
231 /* Loop over all possible file names for running something at this
232 * particular time, see if a file is there; the first empty slot at any
233 * particular time is used. Lock the file LFILE first to make sure
234 * we're alone when doing this.
239 if ((lockdes
= open(LFILE
, O_WRONLY
| O_CREAT
, S_IWUSR
| S_IRUSR
)) < 0)
240 perr("cannot open lockfile " LFILE
);
242 lock
.l_type
= F_WRLCK
; lock
.l_whence
= SEEK_SET
; lock
.l_start
= 0;
245 act
.sa_handler
= alarmc
;
246 sigemptyset(&(act
.sa_mask
));
249 /* Set an alarm so a timeout occurs after ALARMC seconds, in case
250 * something is seriously broken.
252 sigaction(SIGALRM
, &act
, NULL
);
254 fcntl(lockdes
, F_SETLKW
, &lock
);
257 if ((jobno
= nextjob()) == EOF
)
258 perr("cannot generate job number");
260 ppos
= atfile
+ strlen(atfile
);
261 snprintf(ppos
, sizeof(atfile
) - strlen(atfile
), "%c%5lx%8lx", queue
,
262 jobno
, (unsigned long) (runtimer
/60));
264 for(ap
=ppos
; *ap
!= '\0'; ap
++)
268 if (stat(atfile
, &statbuf
) != 0)
270 perr("cannot access " ATJOB_DIR
);
272 /* Create the file. The x bit is only going to be set after it has
273 * been completely written out, to make sure it is not executed in the
274 * meantime. To make sure they do not get deleted, turn off their r
275 * bit. Yes, this is a kluge.
277 cmask
= umask(S_IRUSR
| S_IWUSR
| S_IXUSR
);
278 if ((fdes
= creat(atfile
, O_WRONLY
)) == -1)
279 perr("cannot create atjob file");
281 if ((fd2
= dup(fdes
)) <0)
282 perr("error in dup() of job file");
284 if(fchown(fd2
, real_uid
, real_gid
) != 0)
285 perr("cannot give away file");
289 /* We no longer need suid root; now we just need to be able to write
290 * to the directory, if necessary.
293 REDUCE_PRIV(DAEMON_UID
, DAEMON_GID
)
295 /* We've successfully created the file; let's set the flag so it
296 * gets removed in case of an interrupt or error.
300 /* Now we can release the lock, so other people can access it
302 lock
.l_type
= F_UNLCK
; lock
.l_whence
= SEEK_SET
; lock
.l_start
= 0;
304 fcntl(lockdes
, F_SETLKW
, &lock
);
307 if((fp
= fdopen(fdes
, "w")) == NULL
)
308 panic("cannot reopen atjob file");
310 /* Get the userid to mail to, first by trying getlogin(), which reads
311 * /etc/utmp, then from LOGNAME, finally from getpwuid().
313 mailname
= getlogin();
314 if (mailname
== NULL
)
315 mailname
= getenv("LOGNAME");
317 if ((mailname
== NULL
) || (mailname
[0] == '\0')
318 || (strlen(mailname
) > LOGNAMESIZE
) || (getpwnam(mailname
)==NULL
))
320 pass_entry
= getpwuid(real_uid
);
321 if (pass_entry
!= NULL
)
322 mailname
= pass_entry
->pw_name
;
325 if (atinput
!= (char *) NULL
)
327 fpin
= freopen(atinput
, "r", stdin
);
329 perr("cannot open input file");
331 fprintf(fp
, "#!/bin/sh\n# atrun uid=%ld gid=%ld\n# mail %*s %d\n",
332 (long) real_uid
, (long) real_gid
, LOGNAMESIZE
, mailname
, send_mail
);
334 /* Write out the umask at the time of invocation
336 fprintf(fp
, "umask %lo\n", (unsigned long) cmask
);
338 /* Write out the environment. Anything that may look like a
339 * special character to the shell is quoted, except for \n, which is
340 * done with a pair of "'s. Don't export the no_export list (such
341 * as TERM or DISPLAY) because we don't want these.
343 for (atenv
= environ
; *atenv
!= NULL
; atenv
++)
348 eqp
= strchr(*atenv
, '=');
354 for (i
=0; i
<sizeof(no_export
)/sizeof(no_export
[0]); i
++)
357 && (strncmp(*atenv
, no_export
[i
],
358 (size_t) (eqp
-*atenv
)) != 0);
365 fwrite(*atenv
, sizeof(char), eqp
-*atenv
, fp
);
366 for(ap
= eqp
;*ap
!= '\0'; ap
++)
369 fprintf(fp
, "\"\n\"");
374 case '%': case '/': case '{': case '[':
375 case ']': case '=': case '}': case '@':
376 case '+': case '#': case ',': case '.':
377 case ':': case '-': case '_':
387 fputs("; export ", fp
);
388 fwrite(*atenv
, sizeof(char), eqp
-*atenv
-1, fp
);
393 /* Cd to the directory at the time and write out all the
394 * commands the user supplies from stdin.
397 for (ap
= cwdname(); *ap
!= '\0'; ap
++)
400 fprintf(fp
, "\"\n\"");
403 if (*ap
!= '/' && !isalnum(*ap
))
409 /* Test cd's exit status: die if the original directory has been
410 * removed, become unreadable or whatever
412 fprintf(fp
, " || {\n\t echo 'Execution directory "
413 "inaccessible' >&2\n\t exit 1\n}\n");
415 while((ch
= getchar()) != EOF
)
420 panic("output error");
423 panic("input error");
427 /* Set the x bit so that we're ready to start executing
430 if (fchmod(fd2
, S_IRUSR
| S_IWUSR
| S_IXUSR
) < 0)
431 perr("cannot give away file");
434 fprintf(stderr
, "Job %ld will be executed using /bin/sh\n", jobno
);
440 /* List all a user's jobs in the queue, by looping through ATJOB_DIR,
441 * or everybody's if we are root
445 struct dirent
*dirent
;
452 char timestr
[TIMESIZE
];
455 setlocale(LC_TIME
, "");
459 if (chdir(ATJOB_DIR
) != 0)
460 perr("cannot change to " ATJOB_DIR
);
462 if ((spool
= opendir(".")) == NULL
)
463 perr("cannot open " ATJOB_DIR
);
465 /* Loop over every file in the directory
467 while((dirent
= readdir(spool
)) != NULL
) {
468 if (stat(dirent
->d_name
, &buf
) != 0)
469 perr("cannot stat in " ATJOB_DIR
);
471 /* See it's a regular file and has its x bit turned on and
474 if (!S_ISREG(buf
.st_mode
)
475 || ((buf
.st_uid
!= real_uid
) && ! (real_uid
== 0))
476 || !(S_IXUSR
& buf
.st_mode
|| atverify
))
479 if(sscanf(dirent
->d_name
, "%c%5lx%8lx", &queue
, &jobno
, &ctm
)!=3)
482 if (atqueue
&& (queue
!= atqueue
))
485 runtimer
= 60*(time_t) ctm
;
486 runtime
= *localtime(&runtimer
);
487 strftime(timestr
, TIMESIZE
, "%X %x", &runtime
);
489 printf("Date\t\t\tOwner\tQueue\tJob#\n");
492 pw
= getpwuid(buf
.st_uid
);
494 printf("%s\t%s\t%c%s\t%ld\n",
496 pw
? pw
->pw_name
: "???",
498 (S_IXUSR
& buf
.st_mode
) ? "":"(done)",
507 process_jobs(int argc
, char **argv
, int what
)
509 /* Delete every argument (job - ID) given
514 struct dirent
*dirent
;
521 if (chdir(ATJOB_DIR
) != 0)
522 perr("cannot change to " ATJOB_DIR
);
524 if ((spool
= opendir(".")) == NULL
)
525 perr("cannot open " ATJOB_DIR
);
529 /* Loop over every file in the directory
531 while((dirent
= readdir(spool
)) != NULL
) {
534 if (stat(dirent
->d_name
, &buf
) != 0)
535 perr("cannot stat in " ATJOB_DIR
);
538 if(sscanf(dirent
->d_name
, "%c%5lx%8lx", &queue
, &jobno
, &ctm
)!=3)
541 for (i
=optind
; i
< argc
; i
++) {
542 if (atoi(argv
[i
]) == jobno
) {
543 if ((buf
.st_uid
!= real_uid
) && !(real_uid
== 0))
544 errx(EXIT_FAILURE
, "%s: not owner", argv
[i
]);
550 if (unlink(dirent
->d_name
) != 0)
551 perr(dirent
->d_name
);
564 fp
= fopen(dirent
->d_name
,"r");
569 perr("cannot open file");
571 while((ch
= getc(fp
)) != EOF
) {
579 errx(EXIT_FAILURE
, "internal error, process_jobs = %d",
589 main(int argc
, char **argv
)
592 char queue
= DEFAULT_AT_QUEUE
;
596 int program
= AT
; /* our default program */
597 const char *options
= "q:f:mvldbVc"; /* default options for at */
598 int disp_version
= 0;
603 /* Eat any leading paths
605 if ((pgm
= strrchr(argv
[0], '/')) == NULL
)
610 /* find out what this program is supposed to do
612 if (strcmp(pgm
, "atq") == 0) {
616 else if (strcmp(pgm
, "atrm") == 0) {
620 else if (strcmp(pgm
, "batch") == 0) {
625 /* process whatever options we can process
628 while ((c
=getopt(argc
, argv
, options
)) != -1)
630 case 'v': /* verify time settings */
634 case 'm': /* send mail when job is complete */
642 case 'q': /* specify queue */
643 if (strlen(optarg
) > 1)
646 atqueue
= queue
= *optarg
;
647 if (!(islower(queue
)||isupper(queue
)))
690 /* end of options eating
694 fprintf(stderr
, "at version " VERSION
"\n"
695 "Bug reports to: ig25@rz.uni-karlsruhe.de (Thomas Koenig)\n");
697 /* select our program
699 if(!check_permission())
700 errx(EXIT_FAILURE
, "you do not have permission to use this program");
704 REDUCE_PRIV(DAEMON_UID
, DAEMON_GID
)
711 REDUCE_PRIV(DAEMON_UID
, DAEMON_GID
)
713 process_jobs(argc
, argv
, ATRM
);
718 process_jobs(argc
, argv
, CAT
);
722 timer
= parsetime(argc
, argv
);
725 struct tm
*tm
= localtime(&timer
);
726 fprintf(stderr
, "%s\n", asctime(tm
));
728 writefile(timer
, queue
);
733 queue
= toupper(queue
);
735 queue
= DEFAULT_BATCH_QUEUE
;
738 timer
= parsetime(argc
, argv
);
744 struct tm
*tm
= localtime(&timer
);
745 fprintf(stderr
, "%s\n", asctime(tm
));
748 writefile(timer
, queue
);
752 panic("internal error");