diff options
Diffstat (limited to 'libnetdata/popen')
-rw-r--r-- | libnetdata/popen/popen.c | 437 | ||||
-rw-r--r-- | libnetdata/popen/popen.h | 26 |
2 files changed, 257 insertions, 206 deletions
diff --git a/libnetdata/popen/popen.c b/libnetdata/popen/popen.c index eaeffd32dd..b67d6b096b 100644 --- a/libnetdata/popen/popen.c +++ b/libnetdata/popen/popen.c @@ -2,81 +2,129 @@ #include "../libnetdata.h" -static pthread_mutex_t myp_lock; -static int myp_tracking = 0; +// ---------------------------------------------------------------------------- +// popen with tracking -struct mypopen { +static pthread_mutex_t netdata_popen_tracking_mutex; +static bool netdata_popen_tracking_enabled = false; + +struct netdata_popen { pid_t pid; - struct mypopen *next; - struct mypopen *prev; + struct netdata_popen *next; + struct netdata_popen *prev; }; -static struct mypopen *mypopen_root = NULL; +static struct netdata_popen *netdata_popen_root = NULL; // myp_add_lock takes the lock if we're tracking. -static void myp_add_lock(void) { - if (myp_tracking == 0) +static void netdata_popen_tracking_lock(void) { + if(!netdata_popen_tracking_enabled) return; - netdata_mutex_lock(&myp_lock); + netdata_mutex_lock(&netdata_popen_tracking_mutex); } // myp_add_unlock release the lock if we're tracking. -static void myp_add_unlock(void) { - if (myp_tracking == 0) +static void netdata_popen_tracking_unlock(void) { + if(!netdata_popen_tracking_enabled) return; - netdata_mutex_unlock(&myp_lock); + netdata_mutex_unlock(&netdata_popen_tracking_mutex); } // myp_add_locked adds pid if we're tracking. // myp_add_lock must have been called previously. -static void myp_add_locked(pid_t pid) { - struct mypopen *mp; - - if (myp_tracking == 0) +static void netdata_popen_tracking_add_pid_unsafe(pid_t pid) { + if(!netdata_popen_tracking_enabled) return; - mp = mallocz(sizeof(struct mypopen)); + struct netdata_popen *mp; + + mp = mallocz(sizeof(struct netdata_popen)); mp->pid = pid; - mp->next = mypopen_root; - mp->prev = NULL; - if (mypopen_root != NULL) - mypopen_root->prev = mp; - mypopen_root = mp; - netdata_mutex_unlock(&myp_lock); + DOUBLE_LINKED_LIST_PREPEND_UNSAFE(netdata_popen_root, mp, prev, next); } // myp_del deletes pid if we're tracking. -static void myp_del(pid_t pid) { - struct mypopen *mp; - - if (myp_tracking == 0) +static void netdata_popen_tracking_del_pid(pid_t pid) { + if(!netdata_popen_tracking_enabled) return; - netdata_mutex_lock(&myp_lock); - for (mp = mypopen_root; mp != NULL; mp = mp->next) { - if (mp->pid == pid) { - if (mp->next != NULL) - mp->next->prev = mp->prev; - if (mp->prev != NULL) - mp->prev->next = mp->next; - if (mypopen_root == mp) - mypopen_root = mp->next; - freez(mp); + struct netdata_popen *mp; + + netdata_mutex_lock(&netdata_popen_tracking_mutex); + + DOUBLE_LINKED_LIST_FOREACH_FORWARD(netdata_popen_root, mp, prev, next) { + if(unlikely(mp->pid == pid)) break; - } } - if (mp == NULL) + if(mp) { + DOUBLE_LINKED_LIST_REMOVE_UNSAFE(netdata_popen_root, mp, prev, next); + freez(mp); + } + else error("Cannot find pid %d.", pid); - netdata_mutex_unlock(&myp_lock); + netdata_mutex_unlock(&netdata_popen_tracking_mutex); } -#define PIPE_READ 0 -#define PIPE_WRITE 1 +// netdata_popen_tracking_init() should be called by apps which act as init +// (pid 1) so that processes created by mypopen and mypopene +// are tracked. This enables the reaper to ignore processes +// which will be handled internally, by calling myp_reap, to +// avoid issues with already reaped processes during wait calls. +// +// Callers should call myp_free() to clean up resources. +void netdata_popen_tracking_init(void) { + info("process tracking enabled."); + netdata_popen_tracking_enabled = true; + + if (netdata_mutex_init(&netdata_popen_tracking_mutex) != 0) + fatal("netdata_popen_tracking_init() mutex init failed."); +} + +// myp_free cleans up any resources allocated for process +// tracking. +void netdata_popen_tracking_cleanup(void) { + if(!netdata_popen_tracking_enabled) + return; + + netdata_mutex_lock(&netdata_popen_tracking_mutex); + netdata_popen_tracking_enabled = false; + + while(netdata_popen_root) { + struct netdata_popen *mp = netdata_popen_root; + DOUBLE_LINKED_LIST_REMOVE_UNSAFE(netdata_popen_root, mp, prev, next); + freez(mp); + } + + netdata_mutex_unlock(&netdata_popen_tracking_mutex); +} + +// myp_reap returns 1 if pid should be reaped, 0 otherwise. +int netdata_popen_tracking_pid_shoud_be_reaped(pid_t pid) { + if(!netdata_popen_tracking_enabled) + return 0; + + netdata_mutex_lock(&netdata_popen_tracking_mutex); + + int ret = 1; + struct netdata_popen *mp; + DOUBLE_LINKED_LIST_FOREACH_FORWARD(netdata_popen_root, mp, prev, next) { + if(unlikely(mp->pid == pid)) { + ret = 0; + break; + } + } + + netdata_mutex_unlock(&netdata_popen_tracking_mutex); + return ret; +} + +// ---------------------------------------------------------------------------- +// helpers static inline void convert_argv_to_string(char *dst, size_t size, const char *spawn_argv[]) { int i; @@ -89,118 +137,185 @@ static inline void convert_argv_to_string(char *dst, size_t size, const char *sp } } +// ---------------------------------------------------------------------------- +// the core of netdata popen + /* * Returns -1 on failure, 0 on success. When POPEN_FLAG_CREATE_PIPE is set, on success set the FILE *fp pointer. */ -static int custom_popene(volatile pid_t *pidptr, char **env, uint8_t flags, FILE **fpp, const char *command, const char *spawn_argv[]) { +#define PIPE_READ 0 +#define PIPE_WRITE 1 + +static int popene_internal(volatile pid_t *pidptr, char **env, uint8_t flags, FILE **fpp_child_stdin, FILE **fpp_child_stdout, const char *command, const char *spawn_argv[]) { // create a string to be logged about the command we are running char command_to_be_logged[2048]; convert_argv_to_string(command_to_be_logged, sizeof(command_to_be_logged), spawn_argv); // info("custom_popene() running command: %s", command_to_be_logged); - FILE *fp = NULL; - int ret = 0; // success by default - int pipefd[2], error; + int ret = 0; // success by default + int attr_rc = 1; // failure by default + + FILE *fp_child_stdin = NULL, *fp_child_stdout = NULL; + int pipefd_stdin[2] = { -1, -1 }; + int pipefd_stdout[2] = { -1, -1 }; + pid_t pid; posix_spawnattr_t attr; posix_spawn_file_actions_t fa; - if (flags & POPEN_FLAG_CREATE_PIPE) { - if (pipe(pipefd) == -1) - return -1; - if ((fp = fdopen(pipefd[PIPE_READ], "r")) == NULL) { - goto error_after_pipe; + int stdin_fd_to_exclude_from_closing = -1; + int stdout_fd_to_exclude_from_closing = -1; + + if(posix_spawn_file_actions_init(&fa)) { + error("POPEN: posix_spawn_file_actions_init() failed."); + return -1; + } + + if(fpp_child_stdin) { + if (pipe(pipefd_stdin) == -1) { + error("POPEN: stdin pipe() failed"); + ret = -1; + goto cleanup_and_return; + } + + if ((fp_child_stdin = fdopen(pipefd_stdin[PIPE_WRITE], "w")) == NULL) { + error("POPEN: fdopen() stdin failed"); + ret = -1; + goto cleanup_and_return; + } + + if(posix_spawn_file_actions_adddup2(&fa, pipefd_stdin[PIPE_READ], STDIN_FILENO)) { + error("POPEN: posix_spawn_file_actions_adddup2() on stdin failed."); + ret = -1; + goto cleanup_and_return; + } + } + else { + if (posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, "/dev/null", O_RDONLY, 0)) { + error("POPEN: posix_spawn_file_actions_addopen() on stdin to /dev/null failed."); + // this is not a fatal error + stdin_fd_to_exclude_from_closing = STDIN_FILENO; } } - if (flags & POPEN_FLAG_CLOSE_FD) { - // Mark all files to be closed by the exec() stage of posix_spawn() - int i; - for (i = (int) (sysconf(_SC_OPEN_MAX) - 1); i >= 0; i--) { - if (i != STDIN_FILENO && i != STDERR_FILENO) - (void) fcntl(i, F_SETFD, FD_CLOEXEC); + if (fpp_child_stdout) { + if (pipe(pipefd_stdout) == -1) { + error("POPEN: stdout pipe() failed"); + ret = -1; + goto cleanup_and_return; + } + + if ((fp_child_stdout = fdopen(pipefd_stdout[PIPE_READ], "r")) == NULL) { + error("POPEN: fdopen() stdout failed"); + ret = -1; + goto cleanup_and_return; + } + + if(posix_spawn_file_actions_adddup2(&fa, pipefd_stdout[PIPE_WRITE], STDOUT_FILENO)) { + error("POPEN: posix_spawn_file_actions_adddup2() on stdout failed."); + ret = -1; + goto cleanup_and_return; + } + } + else { + if (posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, "/dev/null", O_WRONLY, 0)) { + error("POPEN: posix_spawn_file_actions_addopen() on stdout to /dev/null failed."); + // this is not a fatal error + stdout_fd_to_exclude_from_closing = STDOUT_FILENO; } } - if (!posix_spawn_file_actions_init(&fa)) { - if (flags & POPEN_FLAG_CREATE_PIPE) { - // move the pipe to stdout in the child - if (posix_spawn_file_actions_adddup2(&fa, pipefd[PIPE_WRITE], STDOUT_FILENO)) { - error("posix_spawn_file_actions_adddup2() failed"); - goto error_after_posix_spawn_file_actions_init; - } - } else { - // set stdout to /dev/null - if (posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, "/dev/null", O_WRONLY, 0)) { - error("posix_spawn_file_actions_addopen() failed"); - // this is not a fatal error - } + if(flags & POPEN_FLAG_CLOSE_FD) { + // Mark all files to be closed by the exec() stage of posix_spawn() + for(int i = (int)(sysconf(_SC_OPEN_MAX) - 1); i >= 0; i--) { + if(likely(i != STDERR_FILENO && i != stdin_fd_to_exclude_from_closing && i != stdout_fd_to_exclude_from_closing)) + (void)fcntl(i, F_SETFD, FD_CLOEXEC); } - } else { - error("posix_spawn_file_actions_init() failed."); - goto error_after_pipe; } - if (!(error = posix_spawnattr_init(&attr))) { + + attr_rc = posix_spawnattr_init(&attr); + if(attr_rc) { + // failed + error("POPEN: posix_spawnattr_init() failed."); + } + else { + // success // reset all signals in the child - sigset_t mask; if (posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF)) - error("posix_spawnattr_setflags() failed."); + error("POPEN: posix_spawnattr_setflags() failed."); + + sigset_t mask; sigemptyset(&mask); + if (posix_spawnattr_setsigmask(&attr, &mask)) - error("posix_spawnattr_setsigmask() failed."); - } else { - error("posix_spawnattr_init() failed."); + error("POPEN: posix_spawnattr_setsigmask() failed."); } // Take the lock while we fork to ensure we don't race with SIGCHLD // delivery on a process which exits quickly. - myp_add_lock(); + netdata_popen_tracking_lock(); if (!posix_spawn(&pid, command, &fa, &attr, (char * const*)spawn_argv, env)) { + // success *pidptr = pid; - myp_add_locked(pid); - debug(D_CHILDS, "Spawned command: \"%s\" on pid %d from parent pid %d.", command_to_be_logged, pid, getpid()); - } else { - myp_add_unlock(); - error("Failed to spawn command: \"%s\" from parent pid %d.", command_to_be_logged, getpid()); - if (flags & POPEN_FLAG_CREATE_PIPE) { - fclose(fp); - } - ret = -1; + netdata_popen_tracking_add_pid_unsafe(pid); + netdata_popen_tracking_unlock(); } - if (flags & POPEN_FLAG_CREATE_PIPE) { - close(pipefd[PIPE_WRITE]); - if (0 == ret) // on success set FILE * pointer - if(fpp) *fpp = fp; + else { + // failure + netdata_popen_tracking_unlock(); + error("POPEN: failed to spawn command: \"%s\" from parent pid %d.", command_to_be_logged, getpid()); + ret = -1; + goto cleanup_and_return; } - if (!error) { + // the normal cleanup will run + // but ret == 0 at this point + +cleanup_and_return: + if(!attr_rc) { // posix_spawnattr_init() succeeded if (posix_spawnattr_destroy(&attr)) - error("posix_spawnattr_destroy"); + error("POPEN: posix_spawnattr_destroy() failed"); } - if (posix_spawn_file_actions_destroy(&fa)) - error("posix_spawn_file_actions_destroy"); - return ret; - -error_after_posix_spawn_file_actions_init: if (posix_spawn_file_actions_destroy(&fa)) - error("posix_spawn_file_actions_destroy"); + error("POPEN: posix_spawn_file_actions_destroy() failed"); + + // the child end - close it + if(pipefd_stdin[PIPE_READ] != -1) + close(pipefd_stdin[PIPE_READ]); + + // our end + if(ret == -1 || !fpp_child_stdin) { + if (fp_child_stdin) + fclose(fp_child_stdin); + else if (pipefd_stdin[PIPE_WRITE] != -1) + close(pipefd_stdin[PIPE_WRITE]); + } + else { + *fpp_child_stdin = fp_child_stdin; + } -error_after_pipe: - if (flags & POPEN_FLAG_CREATE_PIPE) { - if (fp) - fclose(fp); - else - close(pipefd[PIPE_READ]); + // the child end - close it + if (pipefd_stdout[PIPE_WRITE] != -1) + close(pipefd_stdout[PIPE_WRITE]); - close(pipefd[PIPE_WRITE]); + // our end + if (ret == -1 || !fpp_child_stdout) { + if (fp_child_stdout) + fclose(fp_child_stdout); + else if (pipefd_stdout[PIPE_READ] != -1) + close(pipefd_stdout[PIPE_READ]); } - return -1; + else { + *fpp_child_stdout = fp_child_stdout; + } + + return ret; } -int custom_popene_variadic_internal_dont_use_directly(volatile pid_t *pidptr, char **env, uint8_t flags, FILE **fpp, const char *command, ...) { +int netdata_popene_variadic_internal_dont_use_directly(volatile pid_t *pidptr, char **env, uint8_t flags, FILE **fpp_child_input, FILE **fpp_child_output, const char *command, ...) { // convert the variable list arguments into what posix_spawn() needs // all arguments are expected strings va_list args; @@ -232,88 +347,34 @@ int custom_popene_variadic_internal_dont_use_directly(volatile pid_t *pidptr, ch va_end(args); } - return custom_popene(pidptr, env, flags, fpp, command, spawn_argv); + return popene_internal(pidptr, env, flags, fpp_child_input, fpp_child_output, command, spawn_argv); } // See man environ extern char **environ; -// myp_init should be called by apps which act as init -// (pid 1) so that processes created by mypopen and mypopene -// are tracked. This enables the reaper to ignore processes -// which will be handled internally, by calling myp_reap, to -// avoid issues with already reaped processes during wait calls. -// -// Callers should call myp_free() to clean up resources. -void myp_init(void) { - info("process tracking enabled."); - myp_tracking = 1; - - if (netdata_mutex_init(&myp_lock) != 0) { - fatal("myp_init() mutex init failed."); - } -} - -// myp_free cleans up any resources allocated for process -// tracking. -void myp_free(void) { - struct mypopen *mp, *next; - - if (myp_tracking == 0) - return; - - netdata_mutex_lock(&myp_lock); - for (mp = mypopen_root; mp != NULL; mp = next) { - next = mp->next; - freez(mp); - } - - mypopen_root = NULL; - myp_tracking = 0; - netdata_mutex_unlock(&myp_lock); -} - -// myp_reap returns 1 if pid should be reaped, 0 otherwise. -int myp_reap(pid_t pid) { - struct mypopen *mp; - - if (myp_tracking == 0) - return 0; - - netdata_mutex_lock(&myp_lock); - for (mp = mypopen_root; mp != NULL; mp = mp->next) { - if (mp->pid == pid) { - netdata_mutex_unlock(&myp_lock); - return 0; - } - } - netdata_mutex_unlock(&myp_lock); - - return 1; -} - -FILE *mypopen(const char *command, volatile pid_t *pidptr) { - FILE *fp = NULL; +FILE *netdata_popen(const char *command, volatile pid_t *pidptr, FILE **fpp_child_input) { + FILE *fp_child_output = NULL; const char *spawn_argv[] = { "sh", "-c", command, NULL }; - (void)custom_popene(pidptr, environ, POPEN_FLAG_CREATE_PIPE|POPEN_FLAG_CLOSE_FD, &fp, "/bin/sh", spawn_argv); - return fp; + (void)popene_internal(pidptr, environ, POPEN_FLAG_CLOSE_FD, fpp_child_input, &fp_child_output, "/bin/sh", spawn_argv); + return fp_child_output; } -FILE *mypopene(const char *command, volatile pid_t *pidptr, char **env) { - FILE *fp = NULL; +FILE *netdata_popene(const char *command, volatile pid_t *pidptr, char **env, FILE **fpp_child_input) { + FILE *fp_child_output = NULL; const char *spawn_argv[] = { "sh", "-c", command, NULL }; - (void)custom_popene( pidptr, env, POPEN_FLAG_CREATE_PIPE|POPEN_FLAG_CLOSE_FD, &fp, "/bin/sh", spawn_argv); - return fp; + (void)popene_internal(pidptr, env, POPEN_FLAG_CLOSE_FD, fpp_child_input, &fp_child_output, "/bin/sh", spawn_argv); + return fp_child_output; } // returns 0 on success, -1 on failure @@ -324,29 +385,25 @@ int netdata_spawn(const char *command, volatile pid_t *pidptr) { command, NULL }; - return custom_popene( pidptr, environ, POPEN_FLAG_NONE, NULL, "/bin/sh", spawn_argv); + return popene_internal(pidptr, environ, POPEN_FLAG_NONE, NULL, NULL, "/bin/sh", spawn_argv); } -int custom_pclose(FILE *fp, pid_t pid) { +int netdata_pclose(FILE *fp_child_input, FILE *fp_child_output, pid_t pid) { int ret; siginfo_t info; - debug(D_EXIT, "Request to mypclose() on pid %d", pid); + debug(D_EXIT, "Request to netdata_pclose() on pid %d", pid); - if (fp) { - // close the pipe fd - // this is required in musl - // without it the childs do not exit - close(fileno(fp)); + if (fp_child_input) + fclose(fp_child_input); - // close the pipe file pointer - fclose(fp); - } + if (fp_child_output) + fclose(fp_child_output); errno = 0; ret = waitid(P_PID, (id_t) pid, &info, WEXITED); - myp_del(pid); + netdata_popen_tracking_del_pid(pid); if (ret != -1) { switch (info.si_code) { @@ -392,12 +449,6 @@ int custom_pclose(FILE *fp, pid_t pid) { return 0; } -int mypclose(FILE *fp, pid_t pid) -{ - return custom_pclose(fp, pid); -} - -int netdata_spawn_waitpid(pid_t pid) -{ - return custom_pclose(NULL, pid); +int netdata_spawn_waitpid(pid_t pid) { + return netdata_pclose(NULL, NULL, pid); } diff --git a/libnetdata/popen/popen.h b/libnetdata/popen/popen.h index 57eb9131e0..a0cf5f101f 100644 --- a/libnetdata/popen/popen.h +++ b/libnetdata/popen/popen.h @@ -10,29 +10,29 @@ /* custom_popene_variadic_internal_dont_use_directly flag definitions */ #define POPEN_FLAG_NONE 0 -#define POPEN_FLAG_CREATE_PIPE 1 // Create a pipe like popen() when set, otherwise set stdout to /dev/null -#define POPEN_FLAG_CLOSE_FD 2 // Close all file descriptors other than STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO +#define POPEN_FLAG_CLOSE_FD (1 << 0) // Close all file descriptors other than STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO // the flags to be used by default -#define POPEN_FLAGS_DEFAULT (POPEN_FLAG_CREATE_PIPE|POPEN_FLAG_CLOSE_FD) +#define POPEN_FLAGS_DEFAULT (POPEN_FLAG_CLOSE_FD) // mypopen_raw is the interface to use instead of custom_popene_variadic_internal_dont_use_directly() // mypopen_raw will add the terminating NULL at the arguments list // we append the parameter 'command' twice - this is because the underlying call needs the command to execute and the argv[0] to pass to it -#define mypopen_raw_default_flags_and_environment(pidptr, fpp, command, args...) custom_popene_variadic_internal_dont_use_directly(pidptr, environ, POPEN_FLAGS_DEFAULT, fpp, command, command, ##args, NULL) -#define mypopen_raw_default_flags(pidptr, env, fpp, command, args...) custom_popene_variadic_internal_dont_use_directly(pidptr, env, POPEN_FLAGS_DEFAULT, fpp, command, command, ##args, NULL) -#define mypopen_raw(pidptr, env, flags, fpp, command, args...) custom_popene_variadic_internal_dont_use_directly(pidptr, env, flags, fpp, command, command, ##args, NULL) +#define netdata_popen_raw_default_flags_and_environment(pidptr, fpp_child_input, fpp_child_output, command, args...) netdata_popene_variadic_internal_dont_use_directly(pidptr, environ, POPEN_FLAGS_DEFAULT, fpp_child_input, fpp_child_output, command, command, ##args, NULL) +#define netdata_popen_raw_default_flags(pidptr, env, fpp_child_input, fpp_child_output, command, args...) netdata_popene_variadic_internal_dont_use_directly(pidptr, env, POPEN_FLAGS_DEFAULT, fpp_child_input, fpp_child_output, command, command, ##args, NULL) +#define netdata_popen_raw(pidptr, env, flags, fpp_child_input, fpp_child_output, command, args...) netdata_popene_variadic_internal_dont_use_directly(pidptr, env, flags, fpp_child_input, fpp_child_output, command, command, ##args, NULL) -extern int custom_popene_variadic_internal_dont_use_directly(volatile pid_t *pidptr, char **env, uint8_t flags, FILE **fpp, const char *command, ...); +extern FILE *netdata_popen(const char *command, volatile pid_t *pidptr, FILE **fp_child_input); +extern FILE *netdata_popene(const char *command, volatile pid_t *pidptr, char **env, FILE **fp_child_input); +extern int netdata_popene_variadic_internal_dont_use_directly(volatile pid_t *pidptr, char **env, uint8_t flags, FILE **fpp_child_input, FILE **fpp_child_output, const char *command, ...); +extern int netdata_pclose(FILE *fp_child_input, FILE *fp_child_output, pid_t pid); -extern FILE *mypopen(const char *command, volatile pid_t *pidptr); -extern FILE *mypopene(const char *command, volatile pid_t *pidptr, char **env); -extern int mypclose(FILE *fp, pid_t pid); extern int netdata_spawn(const char *command, volatile pid_t *pidptr); extern int netdata_spawn_waitpid(pid_t pid); -extern void myp_init(void); -extern void myp_free(void); -extern int myp_reap(pid_t pid); + +extern void netdata_popen_tracking_init(void); +extern void netdata_popen_tracking_cleanup(void); +extern int netdata_popen_tracking_pid_shoud_be_reaped(pid_t pid); extern void signals_unblock(void); extern void signals_reset(void); |