From 6f3a6fc5f15b69c0cf9c6d80d3bf85eebca3c325 Mon Sep 17 00:00:00 2001 From: Mathieu Desnoyers Date: Sat, 20 Nov 2010 12:26:48 -0500 Subject: [PATCH] Add lttngtrace Signed-off-by: Mathieu Desnoyers --- lttng/lttngtrace.c | 332 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 lttng/lttngtrace.c diff --git a/lttng/lttngtrace.c b/lttng/lttngtrace.c new file mode 100644 index 0000000..20f8dad --- /dev/null +++ b/lttng/lttngtrace.c @@ -0,0 +1,332 @@ +/* + * lttngtrace.c + * + * lttngtrace starts/stop system wide tracing around program execution. + * + * Copyright (c) 2010 Mathieu Desnoyers + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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. + * + * This file should be setuid root, and belong to a "tracing" group. Only users + * part of the tracing group can trace and view the traces gathered. + * + * TODO: LTTng should support per-session tracepoint activation. + * TODO: use mkstemp() and save last trace name in user's home directory. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if DEBUG +#define printf_dbg(fmt, args...) printf(fmt, args) +#else +#define printf_dbg(fmt, ...) +#endif + +static char *trace_path; +static char trace_path_pid[PATH_MAX]; +static int autotrace; /* + * Is the trace_path automatically chosen in /tmp ? Can + * we unlink if needed ? + */ +static int sigfwd_pid; + +static int recunlink(const char *dirname) +{ + DIR *dir; + struct dirent *entry; + char path[PATH_MAX]; + + dir = opendir(dirname); + if (dir == NULL) { + if (errno == ENOENT) + return 0; + perror("Error opendir()"); + return -errno; + } + + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")) { + snprintf(path, (size_t) PATH_MAX, "%s/%s", dirname, + entry->d_name); + if (entry->d_type == DT_DIR) + recunlink(path); + else + unlink(path); + } + } + closedir(dir); + rmdir(dirname); + + return 0; +} + +static int start_tracing(void) +{ + int ret; + char command[PATH_MAX]; + + if (autotrace) { + ret = recunlink(trace_path); + if (ret) + return ret; + } + + /* + * Create the directory in /tmp to deal with races (refuse if fail). + * Only allow user and group to read the trace data (to limit + * information disclosure). + */ + ret = mkdir(trace_path, S_IRWXU|S_IRWXG); + if (ret) { + perror("Trace directory creation error"); + return ret; + } + ret = system("ltt-armall > /dev/null"); + if (ret) + return ret; + + ret = snprintf(command, PATH_MAX - 1, + "lttctl -C -w %s autotrace1 > /dev/null", + trace_path); + ret = ret < 0 ? ret : 0; + if (ret) + return ret; + ret = system(command); + if (ret) + return ret; +} + +static int stop_tracing(uid_t uid, gid_t egid) +{ + int ret; + + ret = system("lttctl -D autotrace1 > /dev/null"); + if (ret) + return ret; + ret = system("ltt-disarmall > /dev/null"); + if (ret) + return ret; + /* Hand the trace back to the user after tracing is over */ + ret = chown(trace_path, uid, egid); + if (ret) { + perror("chown error"); + return ret; + } +} + +static int write_child_pid(pid_t pid, uid_t uid, gid_t gid) +{ + int fd; + FILE *fp; + int ret; + + /* Create the file as exclusive to deal with /tmp file creation races */ + fd = open(trace_path_pid, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); + fp = fdopen(fd, "w"); + if (!fp) { + perror("Error writing child pid"); + return -errno; + } + + fprintf(fp, "%u", (unsigned int) pid); + ret = fclose(fp); + if (ret) + perror("Error in fclose"); + /* Hand pid information file back to user */ + ret = chown(trace_path_pid, uid, gid); + if (ret) + perror("chown error"); + return ret; +} + +static int parse_options(int argc, char *argv[], int *arg) +{ + int ret = 0; + + for (;;) { + if (*arg >= argc + || argv[*arg][0] != '-' + || argv[*arg][0] == '\0' + || argv[*arg][1] == '\0' + || !strcmp(argv[*arg], "--")) + break; + switch (argv[*arg][1]) { + case 'o': if (*arg + 1 >= argc) { + printf("Missing -o trace name\n"); + ret = -EINVAL; + break; + } + trace_path = argv[*arg + 1]; + (*arg) += 2; + break; + default: printf("Unknown option -%c\n", argv[*arg][1]); + ret = -EINVAL; + return ret; + } + } + return ret; +} + +static int init_trace_path(void) +{ + int ret; + + if (!trace_path) { + trace_path = "/tmp/autotrace1"; + autotrace = 1; + } + ret = snprintf(trace_path_pid, PATH_MAX - 1, "%s/%s", + trace_path, "pid"); + ret = ret < 0 ? ret : 0; + return ret; +} + +static void sighandler(int signo, siginfo_t *siginfo, void *context) +{ + kill(sigfwd_pid, signo); +} + +static int init_sighand(sigset_t *saved_mask) +{ + sigset_t sig_all_mask; + int gret = 0, ret; + + /* Block signals */ + ret = sigfillset(&sig_all_mask); + if (ret) + perror("Error in sigfillset"); + gret = (gret == 0) ? ret : gret; + ret = sigprocmask(SIG_SETMASK, &sig_all_mask, saved_mask); + if (ret) + perror("Error in sigprocmask"); + gret = (gret == 0) ? ret : gret; + return gret; +} + +static int forward_signals(pid_t pid, sigset_t *saved_mask) +{ + struct sigaction act; + int gret = 0, ret; + + /* Forward SIGINT and SIGTERM */ + sigfwd_pid = pid; + act.sa_sigaction = sighandler; + act.sa_flags = SA_SIGINFO | SA_RESTART; + sigemptyset(&act.sa_mask); + ret = sigaction(SIGINT, &act, NULL); + if (ret) + perror("Error in sigaction"); + gret = (gret == 0) ? ret : gret; + ret = sigaction(SIGTERM, &act, NULL); + if (ret) + perror("Error in sigaction"); + gret = (gret == 0) ? ret : gret; + + /* Reenable signals */ + ret = sigprocmask(SIG_SETMASK, saved_mask, NULL); + if (ret) + perror("Error in sigprocmask"); + gret = (gret == 0) ? ret : gret; + return gret; +} + +int main(int argc, char *argv[]) +{ + uid_t euid, uid; + gid_t egid, gid; + pid_t pid; + int gret = 0, ret = 0; + int arg = 1; + sigset_t saved_mask; + + if (argc < 2) + return -ENOENT; + + euid = geteuid(); + uid = getuid(); + egid = getegid(); + gid = geteuid(); + + if (euid != 0 && uid != 0) { + printf("%s must be setuid root\n", argv[0]); + return -EPERM; + } + + printf_dbg("euid: %d\n", euid); + printf_dbg("uid: %d\n", uid); + printf_dbg("egid: %d\n", egid); + printf_dbg("gid: %d\n", gid); + + if (arg < argc) { + ret = parse_options(argc, argv, &arg); + if (ret) + return ret; + } + + ret = init_trace_path(); + gret = (gret == 0) ? ret : gret; + + ret = init_sighand(&saved_mask); + gret = (gret == 0) ? ret : gret; + + ret = start_tracing(); + if (ret) + return ret; + + pid = fork(); + if (pid > 0) { /* parent */ + int status; + + ret = forward_signals(pid, &saved_mask); + gret = (gret == 0) ? ret : gret; + pid = wait(&status); + if (pid == -1) + gret = (gret == 0) ? -errno : gret; + + ret = stop_tracing(uid, egid); + gret = (gret == 0) ? ret : gret; + ret = write_child_pid(pid, uid, egid); + gret = (gret == 0) ? ret : gret; + } else if (pid == 0) { /* child */ + /* Drop root euid before executing child program */ + seteuid(uid); + /* Reenable signals */ + ret = sigprocmask(SIG_SETMASK, &saved_mask, NULL); + if (ret) { + perror("Error in sigprocmask"); + return ret; + } + ret = execvp(argv[arg], &argv[arg]); + if (ret) + perror("Execution error"); + return ret; + } else { /* error */ + perror("Error in fork"); + return -errno; + } + return ret; +} -- 2.34.1