/* vi: set sw=4 ts=4: */ /* * Mini start-stop-daemon implementation(s) for busybox * * Written by Marek Michalkiewicz , * Adapted for busybox David Kimdon * * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. */ /* NB: we have a problem here with /proc/NN/exe usage, similar to * one fixed in killall/pidof */ #include /* Override ENABLE_FEATURE_PIDFILE */ #define WANT_PIDFILE 1 #include "libbb.h" struct pid_list { struct pid_list *next; pid_t pid; }; enum { CTX_STOP = (1 << 0), 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 }; #define QUIET (option_mask32 & OPT_QUIET) struct globals { struct pid_list *found; char *userspec; char *cmdname; char *execname; char *pidfile; int user_id; smallint signal_nr; struct stat execstat; }; #define G (*(struct globals*)&bb_common_bufsiz1) #define found (G.found ) #define userspec (G.userspec ) #define cmdname (G.cmdname ) #define execname (G.execname ) #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) 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); if (stat(buf, &st) < 0) return 0; if (st.st_dev == execstat.st_dev && st.st_ino == execstat.st_ino) return 1; return 0; } static int pid_is_user(int 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 == user_id); } static int pid_is_cmd(pid_t pid) { char buf[256]; /* is it big enough? */ char *p, *pe; sprintf(buf, "/proc/%u/stat", pid); if (open_read_close(buf, buf, sizeof(buf) - 1) < 0) return 0; buf[sizeof(buf) - 1] = '\0'; /* paranoia */ p = strchr(buf, '('); if (!p) return 0; pe = strrchr(++p, ')'); if (!pe) return 0; *pe = '\0'; return !strcmp(p, cmdname); } static void check(int pid) { struct pid_list *p; if (execname && !pid_is_exec(pid)) { return; } if (userspec && !pid_is_user(pid)) { return; } if (cmdname && !pid_is_cmd(pid)) { return; } p = xmalloc(sizeof(*p)); p->next = found; p->pid = pid; found = p; } static void do_pidfile(void) { FILE *f; unsigned pid; f = fopen(pidfile, "r"); if (f) { if (fscanf(f, "%u", &pid) == 1) check(pid); fclose(f); } else if (errno != ENOENT) bb_perror_msg_and_die("open pidfile %s", pidfile); } static void do_procinit(void) { DIR *procdir; struct dirent *entry; int pid; if (pidfile) { do_pidfile(); return; } procdir = xopendir("/proc"); pid = 0; while(1) { errno = 0; /* clear any previous error */ entry = readdir(procdir); // TODO: 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 */ break; pid = bb_strtou(entry->d_name, NULL, 10); if (errno) /* NaN */ continue; check(pid); } closedir(procdir); if (!pid) bb_error_msg_and_die("nothing in /proc - not mounted?"); } static int do_stop(void) { char *what; struct pid_list *p; int killed = 0; if (cmdname) { if (ENABLE_FEATURE_CLEAN_UP) what = xstrdup(cmdname); if (!ENABLE_FEATURE_CLEAN_UP) what = cmdname; } else if (execname) { if (ENABLE_FEATURE_CLEAN_UP) what = xstrdup(execname); if (!ENABLE_FEATURE_CLEAN_UP) what = execname; } else if (pidfile) what = xasprintf("process in pidfile '%s'", pidfile); else if (userspec) what = xasprintf("process(es) owned by '%s'", userspec); else bb_error_msg_and_die("internal error, please report"); if (!found) { if (!QUIET) printf("no %s found; none killed\n", what); killed = -1; goto ret; } for (p = found; p; p = p->next) { if (kill(p->pid, signal_nr) == 0) { p->pid = - p->pid; killed++; } else { bb_perror_msg("warning: killing process %u", 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); puts(")"); } ret: if (ENABLE_FEATURE_CLEAN_UP) free(what); return killed; } #if ENABLE_FEATURE_START_STOP_DAEMON_LONG_OPTIONS static const char start_stop_daemon_longopts[] ALIGN1 = "stop\0" No_argument "K" "start\0" No_argument "S" "background\0" No_argument "b" "quiet\0" No_argument "q" "make-pidfile\0" No_argument "m" #if ENABLE_FEATURE_START_STOP_DAEMON_FANCY "oknodo\0" No_argument "o" "verbose\0" No_argument "v" "nicelevel\0" Required_argument "N" #endif "startas\0" Required_argument "a" "name\0" Required_argument "n" "signal\0" Required_argument "s" "user\0" Required_argument "u" "chuid\0" Required_argument "c" "exec\0" Required_argument "x" "pidfile\0" Required_argument "p" #if ENABLE_FEATURE_START_STOP_DAEMON_FANCY "retry\0" Required_argument "R" #endif ; #endif int start_stop_daemon_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int start_stop_daemon_main(int argc ATTRIBUTE_UNUSED, char **argv) { unsigned opt; char *signame; char *startas; char *chuid; #if ENABLE_FEATURE_START_STOP_DAEMON_FANCY // char *retry_arg = NULL; // int retries = -1; char *opt_N; #endif INIT_G(); #if ENABLE_FEATURE_START_STOP_DAEMON_LONG_OPTIONS applet_long_options = start_stop_daemon_longopts; #endif /* -K or -S is required; they are mutually exclusive */ /* -p is required if -m is given */ /* -xpun (at least one) is required if -K is given */ /* -xa (at least one) is required if -S is given */ /* -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:" USE_FEATURE_START_STOP_DAEMON_FANCY("ovN:"), // USE_FEATURE_START_STOP_DAEMON_FANCY("ovN:R:"), &startas, &cmdname, &signame, &userspec, &chuid, &execname, &pidfile USE_FEATURE_START_STOP_DAEMON_FANCY(,&opt_N) // USE_FEATURE_START_STOP_DAEMON_FANCY(,&retry_arg) ); if (opt & OPT_s) { signal_nr = get_signum(signame); if (signal_nr < 0) bb_show_usage(); } if (!(opt & OPT_a)) startas = execname; // USE_FEATURE_START_STOP_DAEMON_FANCY( // if (retry_arg) // retries = xatoi_u(retry_arg); // ) //argc -= optind; argv += optind; if (userspec) { user_id = bb_strtou(userspec, NULL, 10); 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) { int i = do_stop(); return (opt & OPT_OKNODO) ? 0 : (i <= 0); } if (found) { if (!QUIET) printf("%s already running\n%d\n", execname, found->pid); return !(opt & OPT_OKNODO); } *--argv = startas; if (opt & OPT_BACKGROUND) { #if BB_MMU bb_daemonize(0); #else pid_t pid = vfork(); if (pid < 0) /* error */ bb_perror_msg_and_die("vfork"); if (pid != 0) { /* parent */ /* why _exit? the child may have changed the stack, * so "return 0" may do bad things */ _exit(0); } /* child */ setsid(); /* detach from controlling tty */ /* Redirect stdio to /dev/null, close extra FDs. * We do not actually daemonize because of DAEMON_ONLY_SANITIZE */ bb_daemonize_or_rexec( DAEMON_DEVNULL_STDIO + DAEMON_CLOSE_EXTRA_FDS + DAEMON_ONLY_SANITIZE, NULL /* argv, unused */ ); #endif } if (opt & OPT_MAKEPID) { /* user wants _us_ to make the pidfile */ write_pidfile(pidfile); } if (opt & OPT_c) { struct bb_uidgid_t ugid; parse_chown_usergroup_or_die(&ugid, chuid); if (ugid.gid != (gid_t) -1) xsetgid(ugid.gid); if (ugid.uid != (uid_t) -1) xsetuid(ugid.uid); } #if ENABLE_FEATURE_START_STOP_DAEMON_FANCY if (opt & OPT_NICELEVEL) { /* Set process priority */ int prio = getpriority(PRIO_PROCESS, 0) + xatoi_range(opt_N, INT_MIN/2, INT_MAX/2); if (setpriority(PRIO_PROCESS, 0, prio) < 0) { bb_perror_msg_and_die("setpriority(%d)", prio); } } #endif execv(startas, argv); bb_perror_msg_and_die("cannot start %s", startas); }