atrun.c revision 251627
1218792Snp/*
2218792Snp *  atrun.c - run jobs queued by at; run with root privileges.
3218792Snp *  Copyright (C) 1993, 1994 Thomas Koenig
4218792Snp *
5218792Snp * Redistribution and use in source and binary forms, with or without
6218792Snp * modification, are permitted provided that the following conditions
7218792Snp * are met:
8218792Snp * 1. Redistributions of source code must retain the above copyright
9218792Snp *    notice, this list of conditions and the following disclaimer.
10218792Snp * 2. The name of the author(s) may not be used to endorse or promote
11218792Snp *    products derived from this software without specific prior written
12218792Snp *    permission.
13218792Snp *
14218792Snp * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15218792Snp * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16218792Snp * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17218792Snp * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18218792Snp * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19218792Snp * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20218792Snp * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21218792Snp * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22218792Snp * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23218792Snp * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24218792Snp */
25218792Snp
26218792Snp#ifndef lint
27218792Snpstatic const char rcsid[] =
28218792Snp  "$FreeBSD: head/libexec/atrun/atrun.c 251627 2013-06-11 18:46:46Z ghelmer $";
29218792Snp#endif /* not lint */
30218792Snp
31218792Snp/* System Headers */
32218792Snp
33218792Snp#include <sys/fcntl.h>
34218792Snp#include <sys/file.h>
35218792Snp#include <sys/types.h>
36218792Snp#include <sys/stat.h>
37218792Snp#ifdef __FreeBSD__
38218792Snp#include <sys/sysctl.h>
39218792Snp#endif
40218792Snp#include <sys/wait.h>
41218792Snp#include <sys/param.h>
42218792Snp#include <ctype.h>
43218792Snp#include <dirent.h>
44218792Snp#include <err.h>
45218792Snp#include <grp.h>
46218792Snp#include <pwd.h>
47218792Snp#include <signal.h>
48218792Snp#include <stdarg.h>
49218792Snp#include <stddef.h>
50218792Snp#include <stdio.h>
51218792Snp#include <stdlib.h>
52218792Snp#include <string.h>
53218792Snp#include <syslog.h>
54237436Snp#include <time.h>
55218792Snp#include <unistd.h>
56218792Snp#ifdef __FreeBSD__
57237436Snp#include <paths.h>
58237436Snp#else
59237436Snp#include <getopt.h>
60218792Snp#endif
61218792Snp#ifdef LOGIN_CAP
62218792Snp#include <login_cap.h>
63218792Snp#endif
64218792Snp#ifdef PAM
65218792Snp#include <security/pam_appl.h>
66218792Snp#include <security/openpam.h>
67218792Snp#endif
68218792Snp
69218792Snp/* Local headers */
70218792Snp
71237436Snp#include "gloadavg.h"
72218792Snp#define MAIN
73218792Snp#include "privs.h"
74218792Snp
75218792Snp/* Macros */
76218792Snp
77218792Snp#ifndef ATJOB_DIR
78218792Snp#define ATJOB_DIR "/usr/spool/atjobs/"
79218792Snp#endif
80218792Snp
81218792Snp#ifndef ATSPOOL_DIR
82218792Snp#define ATSPOOL_DIR "/usr/spool/atspool/"
83218792Snp#endif
84218792Snp
85218792Snp#ifndef LOADAVG_MX
86218792Snp#define LOADAVG_MX 1.5
87218792Snp#endif
88218792Snp
89218792Snp/* File scope variables */
90237436Snp
91218792Snpstatic const char * const atrun = "atrun"; /* service name for syslog etc. */
92218792Snpstatic int debug = 0;
93218792Snp
94218792Snpvoid perr(const char *fmt, ...);
95218792Snpvoid perrx(const char *fmt, ...);
96218792Snpstatic void usage(void);
97218792Snp
98218792Snp/* Local functions */
99218792Snpstatic int
100218792Snpwrite_string(int fd, const char* a)
101218792Snp{
102218792Snp    return write(fd, a, strlen(a));
103237436Snp}
104237436Snp
105237436Snp#undef DEBUG_FORK
106237436Snp#ifdef DEBUG_FORK
107248925Snpstatic pid_t
108218792Snpmyfork(void)
109218792Snp{
110237436Snp	pid_t res;
111218792Snp	res = fork();
112218792Snp	if (res == 0)
113218792Snp	    kill(getpid(),SIGSTOP);
114218792Snp	return res;
115218792Snp}
116218792Snp
117218792Snp#define fork myfork
118218792Snp#endif
119218792Snp
120218792Snpstatic void
121218792Snprun_file(const char *filename, uid_t uid, gid_t gid)
122237436Snp{
123237436Snp/* Run a file by spawning off a process which redirects I/O,
124237436Snp * spawns a subshell, then waits for it to complete and sends
125237436Snp * mail to the user.
126237436Snp */
127218792Snp    pid_t pid;
128218792Snp    int fd_out, fd_in;
129248925Snp    int queue;
130218792Snp    char mailbuf[MAXLOGNAME], fmt[64];
131237436Snp    char *mailname = NULL;
132237436Snp    FILE *stream;
133218792Snp    int send_mail = 0;
134218792Snp    struct stat buf, lbuf;
135218792Snp    off_t size;
136218792Snp    struct passwd *pentry;
137218792Snp    int fflags;
138218792Snp    long nuid;
139218792Snp    long ngid;
140218792Snp#ifdef PAM
141218792Snp    pam_handle_t *pamh = NULL;
142218792Snp    int pam_err;
143218792Snp    struct pam_conv pamc = {
144218792Snp	.conv = openpam_nullconv,
145218792Snp	.appdata_ptr = NULL
146218792Snp    };
147218792Snp#endif
148218792Snp
149218792Snp    PRIV_START
150218792Snp
151218792Snp    if (chmod(filename, S_IRUSR) != 0)
152218792Snp    {
153218792Snp	perr("cannot change file permissions");
154218792Snp    }
155218792Snp
156218792Snp    PRIV_END
157218792Snp
158218792Snp    pid = fork();
159218792Snp    if (pid == -1)
160218792Snp	perr("cannot fork");
161218792Snp
162218792Snp    else if (pid != 0)
163218792Snp	return;
164245935Snp
165245935Snp    /* Let's see who we mail to.  Hopefully, we can read it from
166218792Snp     * the command file; if not, send it to the owner, or, failing that,
167218792Snp     * to root.
168218792Snp     */
169218792Snp
170218792Snp    pentry = getpwuid(uid);
171218792Snp    if (pentry == NULL)
172218792Snp	perrx("Userid %lu not found - aborting job %s",
173218792Snp		(unsigned long) uid, filename);
174218792Snp
175218792Snp#ifdef PAM
176218792Snp    PRIV_START
177218792Snp
178218792Snp    pam_err = pam_start(atrun, pentry->pw_name, &pamc, &pamh);
179218792Snp    if (pam_err != PAM_SUCCESS)
180218792Snp	perrx("cannot start PAM: %s", pam_strerror(pamh, pam_err));
181218792Snp
182218792Snp    pam_err = pam_acct_mgmt(pamh, PAM_SILENT);
183218792Snp    /* Expired password shouldn't prevent the job from running. */
184218792Snp    if (pam_err != PAM_SUCCESS && pam_err != PAM_NEW_AUTHTOK_REQD)
185218792Snp	perrx("Account %s (userid %lu) unavailable for job %s: %s",
186218792Snp	    pentry->pw_name, (unsigned long)uid,
187218792Snp	    filename, pam_strerror(pamh, pam_err));
188218792Snp
189218792Snp    pam_end(pamh, pam_err);
190218792Snp
191218792Snp    PRIV_END
192218792Snp#endif /* PAM */
193218792Snp
194218792Snp    PRIV_START
195218792Snp
196218792Snp    stream=fopen(filename, "r");
197218792Snp
198218792Snp    PRIV_END
199218792Snp
200218792Snp    if (stream == NULL)
201218792Snp	perr("cannot open input file %s", filename);
202218792Snp
203218792Snp    if ((fd_in = dup(fileno(stream))) <0)
204218792Snp	perr("error duplicating input file descriptor");
205218792Snp
206218792Snp    if (fstat(fd_in, &buf) == -1)
207218792Snp	perr("error in fstat of input file descriptor");
208218792Snp
209218792Snp    if (lstat(filename, &lbuf) == -1)
210218792Snp	perr("error in fstat of input file");
211218792Snp
212218792Snp    if (S_ISLNK(lbuf.st_mode))
213218792Snp	perrx("Symbolic link encountered in job %s - aborting", filename);
214218792Snp
215218792Snp    if ((lbuf.st_dev != buf.st_dev) || (lbuf.st_ino != buf.st_ino) ||
216218792Snp        (lbuf.st_uid != buf.st_uid) || (lbuf.st_gid != buf.st_gid) ||
217218792Snp        (lbuf.st_size!=buf.st_size))
218218792Snp	perrx("Somebody changed files from under us for job %s - aborting",
219218792Snp		filename);
220218792Snp
221218792Snp    if (buf.st_nlink > 1)
222218792Snp	perrx("Somebody is trying to run a linked script for job %s", filename);
223218792Snp
224218792Snp    if ((fflags = fcntl(fd_in, F_GETFD)) <0)
225218792Snp	perr("error in fcntl");
226218792Snp
227218792Snp    fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC);
228218792Snp
229218792Snp    snprintf(fmt, sizeof(fmt),
230218792Snp	"#!/bin/sh\n# atrun uid=%%ld gid=%%ld\n# mail %%%ds %%d",
231218792Snp                          MAXLOGNAME - 1);
232218792Snp
233218792Snp    if (fscanf(stream, fmt, &nuid, &ngid, mailbuf, &send_mail) != 4)
234218792Snp	perrx("File %s is in wrong format - aborting", filename);
235218792Snp
236218792Snp    if (mailbuf[0] == '-')
237218792Snp	perrx("Illegal mail name %s in %s", mailbuf, filename);
238218792Snp
239218792Snp    mailname = mailbuf;
240218792Snp
241218792Snp    if (nuid != uid)
242218792Snp	perrx("Job %s - userid %ld does not match file uid %lu",
243218792Snp		filename, nuid, (unsigned long)uid);
244218792Snp
245218792Snp    if (ngid != gid)
246218792Snp	perrx("Job %s - groupid %ld does not match file gid %lu",
247218792Snp		filename, ngid, (unsigned long)gid);
248218792Snp
249218792Snp    fclose(stream);
250218792Snp
251218792Snp    if (chdir(ATSPOOL_DIR) < 0)
252218792Snp	perr("cannot chdir to %s", ATSPOOL_DIR);
253218792Snp
254218792Snp    /* Create a file to hold the output of the job we are about to run.
255218792Snp     * Write the mail header.
256218792Snp     */
257218792Snp    if((fd_out=open(filename,
258218792Snp		O_WRONLY | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR)) < 0)
259218792Snp	perr("cannot create output file");
260218792Snp
261218792Snp    write_string(fd_out, "Subject: Output from your job ");
262218792Snp    write_string(fd_out, filename);
263218792Snp    write_string(fd_out, "\n\n");
264218792Snp    fstat(fd_out, &buf);
265218792Snp    size = buf.st_size;
266218792Snp
267218792Snp    close(STDIN_FILENO);
268218792Snp    close(STDOUT_FILENO);
269218792Snp    close(STDERR_FILENO);
270218792Snp
271218792Snp    pid = fork();
272218792Snp    if (pid < 0)
273218792Snp	perr("error in fork");
274218792Snp
275218792Snp    else if (pid == 0)
276283854Snp    {
277218792Snp	char *nul = NULL;
278218792Snp	char **nenvp = &nul;
279218792Snp
280218792Snp	/* Set up things for the child; we want standard input from the input file,
281218792Snp	 * and standard output and error sent to our output file.
282218792Snp	 */
283218792Snp
284218792Snp	if (lseek(fd_in, (off_t) 0, SEEK_SET) < 0)
285218792Snp	    perr("error in lseek");
286218792Snp
287218792Snp	if (dup(fd_in) != STDIN_FILENO)
288218792Snp	    perr("error in I/O redirection");
289218792Snp
290218792Snp	if (dup(fd_out) != STDOUT_FILENO)
291218792Snp	    perr("error in I/O redirection");
292218792Snp
293218792Snp	if (dup(fd_out) != STDERR_FILENO)
294218792Snp	    perr("error in I/O redirection");
295218792Snp
296218792Snp	close(fd_in);
297218792Snp	close(fd_out);
298218792Snp	if (chdir(ATJOB_DIR) < 0)
299218792Snp	    perr("cannot chdir to %s", ATJOB_DIR);
300237436Snp
301218792Snp	queue = *filename;
302218792Snp
303218792Snp	PRIV_START
304218792Snp
305218792Snp        nice(tolower(queue) - 'a');
306237436Snp
307218792Snp#ifdef LOGIN_CAP
308218792Snp	/*
309218792Snp	 * For simplicity and safety, set all aspects of the user context
310218792Snp	 * except for a selected subset:  Don't set priority, which was
311218792Snp	 * set based on the queue file name according to the tradition.
312218792Snp	 * Don't bother to set environment, including path vars, either
313218792Snp	 * because it will be discarded anyway.  Although the job file
314218792Snp	 * should set umask, preset it here just in case.
315218792Snp	 */
316218792Snp	if (setusercontext(NULL, pentry, uid, LOGIN_SETALL &
317218792Snp		~(LOGIN_SETPRIORITY | LOGIN_SETPATH | LOGIN_SETENV)) != 0)
318218792Snp	    exit(EXIT_FAILURE);	/* setusercontext() logged the error */
319218792Snp#else /* LOGIN_CAP */
320218792Snp	if (initgroups(pentry->pw_name,pentry->pw_gid))
321218792Snp	    perr("cannot init group access list");
322218792Snp
323218792Snp	if (setgid(gid) < 0 || setegid(pentry->pw_gid) < 0)
324218792Snp	    perr("cannot change group");
325218792Snp
326218792Snp	if (setlogin(pentry->pw_name))
327218792Snp	    perr("cannot set login name");
328218792Snp
329218792Snp	if (setuid(uid) < 0 || seteuid(uid) < 0)
330218792Snp	    perr("cannot set user id");
331218792Snp#endif /* LOGIN_CAP */
332218792Snp
333218792Snp	if (chdir(pentry->pw_dir))
334218792Snp		chdir("/");
335218792Snp
336218792Snp	if(execle("/bin/sh","sh",(char *) NULL, nenvp) != 0)
337218792Snp	    perr("exec failed for /bin/sh");
338218792Snp
339218792Snp	PRIV_END
340218792Snp    }
341218792Snp    /* We're the parent.  Let's wait.
342218792Snp     */
343218792Snp    close(fd_in);
344218792Snp    close(fd_out);
345218792Snp    waitpid(pid, (int *) NULL, 0);
346218792Snp
347218792Snp    /* Send mail.  Unlink the output file first, so it is deleted after
348218792Snp     * the run.
349218792Snp     */
350218792Snp    stat(filename, &buf);
351218792Snp    if (open(filename, O_RDONLY) != STDIN_FILENO)
352218792Snp        perr("open of jobfile failed");
353218792Snp
354218792Snp    unlink(filename);
355218792Snp    if ((buf.st_size != size) || send_mail)
356218792Snp    {
357218792Snp	PRIV_START
358218792Snp
359218792Snp#ifdef LOGIN_CAP
360218792Snp	/*
361218792Snp	 * This time set full context to run the mailer.
362218792Snp	 */
363218792Snp	if (setusercontext(NULL, pentry, uid, LOGIN_SETALL) != 0)
364218792Snp	    exit(EXIT_FAILURE);	/* setusercontext() logged the error */
365218792Snp#else /* LOGIN_CAP */
366218792Snp	if (initgroups(pentry->pw_name,pentry->pw_gid))
367218792Snp	    perr("cannot init group access list");
368218792Snp
369218792Snp	if (setgid(gid) < 0 || setegid(pentry->pw_gid) < 0)
370218792Snp	    perr("cannot change group");
371218792Snp
372218792Snp	if (setlogin(pentry->pw_name))
373218792Snp	    perr("cannot set login name");
374218792Snp
375218792Snp	if (setuid(uid) < 0 || seteuid(uid) < 0)
376218792Snp	    perr("cannot set user id");
377218792Snp#endif /* LOGIN_CAP */
378218792Snp
379218792Snp	if (chdir(pentry->pw_dir))
380218792Snp		chdir("/");
381218792Snp
382218792Snp#ifdef __FreeBSD__
383218792Snp	execl(_PATH_SENDMAIL, "sendmail", "-F", "Atrun Service",
384218792Snp			"-odi", "-oem",
385218792Snp			mailname, (char *) NULL);
386218792Snp#else
387218792Snp        execl(MAIL_CMD, MAIL_CMD, mailname, (char *) NULL);
388218792Snp#endif
389218792Snp	    perr("exec failed for mail command");
390218792Snp
391218792Snp	PRIV_END
392218792Snp    }
393218792Snp    exit(EXIT_SUCCESS);
394218792Snp}
395218792Snp
396218792Snp/* Global functions */
397218792Snp
398218792Snp/* Needed in gloadavg.c */
399218792Snpvoid
400218792Snpperr(const char *fmt, ...)
401218792Snp{
402218792Snp    const char * const fmtadd = ": %m";
403218792Snp    char nfmt[strlen(fmt) + strlen(fmtadd) + 1];
404218792Snp    va_list ap;
405218792Snp
406218792Snp    va_start(ap, fmt);
407218792Snp    if (debug)
408218792Snp    {
409218792Snp	vwarn(fmt, ap);
410218792Snp    }
411218792Snp    else
412218792Snp    {
413218792Snp	snprintf(nfmt, sizeof(nfmt), "%s%s", fmt, fmtadd);
414218792Snp	vsyslog(LOG_ERR, nfmt, ap);
415218792Snp    }
416218792Snp    va_end(ap);
417218792Snp
418218792Snp    exit(EXIT_FAILURE);
419218792Snp}
420218792Snp
421218792Snpvoid
422218792Snpperrx(const char *fmt, ...)
423218792Snp{
424218792Snp    va_list ap;
425218792Snp
426218792Snp    va_start(ap, fmt);
427218792Snp    if (debug)
428218792Snp	vwarnx(fmt, ap);
429218792Snp    else
430218792Snp	vsyslog(LOG_ERR, fmt, ap);
431218792Snp    va_end(ap);
432218792Snp
433218792Snp    exit(EXIT_FAILURE);
434218792Snp}
435218792Snp
436218792Snpint
437218792Snpmain(int argc, char *argv[])
438218792Snp{
439218792Snp/* Browse through  ATJOB_DIR, checking all the jobfiles wether they should
440218792Snp * be executed and or deleted. The queue is coded into the first byte of
441218792Snp * the job filename, the date (in minutes since Eon) as a hex number in the
442218792Snp * following eight bytes, followed by a dot and a serial number.  A file
443218792Snp * which has not been executed yet is denoted by its execute - bit set.
444218792Snp * For those files which are to be executed, run_file() is called, which forks
445218792Snp * off a child which takes care of I/O redirection, forks off another child
446218792Snp * for execution and yet another one, optionally, for sending mail.
447218792Snp * Files which already have run are removed during the next invocation.
448218792Snp */
449218792Snp    DIR *spool;
450218792Snp    struct dirent *dirent;
451218792Snp    struct stat buf;
452218792Snp    unsigned long ctm;
453218792Snp    unsigned long jobno;
454218792Snp    char queue;
455218792Snp    time_t now, run_time;
456218792Snp    char batch_name[] = "Z2345678901234";
457218792Snp    uid_t batch_uid;
458218792Snp    gid_t batch_gid;
459218792Snp    int c;
460218792Snp    int run_batch;
461218792Snp#ifdef __FreeBSD__
462218792Snp    size_t ncpu, ncpusz;
463218792Snp    double load_avg = -1;
464218792Snp#else
465218792Snp    double load_avg = LOADAVG_MX;
466218792Snp#endif
467218792Snp
468218792Snp/* We don't need root privileges all the time; running under uid and gid daemon
469218792Snp * is fine.
470218792Snp */
471218792Snp
472218792Snp    RELINQUISH_PRIVS_ROOT(DAEMON_UID, DAEMON_GID)
473218792Snp
474218792Snp    openlog(atrun, LOG_PID, LOG_CRON);
475218792Snp
476218792Snp    opterr = 0;
477218792Snp    while((c=getopt(argc, argv, "dl:"))!= -1)
478218792Snp    {
479218792Snp	switch (c)
480218792Snp	{
481218792Snp	case 'l':
482218792Snp	    if (sscanf(optarg, "%lf", &load_avg) != 1)
483218792Snp		perr("garbled option -l");
484248925Snp#ifndef __FreeBSD__
485248925Snp	    if (load_avg <= 0.)
486248925Snp		load_avg = LOADAVG_MX;
487248925Snp#endif
488248925Snp	    break;
489218792Snp
490218792Snp	case 'd':
491218792Snp	    debug ++;
492218792Snp	    break;
493218792Snp
494218792Snp	case '?':
495218792Snp	default:
496218792Snp	    usage();
497218792Snp	}
498218792Snp    }
499218792Snp
500218792Snp    if (chdir(ATJOB_DIR) != 0)
501218792Snp	perr("cannot change to %s", ATJOB_DIR);
502218792Snp
503218792Snp#ifdef __FreeBSD__
504218792Snp    if (load_avg <= 0.) {
505218792Snp	ncpusz = sizeof(size_t);
506218792Snp	if (sysctlbyname("hw.ncpu", &ncpu, &ncpusz, NULL, 0) < 0)
507218792Snp		ncpu = 1;
508218792Snp	load_avg = LOADAVG_MX * ncpu;
509218792Snp    }
510218792Snp#endif
511218792Snp
512218792Snp    /* Main loop. Open spool directory for reading and look over all the
513218792Snp     * files in there. If the filename indicates that the job should be run
514218792Snp     * and the x bit is set, fork off a child which sets its user and group
515218792Snp     * id to that of the files and exec a /bin/sh which executes the shell
516218792Snp     * script. Unlink older files if they should no longer be run.  For
517218792Snp     * deletion, their r bit has to be turned on.
518218792Snp     *
519218792Snp     * Also, pick the oldest batch job to run, at most one per invocation of
520218792Snp     * atrun.
521218792Snp     */
522218792Snp    if ((spool = opendir(".")) == NULL)
523218792Snp	perr("cannot read %s", ATJOB_DIR);
524218792Snp
525218792Snp    if (flock(dirfd(spool), LOCK_EX) == -1)
526218792Snp	perr("cannot lock %s", ATJOB_DIR);
527218792Snp
528218792Snp    now = time(NULL);
529218792Snp    run_batch = 0;
530218792Snp    batch_uid = (uid_t) -1;
531218792Snp    batch_gid = (gid_t) -1;
532218792Snp
533218792Snp    while ((dirent = readdir(spool)) != NULL) {
534218792Snp	if (stat(dirent->d_name,&buf) != 0)
535218792Snp	    perr("cannot stat in %s", ATJOB_DIR);
536218792Snp
537218792Snp	/* We don't want directories
538218792Snp	 */
539218792Snp	if (!S_ISREG(buf.st_mode))
540218792Snp	    continue;
541218792Snp
542218792Snp	if (sscanf(dirent->d_name,"%c%5lx%8lx",&queue,&jobno,&ctm) != 3)
543218792Snp	    continue;
544218792Snp
545218792Snp	run_time = (time_t) ctm*60;
546218792Snp
547218792Snp	if ((S_IXUSR & buf.st_mode) && (run_time <=now)) {
548218792Snp	    if (isupper(queue) && (strcmp(batch_name,dirent->d_name) > 0)) {
549218792Snp		run_batch = 1;
550218792Snp		strlcpy(batch_name, dirent->d_name, sizeof(batch_name));
551218792Snp		batch_uid = buf.st_uid;
552218792Snp		batch_gid = buf.st_gid;
553218792Snp	    }
554218792Snp
555218792Snp	/* The file is executable and old enough
556218792Snp	 */
557218792Snp	    if (islower(queue))
558218792Snp		run_file(dirent->d_name, buf.st_uid, buf.st_gid);
559218792Snp	}
560218792Snp	/*  Delete older files
561218792Snp	 */
562218792Snp	if ((run_time < now) && !(S_IXUSR & buf.st_mode) && (S_IRUSR & buf.st_mode))
563248925Snp	    unlink(dirent->d_name);
564248925Snp    }
565248925Snp    /* run the single batch file, if any
566248925Snp    */
567218792Snp    if (run_batch && (gloadavg() < load_avg))
568218792Snp	run_file(batch_name, batch_uid, batch_gid);
569218792Snp
570218792Snp    closelog();
571218792Snp    exit(EXIT_SUCCESS);
572218792Snp}
573218792Snp
574218792Snpstatic void
575218792Snpusage(void)
576218792Snp{
577218792Snp    if (debug)
578218792Snp	fprintf(stderr, "usage: atrun [-l load_avg] [-d]\n");
579218792Snp    else
580218792Snp	syslog(LOG_ERR, "usage: atrun [-l load_avg] [-d]");
581218792Snp
582218792Snp    exit(EXIT_FAILURE);
583218792Snp}
584218792Snp