start_stop_daemon: add -test, fix -x to not match by inode, vastly improve hext text. (mostly by Roy Marples
No binary (build failed)
—
(log yok)
| @@ -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 | |
| @@ -3613,46 +3613,65 @@ | |
| 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..." | |