|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | #include <internal/lib.h> | 
|  | #include <subcmd/parse-options.h> | 
|  | #include <api/fd/array.h> | 
|  | #include <api/fs/fs.h> | 
|  | #include <linux/zalloc.h> | 
|  | #include <linux/string.h> | 
|  | #include <linux/limits.h> | 
|  | #include <string.h> | 
|  | #include <sys/file.h> | 
|  | #include <signal.h> | 
|  | #include <stdlib.h> | 
|  | #include <time.h> | 
|  | #include <stdio.h> | 
|  | #include <unistd.h> | 
|  | #include <errno.h> | 
|  | #include <sys/inotify.h> | 
|  | #include <libgen.h> | 
|  | #include <sys/types.h> | 
|  | #include <sys/socket.h> | 
|  | #include <sys/un.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/signalfd.h> | 
|  | #include <sys/wait.h> | 
|  | #include <poll.h> | 
|  | #include "builtin.h" | 
|  | #include "perf.h" | 
|  | #include "debug.h" | 
|  | #include "config.h" | 
|  | #include "util.h" | 
|  |  | 
|  | #define SESSION_OUTPUT  "output" | 
|  | #define SESSION_CONTROL "control" | 
|  | #define SESSION_ACK     "ack" | 
|  |  | 
|  | /* | 
|  | * Session states: | 
|  | * | 
|  | *   OK       - session is up and running | 
|  | *   RECONFIG - session is pending for reconfiguration, | 
|  | *              new values are already loaded in session object | 
|  | *   KILL     - session is pending to be killed | 
|  | * | 
|  | * Session object life and its state is maintained by | 
|  | * following functions: | 
|  | * | 
|  | *  setup_server_config | 
|  | *    - reads config file and setup session objects | 
|  | *      with following states: | 
|  | * | 
|  | *      OK       - no change needed | 
|  | *      RECONFIG - session needs to be changed | 
|  | *                 (run variable changed) | 
|  | *      KILL     - session needs to be killed | 
|  | *                 (session is no longer in config file) | 
|  | * | 
|  | *  daemon__reconfig | 
|  | *    - scans session objects and does following actions | 
|  | *      for states: | 
|  | * | 
|  | *      OK       - skip | 
|  | *      RECONFIG - session is killed and re-run with new config | 
|  | *      KILL     - session is killed | 
|  | * | 
|  | *    - all sessions have OK state on the function exit | 
|  | */ | 
|  | enum daemon_session_state { | 
|  | OK, | 
|  | RECONFIG, | 
|  | KILL, | 
|  | }; | 
|  |  | 
|  | struct daemon_session { | 
|  | char				*base; | 
|  | char				*name; | 
|  | char				*run; | 
|  | char				*control; | 
|  | int				 pid; | 
|  | struct list_head		 list; | 
|  | enum daemon_session_state	 state; | 
|  | time_t				 start; | 
|  | }; | 
|  |  | 
|  | struct daemon { | 
|  | const char		*config; | 
|  | char			*config_real; | 
|  | char			*config_base; | 
|  | const char		*csv_sep; | 
|  | const char		*base_user; | 
|  | char			*base; | 
|  | struct list_head	 sessions; | 
|  | FILE			*out; | 
|  | char			 perf[PATH_MAX]; | 
|  | int			 signal_fd; | 
|  | time_t			 start; | 
|  | }; | 
|  |  | 
|  | static struct daemon __daemon = { | 
|  | .sessions = LIST_HEAD_INIT(__daemon.sessions), | 
|  | }; | 
|  |  | 
|  | static const char * const daemon_usage[] = { | 
|  | "perf daemon start [<options>]", | 
|  | "perf daemon [<options>]", | 
|  | NULL | 
|  | }; | 
|  |  | 
|  | static bool done; | 
|  |  | 
|  | static void sig_handler(int sig __maybe_unused) | 
|  | { | 
|  | done = true; | 
|  | } | 
|  |  | 
|  | static struct daemon_session *daemon__add_session(struct daemon *config, char *name) | 
|  | { | 
|  | struct daemon_session *session = zalloc(sizeof(*session)); | 
|  |  | 
|  | if (!session) | 
|  | return NULL; | 
|  |  | 
|  | session->name = strdup(name); | 
|  | if (!session->name) { | 
|  | free(session); | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | session->pid = -1; | 
|  | list_add_tail(&session->list, &config->sessions); | 
|  | return session; | 
|  | } | 
|  |  | 
|  | static struct daemon_session *daemon__find_session(struct daemon *daemon, char *name) | 
|  | { | 
|  | struct daemon_session *session; | 
|  |  | 
|  | list_for_each_entry(session, &daemon->sessions, list) { | 
|  | if (!strcmp(session->name, name)) | 
|  | return session; | 
|  | } | 
|  |  | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | static int get_session_name(const char *var, char *session, int len) | 
|  | { | 
|  | const char *p = var + sizeof("session-") - 1; | 
|  |  | 
|  | while (*p != '.' && *p != 0x0 && len--) | 
|  | *session++ = *p++; | 
|  |  | 
|  | *session = 0; | 
|  | return *p == '.' ? 0 : -EINVAL; | 
|  | } | 
|  |  | 
|  | static int session_config(struct daemon *daemon, const char *var, const char *value) | 
|  | { | 
|  | struct daemon_session *session; | 
|  | char name[100]; | 
|  |  | 
|  | if (get_session_name(var, name, sizeof(name) - 1)) | 
|  | return -EINVAL; | 
|  |  | 
|  | var = strchr(var, '.'); | 
|  | if (!var) | 
|  | return -EINVAL; | 
|  |  | 
|  | var++; | 
|  |  | 
|  | session = daemon__find_session(daemon, name); | 
|  |  | 
|  | if (!session) { | 
|  | /* New session is defined. */ | 
|  | session = daemon__add_session(daemon, name); | 
|  | if (!session) | 
|  | return -ENOMEM; | 
|  |  | 
|  | pr_debug("reconfig: found new session %s\n", name); | 
|  |  | 
|  | /* Trigger reconfig to start it. */ | 
|  | session->state = RECONFIG; | 
|  | } else if (session->state == KILL) { | 
|  | /* Current session is defined, no action needed. */ | 
|  | pr_debug("reconfig: found current session %s\n", name); | 
|  | session->state = OK; | 
|  | } | 
|  |  | 
|  | if (!strcmp(var, "run")) { | 
|  | bool same = false; | 
|  |  | 
|  | if (session->run) | 
|  | same = !strcmp(session->run, value); | 
|  |  | 
|  | if (!same) { | 
|  | if (session->run) { | 
|  | free(session->run); | 
|  | pr_debug("reconfig: session %s is changed\n", name); | 
|  | } | 
|  |  | 
|  | session->run = strdup(value); | 
|  | if (!session->run) | 
|  | return -ENOMEM; | 
|  |  | 
|  | /* | 
|  | * Either new or changed run value is defined, | 
|  | * trigger reconfig for the session. | 
|  | */ | 
|  | session->state = RECONFIG; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int server_config(const char *var, const char *value, void *cb) | 
|  | { | 
|  | struct daemon *daemon = cb; | 
|  |  | 
|  | if (strstarts(var, "session-")) { | 
|  | return session_config(daemon, var, value); | 
|  | } else if (!strcmp(var, "daemon.base") && !daemon->base_user) { | 
|  | if (daemon->base && strcmp(daemon->base, value)) { | 
|  | pr_err("failed: can't redefine base, bailing out\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  | daemon->base = strdup(value); | 
|  | if (!daemon->base) | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int client_config(const char *var, const char *value, void *cb) | 
|  | { | 
|  | struct daemon *daemon = cb; | 
|  |  | 
|  | if (!strcmp(var, "daemon.base") && !daemon->base_user) { | 
|  | daemon->base = strdup(value); | 
|  | if (!daemon->base) | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int check_base(struct daemon *daemon) | 
|  | { | 
|  | struct stat st; | 
|  |  | 
|  | if (!daemon->base) { | 
|  | pr_err("failed: base not defined\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (stat(daemon->base, &st)) { | 
|  | switch (errno) { | 
|  | case EACCES: | 
|  | pr_err("failed: permission denied for '%s' base\n", | 
|  | daemon->base); | 
|  | return -EACCES; | 
|  | case ENOENT: | 
|  | pr_err("failed: base '%s' does not exists\n", | 
|  | daemon->base); | 
|  | return -EACCES; | 
|  | default: | 
|  | pr_err("failed: can't access base '%s': %s\n", | 
|  | daemon->base, strerror(errno)); | 
|  | return -errno; | 
|  | } | 
|  | } | 
|  |  | 
|  | if ((st.st_mode & S_IFMT) != S_IFDIR) { | 
|  | pr_err("failed: base '%s' is not directory\n", | 
|  | daemon->base); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int setup_client_config(struct daemon *daemon) | 
|  | { | 
|  | struct perf_config_set *set = perf_config_set__load_file(daemon->config_real); | 
|  | int err = -ENOMEM; | 
|  |  | 
|  | if (set) { | 
|  | err = perf_config_set(set, client_config, daemon); | 
|  | perf_config_set__delete(set); | 
|  | } | 
|  |  | 
|  | return err ?: check_base(daemon); | 
|  | } | 
|  |  | 
|  | static int setup_server_config(struct daemon *daemon) | 
|  | { | 
|  | struct perf_config_set *set; | 
|  | struct daemon_session *session; | 
|  | int err = -ENOMEM; | 
|  |  | 
|  | pr_debug("reconfig: started\n"); | 
|  |  | 
|  | /* | 
|  | * Mark all sessions for kill, the server config | 
|  | * will set following states, see explanation at | 
|  | * enum daemon_session_state declaration. | 
|  | */ | 
|  | list_for_each_entry(session, &daemon->sessions, list) | 
|  | session->state = KILL; | 
|  |  | 
|  | set = perf_config_set__load_file(daemon->config_real); | 
|  | if (set) { | 
|  | err = perf_config_set(set, server_config, daemon); | 
|  | perf_config_set__delete(set); | 
|  | } | 
|  |  | 
|  | return err ?: check_base(daemon); | 
|  | } | 
|  |  | 
|  | static int daemon_session__run(struct daemon_session *session, | 
|  | struct daemon *daemon) | 
|  | { | 
|  | char buf[PATH_MAX]; | 
|  | char **argv; | 
|  | int argc, fd; | 
|  |  | 
|  | if (asprintf(&session->base, "%s/session-%s", | 
|  | daemon->base, session->name) < 0) { | 
|  | perror("failed: asprintf"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (mkdir(session->base, 0755) && errno != EEXIST) { | 
|  | perror("failed: mkdir"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | session->start = time(NULL); | 
|  |  | 
|  | session->pid = fork(); | 
|  | if (session->pid < 0) | 
|  | return -1; | 
|  | if (session->pid > 0) { | 
|  | pr_info("reconfig: ruining session [%s:%d]: %s\n", | 
|  | session->name, session->pid, session->run); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (chdir(session->base)) { | 
|  | perror("failed: chdir"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | fd = open("/dev/null", O_RDONLY); | 
|  | if (fd < 0) { | 
|  | perror("failed: open /dev/null"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | dup2(fd, 0); | 
|  | close(fd); | 
|  |  | 
|  | fd = open(SESSION_OUTPUT, O_RDWR|O_CREAT|O_TRUNC, 0644); | 
|  | if (fd < 0) { | 
|  | perror("failed: open session output"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | dup2(fd, 1); | 
|  | dup2(fd, 2); | 
|  | close(fd); | 
|  |  | 
|  | if (mkfifo(SESSION_CONTROL, 0600) && errno != EEXIST) { | 
|  | perror("failed: create control fifo"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (mkfifo(SESSION_ACK, 0600) && errno != EEXIST) { | 
|  | perror("failed: create ack fifo"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | scnprintf(buf, sizeof(buf), "%s record --control=fifo:%s,%s %s", | 
|  | daemon->perf, SESSION_CONTROL, SESSION_ACK, session->run); | 
|  |  | 
|  | argv = argv_split(buf, &argc); | 
|  | if (!argv) | 
|  | exit(-1); | 
|  |  | 
|  | exit(execve(daemon->perf, argv, NULL)); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | static pid_t handle_signalfd(struct daemon *daemon) | 
|  | { | 
|  | struct daemon_session *session; | 
|  | struct signalfd_siginfo si; | 
|  | ssize_t err; | 
|  | int status; | 
|  | pid_t pid; | 
|  |  | 
|  | /* | 
|  | * Take signal fd data as pure signal notification and check all | 
|  | * the sessions state. The reason is that multiple signals can get | 
|  | * coalesced in kernel and we can receive only single signal even | 
|  | * if multiple SIGCHLD were generated. | 
|  | */ | 
|  | err = read(daemon->signal_fd, &si, sizeof(struct signalfd_siginfo)); | 
|  | if (err != sizeof(struct signalfd_siginfo)) { | 
|  | pr_err("failed to read signal fd\n"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | list_for_each_entry(session, &daemon->sessions, list) { | 
|  | if (session->pid == -1) | 
|  | continue; | 
|  |  | 
|  | pid = waitpid(session->pid, &status, WNOHANG); | 
|  | if (pid <= 0) | 
|  | continue; | 
|  |  | 
|  | if (WIFEXITED(status)) { | 
|  | pr_info("session '%s' exited, status=%d\n", | 
|  | session->name, WEXITSTATUS(status)); | 
|  | } else if (WIFSIGNALED(status)) { | 
|  | pr_info("session '%s' killed (signal %d)\n", | 
|  | session->name, WTERMSIG(status)); | 
|  | } else if (WIFSTOPPED(status)) { | 
|  | pr_info("session '%s' stopped (signal %d)\n", | 
|  | session->name, WSTOPSIG(status)); | 
|  | } else { | 
|  | pr_info("session '%s' Unexpected status (0x%x)\n", | 
|  | session->name, status); | 
|  | } | 
|  |  | 
|  | session->state = KILL; | 
|  | session->pid = -1; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int daemon_session__wait(struct daemon_session *session, struct daemon *daemon, | 
|  | int secs) | 
|  | { | 
|  | struct pollfd pollfd = { | 
|  | .fd	= daemon->signal_fd, | 
|  | .events	= POLLIN, | 
|  | }; | 
|  | time_t start; | 
|  |  | 
|  | start = time(NULL); | 
|  |  | 
|  | do { | 
|  | int err = poll(&pollfd, 1, 1000); | 
|  |  | 
|  | if (err > 0) { | 
|  | handle_signalfd(daemon); | 
|  | } else if (err < 0) { | 
|  | perror("failed: poll\n"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (start + secs < time(NULL)) | 
|  | return -1; | 
|  | } while (session->pid != -1); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static bool daemon__has_alive_session(struct daemon *daemon) | 
|  | { | 
|  | struct daemon_session *session; | 
|  |  | 
|  | list_for_each_entry(session, &daemon->sessions, list) { | 
|  | if (session->pid != -1) | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static int daemon__wait(struct daemon *daemon, int secs) | 
|  | { | 
|  | struct pollfd pollfd = { | 
|  | .fd	= daemon->signal_fd, | 
|  | .events	= POLLIN, | 
|  | }; | 
|  | time_t start; | 
|  |  | 
|  | start = time(NULL); | 
|  |  | 
|  | do { | 
|  | int err = poll(&pollfd, 1, 1000); | 
|  |  | 
|  | if (err > 0) { | 
|  | handle_signalfd(daemon); | 
|  | } else if (err < 0) { | 
|  | perror("failed: poll\n"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (start + secs < time(NULL)) | 
|  | return -1; | 
|  | } while (daemon__has_alive_session(daemon)); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int daemon_session__control(struct daemon_session *session, | 
|  | const char *msg, bool do_ack) | 
|  | { | 
|  | struct pollfd pollfd = { .events = POLLIN, }; | 
|  | char control_path[PATH_MAX]; | 
|  | char ack_path[PATH_MAX]; | 
|  | int control, ack = -1, len; | 
|  | char buf[20]; | 
|  | int ret = -1; | 
|  | ssize_t err; | 
|  |  | 
|  | /* open the control file */ | 
|  | scnprintf(control_path, sizeof(control_path), "%s/%s", | 
|  | session->base, SESSION_CONTROL); | 
|  |  | 
|  | control = open(control_path, O_WRONLY|O_NONBLOCK); | 
|  | if (!control) | 
|  | return -1; | 
|  |  | 
|  | if (do_ack) { | 
|  | /* open the ack file */ | 
|  | scnprintf(ack_path, sizeof(ack_path), "%s/%s", | 
|  | session->base, SESSION_ACK); | 
|  |  | 
|  | ack = open(ack_path, O_RDONLY, O_NONBLOCK); | 
|  | if (!ack) { | 
|  | close(control); | 
|  | return -1; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* write the command */ | 
|  | len = strlen(msg); | 
|  |  | 
|  | err = writen(control, msg, len); | 
|  | if (err != len) { | 
|  | pr_err("failed: write to control pipe: %d (%s)\n", | 
|  | errno, control_path); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (!do_ack) | 
|  | goto out; | 
|  |  | 
|  | /* wait for an ack */ | 
|  | pollfd.fd = ack; | 
|  |  | 
|  | if (!poll(&pollfd, 1, 2000)) { | 
|  | pr_err("failed: control ack timeout\n"); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | if (!(pollfd.revents & POLLIN)) { | 
|  | pr_err("failed: did not received an ack\n"); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | err = read(ack, buf, sizeof(buf)); | 
|  | if (err > 0) | 
|  | ret = strcmp(buf, "ack\n"); | 
|  | else | 
|  | perror("failed: read ack %d\n"); | 
|  |  | 
|  | out: | 
|  | if (ack != -1) | 
|  | close(ack); | 
|  |  | 
|  | close(control); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int setup_server_socket(struct daemon *daemon) | 
|  | { | 
|  | struct sockaddr_un addr; | 
|  | char path[PATH_MAX]; | 
|  | int fd = socket(AF_UNIX, SOCK_STREAM, 0); | 
|  |  | 
|  | if (fd < 0) { | 
|  | fprintf(stderr, "socket: %s\n", strerror(errno)); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { | 
|  | perror("failed: fcntl FD_CLOEXEC"); | 
|  | close(fd); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | scnprintf(path, sizeof(path), "%s/control", daemon->base); | 
|  |  | 
|  | if (strlen(path) + 1 >= sizeof(addr.sun_path)) { | 
|  | pr_err("failed: control path too long '%s'\n", path); | 
|  | close(fd); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | memset(&addr, 0, sizeof(addr)); | 
|  | addr.sun_family = AF_UNIX; | 
|  |  | 
|  | strlcpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); | 
|  | unlink(path); | 
|  |  | 
|  | if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { | 
|  | perror("failed: bind"); | 
|  | close(fd); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (listen(fd, 1) == -1) { | 
|  | perror("failed: listen"); | 
|  | close(fd); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return fd; | 
|  | } | 
|  |  | 
|  | enum { | 
|  | CMD_LIST   = 0, | 
|  | CMD_SIGNAL = 1, | 
|  | CMD_STOP   = 2, | 
|  | CMD_PING   = 3, | 
|  | CMD_MAX, | 
|  | }; | 
|  |  | 
|  | #define SESSION_MAX 64 | 
|  |  | 
|  | union cmd { | 
|  | int cmd; | 
|  |  | 
|  | /* CMD_LIST */ | 
|  | struct { | 
|  | int	cmd; | 
|  | int	verbose; | 
|  | char	csv_sep; | 
|  | } list; | 
|  |  | 
|  | /* CMD_SIGNAL */ | 
|  | struct { | 
|  | int	cmd; | 
|  | int	sig; | 
|  | char	name[SESSION_MAX]; | 
|  | } signal; | 
|  |  | 
|  | /* CMD_PING */ | 
|  | struct { | 
|  | int	cmd; | 
|  | char	name[SESSION_MAX]; | 
|  | } ping; | 
|  | }; | 
|  |  | 
|  | enum { | 
|  | PING_OK	  = 0, | 
|  | PING_FAIL = 1, | 
|  | PING_MAX, | 
|  | }; | 
|  |  | 
|  | static int daemon_session__ping(struct daemon_session *session) | 
|  | { | 
|  | return daemon_session__control(session, "ping", true) ?  PING_FAIL : PING_OK; | 
|  | } | 
|  |  | 
|  | static int cmd_session_list(struct daemon *daemon, union cmd *cmd, FILE *out) | 
|  | { | 
|  | char csv_sep = cmd->list.csv_sep; | 
|  | struct daemon_session *session; | 
|  | time_t curr = time(NULL); | 
|  |  | 
|  | if (csv_sep) { | 
|  | fprintf(out, "%d%c%s%c%s%c%s/%s", | 
|  | /* pid daemon  */ | 
|  | getpid(), csv_sep, "daemon", | 
|  | /* base */ | 
|  | csv_sep, daemon->base, | 
|  | /* output */ | 
|  | csv_sep, daemon->base, SESSION_OUTPUT); | 
|  |  | 
|  | fprintf(out, "%c%s/%s", | 
|  | /* lock */ | 
|  | csv_sep, daemon->base, "lock"); | 
|  |  | 
|  | fprintf(out, "%c%lu", | 
|  | /* session up time */ | 
|  | csv_sep, (curr - daemon->start) / 60); | 
|  |  | 
|  | fprintf(out, "\n"); | 
|  | } else { | 
|  | fprintf(out, "[%d:daemon] base: %s\n", getpid(), daemon->base); | 
|  | if (cmd->list.verbose) { | 
|  | fprintf(out, "  output:  %s/%s\n", | 
|  | daemon->base, SESSION_OUTPUT); | 
|  | fprintf(out, "  lock:    %s/lock\n", | 
|  | daemon->base); | 
|  | fprintf(out, "  up:      %lu minutes\n", | 
|  | (curr - daemon->start) / 60); | 
|  | } | 
|  | } | 
|  |  | 
|  | list_for_each_entry(session, &daemon->sessions, list) { | 
|  | if (csv_sep) { | 
|  | fprintf(out, "%d%c%s%c%s", | 
|  | /* pid */ | 
|  | session->pid, | 
|  | /* name */ | 
|  | csv_sep, session->name, | 
|  | /* base */ | 
|  | csv_sep, session->run); | 
|  |  | 
|  | fprintf(out, "%c%s%c%s/%s", | 
|  | /* session dir */ | 
|  | csv_sep, session->base, | 
|  | /* session output */ | 
|  | csv_sep, session->base, SESSION_OUTPUT); | 
|  |  | 
|  | fprintf(out, "%c%s/%s%c%s/%s", | 
|  | /* session control */ | 
|  | csv_sep, session->base, SESSION_CONTROL, | 
|  | /* session ack */ | 
|  | csv_sep, session->base, SESSION_ACK); | 
|  |  | 
|  | fprintf(out, "%c%lu", | 
|  | /* session up time */ | 
|  | csv_sep, (curr - session->start) / 60); | 
|  |  | 
|  | fprintf(out, "\n"); | 
|  | } else { | 
|  | fprintf(out, "[%d:%s] perf record %s\n", | 
|  | session->pid, session->name, session->run); | 
|  | if (!cmd->list.verbose) | 
|  | continue; | 
|  | fprintf(out, "  base:    %s\n", | 
|  | session->base); | 
|  | fprintf(out, "  output:  %s/%s\n", | 
|  | session->base, SESSION_OUTPUT); | 
|  | fprintf(out, "  control: %s/%s\n", | 
|  | session->base, SESSION_CONTROL); | 
|  | fprintf(out, "  ack:     %s/%s\n", | 
|  | session->base, SESSION_ACK); | 
|  | fprintf(out, "  up:      %lu minutes\n", | 
|  | (curr - session->start) / 60); | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int daemon_session__signal(struct daemon_session *session, int sig) | 
|  | { | 
|  | if (session->pid < 0) | 
|  | return -1; | 
|  | return kill(session->pid, sig); | 
|  | } | 
|  |  | 
|  | static int cmd_session_kill(struct daemon *daemon, union cmd *cmd, FILE *out) | 
|  | { | 
|  | struct daemon_session *session; | 
|  | bool all = false; | 
|  |  | 
|  | all = !strcmp(cmd->signal.name, "all"); | 
|  |  | 
|  | list_for_each_entry(session, &daemon->sessions, list) { | 
|  | if (all || !strcmp(cmd->signal.name, session->name)) { | 
|  | daemon_session__signal(session, cmd->signal.sig); | 
|  | fprintf(out, "signal %d sent to session '%s [%d]'\n", | 
|  | cmd->signal.sig, session->name, session->pid); | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const char *ping_str[PING_MAX] = { | 
|  | [PING_OK]   = "OK", | 
|  | [PING_FAIL] = "FAIL", | 
|  | }; | 
|  |  | 
|  | static int cmd_session_ping(struct daemon *daemon, union cmd *cmd, FILE *out) | 
|  | { | 
|  | struct daemon_session *session; | 
|  | bool all = false, found = false; | 
|  |  | 
|  | all = !strcmp(cmd->ping.name, "all"); | 
|  |  | 
|  | list_for_each_entry(session, &daemon->sessions, list) { | 
|  | if (all || !strcmp(cmd->ping.name, session->name)) { | 
|  | int state = daemon_session__ping(session); | 
|  |  | 
|  | fprintf(out, "%-4s %s\n", ping_str[state], session->name); | 
|  | found = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!found && !all) { | 
|  | fprintf(out, "%-4s %s (not found)\n", | 
|  | ping_str[PING_FAIL], cmd->ping.name); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int handle_server_socket(struct daemon *daemon, int sock_fd) | 
|  | { | 
|  | int ret = -1, fd; | 
|  | FILE *out = NULL; | 
|  | union cmd cmd; | 
|  |  | 
|  | fd = accept(sock_fd, NULL, NULL); | 
|  | if (fd < 0) { | 
|  | perror("failed: accept"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (sizeof(cmd) != readn(fd, &cmd, sizeof(cmd))) { | 
|  | perror("failed: read"); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | out = fdopen(fd, "w"); | 
|  | if (!out) { | 
|  | perror("failed: fdopen"); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | switch (cmd.cmd) { | 
|  | case CMD_LIST: | 
|  | ret = cmd_session_list(daemon, &cmd, out); | 
|  | break; | 
|  | case CMD_SIGNAL: | 
|  | ret = cmd_session_kill(daemon, &cmd, out); | 
|  | break; | 
|  | case CMD_STOP: | 
|  | done = 1; | 
|  | ret = 0; | 
|  | pr_debug("perf daemon is exciting\n"); | 
|  | break; | 
|  | case CMD_PING: | 
|  | ret = cmd_session_ping(daemon, &cmd, out); | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | fclose(out); | 
|  | out: | 
|  | /* If out is defined, then fd is closed via fclose. */ | 
|  | if (!out) | 
|  | close(fd); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int setup_client_socket(struct daemon *daemon) | 
|  | { | 
|  | struct sockaddr_un addr; | 
|  | char path[PATH_MAX]; | 
|  | int fd = socket(AF_UNIX, SOCK_STREAM, 0); | 
|  |  | 
|  | if (fd == -1) { | 
|  | perror("failed: socket"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | scnprintf(path, sizeof(path), "%s/control", daemon->base); | 
|  |  | 
|  | if (strlen(path) + 1 >= sizeof(addr.sun_path)) { | 
|  | pr_err("failed: control path too long '%s'\n", path); | 
|  | close(fd); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | memset(&addr, 0, sizeof(addr)); | 
|  | addr.sun_family = AF_UNIX; | 
|  | strlcpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); | 
|  |  | 
|  | if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) { | 
|  | perror("failed: connect"); | 
|  | close(fd); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return fd; | 
|  | } | 
|  |  | 
|  | static void daemon_session__kill(struct daemon_session *session, | 
|  | struct daemon *daemon) | 
|  | { | 
|  | int how = 0; | 
|  |  | 
|  | do { | 
|  | switch (how) { | 
|  | case 0: | 
|  | daemon_session__control(session, "stop", false); | 
|  | break; | 
|  | case 1: | 
|  | daemon_session__signal(session, SIGTERM); | 
|  | break; | 
|  | case 2: | 
|  | daemon_session__signal(session, SIGKILL); | 
|  | break; | 
|  | default: | 
|  | pr_err("failed to wait for session %s\n", | 
|  | session->name); | 
|  | return; | 
|  | } | 
|  | how++; | 
|  |  | 
|  | } while (daemon_session__wait(session, daemon, 10)); | 
|  | } | 
|  |  | 
|  | static void daemon__signal(struct daemon *daemon, int sig) | 
|  | { | 
|  | struct daemon_session *session; | 
|  |  | 
|  | list_for_each_entry(session, &daemon->sessions, list) | 
|  | daemon_session__signal(session, sig); | 
|  | } | 
|  |  | 
|  | static void daemon_session__delete(struct daemon_session *session) | 
|  | { | 
|  | free(session->base); | 
|  | free(session->name); | 
|  | free(session->run); | 
|  | free(session); | 
|  | } | 
|  |  | 
|  | static void daemon_session__remove(struct daemon_session *session) | 
|  | { | 
|  | list_del(&session->list); | 
|  | daemon_session__delete(session); | 
|  | } | 
|  |  | 
|  | static void daemon__stop(struct daemon *daemon) | 
|  | { | 
|  | struct daemon_session *session; | 
|  |  | 
|  | list_for_each_entry(session, &daemon->sessions, list) | 
|  | daemon_session__control(session, "stop", false); | 
|  | } | 
|  |  | 
|  | static void daemon__kill(struct daemon *daemon) | 
|  | { | 
|  | int how = 0; | 
|  |  | 
|  | do { | 
|  | switch (how) { | 
|  | case 0: | 
|  | daemon__stop(daemon); | 
|  | break; | 
|  | case 1: | 
|  | daemon__signal(daemon, SIGTERM); | 
|  | break; | 
|  | case 2: | 
|  | daemon__signal(daemon, SIGKILL); | 
|  | break; | 
|  | default: | 
|  | pr_err("failed to wait for sessions\n"); | 
|  | return; | 
|  | } | 
|  | how++; | 
|  |  | 
|  | } while (daemon__wait(daemon, 10)); | 
|  | } | 
|  |  | 
|  | static void daemon__exit(struct daemon *daemon) | 
|  | { | 
|  | struct daemon_session *session, *h; | 
|  |  | 
|  | list_for_each_entry_safe(session, h, &daemon->sessions, list) | 
|  | daemon_session__remove(session); | 
|  |  | 
|  | free(daemon->config_real); | 
|  | free(daemon->config_base); | 
|  | free(daemon->base); | 
|  | } | 
|  |  | 
|  | static int daemon__reconfig(struct daemon *daemon) | 
|  | { | 
|  | struct daemon_session *session, *n; | 
|  |  | 
|  | list_for_each_entry_safe(session, n, &daemon->sessions, list) { | 
|  | /* No change. */ | 
|  | if (session->state == OK) | 
|  | continue; | 
|  |  | 
|  | /* Remove session. */ | 
|  | if (session->state == KILL) { | 
|  | if (session->pid > 0) { | 
|  | daemon_session__kill(session, daemon); | 
|  | pr_info("reconfig: session '%s' killed\n", session->name); | 
|  | } | 
|  | daemon_session__remove(session); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | /* Reconfig session. */ | 
|  | if (session->pid > 0) { | 
|  | daemon_session__kill(session, daemon); | 
|  | pr_info("reconfig: session '%s' killed\n", session->name); | 
|  | } | 
|  | if (daemon_session__run(session, daemon)) | 
|  | return -1; | 
|  |  | 
|  | session->state = OK; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int setup_config_changes(struct daemon *daemon) | 
|  | { | 
|  | char *basen = strdup(daemon->config_real); | 
|  | char *dirn  = strdup(daemon->config_real); | 
|  | char *base, *dir; | 
|  | int fd, wd = -1; | 
|  |  | 
|  | if (!dirn || !basen) | 
|  | goto out; | 
|  |  | 
|  | fd = inotify_init1(IN_NONBLOCK|O_CLOEXEC); | 
|  | if (fd < 0) { | 
|  | perror("failed: inotify_init"); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | dir = dirname(dirn); | 
|  | base = basename(basen); | 
|  | pr_debug("config file: %s, dir: %s\n", base, dir); | 
|  |  | 
|  | wd = inotify_add_watch(fd, dir, IN_CLOSE_WRITE); | 
|  | if (wd >= 0) { | 
|  | daemon->config_base = strdup(base); | 
|  | if (!daemon->config_base) { | 
|  | close(fd); | 
|  | wd = -1; | 
|  | } | 
|  | } else { | 
|  | perror("failed: inotify_add_watch"); | 
|  | } | 
|  |  | 
|  | out: | 
|  | free(basen); | 
|  | free(dirn); | 
|  | return wd < 0 ? -1 : fd; | 
|  | } | 
|  |  | 
|  | static bool process_inotify_event(struct daemon *daemon, char *buf, ssize_t len) | 
|  | { | 
|  | char *p = buf; | 
|  |  | 
|  | while (p < (buf + len)) { | 
|  | struct inotify_event *event = (struct inotify_event *) p; | 
|  |  | 
|  | /* | 
|  | * We monitor config directory, check if our | 
|  | * config file was changes. | 
|  | */ | 
|  | if ((event->mask & IN_CLOSE_WRITE) && | 
|  | !(event->mask & IN_ISDIR)) { | 
|  | if (!strcmp(event->name, daemon->config_base)) | 
|  | return true; | 
|  | } | 
|  | p += sizeof(*event) + event->len; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static int handle_config_changes(struct daemon *daemon, int conf_fd, | 
|  | bool *config_changed) | 
|  | { | 
|  | char buf[4096]; | 
|  | ssize_t len; | 
|  |  | 
|  | while (!(*config_changed)) { | 
|  | len = read(conf_fd, buf, sizeof(buf)); | 
|  | if (len == -1) { | 
|  | if (errno != EAGAIN) { | 
|  | perror("failed: read"); | 
|  | return -1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  | *config_changed = process_inotify_event(daemon, buf, len); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int setup_config(struct daemon *daemon) | 
|  | { | 
|  | if (daemon->base_user) { | 
|  | daemon->base = strdup(daemon->base_user); | 
|  | if (!daemon->base) | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | if (daemon->config) { | 
|  | char *real = realpath(daemon->config, NULL); | 
|  |  | 
|  | if (!real) { | 
|  | perror("failed: realpath"); | 
|  | return -1; | 
|  | } | 
|  | daemon->config_real = real; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (perf_config_system() && !access(perf_etc_perfconfig(), R_OK)) | 
|  | daemon->config_real = strdup(perf_etc_perfconfig()); | 
|  | else if (perf_config_global() && perf_home_perfconfig()) | 
|  | daemon->config_real = strdup(perf_home_perfconfig()); | 
|  |  | 
|  | return daemon->config_real ? 0 : -1; | 
|  | } | 
|  |  | 
|  | #ifndef F_TLOCK | 
|  | #define F_TLOCK 2 | 
|  |  | 
|  | static int lockf(int fd, int cmd, off_t len) | 
|  | { | 
|  | if (cmd != F_TLOCK || len != 0) | 
|  | return -1; | 
|  |  | 
|  | return flock(fd, LOCK_EX | LOCK_NB); | 
|  | } | 
|  | #endif // F_TLOCK | 
|  |  | 
|  | /* | 
|  | * Each daemon tries to create and lock BASE/lock file, | 
|  | * if it's successful we are sure we're the only daemon | 
|  | * running over the BASE. | 
|  | * | 
|  | * Once daemon is finished, file descriptor to lock file | 
|  | * is closed and lock is released. | 
|  | */ | 
|  | static int check_lock(struct daemon *daemon) | 
|  | { | 
|  | char path[PATH_MAX]; | 
|  | char buf[20]; | 
|  | int fd, pid; | 
|  | ssize_t len; | 
|  |  | 
|  | scnprintf(path, sizeof(path), "%s/lock", daemon->base); | 
|  |  | 
|  | fd = open(path, O_RDWR|O_CREAT|O_CLOEXEC, 0640); | 
|  | if (fd < 0) | 
|  | return -1; | 
|  |  | 
|  | if (lockf(fd, F_TLOCK, 0) < 0) { | 
|  | filename__read_int(path, &pid); | 
|  | fprintf(stderr, "failed: another perf daemon (pid %d) owns %s\n", | 
|  | pid, daemon->base); | 
|  | close(fd); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | scnprintf(buf, sizeof(buf), "%d", getpid()); | 
|  | len = strlen(buf); | 
|  |  | 
|  | if (write(fd, buf, len) != len) { | 
|  | perror("failed: write"); | 
|  | close(fd); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (ftruncate(fd, len)) { | 
|  | perror("failed: ftruncate"); | 
|  | close(fd); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int go_background(struct daemon *daemon) | 
|  | { | 
|  | int pid, fd; | 
|  |  | 
|  | pid = fork(); | 
|  | if (pid < 0) | 
|  | return -1; | 
|  |  | 
|  | if (pid > 0) | 
|  | return 1; | 
|  |  | 
|  | if (setsid() < 0) | 
|  | return -1; | 
|  |  | 
|  | if (check_lock(daemon)) | 
|  | return -1; | 
|  |  | 
|  | umask(0); | 
|  |  | 
|  | if (chdir(daemon->base)) { | 
|  | perror("failed: chdir"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | fd = open("output", O_RDWR|O_CREAT|O_TRUNC, 0644); | 
|  | if (fd < 0) { | 
|  | perror("failed: open"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (fcntl(fd, F_SETFD, FD_CLOEXEC)) { | 
|  | perror("failed: fcntl FD_CLOEXEC"); | 
|  | close(fd); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | close(0); | 
|  | dup2(fd, 1); | 
|  | dup2(fd, 2); | 
|  | close(fd); | 
|  |  | 
|  | daemon->out = fdopen(1, "w"); | 
|  | if (!daemon->out) { | 
|  | close(1); | 
|  | close(2); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | setbuf(daemon->out, NULL); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int setup_signalfd(struct daemon *daemon) | 
|  | { | 
|  | sigset_t mask; | 
|  |  | 
|  | sigemptyset(&mask); | 
|  | sigaddset(&mask, SIGCHLD); | 
|  |  | 
|  | if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) | 
|  | return -1; | 
|  |  | 
|  | daemon->signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); | 
|  | return daemon->signal_fd; | 
|  | } | 
|  |  | 
|  | static int __cmd_start(struct daemon *daemon, struct option parent_options[], | 
|  | int argc, const char **argv) | 
|  | { | 
|  | bool foreground = false; | 
|  | struct option start_options[] = { | 
|  | OPT_BOOLEAN('f', "foreground", &foreground, "stay on console"), | 
|  | OPT_PARENT(parent_options), | 
|  | OPT_END() | 
|  | }; | 
|  | int sock_fd = -1, conf_fd = -1, signal_fd = -1; | 
|  | int sock_pos, file_pos, signal_pos; | 
|  | struct fdarray fda; | 
|  | int err = 0; | 
|  |  | 
|  | argc = parse_options(argc, argv, start_options, daemon_usage, 0); | 
|  | if (argc) | 
|  | usage_with_options(daemon_usage, start_options); | 
|  |  | 
|  | daemon->start = time(NULL); | 
|  |  | 
|  | if (setup_config(daemon)) { | 
|  | pr_err("failed: config not found\n"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (setup_server_config(daemon)) | 
|  | return -1; | 
|  |  | 
|  | if (foreground && check_lock(daemon)) | 
|  | return -1; | 
|  |  | 
|  | if (!foreground) { | 
|  | err = go_background(daemon); | 
|  | if (err) { | 
|  | /* original process, exit normally */ | 
|  | if (err == 1) | 
|  | err = 0; | 
|  | daemon__exit(daemon); | 
|  | return err; | 
|  | } | 
|  | } | 
|  |  | 
|  | debug_set_file(daemon->out); | 
|  | debug_set_display_time(true); | 
|  |  | 
|  | pr_info("daemon started (pid %d)\n", getpid()); | 
|  |  | 
|  | fdarray__init(&fda, 3); | 
|  |  | 
|  | sock_fd = setup_server_socket(daemon); | 
|  | if (sock_fd < 0) | 
|  | goto out; | 
|  |  | 
|  | conf_fd = setup_config_changes(daemon); | 
|  | if (conf_fd < 0) | 
|  | goto out; | 
|  |  | 
|  | signal_fd = setup_signalfd(daemon); | 
|  | if (signal_fd < 0) | 
|  | goto out; | 
|  |  | 
|  | sock_pos = fdarray__add(&fda, sock_fd, POLLIN|POLLERR|POLLHUP, 0); | 
|  | if (sock_pos < 0) | 
|  | goto out; | 
|  |  | 
|  | file_pos = fdarray__add(&fda, conf_fd, POLLIN|POLLERR|POLLHUP, 0); | 
|  | if (file_pos < 0) | 
|  | goto out; | 
|  |  | 
|  | signal_pos = fdarray__add(&fda, signal_fd, POLLIN|POLLERR|POLLHUP, 0); | 
|  | if (signal_pos < 0) | 
|  | goto out; | 
|  |  | 
|  | signal(SIGINT, sig_handler); | 
|  | signal(SIGTERM, sig_handler); | 
|  | signal(SIGPIPE, SIG_IGN); | 
|  |  | 
|  | while (!done && !err) { | 
|  | err = daemon__reconfig(daemon); | 
|  |  | 
|  | if (!err && fdarray__poll(&fda, -1)) { | 
|  | bool reconfig = false; | 
|  |  | 
|  | if (fda.entries[sock_pos].revents & POLLIN) | 
|  | err = handle_server_socket(daemon, sock_fd); | 
|  | if (fda.entries[file_pos].revents & POLLIN) | 
|  | err = handle_config_changes(daemon, conf_fd, &reconfig); | 
|  | if (fda.entries[signal_pos].revents & POLLIN) | 
|  | err = handle_signalfd(daemon) < 0; | 
|  |  | 
|  | if (reconfig) | 
|  | err = setup_server_config(daemon); | 
|  | } | 
|  | } | 
|  |  | 
|  | out: | 
|  | fdarray__exit(&fda); | 
|  |  | 
|  | daemon__kill(daemon); | 
|  | daemon__exit(daemon); | 
|  |  | 
|  | if (sock_fd != -1) | 
|  | close(sock_fd); | 
|  | if (conf_fd != -1) | 
|  | close(conf_fd); | 
|  | if (signal_fd != -1) | 
|  | close(signal_fd); | 
|  |  | 
|  | pr_info("daemon exited\n"); | 
|  | fclose(daemon->out); | 
|  | return err; | 
|  | } | 
|  |  | 
|  | static int send_cmd(struct daemon *daemon, union cmd *cmd) | 
|  | { | 
|  | int ret = -1, fd; | 
|  | char *line = NULL; | 
|  | size_t len = 0; | 
|  | ssize_t nread; | 
|  | FILE *in = NULL; | 
|  |  | 
|  | if (setup_client_config(daemon)) | 
|  | return -1; | 
|  |  | 
|  | fd = setup_client_socket(daemon); | 
|  | if (fd < 0) | 
|  | return -1; | 
|  |  | 
|  | if (sizeof(*cmd) != writen(fd, cmd, sizeof(*cmd))) { | 
|  | perror("failed: write"); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | in = fdopen(fd, "r"); | 
|  | if (!in) { | 
|  | perror("failed: fdopen"); | 
|  | goto out; | 
|  | } | 
|  |  | 
|  | while ((nread = getline(&line, &len, in)) != -1) { | 
|  | if (fwrite(line, nread, 1, stdout) != 1) | 
|  | goto out_fclose; | 
|  | fflush(stdout); | 
|  | } | 
|  |  | 
|  | ret = 0; | 
|  | out_fclose: | 
|  | fclose(in); | 
|  | free(line); | 
|  | out: | 
|  | /* If in is defined, then fd is closed via fclose. */ | 
|  | if (!in) | 
|  | close(fd); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int send_cmd_list(struct daemon *daemon) | 
|  | { | 
|  | union cmd cmd; | 
|  |  | 
|  | memset(&cmd, 0, sizeof(cmd)); | 
|  | cmd.list.cmd = CMD_LIST; | 
|  | cmd.list.verbose = verbose; | 
|  | cmd.list.csv_sep = daemon->csv_sep ? *daemon->csv_sep : 0; | 
|  |  | 
|  | return send_cmd(daemon, &cmd); | 
|  | } | 
|  |  | 
|  | static int __cmd_signal(struct daemon *daemon, struct option parent_options[], | 
|  | int argc, const char **argv) | 
|  | { | 
|  | const char *name = "all"; | 
|  | struct option start_options[] = { | 
|  | OPT_STRING(0, "session", &name, "session", | 
|  | "Sent signal to specific session"), | 
|  | OPT_PARENT(parent_options), | 
|  | OPT_END() | 
|  | }; | 
|  | union cmd cmd; | 
|  |  | 
|  | argc = parse_options(argc, argv, start_options, daemon_usage, 0); | 
|  | if (argc) | 
|  | usage_with_options(daemon_usage, start_options); | 
|  |  | 
|  | if (setup_config(daemon)) { | 
|  | pr_err("failed: config not found\n"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | memset(&cmd, 0, sizeof(cmd)); | 
|  | cmd.signal.cmd = CMD_SIGNAL, | 
|  | cmd.signal.sig = SIGUSR2; | 
|  | strncpy(cmd.signal.name, name, sizeof(cmd.signal.name) - 1); | 
|  |  | 
|  | return send_cmd(daemon, &cmd); | 
|  | } | 
|  |  | 
|  | static int __cmd_stop(struct daemon *daemon, struct option parent_options[], | 
|  | int argc, const char **argv) | 
|  | { | 
|  | struct option start_options[] = { | 
|  | OPT_PARENT(parent_options), | 
|  | OPT_END() | 
|  | }; | 
|  | union cmd cmd; | 
|  |  | 
|  | argc = parse_options(argc, argv, start_options, daemon_usage, 0); | 
|  | if (argc) | 
|  | usage_with_options(daemon_usage, start_options); | 
|  |  | 
|  | if (setup_config(daemon)) { | 
|  | pr_err("failed: config not found\n"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | memset(&cmd, 0, sizeof(cmd)); | 
|  | cmd.cmd = CMD_STOP; | 
|  | return send_cmd(daemon, &cmd); | 
|  | } | 
|  |  | 
|  | static int __cmd_ping(struct daemon *daemon, struct option parent_options[], | 
|  | int argc, const char **argv) | 
|  | { | 
|  | const char *name = "all"; | 
|  | struct option ping_options[] = { | 
|  | OPT_STRING(0, "session", &name, "session", | 
|  | "Ping to specific session"), | 
|  | OPT_PARENT(parent_options), | 
|  | OPT_END() | 
|  | }; | 
|  | union cmd cmd; | 
|  |  | 
|  | argc = parse_options(argc, argv, ping_options, daemon_usage, 0); | 
|  | if (argc) | 
|  | usage_with_options(daemon_usage, ping_options); | 
|  |  | 
|  | if (setup_config(daemon)) { | 
|  | pr_err("failed: config not found\n"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | memset(&cmd, 0, sizeof(cmd)); | 
|  | cmd.cmd = CMD_PING; | 
|  | scnprintf(cmd.ping.name, sizeof(cmd.ping.name), "%s", name); | 
|  | return send_cmd(daemon, &cmd); | 
|  | } | 
|  |  | 
|  | int cmd_daemon(int argc, const char **argv) | 
|  | { | 
|  | struct option daemon_options[] = { | 
|  | OPT_INCR('v', "verbose", &verbose, "be more verbose"), | 
|  | OPT_STRING(0, "config", &__daemon.config, | 
|  | "config file", "config file path"), | 
|  | OPT_STRING(0, "base", &__daemon.base_user, | 
|  | "directory", "base directory"), | 
|  | OPT_STRING_OPTARG('x', "field-separator", &__daemon.csv_sep, | 
|  | "field separator", "print counts with custom separator", ","), | 
|  | OPT_END() | 
|  | }; | 
|  |  | 
|  | perf_exe(__daemon.perf, sizeof(__daemon.perf)); | 
|  | __daemon.out = stdout; | 
|  |  | 
|  | argc = parse_options(argc, argv, daemon_options, daemon_usage, | 
|  | PARSE_OPT_STOP_AT_NON_OPTION); | 
|  |  | 
|  | if (argc) { | 
|  | if (!strcmp(argv[0], "start")) | 
|  | return __cmd_start(&__daemon, daemon_options, argc, argv); | 
|  | if (!strcmp(argv[0], "signal")) | 
|  | return __cmd_signal(&__daemon, daemon_options, argc, argv); | 
|  | else if (!strcmp(argv[0], "stop")) | 
|  | return __cmd_stop(&__daemon, daemon_options, argc, argv); | 
|  | else if (!strcmp(argv[0], "ping")) | 
|  | return __cmd_ping(&__daemon, daemon_options, argc, argv); | 
|  |  | 
|  | pr_err("failed: unknown command '%s'\n", argv[0]); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (setup_config(&__daemon)) { | 
|  | pr_err("failed: config not found\n"); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | return send_cmd_list(&__daemon); | 
|  | } |