start_stop_daemon: add -test, fix -x to not match by inode,
authorDenis Vlasenko <vda.linux@googlemail.com>
Sun, 18 May 2008 21:17:52 +0000 (21:17 -0000)
committerDenis Vlasenko <vda.linux@googlemail.com>
Sun, 18 May 2008 21:17:52 +0000 (21:17 -0000)
vastly improve hext text. (mostly by Roy Marples <roy AT marples.name>)

function                                             old     new   delta
packed_usage                                       24124   24190     +66
start_stop_daemon_main                               959     991     +32
start_stop_daemon_longopts                           149     156      +7
check                                               1632    1589     -43
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 3/1 up/down: 105/-43)            Total: 62 bytes

debianutils/start_stop_daemon.c
include/usage.h

index 094b3b1..5242599 100644 (file)
@@ -23,15 +23,18 @@ If --start if given, start a new process unless a matching process was found.
 
 Options controlling process matching:
         -u,--user USERNAME|UID  Only consider this user's processes
-        -n,--name PROCESS_NAME  Look for processes with matching argv[0]
-                                or /proc/$PID/exe or /proc/$PID/stat (comm field).
+        -n,--name PROCESS_NAME  Look for processes by matching PROCESS_NAME
+                                with comm field in /proc/$PID/stat.
                                 Only basename is compared:
                                 "ntpd" == "./ntpd" == "/path/to/ntpd".
 [TODO: can PROCESS_NAME be a full pathname? Should we require full match then
 with /proc/$PID/exe or argv[0] (comm can't be matched, it never contains path)]
-        -x,--exec EXECUTABLE    Look for processes with matching /proc/$PID/exe.
-                                Match is performed using device+inode.
+        -x,--exec EXECUTABLE    Look for processes that were started with this
+                                command in /proc/$PID/cmdline.
+                                Unlike -n, we match against the full path:
+                                "ntpd" != "./ntpd" != "/path/to/ntpd"
         -p,--pidfile PID_FILE   Look for processes with PID from this file
+If multiple conditions are specified, all must match.
 
 Options which are valid for --start only:
         -x,--exec EXECUTABLE    Program to run (1st arg of execvp). Mandatory.
@@ -41,8 +44,13 @@ Options which are valid for --start only:
         -c,--chuid USER[:[GRP]] Change to specified user [and group]
         -m,--make-pidfile       Write PID to the pidfile
                                 (both -m and -p must be given!)
-Misc options:
+
+Options which are valid for --stop only:
         -s,--signal SIG         Signal to send (default:TERM)
+        -t,--test               Exit with status 0 if process is found
+                                (we don't actually start or stop daemons)
+
+Misc options:
         -o,--oknodo             Exit with status 0 if nothing is done
         -q,--quiet              Quiet
         -v,--verbose            Verbose
@@ -64,19 +72,21 @@ enum {
        CTX_START      = (1 <<  1),
        OPT_BACKGROUND = (1 <<  2), // -b
        OPT_QUIET      = (1 <<  3), // -q
-       OPT_MAKEPID    = (1 <<  4), // -m
-       OPT_a          = (1 <<  5), // -a
-       OPT_n          = (1 <<  6), // -n
-       OPT_s          = (1 <<  7), // -s
-       OPT_u          = (1 <<  8), // -u
-       OPT_c          = (1 <<  9), // -c
-       OPT_x          = (1 << 10), // -x
-       OPT_p          = (1 << 11), // -p
-       OPT_OKNODO     = (1 << 12) * ENABLE_FEATURE_START_STOP_DAEMON_FANCY, // -o
-       OPT_VERBOSE    = (1 << 13) * ENABLE_FEATURE_START_STOP_DAEMON_FANCY, // -v
-       OPT_NICELEVEL  = (1 << 14) * ENABLE_FEATURE_START_STOP_DAEMON_FANCY, // -N
+       OPT_TEST       = (1 <<  4), // -t
+       OPT_MAKEPID    = (1 <<  5), // -m
+       OPT_a          = (1 <<  6), // -a
+       OPT_n          = (1 <<  7), // -n
+       OPT_s          = (1 <<  8), // -s
+       OPT_u          = (1 <<  9), // -u
+       OPT_c          = (1 << 10), // -c
+       OPT_x          = (1 << 11), // -x
+       OPT_p          = (1 << 12), // -p
+       OPT_OKNODO     = (1 << 13) * ENABLE_FEATURE_START_STOP_DAEMON_FANCY, // -o
+       OPT_VERBOSE    = (1 << 14) * ENABLE_FEATURE_START_STOP_DAEMON_FANCY, // -v
+       OPT_NICELEVEL  = (1 << 15) * ENABLE_FEATURE_START_STOP_DAEMON_FANCY, // -N
 };
 #define QUIET (option_mask32 & OPT_QUIET)
+#define TEST  (option_mask32 & OPT_TEST)
 
 struct globals {
        struct pid_list *found;
@@ -86,7 +96,6 @@ struct globals {
        char *pidfile;
        int user_id;
        smallint signal_nr;
-       struct stat execstat;
 };
 #define G (*(struct globals*)&bb_common_bufsiz1)
 #define found             (G.found               )
@@ -96,20 +105,23 @@ struct globals {
 #define pidfile           (G.pidfile             )
 #define user_id           (G.user_id             )
 #define signal_nr         (G.signal_nr           )
-#define execstat          (G.execstat            )
 #define INIT_G() \
         do { \
                user_id = -1; \
                signal_nr = 15; \
         } while (0)
 
-
+#ifdef OLDER_VERSION_OF_X
+/* -x,--exec EXECUTABLE
+ * Look for processes with matching /proc/$PID/exe.
+ * Match is performed using device+inode.
+ */
 static int pid_is_exec(pid_t pid)
 {
        struct stat st;
        char buf[sizeof("/proc//exe") + sizeof(int)*3];
 
-       sprintf(buf, "/proc/%u/exe", pid);
+       sprintf(buf, "/proc/%u/exe", (unsigned)pid);
        if (stat(buf, &st) < 0)
                return 0;
        if (st.st_dev == execstat.st_dev
@@ -117,24 +129,29 @@ static int pid_is_exec(pid_t pid)
                return 1;
        return 0;
 }
+#endif
 
-static int pid_is_user(int pid)
+static int pid_is_exec(pid_t pid)
 {
-       struct stat sb;
-       char buf[sizeof("/proc/") + sizeof(int)*3];
-
-       sprintf(buf, "/proc/%u", pid);
-       if (stat(buf, &sb) != 0)
-               return 0;
-       return (sb.st_uid == (uid_t)user_id);
+       ssize_t bytes;
+       char buf[PATH_MAX];
+
+       sprintf(buf, "/proc/%u/cmdline", (unsigned)pid);
+       bytes = open_read_close(buf, buf, sizeof(buf) - 1);
+       if (bytes > 0) {
+               buf[bytes] = '\0';
+               return strcmp(buf, execname) == 0;
+       }
+       return 0;
 }
 
-static int pid_is_cmd(pid_t pid)
+static int pid_is_name(pid_t pid)
 {
-       char buf[256]; /* is it big enough? */
+       /* /proc/PID/stat is "PID (comm_15_bytes_max) ..." */
+       char buf[32]; /* should be enough */
        char *p, *pe;
 
-       sprintf(buf, "/proc/%u/stat", pid);
+       sprintf(buf, "/proc/%u/stat", (unsigned)pid);
        if (open_read_close(buf, buf, sizeof(buf) - 1) < 0)
                return 0;
        buf[sizeof(buf) - 1] = '\0'; /* paranoia */
@@ -145,7 +162,23 @@ static int pid_is_cmd(pid_t pid)
        if (!pe)
                return 0;
        *pe = '\0';
-       return !strcmp(p, cmdname);
+       /* we require comm to match and to not be truncated */
+       /* in Linux, if comm is 15 chars, it may be a truncated
+        * name, so we don't allow that to match */
+       if (strlen(p) >= COMM_LEN - 1) /* COMM_LEN is 16 */
+               return 0;
+       return strcmp(p, cmdname) == 0;
+}
+
+static int pid_is_user(int pid)
+{
+       struct stat sb;
+       char buf[sizeof("/proc/") + sizeof(int)*3];
+
+       sprintf(buf, "/proc/%u", (unsigned)pid);
+       if (stat(buf, &sb) != 0)
+               return 0;
+       return (sb.st_uid == (uid_t)user_id);
 }
 
 static void check(int pid)
@@ -155,10 +188,10 @@ static void check(int pid)
        if (execname && !pid_is_exec(pid)) {
                return;
        }
-       if (userspec && !pid_is_user(pid)) {
+       if (cmdname && !pid_is_name(pid)) {
                return;
        }
-       if (cmdname && !pid_is_cmd(pid)) {
+       if (userspec && !pid_is_user(pid)) {
                return;
        }
        p = xmalloc(sizeof(*p));
@@ -195,10 +228,11 @@ static void do_procinit(void)
        procdir = xopendir("/proc");
 
        pid = 0;
-       while(1) {
+       while (1) {
                errno = 0; /* clear any previous error */
                entry = readdir(procdir);
-// TODO: check for exact errno(s) which mean that we got stale entry
+// TODO: this check is too generic, it's better
+// to check for exact errno(s) which mean that we got stale entry
                if (errno) /* Stale entry, process has died after opendir */
                        continue;
                if (!entry) /* EOF, no more entries */
@@ -225,12 +259,13 @@ static int do_stop(void)
        } else if (execname) {
                if (ENABLE_FEATURE_CLEAN_UP) what = xstrdup(execname);
                if (!ENABLE_FEATURE_CLEAN_UP) what = execname;
-       } else if (pidfile)
+       } else if (pidfile) {
                what = xasprintf("process in pidfile '%s'", pidfile);
-       else if (userspec)
+       } else if (userspec) {
                what = xasprintf("process(es) owned by '%s'", userspec);
-       else
+       } else {
                bb_error_msg_and_die("internal error, please report");
+       }
 
        if (!found) {
                if (!QUIET)
@@ -239,18 +274,18 @@ static int do_stop(void)
                goto ret;
        }
        for (p = found; p; p = p->next) {
-               if (kill(p->pid, signal_nr) == 0) {
-                       p->pid = - p->pid;
+               if (TEST || kill(p->pid, signal_nr) == 0) {
                        killed++;
                } else {
-                       bb_perror_msg("warning: killing process %u", p->pid);
+                       p->pid = 0;
+                       bb_perror_msg("warning: killing process %u", (unsigned)p->pid);
                }
        }
        if (!QUIET && killed) {
                printf("stopped %s (pid", what);
                for (p = found; p; p = p->next)
-                       if (p->pid < 0)
-                               printf(" %u", p->pid);
+                       if (p->pid)
+                               printf(" %u", (unsigned)p->pid);
                puts(")");
        }
  ret:
@@ -265,6 +300,7 @@ static const char start_stop_daemon_longopts[] ALIGN1 =
        "start\0"        No_argument       "S"
        "background\0"   No_argument       "b"
        "quiet\0"        No_argument       "q"
+       "test\0"         No_argument       "t"
        "make-pidfile\0" No_argument       "m"
 #if ENABLE_FEATURE_START_STOP_DAEMON_FANCY
        "oknodo\0"       No_argument       "o"
@@ -291,6 +327,7 @@ int start_stop_daemon_main(int argc ATTRIBUTE_UNUSED, char **argv)
        char *signame;
        char *startas;
        char *chuid;
+       struct stat execstat;
 #if ENABLE_FEATURE_START_STOP_DAEMON_FANCY
 //     char *retry_arg = NULL;
 //     int retries = -1;
@@ -310,7 +347,7 @@ int start_stop_daemon_main(int argc ATTRIBUTE_UNUSED, char **argv)
        /* -q turns off -v */
        opt_complementary = "K:S:K--S:S--K:m?p:K?xpun:S?xa"
                USE_FEATURE_START_STOP_DAEMON_FANCY("q-v");
-       opt = getopt32(argv, "KSbqma:n:s:u:c:x:p:"
+       opt = getopt32(argv, "KSbqtma:n:s:u:c:x:p:"
                USE_FEATURE_START_STOP_DAEMON_FANCY("ovN:"),
 //             USE_FEATURE_START_STOP_DAEMON_FANCY("ovN:R:"),
                &startas, &cmdname, &signame, &userspec, &chuid, &execname, &pidfile
@@ -338,9 +375,6 @@ int start_stop_daemon_main(int argc ATTRIBUTE_UNUSED, char **argv)
                if (errno)
                        user_id = xuname2uid(userspec);
        }
-       if (execname)
-               xstat(execname, &execstat);
-
        do_procinit(); /* Both start and stop needs to know current processes */
 
        if (opt & CTX_STOP) {
@@ -353,6 +387,10 @@ int start_stop_daemon_main(int argc ATTRIBUTE_UNUSED, char **argv)
                        printf("%s already running\n%d\n", execname, found->pid);
                return !(opt & OPT_OKNODO);
        }
+
+       if (execname)
+               xstat(execname, &execstat);
+
        *--argv = startas;
        if (opt & OPT_BACKGROUND) {
 #if BB_MMU
index f912d7b..56198cf 100644 (file)
        USE_GETOPT_LONG("--start|--stop") SKIP_GETOPT_LONG("-S|-K") \
        "] ... [-- arguments...]"
 #define start_stop_daemon_full_usage "\n\n" \
-       "Start and stop services\n" \
-     "\nOptions:" \
+       "Search for matching processes, and then\n" \
+       "-S: stop all matching processes.\n" \
+       "-K: start a process unless a matching process is found.\n" \
        USE_GETOPT_LONG( \
-     "\n       -S,--start              Start" \
-     "\n       -K,--stop               Stop" \
-     "\n       -a,--startas pathname   Start process specified by pathname" \
-     "\n       -b,--background         Put process into background" \
-     "\n       -u,--user username|uid  Stop this user's processes" \
-     "\n       -x,--exec executable    Program to either start or check" \
-     "\n       -n,--name process-name  Stop processes with this name" \
-     "\n       -p,--pidfile pid-file   Save or load pid using a pid-file" \
-     "\n       -m,--make-pidfile       Create the -p file and enter pid in it" \
-     "\n       -q,--quiet              Quiet" \
+     "\nProcess matching:" \
+     "\n       -u,--user USERNAME|UID  Match only this user's processes" \
+     "\n       -n,--name NAME          Match processes with NAME" \
+     "\n                               in comm field in /proc/PID/stat" \
+     "\n       -x,--exec EXECUTABLE    Match processes with this command" \
+     "\n                               in /proc/PID/cmdline" \
+     "\n       -p,--pidfile FILE       Match a process with PID from the file" \
+     "\n       All specified conditions must match" \
+     "\n-K only:" \
+     "\n       -x,--exec EXECUTABLE    Program to run" \
+     "\n       -a,--startas NAME       Zeroth argument" \
+     "\n       -b,--background         Background" \
        USE_FEATURE_START_STOP_DAEMON_FANCY( \
-     "\n       -o,--oknodo             Exit status 0 if nothing done" \
-     "\n       -v,--verbose            Verbose" \
-     "\n       -N,--nicelevel N        Add N to process's nice level" \
+     "\n       -N,--nicelevel N        Change nice level" \
        ) \
-     "\n       -s,--signal signal      Signal to send (default TERM)" \
-     "\n       -c,--chuid user[:[grp]] Change to specified user/group" \
+     "\n       -c,--chuid USER[:[GRP]] Change to user/group" \
+     "\n       -m,--make-pidfile       Write PID to the pidfile specified by -p" \
+     "\n-S only:" \
+     "\n       -s,--signal SIG         Signal to send" \
+     "\n       -t,--test               Match only, exit with 0 if a process is found" \
+     "\nOther:" \
+       USE_FEATURE_START_STOP_DAEMON_FANCY( \
+     "\n       -o,--oknodo             Exit with status 0 if nothing is done" \
+     "\n       -q,--quiet              Quiet" \
+       ) \
+     "\n       -v,--verbose            Verbose" \
        ) \
        SKIP_GETOPT_LONG( \
-     "\n       -S              Start" \
-     "\n       -K              Stop" \
-     "\n       -a pathname     Start process specified by pathname" \
-     "\n       -b              Put process into background" \
-     "\n       -u username|uid Stop this user's processes" \
-     "\n       -x executable   Program to either start or check" \
-     "\n       -n process-name Stop processes with this name" \
-     "\n       -p pid-file     Save or load pid using a pid-file" \
-     "\n       -m              Create the -p file and enter pid in it" \
-     "\n       -q              Quiet" \
+     "\nProcess matching:" \
+     "\n       -u USERNAME|UID Match only this user's processes" \
+     "\n       -n NAME         Match processes with NAME" \
+     "\n                       in comm field in /proc/PID/stat" \
+     "\n       -x EXECUTABLE   Match processes with this command" \
+     "\n                       command in /proc/PID/cmdline" \
+     "\n       -p FILE         Match a process with PID from the file" \
+     "\n       All specified conditions must match" \
+     "\n-K only:" \
+     "\n       -x EXECUTABLE   Program to run" \
+     "\n       -a NAME         Zeroth argument" \
+     "\n       -b              Background" \
+       USE_FEATURE_START_STOP_DAEMON_FANCY( \
+     "\n       -N N            Change nice level" \
+       ) \
+     "\n       -c USER[:[GRP]] Change to user/group" \
+     "\n       -m              Write PID to the pidfile specified by -p" \
+     "\n-S only:" \
+     "\n       -s SIG          Signal to send" \
+     "\n       -t              Match only, exit with 0 if a process is found" \
+     "\nOther:" \
        USE_FEATURE_START_STOP_DAEMON_FANCY( \
-     "\n       -o              Exit status 0 if nothing done" \
+     "\n       -o              Exit with status 0 if nothing is done" \
+     "\n       -q              Quiet" \
+       ) \
      "\n       -v              Verbose" \
-     "\n       -N N            Add N to process's nice level" \
        ) \
-     "\n       -s signal       Signal to send (default TERM)" \
-     "\n       -c user[:[grp]] Change to specified user/group" \
-       )
 
 #define stat_trivial_usage \
        "[OPTION] FILE..."