]> git.saurik.com Git - apple/system_cmds.git/blobdiff - atrun.tproj/atrun.c
system_cmds-336.17.tar.gz
[apple/system_cmds.git] / atrun.tproj / atrun.c
index 0634fb2ec6025cac86ddd5389160fc79f8c43f8c..9d52ffa888ecfc55b845f75b41bdeab1b1c1b7ab 100644 (file)
@@ -1,31 +1,6 @@
-/*
- * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
- *
- * @APPLE_LICENSE_HEADER_START@
- * 
- * Copyright (c) 1999-2003 Apple Computer, Inc.  All Rights Reserved.
- * 
- * This file contains Original Code and/or Modifications of Original Code
- * as defined in and that are subject to the Apple Public Source License
- * Version 2.0 (the 'License'). You may not use this file except in
- * compliance with the License. Please obtain a copy of the License at
- * http://www.opensource.apple.com/apsl/ and read it before using this
- * file.
- * 
- * The Original Code and all software distributed under the License are
- * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
- * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
- * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
- * Please see the License for the specific language governing rights and
- * limitations under the License.
- * 
- * @APPLE_LICENSE_HEADER_END@
- */
-/*
- * atrun.c - run jobs queued by at; run with root privileges.
- * Copyright (c) 1993 by Thomas Koenig
- * All rights reserved.
+/* 
+ *  atrun.c - run jobs queued by at; run with root privileges.
+ *  Copyright (C) 1993, 1994 Thomas Koenig
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -39,7 +14,7 @@
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#ifndef lint
+static const char rcsid[] =
+  "$FreeBSD: src/libexec/atrun/atrun.c,v 1.18 2004/07/11 17:37:32 stefanf Exp $";
+#endif /* not lint */
+
 /* System Headers */
 
 #include <sys/fcntl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
+#include <sys/param.h>
+#include <ctype.h>
 #include <dirent.h>
-#include <errno.h>
+#include <err.h>
+#include <grp.h>
 #include <pwd.h>
 #include <signal.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <syslog.h>
 #include <time.h>
 #include <unistd.h>
-#include <syslog.h>
-
+#include <utmp.h>
+#if 1
 #include <paths.h>
+#else
+#include <getopt.h>
+#endif
+
+#if (MAXLOGNAME-1) > UT_NAMESIZE
+#define LOGNAMESIZE UT_NAMESIZE
+#else
+#define LOGNAMESIZE (MAXLOGNAME-1)
+#endif
 
 /* Local headers */
 
 #define MAIN
 #include "privs.h"
 #include "pathnames.h"
-#include "atrun.h"
+/* Macros */
+
+#ifndef ATJOB_DIR
+#define ATJOB_DIR _PATH_ATJOBS
+#endif
+
+#ifndef ATSPOOL_DIR
+#define ATSPOOL_DIR _PATH_ATSPOOL
+#endif
+
+#ifndef LOADAVG_MX
+#define LOADAVG_MX 1.5
+#endif
 
 /* File scope variables */
 
-static char *namep;
-static char rcsid[] = "$Id: atrun.c,v 1.1.1.2 2000/01/11 02:10:06 wsanchez Exp $";
+static int debug = 0;
+
+void perr(const char *a);
+static void usage(void);
 
 /* Local functions */
-static void
-perr(a)
-       const char *a;
+static int
+write_string(int fd, const char* a)
 {
-       syslog(LOG_ERR, "%s: %m", a);
-       exit(EXIT_FAILURE);
+    return write(fd, a, strlen(a));
 }
 
-static int
-write_string(fd, a)
-       int fd;
-       const char *a;
+#undef DEBUG_FORK
+#ifdef DEBUG_FORK
+static pid_t
+myfork(void)
 {
-       return write(fd, a, strlen(a));
+       pid_t res;
+       res = fork();
+       if (res == 0)
+           kill(getpid(),SIGSTOP);
+       return res;
 }
 
+#define fork myfork
+#endif
+
 static void
-run_file(filename, uid, gid)
-       const char *filename;
-       uid_t uid;
-       gid_t gid;
+run_file(const char *filename, uid_t uid, gid_t gid)
 {
-       /*
-        * Run a file by by spawning off a process which redirects I/O,
-        * spawns a subshell, then waits for it to complete and spawns another
-        * process to send mail to the user.
-        */
-       pid_t pid;
-       int fd_out, fd_in;
-       int queue;
-       char mailbuf[9];
-       char *mailname = NULL;
-       FILE *stream;
-       int send_mail = 0;
-       struct stat buf;
-       off_t size;
-       struct passwd *pentry;
-       int fflags;
-
-       pid = fork();
-       if (pid == -1)
-               perr("Cannot fork");
-       else if (pid > 0)
-               return;
-
-       /*
-        * Let's see who we mail to.  Hopefully, we can read it from the
-        * command file; if not, send it to the owner, or, failing that, to
-        * root.
-        */
+/* Run a file by spawning off a process which redirects I/O,
+ * spawns a subshell, then waits for it to complete and sends
+ * mail to the user.
+ */
+    pid_t pid;
+    int fd_out, fd_in;
+    int queue;
+    char mailbuf[LOGNAMESIZE + 1], fmt[49];
+    char *mailname = NULL;
+    FILE *stream;
+    int send_mail = 0;
+    struct stat buf, lbuf;
+    off_t size;
+    struct passwd *pentry;
+    int fflags;
+    long nuid;
+    long ngid;
+
+
+    PRIV_START
+
+    if (chmod(filename, S_IRUSR) != 0)
+    {
+       perr("cannot change file permissions");
+    }
+
+    PRIV_END
+
+    pid = fork();
+    if (pid == -1)
+       perr("cannot fork");
+    
+    else if (pid != 0)
+       return;
+
+    /* Let's see who we mail to.  Hopefully, we can read it from
+     * the command file; if not, send it to the owner, or, failing that,
+     * to root.
+     */
+
+    pentry = getpwuid(uid);
+    if (pentry == NULL)
+    {
+       syslog(LOG_ERR,"Userid %lu not found - aborting job %s",
+              (unsigned long) uid, filename);
+        exit(EXIT_FAILURE);
+    }
+    PRIV_START
+
+    stream=fopen(filename, "r");
+
+    PRIV_END
+
+#ifdef __FreeBSD__
+    if (pentry->pw_expire && time(NULL) >= pentry->pw_expire)
+    {
+       syslog(LOG_ERR, "Userid %lu is expired - aborting job %s",
+               (unsigned long) uid, filename);
+       exit(EXIT_FAILURE);
+    }
+#endif
 
-       PRIV_START
+    if (stream == NULL)
+       perr("cannot open input file");
 
-           stream = fopen(filename, "r");
+    if ((fd_in = dup(fileno(stream))) <0)
+       perr("error duplicating input file descriptor");
 
-       PRIV_END
+    if (fstat(fd_in, &buf) == -1)
+       perr("error in fstat of input file descriptor");
 
-       if (stream == NULL)
-               perr("Cannot open input file");
+    if (lstat(filename, &lbuf) == -1)
+       perr("error in fstat of input file");
 
-       if ((fd_in = dup(fileno(stream))) < 0)
-               perr("Error duplicating input file descriptor");
+    if (S_ISLNK(lbuf.st_mode)) {
+       syslog(LOG_ERR,"Symbolic link encountered in job %s - aborting",
+               filename);
+       exit(EXIT_FAILURE);
+    }
+    if ((lbuf.st_dev != buf.st_dev) || (lbuf.st_ino != buf.st_ino) ||
+        (lbuf.st_uid != buf.st_uid) || (lbuf.st_gid != buf.st_gid) ||
+        (lbuf.st_size!=buf.st_size)) {
+       syslog(LOG_ERR,"Somebody changed files from under us for job %s - "
+       "aborting",filename);
+       exit(EXIT_FAILURE);
+    }
+    if (buf.st_nlink > 1) {
+       syslog(LOG_ERR,"Someboy is trying to run a linked script for job %s",
+               filename);
+       exit(EXIT_FAILURE);
+    }
+    if ((fflags = fcntl(fd_in, F_GETFD)) <0)
+       perr("error in fcntl");
 
-       if ((fflags = fcntl(fd_in, F_GETFD)) < 0)
-               perr("Error in fcntl");
+    fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC);
 
-       fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC);
+    snprintf(fmt, sizeof(fmt),
+       "#!/bin/sh\n# atrun uid=%%ld gid=%%ld\n# mail %%%ds %%d",
+                          LOGNAMESIZE);
+    if (fscanf(stream, fmt, &nuid, &ngid, mailbuf, &send_mail) != 4) {
+       syslog(LOG_ERR,"File %s is in wrong format - aborting", filename);
+       exit(EXIT_FAILURE);
+    }
+    if (mailbuf[0] == '-') {
+       syslog(LOG_ERR,"illegal mail name %s in %s",mailbuf,filename);
+       exit(EXIT_FAILURE);
+    }
+    mailname = mailbuf;
+    if (nuid != uid) {
+       syslog(LOG_ERR,"Job %s - userid %ld does not match file uid %lu",
+               filename, nuid, (unsigned long)uid);
+       exit(EXIT_FAILURE);
+    }
+    if (ngid != gid) {
+       syslog(LOG_ERR,"Job %s - groupid %ld does not match file gid %lu",
+               filename, ngid, (unsigned long)gid);
+       exit(EXIT_FAILURE);
+    }
+    fclose(stream);
+    if (chdir(ATSPOOL_DIR) < 0)
+       perr("cannot chdir to " ATSPOOL_DIR);
+    
+    /* Create a file to hold the output of the job we are about to run.
+     * Write the mail header.
+     */    
+    if((fd_out=open(filename,
+               O_WRONLY | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR)) < 0)
+       perr("cannot create output file");
+
+    write_string(fd_out, "Subject: Output from your job ");
+    write_string(fd_out, filename);
+    write_string(fd_out, "\n\n");
+    fstat(fd_out, &buf);
+    size = buf.st_size;
+
+    close(STDIN_FILENO);
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+    pid = fork();
+    if (pid < 0)
+       perr("error in fork");
+
+    else if (pid == 0)
+    {
+       char *nul = NULL;
+       char **nenvp = &nul;
+
+       /* Set up things for the child; we want standard input from the input file,
+        * and standard output and error sent to our output file.
+        */
 
-       if (fscanf(stream, "#! /bin/sh\n# mail %8s %d", mailbuf, &send_mail) == 2) {
-               mailname = mailbuf;
-       } else {
-               pentry = getpwuid(uid);
-               if (pentry == NULL)
-                       mailname = "root";
-               else
-                       mailname = pentry->pw_name;
-       }
-       fclose(stream);
-       if (chdir(_PATH_ATSPOOL) < 0)
-               perr("Cannot chdir to " _PATH_ATSPOOL);
+       if (lseek(fd_in, (off_t) 0, SEEK_SET) < 0)
+           perr("error in lseek");
 
-       /*
-        * Create a file to hold the output of the job we are  about to
-        * run. Write the mail header.
-        */
-       if ((fd_out = open(filename,
-                   O_WRONLY | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR)) < 0)
-               perr("Cannot create output file");
+       if (dup(fd_in) != STDIN_FILENO)
+           perr("error in I/O redirection");
+
+       if (dup(fd_out) != STDOUT_FILENO)
+           perr("error in I/O redirection");
+
+       if (dup(fd_out) != STDERR_FILENO)
+           perr("error in I/O redirection");
 
-       write_string(fd_out, "Subject: Output from your job ");
-       write_string(fd_out, filename);
-       write_string(fd_out, "\n\n");
-       fstat(fd_out, &buf);
-       size = buf.st_size;
+       close(fd_in);
+       close(fd_out);
+       if (chdir(ATJOB_DIR) < 0)
+           perr("cannot chdir to " ATJOB_DIR);
 
-       close(STDIN_FILENO);
-       close(STDOUT_FILENO);
-       close(STDERR_FILENO);
+       queue = *filename;
 
-       pid = fork();
-       if (pid < 0)
-               perr("Error in fork");
-       else if (pid == 0) {
-               char *nul = NULL;
-               char **nenvp = &nul;
+       PRIV_START
 
-               /*
-                * Set up things for the child; we want standard input from
-                * the input file, and standard output and error sent to
-                * our output file.
-                */
+        nice(tolower(queue) - 'a');
+       
+       if (initgroups(pentry->pw_name,pentry->pw_gid))
+           perr("cannot delete saved userids");
 
-               if (lseek(fd_in, (off_t) 0, SEEK_SET) < 0)
-                       perr("Error in lseek");
+       if (setgid(gid) < 0 || setegid(pentry->pw_gid) < 0)
+           perr("cannot change group");
 
-               if (dup(fd_in) != STDIN_FILENO)
-                       perr("Error in I/O redirection");
+       if (setlogin(pentry->pw_name))
+           perr("cannot set login name");
 
-               if (dup(fd_out) != STDOUT_FILENO)
-                       perr("Error in I/O redirection");
+       if (setuid(uid) < 0 || seteuid(uid) < 0)
+           perr("cannot set user id");
 
-               if (dup(fd_out) != STDERR_FILENO)
-                       perr("Error in I/O redirection");
+       if (chdir(pentry->pw_dir))
+               chdir("/");
 
-               close(fd_in);
-               close(fd_out);
-               if (chdir(_PATH_ATJOBS) < 0)
-                       perr("Cannot chdir to " _PATH_ATJOBS);
+       if(execle("/bin/sh","sh",(char *) NULL, nenvp) != 0)
+           perr("exec failed for /bin/sh");
 
-               queue = *filename;
+       PRIV_END
+    }
+    /* We're the parent.  Let's wait.
+     */
+    close(fd_in);
+    close(fd_out);
+    waitpid(pid, (int *) NULL, 0);
+
+    /* Send mail.  Unlink the output file first, so it is deleted after
+     * the run.
+     */
+    stat(filename, &buf);
+    if (open(filename, O_RDONLY) != STDIN_FILENO)
+        perr("open of jobfile failed");
+
+    unlink(filename);
+    if ((buf.st_size != size) || send_mail)
+    {    
+       PRIV_START
 
-               PRIV_START
+       if (initgroups(pentry->pw_name,pentry->pw_gid))
+           perr("cannot delete saved userids");
 
-                   if (queue > 'b')
-                       nice(queue - 'b');
+       if (setgid(gid) < 0 || setegid(pentry->pw_gid) < 0)
+           perr("cannot change group");
 
-               if (setgid(gid) < 0)
-                       perr("Cannot change group");
+       if (setlogin(pentry->pw_name))
+           perr("cannot set login name");
 
-               if (setuid(uid) < 0)
-                       perr("Cannot set user id");
+       if (setuid(uid) < 0 || seteuid(uid) < 0)
+           perr("cannot set user id");
 
+       if (chdir(pentry->pw_dir))
                chdir("/");
 
-               if (execle("/bin/sh", "sh", (char *) NULL, nenvp) != 0)
-                       perr("Exec failed");
+#if 1
+       execl(_PATH_SENDMAIL, "sendmail", "-F", "Atrun Service",
+                       "-odi", "-oem",
+                       mailname, (char *) NULL);
+#else
+        execl(MAIL_CMD, MAIL_CMD, mailname, (char *) NULL);
+#endif
+           perr("exec failed for mail command");
 
-               PRIV_END
-       }
-       /* We're the parent.  Let's wait. */
-       close(fd_in);
-       close(fd_out);
-       waitpid(pid, (int *) NULL, 0);
-
-       stat(filename, &buf);
-       if ((buf.st_size != size) || send_mail) {
-               /* Fork off a child for sending mail */
-               pid = fork();
-               if (pid < 0)
-                       perr("Fork failed");
-               else if (pid == 0) {
-                       if (open(filename, O_RDONLY) != STDIN_FILENO)
-                               perr("Cannot reopen output file");
-
-                       execl(_PATH_SENDMAIL, _PATH_SENDMAIL, mailname,
-                           (char *) NULL);
-                       perr("Exec failed");
-               }
-               waitpid(pid, (int *) NULL, 0);
-       }
-       unlink(filename);
-       exit(EXIT_SUCCESS);
+       PRIV_END
+    }
+    exit(EXIT_SUCCESS);
 }
 
 /* Global functions */
 
+/* Needed in gloadavg.c */
+void
+perr(const char *a)
+{
+    if (debug)
+    {
+       warn("%s", a);
+    }
+    else
+       syslog(LOG_ERR, "%s: %m", a);
+
+    exit(EXIT_FAILURE);
+}
+
 int
-main(argc, argv)
-       int argc;
-       char *argv[];
+main(int argc, char *argv[])
 {
-       /*
-        * Browse through  _PATH_ATJOBS, checking all the jobfiles wether
-        * they should be executed and or deleted. The queue is coded into
-        * the first byte of the job filename, the date (in minutes since
-        * Eon) as a hex number in the following eight bytes, followed by
-        * a dot and a serial number.  A file which has not been executed
-        * yet is denoted by its execute - bit set.  For those files which
-        * are to be executed, run_file() is called, which forks off a
-        * child which takes care of I/O redirection, forks off another
-        * child for execution and yet another one, optionally, for sending
-        * mail.  Files which already have run are removed during the
-        * next invocation.
+/* Browse through  ATJOB_DIR, checking all the jobfiles wether they should
+ * be executed and or deleted. The queue is coded into the first byte of
+ * the job filename, the date (in minutes since Eon) as a hex number in the
+ * following eight bytes, followed by a dot and a serial number.  A file
+ * which has not been executed yet is denoted by its execute - bit set.
+ * For those files which are to be executed, run_file() is called, which forks
+ * off a child which takes care of I/O redirection, forks off another child
+ * for execution and yet another one, optionally, for sending mail.
+ * Files which already have run are removed during the next invocation.
+ */
+    DIR *spool;
+    struct dirent *dirent;
+    struct stat buf;
+    unsigned long ctm;
+    unsigned long jobno;
+    char queue;
+    time_t now, run_time;
+    char batch_name[] = "Z2345678901234";
+    uid_t batch_uid;
+    gid_t batch_gid;
+    int c;
+    int run_batch;
+    double load_avg = LOADAVG_MX, la;
+
+/* We don't need root privileges all the time; running under uid and gid daemon
+ * is fine.
+ */
+
+    RELINQUISH_PRIVS_ROOT(DAEMON_UID, DAEMON_GID)
+
+    openlog("atrun", LOG_PID, LOG_CRON);
+
+    opterr = 0;
+    while((c=getopt(argc, argv, "dl:"))!= -1)
+    {
+       switch (c)
+       {
+       case 'l': 
+           if (sscanf(optarg, "%lf", &load_avg) != 1)
+               perr("garbled option -l");
+           if (load_avg <= 0.)
+               load_avg = LOADAVG_MX;
+           break;
+
+       case 'd':
+           debug ++;
+           break;
+
+       case '?':
+       default:
+           usage();
+       }
+    }
+
+    if (chdir(ATJOB_DIR) != 0)
+       perr("cannot change to " ATJOB_DIR);
+
+    /* Main loop. Open spool directory for reading and look over all the
+     * files in there. If the filename indicates that the job should be run
+     * and the x bit is set, fork off a child which sets its user and group
+     * id to that of the files and exec a /bin/sh which executes the shell
+     * script. Unlink older files if they should no longer be run.  For
+     * deletion, their r bit has to be turned on.
+     *
+     * Also, pick the oldest batch job to run, at most one per invocation of
+     * atrun.
+     */
+    if ((spool = opendir(".")) == NULL)
+       perr("cannot read " ATJOB_DIR);
+
+    now = time(NULL);
+    run_batch = 0;
+    batch_uid = (uid_t) -1;
+    batch_gid = (gid_t) -1;
+
+    while ((dirent = readdir(spool)) != NULL) {
+       if (stat(dirent->d_name,&buf) != 0)
+           perr("cannot stat in " ATJOB_DIR);
+
+       /* We don't want directories
         */
-       DIR *spool;
-       struct dirent *dirent;
-       struct stat buf;
-       int older;
-       unsigned long ctm;
-       char queue;
-
-       /*
-        * We don't need root privileges all the time; running under uid
-        * and gid daemon is fine.
+       if (!S_ISREG(buf.st_mode)) 
+           continue;
+
+       if (sscanf(dirent->d_name,"%c%5lx%8lx",&queue,&jobno,&ctm) != 3)
+           continue;
+
+       run_time = (time_t) ctm*60;
+
+       if ((S_IXUSR & buf.st_mode) && (run_time <=now)) {
+           if (isupper(queue) && (strcmp(batch_name,dirent->d_name) > 0)) {
+               run_batch = 1;
+               strncpy(batch_name, dirent->d_name, sizeof(batch_name));
+               batch_uid = buf.st_uid;
+               batch_gid = buf.st_gid;
+           }
+       
+       /* The file is executable and old enough
         */
-
-       RELINQUISH_PRIVS_ROOT(0) /* it's setuid root */
-       openlog("atrun", LOG_PID, LOG_CRON);
-
-       namep = argv[0];
-       if (chdir(_PATH_ATJOBS) != 0)
-               perr("Cannot change to " _PATH_ATJOBS);
-
-       /*
-        * Main loop. Open spool directory for reading and look over all
-        * the files in there. If the filename indicates that the job
-        * should be run and the x bit is set, fork off a child which sets
-        * its user and group id to that of the files and exec a /bin/sh
-        * which executes the shell script. Unlink older files if they
-        * should no longer be run.  For deletion, their r bit has to be
-        * turned on.
+           if (islower(queue))
+               run_file(dirent->d_name, buf.st_uid, buf.st_gid);
+       }
+       /*  Delete older files
         */
-       if ((spool = opendir(".")) == NULL)
-               perr("Cannot read " _PATH_ATJOBS);
-
-       while ((dirent = readdir(spool)) != NULL) {
-               double la;
-
-               if (stat(dirent->d_name, &buf) != 0)
-                       perr("Cannot stat in " _PATH_ATJOBS);
-
-               /* We don't want directories */
-               if (!S_ISREG(buf.st_mode))
-                       continue;
-
-               if (sscanf(dirent->d_name, "%c%8lx", &queue, &ctm) != 2)
-                       continue;
-
-               if ((queue == 'b') && ((getloadavg(&la, 1) != 1) ||
-                   (la > ATRUN_MAXLOAD)))
-                       continue;
-
-               older = (time_t) ctm *60 <= time(NULL);
-
-               /* The file is executable and old enough */
-               if (older && (S_IXUSR & buf.st_mode)) {
-                       /*
-                        * Now we know we want to run the file, we can turn
-                        * off the execute bit
-                        */
-
-                       PRIV_START
-
-                           if (chmod(dirent->d_name, S_IRUSR) != 0)
-                               perr("Cannot change file permissions");
+       if ((run_time < now) && !(S_IXUSR & buf.st_mode) && (S_IRUSR & buf.st_mode))
+           unlink(dirent->d_name);
+    }
+    /* run the single batch file, if any
+    */
+    if (run_batch && (getloadavg(&la, 1) == 1) && la < load_avg)
+       run_file(batch_name, batch_uid, batch_gid);
+
+    closelog();
+    exit(EXIT_SUCCESS);
+}
 
-                       PRIV_END
+static void
+usage(void)
+{
+    if (debug)
+       fprintf(stderr, "usage: atrun [-l load_avg] [-d]\n");
+    else
+       syslog(LOG_ERR, "usage: atrun [-l load_avg] [-d]"); 
 
-                       run_file(dirent->d_name, buf.st_uid, buf.st_gid);
-               }
-               /* Delete older files */
-               if (older && !(S_IXUSR & buf.st_mode) &&
-                   (S_IRUSR & buf.st_mode))
-                       unlink(dirent->d_name);
-       }
-       closelog();
-       exit(EXIT_SUCCESS);
+    exit(EXIT_FAILURE);
 }