110154Sache/* 27768Sache * atrun.c - run jobs queued by at; run with root privileges. 37768Sache * Copyright (C) 1993, 1994 Thomas Koenig 4939Snate * 5939Snate * Redistribution and use in source and binary forms, with or without 6939Snate * modification, are permitted provided that the following conditions 7939Snate * are met: 8939Snate * 1. Redistributions of source code must retain the above copyright 9939Snate * notice, this list of conditions and the following disclaimer. 10939Snate * 2. The name of the author(s) may not be used to endorse or promote 11939Snate * products derived from this software without specific prior written 12939Snate * permission. 13939Snate * 14939Snate * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 15939Snate * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16939Snate * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 1710154Sache * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 18939Snate * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19939Snate * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20939Snate * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21939Snate * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22939Snate * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23939Snate * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24939Snate */ 25939Snate 2631306Scharnier#ifndef lint 2731306Scharnierstatic const char rcsid[] = 2850476Speter "$FreeBSD$"; 2931306Scharnier#endif /* not lint */ 3031306Scharnier 31939Snate/* System Headers */ 32939Snate 33939Snate#include <sys/fcntl.h> 34251625Sghelmer#include <sys/file.h> 35939Snate#include <sys/types.h> 36939Snate#include <sys/stat.h> 37242372Smjg#ifdef __FreeBSD__ 38242372Smjg#include <sys/sysctl.h> 39242372Smjg#endif 40939Snate#include <sys/wait.h> 4124829Sdavidn#include <sys/param.h> 427768Sache#include <ctype.h> 43939Snate#include <dirent.h> 4431306Scharnier#include <err.h> 4531306Scharnier#include <grp.h> 46939Snate#include <pwd.h> 47939Snate#include <signal.h> 48170767Syar#include <stdarg.h> 49939Snate#include <stddef.h> 50939Snate#include <stdio.h> 51939Snate#include <stdlib.h> 52939Snate#include <string.h> 5331306Scharnier#include <syslog.h> 54939Snate#include <time.h> 55939Snate#include <unistd.h> 5610154Sache#ifdef __FreeBSD__ 5710154Sache#include <paths.h> 5810154Sache#else 597768Sache#include <getopt.h> 607768Sache#endif 61170741Syar#ifdef LOGIN_CAP 62170741Syar#include <login_cap.h> 63170741Syar#endif 64170773Syar#ifdef PAM 65170773Syar#include <security/pam_appl.h> 66170773Syar#include <security/openpam.h> 67170773Syar#endif 68939Snate 69939Snate/* Local headers */ 70939Snate 717768Sache#include "gloadavg.h" 72939Snate#define MAIN 73939Snate#include "privs.h" 74939Snate 757768Sache/* Macros */ 767768Sache 777768Sache#ifndef ATJOB_DIR 787768Sache#define ATJOB_DIR "/usr/spool/atjobs/" 797768Sache#endif 807768Sache 817768Sache#ifndef ATSPOOL_DIR 827768Sache#define ATSPOOL_DIR "/usr/spool/atspool/" 837768Sache#endif 847768Sache 857768Sache#ifndef LOADAVG_MX 867768Sache#define LOADAVG_MX 1.5 877768Sache#endif 887768Sache 89939Snate/* File scope variables */ 90939Snate 91170773Syarstatic const char * const atrun = "atrun"; /* service name for syslog etc. */ 92131990Sstefanfstatic int debug = 0; 93939Snate 94170767Syarvoid perr(const char *fmt, ...); 95170767Syarvoid perrx(const char *fmt, ...); 9690148Simpstatic void usage(void); 9710154Sache 98939Snate/* Local functions */ 99939Snatestatic int 1007768Sachewrite_string(int fd, const char* a) 101939Snate{ 1027768Sache return write(fd, a, strlen(a)); 103939Snate} 104939Snate 10510154Sache#undef DEBUG_FORK 1067768Sache#ifdef DEBUG_FORK 1077768Sachestatic pid_t 10890148Simpmyfork(void) 1097768Sache{ 1107768Sache pid_t res; 1117768Sache res = fork(); 1127768Sache if (res == 0) 1137768Sache kill(getpid(),SIGSTOP); 1147768Sache return res; 1157768Sache} 1167768Sache 1177768Sache#define fork myfork 1187768Sache#endif 1197768Sache 120939Snatestatic void 1217768Sacherun_file(const char *filename, uid_t uid, gid_t gid) 122939Snate{ 12380201Skris/* Run a file by spawning off a process which redirects I/O, 1247768Sache * spawns a subshell, then waits for it to complete and sends 1257768Sache * mail to the user. 1267768Sache */ 1277768Sache pid_t pid; 1287768Sache int fd_out, fd_in; 1297768Sache int queue; 130240974Sjilles char mailbuf[MAXLOGNAME], fmt[64]; 1317768Sache char *mailname = NULL; 1327768Sache FILE *stream; 1337768Sache int send_mail = 0; 13410154Sache struct stat buf, lbuf; 1357768Sache off_t size; 1367768Sache struct passwd *pentry; 1377768Sache int fflags; 13810154Sache long nuid; 13910154Sache long ngid; 140170773Syar#ifdef PAM 141170773Syar pam_handle_t *pamh = NULL; 142170773Syar int pam_err; 143170773Syar struct pam_conv pamc = { 144170773Syar .conv = openpam_nullconv, 145170773Syar .appdata_ptr = NULL 146170773Syar }; 147170773Syar#endif 148939Snate 1497768Sache PRIV_START 150939Snate 1517768Sache if (chmod(filename, S_IRUSR) != 0) 1527768Sache { 15331306Scharnier perr("cannot change file permissions"); 1547768Sache } 155939Snate 1567768Sache PRIV_END 157939Snate 1587768Sache pid = fork(); 1597768Sache if (pid == -1) 16031306Scharnier perr("cannot fork"); 16110154Sache 16210154Sache else if (pid != 0) 1637768Sache return; 164939Snate 1657768Sache /* Let's see who we mail to. Hopefully, we can read it from 1667768Sache * the command file; if not, send it to the owner, or, failing that, 1677768Sache * to root. 1687768Sache */ 169939Snate 1707768Sache pentry = getpwuid(uid); 1717768Sache if (pentry == NULL) 172170769Syar perrx("Userid %lu not found - aborting job %s", 173170769Syar (unsigned long) uid, filename); 174170769Syar 175170773Syar#ifdef PAM 1767768Sache PRIV_START 177939Snate 178170773Syar pam_err = pam_start(atrun, pentry->pw_name, &pamc, &pamh); 179170773Syar if (pam_err != PAM_SUCCESS) 180170773Syar perrx("cannot start PAM: %s", pam_strerror(pamh, pam_err)); 181170773Syar 182170773Syar pam_err = pam_acct_mgmt(pamh, PAM_SILENT); 183170773Syar /* Expired password shouldn't prevent the job from running. */ 184170773Syar if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD) 185170773Syar perrx("Account %s (userid %lu) unavailable for job %s: %s", 186170773Syar pentry->pw_name, (unsigned long)uid, 187170773Syar filename, pam_strerror(pamh, pam_err)); 188170773Syar 189170773Syar pam_end(pamh, pam_err); 190170773Syar 191170773Syar PRIV_END 192170773Syar#endif /* PAM */ 193170773Syar 194170773Syar PRIV_START 195170773Syar 1967768Sache stream=fopen(filename, "r"); 197939Snate 1987768Sache PRIV_END 199939Snate 2007768Sache if (stream == NULL) 201251627Sghelmer perr("cannot open input file %s", filename); 2027768Sache 2037768Sache if ((fd_in = dup(fileno(stream))) <0) 20431306Scharnier perr("error duplicating input file descriptor"); 2057768Sache 20610154Sache if (fstat(fd_in, &buf) == -1) 20731306Scharnier perr("error in fstat of input file descriptor"); 20810154Sache 20910154Sache if (lstat(filename, &lbuf) == -1) 21031306Scharnier perr("error in fstat of input file"); 21110154Sache 212170769Syar if (S_ISLNK(lbuf.st_mode)) 213170769Syar perrx("Symbolic link encountered in job %s - aborting", filename); 214170769Syar 21510154Sache if ((lbuf.st_dev != buf.st_dev) || (lbuf.st_ino != buf.st_ino) || 21610154Sache (lbuf.st_uid != buf.st_uid) || (lbuf.st_gid != buf.st_gid) || 217170769Syar (lbuf.st_size!=buf.st_size)) 218170769Syar perrx("Somebody changed files from under us for job %s - aborting", 21910154Sache filename); 220170769Syar 221170769Syar if (buf.st_nlink > 1) 222170769Syar perrx("Somebody is trying to run a linked script for job %s", filename); 223170769Syar 2247768Sache if ((fflags = fcntl(fd_in, F_GETFD)) <0) 22531306Scharnier perr("error in fcntl"); 2267768Sache 2277768Sache fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC); 2287768Sache 22969199Skris snprintf(fmt, sizeof(fmt), 23069199Skris "#!/bin/sh\n# atrun uid=%%ld gid=%%ld\n# mail %%%ds %%d", 231200982Sed MAXLOGNAME - 1); 232170769Syar 233170769Syar if (fscanf(stream, fmt, &nuid, &ngid, mailbuf, &send_mail) != 4) 234170769Syar perrx("File %s is in wrong format - aborting", filename); 235170769Syar 236170769Syar if (mailbuf[0] == '-') 237170769Syar perrx("Illegal mail name %s in %s", mailbuf, filename); 238170769Syar 23910154Sache mailname = mailbuf; 240170769Syar 241170769Syar if (nuid != uid) 242170769Syar perrx("Job %s - userid %ld does not match file uid %lu", 24338024Sbde filename, nuid, (unsigned long)uid); 244170769Syar 245170769Syar if (ngid != gid) 246170769Syar perrx("Job %s - groupid %ld does not match file gid %lu", 24738024Sbde filename, ngid, (unsigned long)gid); 248170769Syar 2497768Sache fclose(stream); 250170769Syar 2517768Sache if (chdir(ATSPOOL_DIR) < 0) 252170769Syar perr("cannot chdir to %s", ATSPOOL_DIR); 25310154Sache 2547768Sache /* Create a file to hold the output of the job we are about to run. 2557768Sache * Write the mail header. 25610154Sache */ 2577768Sache if((fd_out=open(filename, 2587768Sache O_WRONLY | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR)) < 0) 25931306Scharnier perr("cannot create output file"); 260939Snate 2617768Sache write_string(fd_out, "Subject: Output from your job "); 2627768Sache write_string(fd_out, filename); 2637768Sache write_string(fd_out, "\n\n"); 2647768Sache fstat(fd_out, &buf); 2657768Sache size = buf.st_size; 266939Snate 2677768Sache close(STDIN_FILENO); 2687768Sache close(STDOUT_FILENO); 2697768Sache close(STDERR_FILENO); 27010154Sache 2717768Sache pid = fork(); 2727768Sache if (pid < 0) 27331306Scharnier perr("error in fork"); 274939Snate 2757768Sache else if (pid == 0) 2767768Sache { 2777768Sache char *nul = NULL; 2787768Sache char **nenvp = &nul; 279939Snate 2807768Sache /* Set up things for the child; we want standard input from the input file, 2817768Sache * and standard output and error sent to our output file. 2827768Sache */ 283939Snate 2847768Sache if (lseek(fd_in, (off_t) 0, SEEK_SET) < 0) 28531306Scharnier perr("error in lseek"); 286939Snate 2877768Sache if (dup(fd_in) != STDIN_FILENO) 28831306Scharnier perr("error in I/O redirection"); 289939Snate 2907768Sache if (dup(fd_out) != STDOUT_FILENO) 29131306Scharnier perr("error in I/O redirection"); 292939Snate 2937768Sache if (dup(fd_out) != STDERR_FILENO) 29431306Scharnier perr("error in I/O redirection"); 295939Snate 2967768Sache close(fd_in); 2977768Sache close(fd_out); 2987768Sache if (chdir(ATJOB_DIR) < 0) 299170769Syar perr("cannot chdir to %s", ATJOB_DIR); 300939Snate 3017768Sache queue = *filename; 302939Snate 3037768Sache PRIV_START 304939Snate 3057768Sache nice(tolower(queue) - 'a'); 30610154Sache 307170741Syar#ifdef LOGIN_CAP 308170741Syar /* 309170741Syar * For simplicity and safety, set all aspects of the user context 310170741Syar * except for a selected subset: Don't set priority, which was 311170741Syar * set based on the queue file name according to the tradition. 312170741Syar * Don't bother to set environment, including path vars, either 313170741Syar * because it will be discarded anyway. Although the job file 314170741Syar * should set umask, preset it here just in case. 315170741Syar */ 316170741Syar if (setusercontext(NULL, pentry, uid, LOGIN_SETALL & 317170741Syar ~(LOGIN_SETPRIORITY | LOGIN_SETPATH | LOGIN_SETENV)) != 0) 318170741Syar exit(EXIT_FAILURE); /* setusercontext() logged the error */ 319170741Syar#else /* LOGIN_CAP */ 3207768Sache if (initgroups(pentry->pw_name,pentry->pw_gid)) 321170728Syar perr("cannot init group access list"); 322939Snate 32329231Sdima if (setgid(gid) < 0 || setegid(pentry->pw_gid) < 0) 32431306Scharnier perr("cannot change group"); 325939Snate 32629231Sdima if (setlogin(pentry->pw_name)) 32731306Scharnier perr("cannot set login name"); 32829231Sdima 32929231Sdima if (setuid(uid) < 0 || seteuid(uid) < 0) 33031306Scharnier perr("cannot set user id"); 331170741Syar#endif /* LOGIN_CAP */ 332939Snate 33329231Sdima if (chdir(pentry->pw_dir)) 33429231Sdima chdir("/"); 33529231Sdima 3367768Sache if(execle("/bin/sh","sh",(char *) NULL, nenvp) != 0) 33731306Scharnier perr("exec failed for /bin/sh"); 338939Snate 3397768Sache PRIV_END 3407768Sache } 3417768Sache /* We're the parent. Let's wait. 3427768Sache */ 3437768Sache close(fd_in); 3447768Sache close(fd_out); 3457768Sache waitpid(pid, (int *) NULL, 0); 346939Snate 3477768Sache /* Send mail. Unlink the output file first, so it is deleted after 3487768Sache * the run. 3497768Sache */ 3507768Sache stat(filename, &buf); 3517768Sache if (open(filename, O_RDONLY) != STDIN_FILENO) 35231306Scharnier perr("open of jobfile failed"); 353939Snate 3547768Sache unlink(filename); 3557768Sache if ((buf.st_size != size) || send_mail) 35610154Sache { 35710154Sache PRIV_START 35810154Sache 359170741Syar#ifdef LOGIN_CAP 360170741Syar /* 361170741Syar * This time set full context to run the mailer. 362170741Syar */ 363170741Syar if (setusercontext(NULL, pentry, uid, LOGIN_SETALL) != 0) 364170741Syar exit(EXIT_FAILURE); /* setusercontext() logged the error */ 365170741Syar#else /* LOGIN_CAP */ 36610154Sache if (initgroups(pentry->pw_name,pentry->pw_gid)) 367170739Syar perr("cannot init group access list"); 36810154Sache 36929231Sdima if (setgid(gid) < 0 || setegid(pentry->pw_gid) < 0) 37031306Scharnier perr("cannot change group"); 37110154Sache 37229231Sdima if (setlogin(pentry->pw_name)) 37331306Scharnier perr("cannot set login name"); 37429231Sdima 37529231Sdima if (setuid(uid) < 0 || seteuid(uid) < 0) 37631306Scharnier perr("cannot set user id"); 377170741Syar#endif /* LOGIN_CAP */ 37810154Sache 37929231Sdima if (chdir(pentry->pw_dir)) 38029231Sdima chdir("/"); 38129231Sdima 3827768Sache#ifdef __FreeBSD__ 3837768Sache execl(_PATH_SENDMAIL, "sendmail", "-F", "Atrun Service", 3847777Sache "-odi", "-oem", 3857768Sache mailname, (char *) NULL); 3867768Sache#else 3877768Sache execl(MAIL_CMD, MAIL_CMD, mailname, (char *) NULL); 3887768Sache#endif 38931306Scharnier perr("exec failed for mail command"); 39010154Sache 39110154Sache PRIV_END 3927768Sache } 3937768Sache exit(EXIT_SUCCESS); 394939Snate} 395939Snate 396939Snate/* Global functions */ 397939Snate 39810154Sache/* Needed in gloadavg.c */ 39910154Sachevoid 400170767Syarperr(const char *fmt, ...) 40110154Sache{ 402170767Syar const char * const fmtadd = ": %m"; 403170767Syar char nfmt[strlen(fmt) + strlen(fmtadd) + 1]; 404170767Syar va_list ap; 405170767Syar 406170767Syar va_start(ap, fmt); 40710154Sache if (debug) 40810154Sache { 409170767Syar vwarn(fmt, ap); 41010154Sache } 41110154Sache else 412170767Syar { 413170767Syar snprintf(nfmt, sizeof(nfmt), "%s%s", fmt, fmtadd); 414170767Syar vsyslog(LOG_ERR, nfmt, ap); 415170767Syar } 416170767Syar va_end(ap); 41710154Sache 41810154Sache exit(EXIT_FAILURE); 41910154Sache} 42010154Sache 421170767Syarvoid 422170767Syarperrx(const char *fmt, ...) 423170767Syar{ 424170767Syar va_list ap; 425170767Syar 426170767Syar va_start(ap, fmt); 427170767Syar if (debug) 428170767Syar vwarnx(fmt, ap); 429170767Syar else 430170767Syar vsyslog(LOG_ERR, fmt, ap); 431170767Syar va_end(ap); 432170767Syar 433170767Syar exit(EXIT_FAILURE); 434170767Syar} 435170767Syar 436939Snateint 4377768Sachemain(int argc, char *argv[]) 438939Snate{ 4397768Sache/* Browse through ATJOB_DIR, checking all the jobfiles wether they should 4407768Sache * be executed and or deleted. The queue is coded into the first byte of 4417768Sache * the job filename, the date (in minutes since Eon) as a hex number in the 4427768Sache * following eight bytes, followed by a dot and a serial number. A file 4437768Sache * which has not been executed yet is denoted by its execute - bit set. 4447768Sache * For those files which are to be executed, run_file() is called, which forks 4457768Sache * off a child which takes care of I/O redirection, forks off another child 4467768Sache * for execution and yet another one, optionally, for sending mail. 4477768Sache * Files which already have run are removed during the next invocation. 4487768Sache */ 4497768Sache DIR *spool; 4507768Sache struct dirent *dirent; 4517768Sache struct stat buf; 4527768Sache unsigned long ctm; 45310154Sache unsigned long jobno; 4547768Sache char queue; 4557768Sache time_t now, run_time; 4567768Sache char batch_name[] = "Z2345678901234"; 4577768Sache uid_t batch_uid; 4587768Sache gid_t batch_gid; 4597768Sache int c; 4607768Sache int run_batch; 461242372Smjg#ifdef __FreeBSD__ 462296756Sngie size_t ncpusz; 463242372Smjg double load_avg = -1; 464296756Sngie int ncpu; 465242372Smjg#else 4667768Sache double load_avg = LOADAVG_MX; 467242372Smjg#endif 468939Snate 4697768Sache/* We don't need root privileges all the time; running under uid and gid daemon 4707768Sache * is fine. 4717768Sache */ 472939Snate 4737768Sache RELINQUISH_PRIVS_ROOT(DAEMON_UID, DAEMON_GID) 474939Snate 475170773Syar openlog(atrun, LOG_PID, LOG_CRON); 476939Snate 4777768Sache opterr = 0; 47824349Simp while((c=getopt(argc, argv, "dl:"))!= -1) 4797768Sache { 4807768Sache switch (c) 4817768Sache { 48210154Sache case 'l': 4837768Sache if (sscanf(optarg, "%lf", &load_avg) != 1) 4847768Sache perr("garbled option -l"); 485242372Smjg#ifndef __FreeBSD__ 4867768Sache if (load_avg <= 0.) 4877768Sache load_avg = LOADAVG_MX; 488242372Smjg#endif 4897768Sache break; 490939Snate 4917768Sache case 'd': 4927768Sache debug ++; 4937768Sache break; 494939Snate 4957768Sache case '?': 4967768Sache default: 49731306Scharnier usage(); 4987768Sache } 4997768Sache } 500939Snate 5017768Sache if (chdir(ATJOB_DIR) != 0) 502170769Syar perr("cannot change to %s", ATJOB_DIR); 503939Snate 504242372Smjg#ifdef __FreeBSD__ 505242372Smjg if (load_avg <= 0.) { 506242372Smjg ncpusz = sizeof(size_t); 507242372Smjg if (sysctlbyname("hw.ncpu", &ncpu, &ncpusz, NULL, 0) < 0) 508242372Smjg ncpu = 1; 509242372Smjg load_avg = LOADAVG_MX * ncpu; 510242372Smjg } 511242372Smjg#endif 512242372Smjg 5137768Sache /* Main loop. Open spool directory for reading and look over all the 5147768Sache * files in there. If the filename indicates that the job should be run 5157768Sache * and the x bit is set, fork off a child which sets its user and group 5167768Sache * id to that of the files and exec a /bin/sh which executes the shell 5177768Sache * script. Unlink older files if they should no longer be run. For 5187768Sache * deletion, their r bit has to be turned on. 5197768Sache * 5207768Sache * Also, pick the oldest batch job to run, at most one per invocation of 5217768Sache * atrun. 5227768Sache */ 5237768Sache if ((spool = opendir(".")) == NULL) 524170769Syar perr("cannot read %s", ATJOB_DIR); 525939Snate 526251625Sghelmer if (flock(dirfd(spool), LOCK_EX) == -1) 527251625Sghelmer perr("cannot lock %s", ATJOB_DIR); 528251625Sghelmer 5297768Sache now = time(NULL); 5307768Sache run_batch = 0; 5317768Sache batch_uid = (uid_t) -1; 5327768Sache batch_gid = (gid_t) -1; 533939Snate 53410154Sache while ((dirent = readdir(spool)) != NULL) { 5357768Sache if (stat(dirent->d_name,&buf) != 0) 536170769Syar perr("cannot stat in %s", ATJOB_DIR); 537939Snate 5387768Sache /* We don't want directories 5397768Sache */ 54010154Sache if (!S_ISREG(buf.st_mode)) 5417768Sache continue; 542939Snate 54310154Sache if (sscanf(dirent->d_name,"%c%5lx%8lx",&queue,&jobno,&ctm) != 3) 5447768Sache continue; 545939Snate 5467768Sache run_time = (time_t) ctm*60; 547939Snate 54810154Sache if ((S_IXUSR & buf.st_mode) && (run_time <=now)) { 54910154Sache if (isupper(queue) && (strcmp(batch_name,dirent->d_name) > 0)) { 5507768Sache run_batch = 1; 551170726Syar strlcpy(batch_name, dirent->d_name, sizeof(batch_name)); 5527768Sache batch_uid = buf.st_uid; 5537768Sache batch_gid = buf.st_gid; 5547768Sache } 55510154Sache 5567768Sache /* The file is executable and old enough 5577768Sache */ 5587768Sache if (islower(queue)) 5597768Sache run_file(dirent->d_name, buf.st_uid, buf.st_gid); 560939Snate } 5617768Sache /* Delete older files 5627768Sache */ 5637768Sache if ((run_time < now) && !(S_IXUSR & buf.st_mode) && (S_IRUSR & buf.st_mode)) 5647768Sache unlink(dirent->d_name); 5657768Sache } 5667768Sache /* run the single batch file, if any 5677768Sache */ 56810154Sache if (run_batch && (gloadavg() < load_avg)) 56910154Sache run_file(batch_name, batch_uid, batch_gid); 5707768Sache 571265368Sghelmer if (flock(dirfd(spool), LOCK_UN) == -1) 572265368Sghelmer perr("cannot unlock %s", ATJOB_DIR); 573265368Sghelmer 574265368Sghelmer if (closedir(spool) == -1) 575265368Sghelmer perr("cannot closedir %s", ATJOB_DIR); 576265368Sghelmer 5777768Sache closelog(); 5787768Sache exit(EXIT_SUCCESS); 579939Snate} 58031306Scharnier 58131306Scharnierstatic void 58290148Simpusage(void) 58331306Scharnier{ 58431306Scharnier if (debug) 58531306Scharnier fprintf(stderr, "usage: atrun [-l load_avg] [-d]\n"); 58631306Scharnier else 58731306Scharnier syslog(LOG_ERR, "usage: atrun [-l load_avg] [-d]"); 58831306Scharnier 58931306Scharnier exit(EXIT_FAILURE); 59031306Scharnier} 591