staprun/stapsh.c - systemtap
Global variables defined
Data types defined
Functions defined
Macros defined
Source code
#include "../config.h"
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <spawn.h>
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <poll.h>
#define STAPSH_TOK_DELIM " \t\r\n"
#define STAPSH_MAX_FILE_SIZE 32000000 XXX#define STAPSH_MAX_ARGS 256
struct stapsh_handler {
const char* name;
int (*fn)(void);
};
static int do_hello(void);
static int do_option(void);
static int do_file(void);
static int do_run(void);
static int do_quit(void);
static const int signals[] = {
SIGHUP, SIGPIPE, SIGINT, SIGTERM, SIGCHLD
};
static const struct stapsh_handler commands[] = {
{ "stap", do_hello },
{ "option", do_option },
{ "file", do_file },
{ "run", do_run },
{ "quit", do_quit },
};
static const unsigned ncommands = sizeof(commands) / sizeof(*commands);
static char tmpdir[FILENAME_MAX] = "";
static pid_t staprun_pid = -1;
static int fd_staprun_out = -1;
static int fd_staprun_err = -1;
struct pollfd pfds[3];
#define PFD_STAP_OUT 0
#define PFD_STAPRUN_OUT 1
#define PFD_STAPRUN_ERR 2
static unsigned prefix_data = 0;
static unsigned verbose = 0;
struct stapsh_option {
const char* name;
unsigned* var;
};
static const struct stapsh_option options[] = {
{ "verbose", &verbose },
{ "data", &prefix_data },
};
static const unsigned noptions = sizeof(options) / sizeof(*options);
static char *listening_port = NULL;
static int listening_port_fd = -1;
static struct utsname uts;
static FILE *stapsh_in, *stapsh_out, *stapsh_err;
static int sigio_enabled = 0;
static int sigio_port_status = 0;
static int host_connected()
{
if (listening_port == NULL || stapsh_out == stdout)
return 1;
if (sigio_enabled)
return sigio_port_status;
struct pollfd p = { listening_port_fd, POLLHUP, 0 };
if (poll(&p, 1, 0) < 0) return 0; else return !(p.revents & POLLHUP);
}
#define dbug(level, format, args...) do { \
if (verbose >= level && host_connected()) \
fprintf (stapsh_err, "stapsh:%s:%d " format, \
__FUNCTION__, __LINE__, ## args); \
} while (0)
#define vdbug(level, format, args) do { \
if (verbose >= level && host_connected()) { \
fprintf (stapsh_err, "stapsh:%s:%d ", __FUNCTION__, __LINE__); \
vfprintf (stapsh_err, format, args); \
} } while (0)
#define die(format, args...) do { \
if (host_connected()) { \
fprintf (stapsh_err, "stapsh:%s:%d " format, \
__FUNCTION__, __LINE__, ## args); \
fprintf (stapsh_err, ": %s (%d)\n", strerror(errno), errno); } \
cleanup(2); } while (0)
static int __attribute__ ((format (printf, 1, 2)))
reply(const char* format, ...)
{
if (!host_connected())
return 1;
va_list args, dbug_args;
va_start (args, format);
va_copy (dbug_args, args);
vdbug (1, format, dbug_args);
int ret = vfprintf (stapsh_out, format, args);
fflush (stapsh_out);
va_end (dbug_args);
va_end (args);
return ret;
}
static void __attribute__ ((noreturn))
cleanup(int status)
{
unsigned i;
sigset_t mask;
sigemptyset (&mask);
for (i = 0; i < sizeof(signals) / sizeof(*signals); ++i)
sigaddset (&mask, signals[i]);
sigprocmask(SIG_BLOCK, &mask, 0);
if (staprun_pid > 0)
{
int rc, ret;
kill(staprun_pid, SIGHUP);
ret = waitpid(staprun_pid, &rc, 0);
if (status == 0)
{
if (ret == staprun_pid)
status = WIFEXITED(rc) ? WEXITSTATUS(rc) : 128 + WTERMSIG(rc);
else
status = 2;
}
}
if (tmpdir[0])
{
pid_t pid = 0;
const char* argv[] = {"rm", "-rf", "--", tmpdir, NULL};
if (chdir("/")) {} if (!posix_spawnp(&pid, argv[0], NULL, NULL,
(char* const*)argv, environ))
waitpid(pid, NULL, 0);
}
if (prefix_data)
reply("quit\n");
if (listening_port)
{
fclose(stapsh_in);
fclose(stapsh_out);
}
exit(status);
}
static void
handle_signal(int sig)
{
dbug(1, "received signal %d: %s\n", sig, strsignal(sig));
cleanup(0);
}
static void
sigio_handler(int sig)
{
if (sig != SIGIO || listening_port_fd == -1)
return;
struct pollfd pfd = { listening_port_fd, POLLHUP, 0 };
if (poll(&pfd, 1, 0) >= 0)
sigio_port_status = !(pfd.revents & POLLHUP);
}
static int
can_sigio(void)
{
return strverscmp("2.6.37", uts.release) <= 0;
}
static void
setup_signals (void)
{
unsigned i;
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handle_signal;
sigemptyset (&sa.sa_mask);
for (i = 0; i < sizeof(signals) / sizeof(*signals); ++i)
sigaddset (&sa.sa_mask, signals[i]);
for (i = 0; i < sizeof(signals) / sizeof(*signals); ++i)
sigaction (signals[i], &sa, NULL);
if (listening_port && can_sigio())
{
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigio_handler;
sigemptyset (&sa.sa_mask);
sigaddset (&sa.sa_mask, SIGIO);
sigaction (SIGIO, &sa, NULL);
sigio_enabled = 1;
}
}
static void __attribute__ ((noreturn))
usage (char *prog, int status)
{
fprintf (stapsh_err, "%s [-v] [-l PORT]\n", prog);
exit (status);
}
static void
parse_args(int argc, char* const argv[])
{
int c;
while ((c = getopt (argc, argv, "vl:")) != -1)
switch (c)
{
case 'v':
++verbose;
break;
case 'l':
listening_port = optarg;
break;
case '?':
default:
usage (argv[0], 2);
}
if (optind < argc)
{
fprintf (stapsh_err, "%s: invalid extraneous arguments\n", argv[0]);
usage (argv[0], 2);
}
}
static int
qpdecode(char* s)
{
char* o = s;
while (*s)
if (*s != '=')
*o++ = *s++;
else
{
if (s[1] == '\r' || s[1] == '\n')
s += 2;
else if (s[1] == '\r' && s[2] == '\n')
s += 3;
else if (!s[1] || !s[2])
{
dbug(2, "truncated quoted-printable escape \"%s\"\n", s);
return 1;
}
else
{
errno = 0;
char *end = 0, hex[] = { s[1], s[2], 0 };
unsigned char c = strtol(hex, &end, 16);
if (errno || end != hex + 2)
{
dbug(2, "invalid quoted-printable escape \"=%s\"\n", hex);
return 1;
}
*o++ = c;
s += 3;
}
}
*o = '\0';
return 0;
}
static int
do_hello()
{
if (staprun_pid > 0)
return 1;
XXX
reply ("stapsh %s %s %s\n", VERSION, uts.machine, uts.release);
return 0;
}
static int
do_option()
{
char *opt = strtok(NULL, STAPSH_TOK_DELIM);
if (opt == NULL)
return reply("ERROR: Could not parse option\n");
unsigned i;
for (i = 0; i < noptions; ++i)
if (strcmp(opt, options[i].name) == 0)
{
dbug(2, "turning on option %s\n", opt);
(*options[i].var)++;
reply("OK\n");
return 0;
}
return reply("ERROR: Invalid option\n");
}
static int
do_file()
{
if (staprun_pid > 0)
return 1;
int ret = 0;
int size = -1;
const char* arg = strtok(NULL, STAPSH_TOK_DELIM);
if (arg)
size = atoi(arg);
if (size <= 0 || size > STAPSH_MAX_FILE_SIZE)
return reply ("ERROR: Bad file size %d\n", size);
const char* name = strtok(NULL, STAPSH_TOK_DELIM);
if (!name)
return reply ("ERROR: Missing file name\n");
for (arg = name; *arg; ++arg)
if (!isalnum(*arg) &&
!(arg > name && (*arg == '.' || *arg == '_')))
return reply ("ERROR: Bad character '%c' in file name\n", *arg);
FILE* f = fopen(name, "w");
if (!f)
return reply ("ERROR: Can't open file \"%s\" for writing\n", name);
while (size > 0 && ret == 0)
{
char buf[1024];
size_t r = sizeof(buf);
if ((size_t)size < sizeof(buf))
r = size;
r = fread(buf, 1, r, stapsh_in);
if (!r && feof(stapsh_in))
ret = reply ("ERROR: Reached EOF while reading file data\n");
else if (!r)
ret = reply ("ERROR: Unable to read file data\n");
else
{
size -= r;
const char* bufp = buf;
while (bufp < buf + r && ret == 0)
{
size_t w = (buf + r) - bufp;
w = fwrite(bufp, 1, w, f);
if (!w)
ret = reply ("ERROR: Unable to write file data\n");
else
bufp += w;
}
}
}
fclose(f);
if (ret == 0)
reply ("OK\n");
return ret;
}
static int
pipe_child_fd(posix_spawn_file_actions_t* fa, int pipefd[2], int childfd)
{
if (pipe(pipefd))
return -1;
int err = 0;
int dir = childfd ? 1 : 0;
if (!fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) &&
!fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) &&
!(err = posix_spawn_file_actions_adddup2(fa, pipefd[dir], childfd)))
return 0;
int olderrno = errno;
close(pipefd[0]);
close(pipefd[1]);
return err ?: olderrno;
}
static pid_t
spawn_staprun_piped(char** args)
{
pid_t pid = -1;
int outfd[2], errfd[2];
posix_spawn_file_actions_t fa;
int err;
if ((err = posix_spawn_file_actions_init(&fa)) != 0)
{
reply("ERROR: Can't initialize posix_spawn actions: %s\n", strerror(err));
return -1;
}
if ((err = pipe_child_fd(&fa, outfd, 1)) != 0)
{
reply("ERROR: Can't create pipe for stdout: %s\n",
err > 0 ? strerror(err) : "pipe_child_fd");
goto cleanup_fa;
}
if ((err = pipe_child_fd(&fa, errfd, 2)) != 0)
{
reply("ERROR: Can't create pipe for stderr: %s\n",
err > 0 ? strerror(err) : "pipe_child_fd");
goto cleanup_out;
}
posix_spawn(&pid, args[0], &fa, NULL, args, environ);
if (pid > 0)
fd_staprun_err = errfd[0];
else
close(errfd[0]);
close(errfd[1]);
cleanup_out:
if (pid > 0)
fd_staprun_out = outfd[0];
else
close(outfd[0]);
close(outfd[1]);
cleanup_fa:
posix_spawn_file_actions_destroy(&fa);
return pid;
}
static pid_t
spawn_staprun(char** args)
{
pid_t pid = -1;
posix_spawn_file_actions_t fa;
int err;
if ((err = posix_spawn_file_actions_init(&fa)) != 0)
{
reply ("ERROR: Can't initialize posix_spawn actions: %s\n", strerror(err));
return -1;
}
if ((err = posix_spawn_file_actions_addopen(&fa, 0, "/dev/null", O_RDONLY, 0)) != 0)
{
reply("ERROR: Can't set posix_spawn actions: %s\n", strerror(err));
posix_spawn_file_actions_destroy(&fa);
return -1;
}
if ((err = posix_spawn(&pid, args[0], &fa, NULL, args, environ)) != 0)
{
reply("ERROR: Can't launch staprun: %s\n", strerror(err));
posix_spawn_file_actions_destroy(&fa);
return -1;
}
posix_spawn_file_actions_destroy(&fa);
return pid;
}
static int
do_run()
{
if (staprun_pid > 0)
return 1;
char staprun[] = BINDIR "/staprun";
char* args[STAPSH_MAX_ARGS + 1] = { staprun, 0 };
unsigned nargs = 1;
char* arg;
while ((arg = strtok(NULL, STAPSH_TOK_DELIM)))
{
if (nargs + 1 > STAPSH_MAX_ARGS)
return reply ("ERROR: Too many arguments\n");
if (qpdecode(arg) != 0)
return reply ("ERROR: Invalid encoding in argument \"%s\"\n", arg);
args[nargs++] = arg;
}
if (access(staprun, X_OK) != 0)
return reply ("ERROR: Can't execute %s (%s)\n", staprun, strerror(errno));
pid_t pid;
if (prefix_data || listening_port != NULL)
pid = spawn_staprun_piped(args);
else
pid = spawn_staprun(args);
if (pid <= 0)
{
staprun_pid = fd_staprun_out = fd_staprun_err = -1;
return reply ("ERROR: Failed to spawn staprun\n");
}
if (prefix_data || listening_port != NULL)
{
int flags;
if ((flags = fcntl(fd_staprun_out, F_GETFL)) == -1
|| fcntl(fd_staprun_out, F_SETFL, flags | O_NONBLOCK) == -1
||(flags = fcntl(fd_staprun_err, F_GETFL)) == -1
|| fcntl(fd_staprun_err, F_SETFL, flags | O_NONBLOCK) == -1)
return reply ("ERROR: Call to fcntl() failed");
pfds[PFD_STAPRUN_OUT].fd = fd_staprun_out;
pfds[PFD_STAPRUN_OUT].events = POLLIN;
pfds[PFD_STAPRUN_ERR].fd = fd_staprun_err;
pfds[PFD_STAPRUN_ERR].events = POLLIN;
}
staprun_pid = pid;
reply ("OK\n");
return 0;
}
static int
do_quit()
{
cleanup(0);
}
static void
process_command(void)
{
static int no_cmd_yet = 1;
char command[4096];
if (fgets(command, sizeof(command), stapsh_in) == NULL)
{
if (feof(stapsh_in)) pfds[PFD_STAP_OUT].events = 0;
return;
}
dbug(1, "command: %s", command);
const char* arg = strtok(command, STAPSH_TOK_DELIM) ?: "(null)";
if (no_cmd_yet && listening_port != NULL && strcmp(arg, "stap") != 0)
return;
no_cmd_yet = 0;
unsigned i;
for (i = 0; i < ncommands; ++i)
if (strcmp(arg, commands[i].name) == 0)
{
int rc = commands[i].fn();
if (rc)
dbug(2, "failed command %s, rc=%d\n", arg, rc);
break;
}
if (i >= ncommands)
dbug(2, "invalid command %s\n", arg);
}
static void
prefix_staprun(int i, FILE *out, const char *stream)
{
char buf[4096];
ssize_t n = read(pfds[i].fd, buf, sizeof buf);
if (n > 0)
{
if (prefix_data)
fprintf(out, "data %s %zd\n", stream, n);
if (fwrite(buf, n, 1, out) != 1)
dbug(2, "failed fwrite\n"); fflush(out);
}
else if (n == 0) pfds[i].events = 0;
}
int
main(int argc, char* const argv[])
{
stapsh_in = stdin;
stapsh_out = stdout;
stapsh_err = stderr;
parse_args(argc, argv);
if (uname(&uts))
die("Error calling uname");
setup_signals();
if (listening_port != NULL)
{
listening_port_fd = open(listening_port, O_RDWR);
if (listening_port_fd == -1)
die("Error calling open()");
if (can_sigio())
{
if (fcntl(listening_port_fd, F_SETOWN, getpid()) < 0)
die("Error calling fcntl F_SETOWN");
int flags = fcntl(listening_port_fd, F_GETFL);
if (flags == -1)
die("Error calling fcntl F_GETFL");
if (fcntl(listening_port_fd, F_SETFL, flags | O_ASYNC) == -1)
die("Error calling fcntl F_SETFL");
}
stapsh_in = fdopen(listening_port_fd, "r");
stapsh_out = fdopen(listening_port_fd, "w");
stapsh_err = stapsh_out;
if (!stapsh_in || !stapsh_out)
die("Could not open serial port");
}
umask(0077);
snprintf(tmpdir, sizeof(tmpdir), "%s/stapsh.XXXXXX",
getenv("TMPDIR") ?: "/tmp");
if (!mkdtemp(tmpdir))
die ("Can't make a temporary working directory");
if (chdir(tmpdir))
die ("Can't change to temporary working directory \"%s\"", tmpdir);
pfds[PFD_STAP_OUT].fd = fileno(stapsh_in);
pfds[PFD_STAP_OUT].events = POLLIN;
pfds[PFD_STAPRUN_OUT].events = 0;
pfds[PFD_STAPRUN_ERR].events = 0;
if (listening_port != NULL)
while (!host_connected())
sleep(2);
while (pfds[PFD_STAP_OUT].events)
{
if (poll(pfds, 3, -1) < 0)
{
if (errno == EINTR)
continue; else
die ("poll() failed with critical error");
}
if (pfds[PFD_STAP_OUT].revents & POLLHUP)
break;
if (pfds[PFD_STAP_OUT].revents & POLLIN)
process_command();
if (pfds[PFD_STAPRUN_OUT].revents & POLLIN)
prefix_staprun(PFD_STAPRUN_OUT, stapsh_out, "stdout");
if (pfds[PFD_STAPRUN_ERR].revents & POLLIN)
prefix_staprun(PFD_STAPRUN_ERR, stapsh_err, "stderr");
}
cleanup(0);
}