1 1.33 simonb /* $NetBSD: at.c,v 1.33 2021/04/03 04:33:08 simonb Exp $ */ 2 1.4 glass 3 1.1 cgd /* 4 1.11 christos * at.c : Put file into atrun queue 5 1.11 christos * Copyright (C) 1993, 1994 Thomas Koenig 6 1.1 cgd * 7 1.11 christos * Atrun & Atq modifications 8 1.11 christos * Copyright (C) 1993 David Parsons 9 1.1 cgd * 10 1.1 cgd * Redistribution and use in source and binary forms, with or without 11 1.1 cgd * modification, are permitted provided that the following conditions 12 1.1 cgd * are met: 13 1.1 cgd * 1. Redistributions of source code must retain the above copyright 14 1.1 cgd * notice, this list of conditions and the following disclaimer. 15 1.1 cgd * 2. The name of the author(s) may not be used to endorse or promote 16 1.1 cgd * products derived from this software without specific prior written 17 1.1 cgd * permission. 18 1.1 cgd * 19 1.1 cgd * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 20 1.1 cgd * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 1.1 cgd * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 1.11 christos * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 23 1.1 cgd * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 24 1.1 cgd * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 1.1 cgd * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 1.1 cgd * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 1.1 cgd * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 1.1 cgd * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 1.1 cgd */ 30 1.1 cgd 31 1.1 cgd /* System Headers */ 32 1.1 cgd #include <sys/types.h> 33 1.11 christos #include <sys/param.h> 34 1.1 cgd #include <sys/stat.h> 35 1.1 cgd #include <sys/wait.h> 36 1.1 cgd #include <ctype.h> 37 1.1 cgd #include <dirent.h> 38 1.16 mjl #include <err.h> 39 1.1 cgd #include <errno.h> 40 1.1 cgd #include <fcntl.h> 41 1.26 christos #include <locale.h> 42 1.1 cgd #include <pwd.h> 43 1.1 cgd #include <signal.h> 44 1.26 christos #include <stdbool.h> 45 1.1 cgd #include <stddef.h> 46 1.1 cgd #include <stdio.h> 47 1.1 cgd #include <stdlib.h> 48 1.1 cgd #include <string.h> 49 1.1 cgd #include <time.h> 50 1.1 cgd #include <unistd.h> 51 1.25 lukem #include <util.h> 52 1.11 christos 53 1.1 cgd /* Local headers */ 54 1.1 cgd #include "at.h" 55 1.1 cgd #include "panic.h" 56 1.33 simonb #include "parsetime.h" 57 1.11 christos #include "perm.h" 58 1.1 cgd #include "pathnames.h" 59 1.18 kleink #include "stime.h" 60 1.1 cgd #include "privs.h" 61 1.1 cgd 62 1.1 cgd /* Macros */ 63 1.1 cgd #define ALARMC 10 /* Number of seconds to wait for timeout */ 64 1.1 cgd 65 1.1 cgd #define TIMESIZE 50 66 1.1 cgd 67 1.11 christos enum { ATQ, ATRM, AT, BATCH, CAT }; /* what program we want to run */ 68 1.11 christos 69 1.1 cgd /* File scope variables */ 70 1.4 glass #ifndef lint 71 1.11 christos #if 0 72 1.11 christos static char rcsid[] = "$OpenBSD: at.c,v 1.15 1998/06/03 16:20:26 deraadt Exp $"; 73 1.11 christos #else 74 1.33 simonb __RCSID("$NetBSD: at.c,v 1.33 2021/04/03 04:33:08 simonb Exp $"); 75 1.11 christos #endif 76 1.4 glass #endif 77 1.4 glass 78 1.26 christos const char *no_export[] = {"TERM", "TERMCAP", "DISPLAY", "_"}; 79 1.8 mrg static int send_mail = 0; 80 1.1 cgd 81 1.1 cgd /* External variables */ 82 1.11 christos 83 1.1 cgd extern char **environ; 84 1.26 christos bool fcreated = false; 85 1.1 cgd char atfile[FILENAME_MAX]; 86 1.1 cgd 87 1.26 christos char *atinput = NULL; /* where to get input from */ 88 1.21 dsl unsigned char atqueue = 0; /* which queue to examine for jobs (atq) */ 89 1.1 cgd char atverify = 0; /* verify time instead of queuing job */ 90 1.1 cgd 91 1.1 cgd /* Function declarations */ 92 1.11 christos 93 1.30 joerg __dead static void sigc (int); 94 1.30 joerg __dead static void alarmc (int); 95 1.17 mjl static char *cwdname (void); 96 1.17 mjl static int nextjob (void); 97 1.21 dsl static void writefile (time_t, unsigned char); 98 1.17 mjl static void list_jobs (void); 99 1.17 mjl static void process_jobs (int, char **, int); 100 1.1 cgd 101 1.1 cgd /* Signal catching functions */ 102 1.1 cgd 103 1.15 simonb /*ARGSUSED*/ 104 1.26 christos static void 105 1.17 mjl sigc(int signo) 106 1.1 cgd { 107 1.26 christos 108 1.24 lukem /* If a signal interrupts us, remove the spool file and exit. */ 109 1.1 cgd if (fcreated) { 110 1.31 dholland privs_enter(); 111 1.11 christos (void)unlink(atfile); 112 1.31 dholland privs_exit(); 113 1.1 cgd } 114 1.25 lukem (void)raise_default_signal(signo); 115 1.1 cgd exit(EXIT_FAILURE); 116 1.1 cgd } 117 1.1 cgd 118 1.15 simonb /*ARGSUSED*/ 119 1.26 christos static void 120 1.17 mjl alarmc(int signo) 121 1.1 cgd { 122 1.26 christos 123 1.11 christos /* Time out after some seconds. */ 124 1.24 lukem warnx("File locking timed out"); 125 1.24 lukem sigc(signo); 126 1.1 cgd } 127 1.1 cgd 128 1.1 cgd /* Local functions */ 129 1.1 cgd 130 1.1 cgd static char * 131 1.17 mjl cwdname(void) 132 1.1 cgd { 133 1.26 christos 134 1.11 christos /* 135 1.11 christos * Read in the current directory; the name will be overwritten on 136 1.11 christos * subsequent calls. 137 1.11 christos */ 138 1.11 christos static char path[MAXPATHLEN]; 139 1.1 cgd 140 1.26 christos return getcwd(path, sizeof(path)); 141 1.11 christos } 142 1.1 cgd 143 1.11 christos static int 144 1.17 mjl nextjob(void) 145 1.11 christos { 146 1.11 christos int jobno; 147 1.11 christos FILE *fid; 148 1.1 cgd 149 1.11 christos if ((fid = fopen(_PATH_SEQFILE, "r+")) != NULL) { 150 1.11 christos if (fscanf(fid, "%5x", &jobno) == 1) { 151 1.11 christos (void)rewind(fid); 152 1.11 christos jobno = (1+jobno) % 0xfffff; /* 2^20 jobs enough? */ 153 1.11 christos (void)fprintf(fid, "%05x\n", jobno); 154 1.11 christos } else 155 1.11 christos jobno = EOF; 156 1.11 christos (void)fclose(fid); 157 1.26 christos return jobno; 158 1.11 christos } else if ((fid = fopen(_PATH_SEQFILE, "w")) != NULL) { 159 1.11 christos (void)fprintf(fid, "%05x\n", jobno = 1); 160 1.11 christos (void)fclose(fid); 161 1.26 christos return 1; 162 1.1 cgd } 163 1.26 christos return EOF; 164 1.1 cgd } 165 1.1 cgd 166 1.1 cgd static void 167 1.21 dsl writefile(time_t runtimer, unsigned char queue) 168 1.1 cgd { 169 1.1 cgd /* 170 1.1 cgd * This does most of the work if at or batch are invoked for 171 1.1 cgd * writing a job. 172 1.1 cgd */ 173 1.11 christos int jobno; 174 1.12 mycroft char *ap, *ppos; 175 1.12 mycroft const char *mailname; 176 1.1 cgd struct passwd *pass_entry; 177 1.1 cgd struct stat statbuf; 178 1.1 cgd int fdes, lockdes, fd2; 179 1.1 cgd FILE *fp, *fpin; 180 1.1 cgd struct sigaction act; 181 1.1 cgd char **atenv; 182 1.1 cgd int ch; 183 1.1 cgd mode_t cmask; 184 1.1 cgd struct flock lock; 185 1.1 cgd 186 1.11 christos (void)setlocale(LC_TIME, ""); 187 1.11 christos 188 1.1 cgd /* 189 1.1 cgd * Install the signal handler for SIGINT; terminate after removing the 190 1.1 cgd * spool file if necessary 191 1.1 cgd */ 192 1.26 christos (void)memset(&act, 0, sizeof(act)); 193 1.1 cgd act.sa_handler = sigc; 194 1.26 christos (void)sigemptyset(&act.sa_mask); 195 1.1 cgd act.sa_flags = 0; 196 1.1 cgd 197 1.28 christos (void)sigaction(SIGINT, &act, NULL); 198 1.1 cgd 199 1.20 itojun (void)strlcpy(atfile, _PATH_ATJOBS, sizeof(atfile)); 200 1.11 christos ppos = atfile + strlen(atfile); 201 1.1 cgd 202 1.1 cgd /* 203 1.1 cgd * Loop over all possible file names for running something at this 204 1.11 christos * particular time, see if a file is there; the first empty slot at 205 1.11 christos * any particular time is used. Lock the file _PATH_LOCKFILE first 206 1.11 christos * to make sure we're alone when doing this. 207 1.1 cgd */ 208 1.1 cgd 209 1.31 dholland privs_enter(); 210 1.1 cgd 211 1.11 christos if ((lockdes = open(_PATH_LOCKFILE, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR)) < 0) 212 1.26 christos perr("Cannot open lockfile " _PATH_LOCKFILE); 213 1.2 cgd 214 1.2 cgd lock.l_type = F_WRLCK; 215 1.2 cgd lock.l_whence = SEEK_SET; 216 1.2 cgd lock.l_start = 0; 217 1.2 cgd lock.l_len = 0; 218 1.1 cgd 219 1.1 cgd act.sa_handler = alarmc; 220 1.26 christos (void)sigemptyset(&act.sa_mask); 221 1.1 cgd act.sa_flags = 0; 222 1.1 cgd 223 1.1 cgd /* 224 1.1 cgd * Set an alarm so a timeout occurs after ALARMC seconds, in case 225 1.1 cgd * something is seriously broken. 226 1.1 cgd */ 227 1.28 christos (void)sigaction(SIGALRM, &act, NULL); 228 1.26 christos (void)alarm(ALARMC); 229 1.26 christos (void)fcntl(lockdes, F_SETLKW, &lock); 230 1.26 christos (void)alarm(0); 231 1.1 cgd 232 1.11 christos if ((jobno = nextjob()) == EOF) 233 1.11 christos perr("Cannot generate job number"); 234 1.11 christos 235 1.11 christos (void)snprintf(ppos, sizeof(atfile) - (ppos - atfile), 236 1.11 christos "%c%5x%8lx", queue, jobno, (unsigned long) (runtimer/60)); 237 1.1 cgd 238 1.11 christos for (ap = ppos; *ap != '\0'; ap++) 239 1.11 christos if (*ap == ' ') 240 1.11 christos *ap = '0'; 241 1.11 christos 242 1.26 christos if (stat(atfile, &statbuf) == -1) 243 1.11 christos if (errno != ENOENT) 244 1.26 christos perr("Cannot access " _PATH_ATJOBS); 245 1.1 cgd 246 1.1 cgd /* 247 1.1 cgd * Create the file. The x bit is only going to be set after it has 248 1.1 cgd * been completely written out, to make sure it is not executed in 249 1.1 cgd * the meantime. To make sure they do not get deleted, turn off 250 1.1 cgd * their r bit. Yes, this is a kluge. 251 1.1 cgd */ 252 1.1 cgd cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR); 253 1.11 christos if ((fdes = open(atfile, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR)) == -1) 254 1.1 cgd perr("Cannot create atjob file"); 255 1.1 cgd 256 1.26 christos if ((fd2 = dup(fdes)) == -1) 257 1.1 cgd perr("Error in dup() of job file"); 258 1.1 cgd 259 1.26 christos if (fchown(fd2, real_uid, real_gid) == -1) 260 1.1 cgd perr("Cannot give away file"); 261 1.1 cgd 262 1.31 dholland privs_exit(); 263 1.1 cgd 264 1.1 cgd /* 265 1.1 cgd * We've successfully created the file; let's set the flag so it 266 1.1 cgd * gets removed in case of an interrupt or error. 267 1.1 cgd */ 268 1.26 christos fcreated = true; 269 1.1 cgd 270 1.1 cgd /* Now we can release the lock, so other people can access it */ 271 1.1 cgd lock.l_type = F_UNLCK; 272 1.1 cgd lock.l_whence = SEEK_SET; 273 1.1 cgd lock.l_start = 0; 274 1.1 cgd lock.l_len = 0; 275 1.11 christos (void)fcntl(lockdes, F_SETLKW, &lock); 276 1.11 christos (void)close(lockdes); 277 1.1 cgd 278 1.1 cgd if ((fp = fdopen(fdes, "w")) == NULL) 279 1.1 cgd panic("Cannot reopen atjob file"); 280 1.1 cgd 281 1.1 cgd /* 282 1.11 christos * Get the userid to mail to, first by trying getlogin(), which reads 283 1.11 christos * /etc/utmp, then from $LOGNAME or $USER, finally from getpwuid(). 284 1.1 cgd */ 285 1.1 cgd mailname = getlogin(); 286 1.11 christos if (mailname == NULL && (mailname = getenv("LOGNAME")) == NULL) 287 1.11 christos mailname = getenv("USER"); 288 1.1 cgd 289 1.26 christos if (mailname == NULL || mailname[0] == '\0' || 290 1.26 christos strlen(mailname) > LOGIN_NAME_MAX || getpwnam(mailname) == NULL) { 291 1.11 christos pass_entry = getpwuid(real_uid); 292 1.1 cgd if (pass_entry != NULL) 293 1.1 cgd mailname = pass_entry->pw_name; 294 1.1 cgd } 295 1.1 cgd 296 1.11 christos if (atinput != NULL) { 297 1.1 cgd fpin = freopen(atinput, "r", stdin); 298 1.1 cgd if (fpin == NULL) 299 1.1 cgd perr("Cannot open input file"); 300 1.1 cgd } 301 1.26 christos (void)fprintf(fp, 302 1.26 christos "#!/bin/sh\n" 303 1.26 christos "# atrun uid=%u gid=%u\n" 304 1.26 christos "# mail %s %d\n", 305 1.15 simonb real_uid, real_gid, mailname, send_mail); 306 1.1 cgd 307 1.1 cgd /* Write out the umask at the time of invocation */ 308 1.11 christos (void)fprintf(fp, "umask %o\n", cmask); 309 1.1 cgd 310 1.1 cgd /* 311 1.1 cgd * Write out the environment. Anything that may look like a special 312 1.1 cgd * character to the shell is quoted, except for \n, which is done 313 1.1 cgd * with a pair of "'s. Dont't export the no_export list (such as 314 1.1 cgd * TERM or DISPLAY) because we don't want these. 315 1.1 cgd */ 316 1.1 cgd for (atenv = environ; *atenv != NULL; atenv++) { 317 1.1 cgd int export = 1; 318 1.1 cgd char *eqp; 319 1.1 cgd 320 1.1 cgd eqp = strchr(*atenv, '='); 321 1.14 mjl if (eqp == NULL) 322 1.1 cgd eqp = *atenv; 323 1.1 cgd else { 324 1.27 lukem size_t i; 325 1.1 cgd 326 1.26 christos for (i = 0; i < __arraycount(no_export); i++) { 327 1.26 christos export = export && 328 1.26 christos strncmp(*atenv, no_export[i], 329 1.26 christos (size_t)(eqp - *atenv)) != 0; 330 1.1 cgd } 331 1.1 cgd eqp++; 332 1.1 cgd } 333 1.1 cgd 334 1.1 cgd if (export) { 335 1.26 christos (void)fwrite(*atenv, sizeof(char), 336 1.26 christos (size_t)(eqp - *atenv), fp); 337 1.1 cgd for (ap = eqp; *ap != '\0'; ap++) { 338 1.1 cgd if (*ap == '\n') 339 1.11 christos (void)fprintf(fp, "\"\n\""); 340 1.1 cgd else { 341 1.21 dsl if (!isalnum((unsigned char)*ap)) { 342 1.11 christos switch (*ap) { 343 1.11 christos case '%': case '/': case '{': 344 1.11 christos case '[': case ']': case '=': 345 1.11 christos case '}': case '@': case '+': 346 1.11 christos case '#': case ',': case '.': 347 1.11 christos case ':': case '-': case '_': 348 1.11 christos break; 349 1.11 christos default: 350 1.11 christos (void)fputc('\\', fp); 351 1.11 christos break; 352 1.11 christos } 353 1.11 christos } 354 1.11 christos (void)fputc(*ap, fp); 355 1.1 cgd } 356 1.1 cgd } 357 1.11 christos (void)fputs("; export ", fp); 358 1.26 christos (void)fwrite(*atenv, sizeof(char), 359 1.26 christos (size_t)(eqp - *atenv - 1), fp); 360 1.11 christos (void)fputc('\n', fp); 361 1.11 christos } 362 1.11 christos } 363 1.11 christos /* 364 1.11 christos * Cd to the directory at the time and write out all the 365 1.11 christos * commands the user supplies from stdin. 366 1.11 christos */ 367 1.11 christos (void)fputs("cd ", fp); 368 1.11 christos for (ap = cwdname(); *ap != '\0'; ap++) { 369 1.11 christos if (*ap == '\n') 370 1.15 simonb (void)fprintf(fp, "\"\n\""); 371 1.11 christos else { 372 1.21 dsl if (*ap != '/' && !isalnum((unsigned char)*ap)) 373 1.11 christos (void)fputc('\\', fp); 374 1.1 cgd 375 1.11 christos (void)fputc(*ap, fp); 376 1.1 cgd } 377 1.1 cgd } 378 1.1 cgd /* 379 1.11 christos * Test cd's exit status: die if the original directory has been 380 1.11 christos * removed, become unreadable or whatever. 381 1.1 cgd */ 382 1.26 christos (void)fprintf(fp, 383 1.26 christos " || {\n" 384 1.26 christos "\t echo 'Execution directory inaccessible' >&2\n" 385 1.26 christos "\t exit 1\n" 386 1.26 christos "}\n"); 387 1.11 christos 388 1.11 christos if ((ch = getchar()) == EOF) 389 1.11 christos panic("Input error"); 390 1.1 cgd 391 1.11 christos do { 392 1.11 christos (void)fputc(ch, fp); 393 1.11 christos } while ((ch = getchar()) != EOF); 394 1.1 cgd 395 1.11 christos (void)fprintf(fp, "\n"); 396 1.1 cgd if (ferror(fp)) 397 1.1 cgd panic("Output error"); 398 1.1 cgd 399 1.1 cgd if (ferror(stdin)) 400 1.1 cgd panic("Input error"); 401 1.1 cgd 402 1.11 christos (void)fclose(fp); 403 1.11 christos 404 1.31 dholland privs_enter(); 405 1.1 cgd 406 1.1 cgd /* 407 1.1 cgd * Set the x bit so that we're ready to start executing 408 1.1 cgd */ 409 1.26 christos if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) == -1) 410 1.1 cgd perr("Cannot give away file"); 411 1.1 cgd 412 1.31 dholland privs_exit(); 413 1.10 mrg 414 1.11 christos (void)close(fd2); 415 1.26 christos (void)fprintf(stderr, 416 1.26 christos "Job %d will be executed using /bin/sh\n", jobno); 417 1.1 cgd } 418 1.1 cgd 419 1.1 cgd static void 420 1.17 mjl list_jobs(void) 421 1.1 cgd { 422 1.1 cgd /* 423 1.1 cgd * List all a user's jobs in the queue, by looping through 424 1.1 cgd * _PATH_ATJOBS, or everybody's if we are root 425 1.1 cgd */ 426 1.1 cgd struct passwd *pw; 427 1.1 cgd DIR *spool; 428 1.1 cgd struct dirent *dirent; 429 1.1 cgd struct stat buf; 430 1.1 cgd struct tm runtime; 431 1.1 cgd unsigned long ctm; 432 1.21 dsl unsigned char queue; 433 1.11 christos int jobno; 434 1.1 cgd time_t runtimer; 435 1.1 cgd char timestr[TIMESIZE]; 436 1.1 cgd int first = 1; 437 1.1 cgd 438 1.31 dholland privs_enter(); 439 1.1 cgd 440 1.26 christos if (chdir(_PATH_ATJOBS) == -1) 441 1.26 christos perr("Cannot change to " _PATH_ATJOBS); 442 1.1 cgd 443 1.1 cgd if ((spool = opendir(".")) == NULL) 444 1.26 christos perr("Cannot open " _PATH_ATJOBS); 445 1.1 cgd 446 1.1 cgd /* Loop over every file in the directory */ 447 1.1 cgd while ((dirent = readdir(spool)) != NULL) { 448 1.26 christos if (stat(dirent->d_name, &buf) == -1) 449 1.26 christos perr("Cannot stat in " _PATH_ATJOBS); 450 1.1 cgd 451 1.1 cgd /* 452 1.1 cgd * See it's a regular file and has its x bit turned on and 453 1.1 cgd * is the user's 454 1.1 cgd */ 455 1.1 cgd if (!S_ISREG(buf.st_mode) 456 1.26 christos || (buf.st_uid != real_uid && real_uid != 0) 457 1.1 cgd || !(S_IXUSR & buf.st_mode || atverify)) 458 1.1 cgd continue; 459 1.1 cgd 460 1.11 christos if (sscanf(dirent->d_name, "%c%5x%8lx", &queue, &jobno, &ctm) != 3) 461 1.1 cgd continue; 462 1.1 cgd 463 1.26 christos if (atqueue && queue != atqueue) 464 1.1 cgd continue; 465 1.1 cgd 466 1.26 christos runtimer = 60 * (time_t)ctm; 467 1.1 cgd runtime = *localtime(&runtimer); 468 1.29 christos #if 1 469 1.29 christos /* 470 1.29 christos * Provide a consistent date/time format instead of a 471 1.29 christos * locale-specific one that might have 2 digit years 472 1.29 christos */ 473 1.29 christos (void)strftime(timestr, TIMESIZE, "%T %F", &runtime); 474 1.29 christos #else 475 1.26 christos (void)strftime(timestr, TIMESIZE, "%X %x", &runtime); 476 1.29 christos #endif 477 1.1 cgd if (first) { 478 1.15 simonb (void)printf("%-*s %-*s %-*s %s\n", 479 1.15 simonb (int)strlen(timestr), "Date", 480 1.15 simonb LOGIN_NAME_MAX, "Owner", 481 1.15 simonb 7, "Queue", 482 1.15 simonb "Job"); 483 1.1 cgd first = 0; 484 1.1 cgd } 485 1.1 cgd pw = getpwuid(buf.st_uid); 486 1.1 cgd 487 1.15 simonb (void)printf("%s %-*s %c%-*s %d\n", 488 1.1 cgd timestr, 489 1.15 simonb LOGIN_NAME_MAX, pw ? pw->pw_name : "???", 490 1.1 cgd queue, 491 1.15 simonb 6, (S_IXUSR & buf.st_mode) ? "" : "(done)", 492 1.11 christos jobno); 493 1.1 cgd } 494 1.28 christos (void)closedir(spool); 495 1.31 dholland privs_exit(); 496 1.1 cgd } 497 1.1 cgd 498 1.1 cgd static void 499 1.17 mjl process_jobs(int argc, char **argv, int what) 500 1.1 cgd { 501 1.1 cgd /* Delete every argument (job - ID) given */ 502 1.1 cgd int i; 503 1.1 cgd struct stat buf; 504 1.11 christos DIR *spool; 505 1.11 christos struct dirent *dirent; 506 1.11 christos unsigned long ctm; 507 1.21 dsl unsigned char queue; 508 1.11 christos int jobno; 509 1.1 cgd 510 1.31 dholland privs_enter(); 511 1.1 cgd 512 1.26 christos if (chdir(_PATH_ATJOBS) == -1) 513 1.26 christos perr("Cannot change to " _PATH_ATJOBS); 514 1.1 cgd 515 1.11 christos if ((spool = opendir(".")) == NULL) 516 1.26 christos perr("Cannot open " _PATH_ATJOBS); 517 1.11 christos 518 1.31 dholland privs_exit(); 519 1.11 christos 520 1.11 christos /* Loop over every file in the directory */ 521 1.11 christos while((dirent = readdir(spool)) != NULL) { 522 1.11 christos 523 1.31 dholland privs_enter(); 524 1.26 christos if (stat(dirent->d_name, &buf) == -1) 525 1.26 christos perr("Cannot stat in " _PATH_ATJOBS); 526 1.31 dholland privs_exit(); 527 1.11 christos 528 1.11 christos if (sscanf(dirent->d_name, "%c%5x%8lx", &queue, &jobno, &ctm) !=3) 529 1.11 christos continue; 530 1.11 christos 531 1.11 christos for (i = optind; i < argc; i++) { 532 1.11 christos if (atoi(argv[i]) == jobno) { 533 1.26 christos if (buf.st_uid != real_uid && real_uid != 0) 534 1.26 christos errx(EXIT_FAILURE, 535 1.26 christos "%s: Not owner", argv[i]); 536 1.26 christos 537 1.11 christos switch (what) { 538 1.11 christos case ATRM: 539 1.31 dholland privs_enter(); 540 1.11 christos 541 1.26 christos if (unlink(dirent->d_name) == -1) 542 1.11 christos perr(dirent->d_name); 543 1.11 christos 544 1.31 dholland privs_exit(); 545 1.11 christos break; 546 1.11 christos 547 1.26 christos case CAT: { 548 1.26 christos FILE *fp; 549 1.26 christos int ch; 550 1.26 christos 551 1.31 dholland privs_enter(); 552 1.26 christos 553 1.26 christos fp = fopen(dirent->d_name, "r"); 554 1.26 christos 555 1.31 dholland privs_exit(); 556 1.26 christos 557 1.26 christos if (!fp) 558 1.26 christos perr("Cannot open file"); 559 1.26 christos else { 560 1.26 christos while((ch = getc(fp)) != EOF) 561 1.26 christos (void)putchar(ch); 562 1.26 christos (void)fclose(fp); 563 1.11 christos } 564 1.26 christos } 565 1.11 christos break; 566 1.11 christos 567 1.11 christos default: 568 1.26 christos errx(EXIT_FAILURE, 569 1.26 christos "Internal error, process_jobs = %d", 570 1.16 mjl what); 571 1.11 christos break; 572 1.11 christos } 573 1.11 christos } 574 1.1 cgd } 575 1.1 cgd } 576 1.28 christos (void)closedir(spool); 577 1.26 christos } 578 1.1 cgd 579 1.1 cgd /* Global functions */ 580 1.1 cgd 581 1.1 cgd int 582 1.17 mjl main(int argc, char **argv) 583 1.1 cgd { 584 1.1 cgd int c; 585 1.21 dsl unsigned char queue = DEFAULT_AT_QUEUE; 586 1.11 christos char queue_set = 0; 587 1.18 kleink char time_set = 0; 588 1.1 cgd char *pgm; 589 1.1 cgd 590 1.18 kleink int program = AT; /* our default program */ 591 1.26 christos const char *options = "q:f:t:mvldbrVc"; /* default options for at */ 592 1.11 christos int disp_version = 0; 593 1.1 cgd time_t timer; 594 1.1 cgd 595 1.31 dholland privs_relinquish(); 596 1.1 cgd 597 1.1 cgd /* Eat any leading paths */ 598 1.1 cgd if ((pgm = strrchr(argv[0], '/')) == NULL) 599 1.1 cgd pgm = argv[0]; 600 1.1 cgd else 601 1.1 cgd pgm++; 602 1.1 cgd 603 1.1 cgd /* find out what this program is supposed to do */ 604 1.1 cgd if (strcmp(pgm, "atq") == 0) { 605 1.1 cgd program = ATQ; 606 1.11 christos options = "q:vV"; 607 1.1 cgd } else if (strcmp(pgm, "atrm") == 0) { 608 1.1 cgd program = ATRM; 609 1.11 christos options = "V"; 610 1.1 cgd } else if (strcmp(pgm, "batch") == 0) { 611 1.1 cgd program = BATCH; 612 1.18 kleink options = "f:q:t:mvV"; 613 1.1 cgd } 614 1.1 cgd 615 1.1 cgd /* process whatever options we can process */ 616 1.1 cgd opterr = 1; 617 1.26 christos while ((c = getopt(argc, argv, options)) != -1) { 618 1.1 cgd switch (c) { 619 1.1 cgd case 'v': /* verify time settings */ 620 1.1 cgd atverify = 1; 621 1.1 cgd break; 622 1.1 cgd 623 1.1 cgd case 'm': /* send mail when job is complete */ 624 1.1 cgd send_mail = 1; 625 1.1 cgd break; 626 1.1 cgd 627 1.1 cgd case 'f': 628 1.1 cgd atinput = optarg; 629 1.1 cgd break; 630 1.1 cgd 631 1.1 cgd case 'q': /* specify queue */ 632 1.1 cgd if (strlen(optarg) > 1) 633 1.1 cgd usage(); 634 1.1 cgd 635 1.1 cgd atqueue = queue = *optarg; 636 1.11 christos if (!(islower(queue) || isupper(queue))) 637 1.11 christos usage(); 638 1.11 christos 639 1.11 christos queue_set = 1; 640 1.11 christos break; 641 1.18 kleink case 't': /* touch(1) date format */ 642 1.18 kleink timer = stime(optarg); 643 1.18 kleink time_set = 1; 644 1.18 kleink break; 645 1.11 christos 646 1.11 christos case 'd': 647 1.13 jwise case 'r': 648 1.11 christos if (program != AT) 649 1.11 christos usage(); 650 1.11 christos 651 1.11 christos program = ATRM; 652 1.11 christos options = "V"; 653 1.11 christos break; 654 1.11 christos 655 1.11 christos case 'l': 656 1.11 christos if (program != AT) 657 1.11 christos usage(); 658 1.11 christos 659 1.11 christos program = ATQ; 660 1.11 christos options = "q:vV"; 661 1.11 christos break; 662 1.11 christos 663 1.11 christos case 'b': 664 1.11 christos if (program != AT) 665 1.1 cgd usage(); 666 1.11 christos 667 1.11 christos program = BATCH; 668 1.11 christos options = "f:q:mvV"; 669 1.11 christos break; 670 1.11 christos 671 1.11 christos case 'V': 672 1.11 christos disp_version = 1; 673 1.11 christos break; 674 1.11 christos 675 1.11 christos case 'c': 676 1.11 christos program = CAT; 677 1.11 christos options = ""; 678 1.1 cgd break; 679 1.1 cgd 680 1.1 cgd default: 681 1.1 cgd usage(); 682 1.1 cgd break; 683 1.1 cgd } 684 1.26 christos } /* end of options eating */ 685 1.1 cgd 686 1.11 christos if (disp_version) 687 1.23 lukem (void)fprintf(stderr, "%s version %.1f\n", pgm, AT_VERSION); 688 1.11 christos 689 1.26 christos if (!check_permission()) 690 1.26 christos errx(EXIT_FAILURE, 691 1.26 christos "You do not have permission to use %s.", pgm); 692 1.11 christos 693 1.1 cgd /* select our program */ 694 1.1 cgd switch (program) { 695 1.1 cgd case ATQ: 696 1.11 christos if (optind != argc) 697 1.11 christos usage(); 698 1.1 cgd list_jobs(); 699 1.1 cgd break; 700 1.1 cgd 701 1.1 cgd case ATRM: 702 1.11 christos case CAT: 703 1.11 christos if (optind == argc) 704 1.11 christos usage(); 705 1.11 christos process_jobs(argc, argv, program); 706 1.1 cgd break; 707 1.1 cgd 708 1.1 cgd case AT: 709 1.18 kleink if (argc > optind) { 710 1.18 kleink /* -t and timespec argument are mutually exclusive */ 711 1.18 kleink if (time_set) { 712 1.18 kleink usage(); 713 1.18 kleink exit(EXIT_FAILURE); 714 1.18 kleink } else { 715 1.18 kleink timer = parsetime(argc, argv); 716 1.18 kleink time_set = 1; 717 1.18 kleink } 718 1.18 kleink } 719 1.18 kleink 720 1.1 cgd if (atverify) { 721 1.1 cgd struct tm *tm = localtime(&timer); 722 1.11 christos (void)fprintf(stderr, "%s\n", asctime(tm)); 723 1.1 cgd } 724 1.1 cgd writefile(timer, queue); 725 1.1 cgd break; 726 1.1 cgd 727 1.1 cgd case BATCH: 728 1.11 christos if (queue_set) 729 1.11 christos queue = toupper(queue); 730 1.11 christos else 731 1.11 christos queue = DEFAULT_BATCH_QUEUE; 732 1.11 christos 733 1.18 kleink if (argc > optind) { 734 1.18 kleink /* -t and timespec argument are mutually exclusive */ 735 1.18 kleink if (time_set) { 736 1.18 kleink usage(); 737 1.18 kleink exit(EXIT_FAILURE); 738 1.18 kleink } else { 739 1.18 kleink timer = parsetime(argc, argv); 740 1.18 kleink time_set = 1; 741 1.18 kleink } 742 1.26 christos } else if (!time_set) 743 1.11 christos timer = time(NULL); 744 1.11 christos 745 1.11 christos if (atverify) { 746 1.11 christos struct tm *tm = localtime(&timer); 747 1.11 christos (void)fprintf(stderr, "%s\n", asctime(tm)); 748 1.11 christos } 749 1.11 christos 750 1.11 christos writefile(timer, queue); 751 1.1 cgd break; 752 1.1 cgd 753 1.1 cgd default: 754 1.1 cgd panic("Internal error"); 755 1.1 cgd break; 756 1.1 cgd } 757 1.26 christos return EXIT_SUCCESS; 758 1.1 cgd } 759