// SPDX-License-Identifier: GPL-2.0
#include <internal/lib.h>
#include <inttypes.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;
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|signal|stop|ping} [<options>]",
"perf daemon [<options>]",
NULL
};
static volatile sig_atomic_t 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) {
zfree(&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)
{
|