-/* Copyright (c) 2011 David Goulet <david.goulet@polymtl.ca>
+/*
+ * Copyright (c) 2011 David Goulet <david.goulet@polymtl.ca>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#define _GNU_SOURCE
-#include <errno.h>
-#include <fcntl.h>
#include <getopt.h>
-#include <grp.h>
-#include <limits.h>
+#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
#include <unistd.h>
-#include <lttng/liblttngctl.h>
+#include <lttng/lttng.h>
-#include "lttng.h"
+#include "cmd.h"
+#include "conf.h"
#include "lttngerr.h"
/* Variables */
static char *progname;
-/* Prototypes */
-static int process_client_opt(void);
-static int process_opt_list_apps(void);
+int opt_quiet;
+int opt_verbose;
+static int opt_no_sessiond;
+static char *opt_sessiond_path;
+
+enum {
+ OPT_SESSION_PATH,
+ OPT_DUMP_OPTIONS,
+ OPT_DUMP_COMMANDS,
+};
+
+/* Getopt options. No first level command. */
+static struct option long_options[] = {
+ {"help", 0, NULL, 'h'},
+ {"group", 1, NULL, 'g'},
+ {"verbose", 0, NULL, 'v'},
+ {"quiet", 0, NULL, 'q'},
+ {"no-sessiond", 0, NULL, 'n'},
+ {"sessiond-path", 1, NULL, OPT_SESSION_PATH},
+ {"list-options", 0, NULL, OPT_DUMP_OPTIONS},
+ {"list-commands", 0, NULL, OPT_DUMP_COMMANDS},
+ {NULL, 0, NULL, 0}
+};
+
+/* First level command */
+static struct cmd_struct commands[] = {
+ { "list", cmd_list},
+ { "create", cmd_create},
+ { "destroy", cmd_destroy},
+ { "start", cmd_start},
+ { "stop", cmd_stop},
+ { "enable-event", cmd_enable_events},
+ { "disable-event", cmd_disable_events},
+ { "enable-channel", cmd_enable_channels},
+ { "disable-channel", cmd_disable_channels},
+ { "add-context", cmd_add_context},
+ { "set-session", cmd_set_session},
+ { "version", cmd_version},
+ { NULL, NULL} /* Array closure */
+};
+
+static void usage(FILE *ofp)
+{
+ fprintf(ofp, "LTTng Trace Control " VERSION"\n\n");
+ fprintf(ofp, "usage: lttng [options] <command>\n");
+ fprintf(ofp, "\n");
+ fprintf(ofp, "Options:\n");
+ fprintf(ofp, " -h, --help Show this help\n");
+ fprintf(ofp, " -g, --group NAME Unix tracing group name. (default: tracing)\n");
+ fprintf(ofp, " -v, --verbose Verbose mode\n");
+ fprintf(ofp, " -q, --quiet Quiet mode\n");
+ fprintf(ofp, " -n, --no-sessiond Don't spawn a session daemon\n");
+ fprintf(ofp, " --sessiond-path Session daemon full path\n");
+ fprintf(ofp, " --list-options Simple listing of lttng options\n");
+ fprintf(ofp, " --list-commands Simple listing of lttng commands\n");
+ fprintf(ofp, "\n");
+ fprintf(ofp, "Commands:\n");
+ fprintf(ofp, " add-context Add context to event or/and channel\n");
+ fprintf(ofp, " create Create tracing session\n");
+ fprintf(ofp, " destroy Teardown tracing session\n");
+ fprintf(ofp, " enable-channel Enable tracing channel\n");
+ fprintf(ofp, " enable-event Enable tracing event\n");
+ fprintf(ofp, " disable-channel Disable tracing channel\n");
+ fprintf(ofp, " disable-event Disable tracing event\n");
+ fprintf(ofp, " list List possible tracing options\n");
+ fprintf(ofp, " set-session Set current session name\n");
+ fprintf(ofp, " start Start tracing\n");
+ fprintf(ofp, " stop Stop tracing\n");
+ fprintf(ofp, " version Show version information\n");
+ fprintf(ofp, "\n");
+ fprintf(ofp, "Please see the lttng(1) man page for full documentation.\n");
+ fprintf(ofp, "See http://lttng.org for updates, bug reports and news.\n");
+}
/*
- * start_client
+ * list_options
*
- * Process client request from the command line
- * options. Every tracing action is done by the
- * liblttngctl API.
+ * List options line by line. This is mostly for bash auto completion and to
+ * avoid difficult parsing.
*/
-static int process_client_opt(void)
+static void list_options(FILE *ofp)
{
- int ret;
+ int i = 0;
+ struct option *option = NULL;
- /* Connect to the session daemon */
- ret = lttng_connect_sessiond();
- if (ret < 0) {
- ERR("%s", lttng_get_readable_code(ret));
+ option = &long_options[i];
+ while (option->name != NULL) {
+ fprintf(ofp, "--%s\n", option->name);
+
+ if (isprint(option->val)) {
+ fprintf(ofp, "-%c\n", option->val);
+ }
+
+ i++;
+ option = &long_options[i];
+ }
+}
+
+/*
+ * list_commands
+ *
+ * List commands line by line. This is mostly for bash auto completion and to
+ * avoid difficult parsing.
+ */
+static void list_commands(FILE *ofp)
+{
+ int i = 0;
+ struct cmd_struct *cmd = NULL;
+
+ cmd = &commands[i];
+ while (cmd->name != NULL) {
+ fprintf(ofp, "%s\n", cmd->name);
+ i++;
+ cmd = &commands[i];
+ }
+}
+
+/*
+ * clean_exit
+ */
+static void clean_exit(int code)
+{
+ DBG("Clean exit");
+ exit(code);
+}
+
+/*
+ * sighandler
+ *
+ * Signal handler for the daemon
+ */
+static void sighandler(int sig)
+{
+ switch (sig) {
+ case SIGTERM:
+ DBG("SIGTERM catched");
+ clean_exit(EXIT_FAILURE);
+ break;
+ case SIGCHLD:
+ /* Notify is done */
+ DBG("SIGCHLD catched");
+ break;
+ default:
+ DBG("Unknown signal %d catched", sig);
+ break;
+ }
+
+ return;
+}
+
+/*
+ * set_signal_handler
+ *
+ * Setup signal handler for SIGCHLD and SIGTERM.
+ */
+static int set_signal_handler(void)
+{
+ int ret = 0;
+ struct sigaction sa;
+ sigset_t sigset;
+
+ if ((ret = sigemptyset(&sigset)) < 0) {
+ perror("sigemptyset");
goto end;
}
- if (opt_list_apps) {
- ret = process_opt_list_apps();
- if (ret < 0) {
- ERR("%s", lttng_get_readable_code(ret));
- goto end;
- }
+ sa.sa_handler = sighandler;
+ sa.sa_mask = sigset;
+ sa.sa_flags = 0;
+ if ((ret = sigaction(SIGCHLD, &sa, NULL)) < 0) {
+ perror("sigaction");
+ goto end;
}
- return 0;
+ if ((ret = sigaction(SIGTERM, &sa, NULL)) < 0) {
+ perror("sigaction");
+ goto end;
+ }
end:
return ret;
}
/*
- * process_opt_list_apps
+ * handle_command
+ *
+ * Handle the full argv list of a first level command. Will find the command
+ * in the global commands array and call the function callback associated.
*
- * Get the UST traceable pid list and print
- * them to the user.
+ * If command not found, return -1
+ * else, return function command error code.
*/
-static int process_opt_list_apps(void)
+static int handle_command(int argc, char **argv)
{
- int i, ret;
- pid_t *pids;
- FILE *fp;
- char path[24]; /* Can't go bigger than /proc/65535/cmdline */
- char cmdline[PATH_MAX];
+ int i = 0, ret;
+ struct cmd_struct *cmd;
- ret = lttng_ust_list_apps(&pids);
- if (ret < 0) {
- goto error;
+ if (*argv == NULL) {
+ ret = CMD_SUCCESS;
+ goto end;
}
- MSG("LTTng UST traceable application [name (pid)]:");
- for (i=0; i < ret; i++) {
- snprintf(path, sizeof(path), "/proc/%d/cmdline", pids[i]);
- fp = fopen(path, "r");
- if (fp == NULL) {
- continue;
+ cmd = &commands[i];
+ while (cmd->func != NULL) {
+ /* Find command */
+ if (strcmp(argv[0], cmd->name) == 0) {
+ ret = cmd->func(argc, (const char**) argv);
+ switch (ret) {
+ case CMD_ERROR:
+ ERR("Command error");
+ break;
+ case CMD_NOT_IMPLEMENTED:
+ ERR("Options not implemented");
+ break;
+ case CMD_UNDEFINED:
+ ERR("Undefined command");
+ break;
+ case CMD_FATAL:
+ ERR("Fatal error");
+ break;
+ }
+ goto end;
}
- ret = fread(cmdline, 1, sizeof(cmdline), fp);
- MSG("\t%s (%d)", cmdline, pids[i]);
- fclose(fp);
+ i++;
+ cmd = &commands[i];
}
- return 0;
+ /* Command not found */
+ ret = -1;
-error:
+end:
return ret;
}
/*
- * check_ltt_sessiond
+ * spawn_sessiond
+ *
+ * Spawn a session daemon by forking and execv.
+ */
+static int spawn_sessiond(char *pathname)
+{
+ int ret = 0;
+ pid_t pid;
+
+ MSG("Spawning a session daemon");
+ pid = fork();
+ if (pid == 0) {
+ /*
+ * Spawn session daemon and tell
+ * it to signal us when ready.
+ */
+ execlp(pathname, "ltt-sessiond", "--sig-parent", "--quiet", NULL);
+ /* execlp only returns if error happened */
+ if (errno == ENOENT) {
+ ERR("No session daemon found. Use --sessiond-path.");
+ } else {
+ perror("execlp");
+ }
+ kill(getppid(), SIGTERM); /* unpause parent */
+ exit(EXIT_FAILURE);
+ } else if (pid > 0) {
+ /* Wait for ltt-sessiond to start */
+ pause();
+ goto end;
+ } else {
+ perror("fork");
+ ret = -1;
+ goto end;
+ }
+
+end:
+ return ret;
+}
+
+/*
+ * check_sessiond
*
* Check if the session daemon is available using
- * the liblttngctl API for the check.
+ * the liblttngctl API for the check. If not, try to
+ * spawn a daemon.
*/
-static int check_ltt_sessiond(void)
+static int check_sessiond(void)
{
int ret;
+ char *pathname = NULL, *alloc_pathname = NULL;
- ret = lttng_check_session_daemon();
- if (ret < 0) {
- ERR("No session daemon found. Aborting.");
+ ret = lttng_session_daemon_alive();
+ if (ret == 0) { /* not alive */
+ /* Try command line option path */
+ if (opt_sessiond_path != NULL) {
+ ret = access(opt_sessiond_path, F_OK | X_OK);
+ if (ret < 0) {
+ ERR("No such file: %s", opt_sessiond_path);
+ goto end;
+ }
+ pathname = opt_sessiond_path;
+ } else {
+ /* Try LTTNG_SESSIOND_PATH env variable */
+ pathname = getenv(LTTNG_SESSIOND_PATH_ENV);
+ }
+
+ /* Let's rock and roll */
+ if (pathname == NULL) {
+ ret = asprintf(&alloc_pathname, "ltt-sessiond");
+ if (ret < 0) {
+ goto end;
+ }
+ pathname = alloc_pathname;
+ }
+
+ ret = spawn_sessiond(pathname);
+ free(alloc_pathname);
+ if (ret < 0) {
+ ERR("Problem occurs when starting %s", pathname);
+ goto end;
+ }
}
+end:
return ret;
}
-
/*
- * clean_exit
+ * parse_args
+ *
+ * Parse command line arguments.
+ * Return 0 if OK, else -1
*/
-void clean_exit(int code)
+static int parse_args(int argc, char **argv)
{
- DBG("Clean exit");
- exit(code);
+ int opt, ret;
+
+ if (argc < 2) {
+ usage(stderr);
+ clean_exit(EXIT_FAILURE);
+ }
+
+ while ((opt = getopt_long(argc, argv, "+hnvqg:", long_options, NULL)) != -1) {
+ switch (opt) {
+ case 'h':
+ usage(stderr);
+ goto error;
+ case 'v':
+ opt_verbose = 1;
+ break;
+ case 'q':
+ opt_quiet = 1;
+ break;
+ case 'g':
+ lttng_set_tracing_group(optarg);
+ break;
+ case 'n':
+ opt_no_sessiond = 1;
+ break;
+ case OPT_SESSION_PATH:
+ opt_sessiond_path = strdup(optarg);
+ break;
+ case OPT_DUMP_OPTIONS:
+ list_options(stdout);
+ ret = 0;
+ goto error;
+ case OPT_DUMP_COMMANDS:
+ list_commands(stdout);
+ ret = 0;
+ goto error;
+ default:
+ usage(stderr);
+ goto error;
+ }
+ }
+
+ /* If both options are specified, quiet wins */
+ if (opt_verbose && opt_quiet) {
+ opt_verbose = 0;
+ }
+
+ /* Spawn session daemon if needed */
+ if (opt_no_sessiond == 0 && (check_sessiond() < 0)) {
+ goto error;
+ }
+
+ /* No leftovers, print usage and quit */
+ if ((argc - optind) == 0) {
+ usage(stderr);
+ goto error;
+ }
+
+ /*
+ * Handle leftovers which is a first level command with the trailing
+ * options.
+ */
+ ret = handle_command(argc - optind, argv + optind);
+ if (ret < 0) {
+ if (ret == -1) {
+ usage(stderr);
+ } else {
+ ERR("%s", lttng_get_readable_code(ret));
+ }
+ goto error;
+ }
+
+ return 0;
+
+error:
+ return -1;
}
+
/*
- * main
+ * main
*/
int main(int argc, char *argv[])
{
MSG("%c[%d;%dmWelcome back Dr Tracing!%c[%dm\n\n", 27,1,33,27,0);
}
- ret = parse_args(argc, (const char **) argv);
+ ret = set_signal_handler();
if (ret < 0) {
- return EXIT_FAILURE;
- }
-
- if (opt_tracing_group != NULL) {
- DBG("Set tracing group to '%s'", opt_tracing_group);
- lttng_set_tracing_group(opt_tracing_group);
- }
-
- /* If ask for kernel tracing, need root perms */
- if (opt_trace_kernel) {
- DBG("Kernel tracing activated");
- if (getuid() != 0) {
- ERR("%s must be setuid root", progname);
- return -EPERM;
- }
- }
-
- /* Check if the lttng session daemon is running.
- * If no, a daemon will be spawned.
- */
- if (check_ltt_sessiond() < 0) {
- return EXIT_FAILURE;
+ clean_exit(ret);
}
- ret = process_client_opt();
+ ret = parse_args(argc, argv);
if (ret < 0) {
- return ret;
+ clean_exit(EXIT_FAILURE);
}
return 0;