/* Copyright (C) 2010 Free Software Foundation, Inc.
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 3 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, see . */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef STAP_SCRIPT_FILENAME
#error "STAP_SCRIPT_FILENAME is required"
#endif
#define STRINGIFY1(lit) #lit
#define STRINGIFY(lit) STRINGIFY1 (lit)
#define _(x) gettext (x)
static int opt_d, opt_f, opt_q, opt_p;
/* Verified we should run the -ff postprocessor. */
static int opt_ff;
static char *opt_o;
static size_t opt_o_len;
/* Script filename. */
static char *opt_Z = STRINGIFY (STAP_SCRIPT_FILENAME);
static void *
xmalloc (size_t size)
{
void *retval = malloc (size);
if (retval == NULL)
error (EXIT_FAILURE, errno, _("memory allocation of %zu bytes"), size);
return retval;
}
/* warning: EOF is also an error. */
static size_t
xread (int fd, void *buf, size_t count)
{
ssize_t retval = read (fd, buf, count);
if (retval <= 0)
error (EXIT_FAILURE, errno, _("Error reading data"));
return retval;
}
/* warning: No events are also an error. */
static void
xpoll (struct pollfd *fds, nfds_t nfds, int timeout)
{
if (poll (fds, nfds, timeout) != 1)
error (EXIT_FAILURE, errno, _("poll returned error"));
}
static pid_t
xfork (void)
{
pid_t retval = fork ();
if (retval == -1)
error (EXIT_FAILURE, errno, _("Error calling fork"));
return retval;
}
static void
xsnprintf (char *str, size_t size, const char *format, ...)
{
int i;
va_list ap;
va_start (ap, format);
i = vsnprintf (str, size, format, ap);
va_end (ap);
if (i <= 0 || strlen (str) >= size - 1)
error (EXIT_FAILURE, errno, _("Error calling sprintf"));
}
static void
xfflush (FILE *stream)
{
if (fflush (stream) != 0)
error (EXIT_FAILURE, errno, _("Error calling fflush"));
}
static void
xdup2 (int oldfd, int newfd)
{
if (dup2 (oldfd, newfd) != newfd)
error (EXIT_FAILURE, errno, _("Error calling dup2 (%d, %d)"), oldfd,
newfd);
}
static void
xclose (int fd)
{
if (close (fd) != 0)
error (EXIT_FAILURE, errno, _("Error calling close (%d)"), fd);
}
static void
xpipe (int pipefd[2])
{
if (pipe (pipefd) != 0)
error (EXIT_FAILURE, errno, _("Error creating internal pipe"));
}
static void
xraise (int sig)
{
if (raise (sig) != 0)
error (EXIT_FAILURE, errno, _("Error calling raise (%d)"), sig);
}
static void
xfcntl_setfl (int fd, int cmdarg)
{
if (fcntl (fd, F_SETFL, cmdarg) != 0)
error (EXIT_FAILURE, errno,
_("Error calling fcntl (%d, F_SETFL, 0%o)"), fd, cmdarg);
}
static void
xwrite (int fd, const void *buf, size_t count)
{
if (!count)
return;
if ((ssize_t) count < 0 || write (fd, buf, count) != (ssize_t) count)
error (EXIT_FAILURE, errno,
_("Error calling write (%d, ..., %zu)"), fd, count);
}
static int
xopen (const char *pathname, int flags, mode_t mode)
{
int retval = open (pathname, flags, mode);
if (retval == -1)
error (EXIT_FAILURE, errno,
_("Error %s file \"%s\" (flags 0x%x, mode 0%o)"),
flags & O_APPEND ? _("appending")
: (flags & O_CREAT ? _("creating")
: _("opening")),
pathname, flags, mode);
return retval;
}
static void
usage (int status)
{
printf ("\
usage: staptrace [-dfq] [-o file] [-p pid] [command [arg ...]]\n\
[-Z ]\n\
\n\
Default -Z is: %s\n\
",
opt_Z);
exit (status);
}
static void
dump_args (char **args)
{
char **argp;
for (argp = args; *argp; argp++)
{
if (argp > args)
fputc (' ', stderr);
fputs (*argp, stderr);
}
fputc ('\n', stderr);
}
static void
ff_filter (int fd_read)
{
char buf[getpagesize ()];
size_t buf_len = 0;
/* Does the buf start on a line boundary? */
int buf_nl = 1;
/* Initial fd if no `\n[' is seen at the start. */
int fd_write = STDOUT_FILENO;
xfcntl_setfl (fd_read, O_NONBLOCK);
for (;;)
{
struct pollfd pollfd;
pollfd.fd = fd_read;
pollfd.events = POLLIN;
xpoll (&pollfd, 1, -1);
if (pollfd.revents & ~(POLLIN | POLLHUP) || pollfd.revents == 0)
error (EXIT_FAILURE, errno, _("poll returned error revents 0x%x"),
pollfd.revents);
if (pollfd.revents & POLLIN)
{
ssize_t want, got;
char *s;
char *start;
int start_nl;
want = sizeof (buf) - buf_len - 1;
assert (want > 0);
got = xread (fd_read, &buf[buf_len], want);
if (memchr (&buf[buf_len], 0, got) != NULL)
error (EXIT_FAILURE, 0,
_("Unexpected \\0 in the internal -ff stream"));
buf_len += got;
buf[buf_len] = 0;
if (opt_d >= 2)
fprintf (stderr, "buf_len %zu + %zd = %zu, read: {%s}\n",
buf_len - got, got, buf_len, &buf[buf_len - got]);
start = buf;
start_nl = buf_nl;
/* Keep the last character for `c' or `n' line continuations. */
while (start + 1 < &buf[buf_len])
{
unsigned long ul;
char *end;
static char fname[FILENAME_MAX];
if (!start_nl)
{
ssize_t len;
s = strchr (start, '\n');
if (!s)
{
len = strlen (start);
assert (len > 0);
if (start[len - 1] == 'c' || start[len - 1] == 'n')
len--;
xwrite (fd_write, start, len);
start += len;
continue;
}
len = s - start - 1;
assert (len >= 0);
if (start[len] == 'n')
start[len++] = '\n';
xwrite (fd_write, start, len);
start = s + 1;
start_nl = 1;
continue;
}
if (*start != '[')
error (EXIT_FAILURE, 0,
_("'[' expected in the internal -ff stream"));
if (start[1] == 0)
{
/* We need more data. */
break;
}
ul = strtoul (&start[1], &end, 10);
assert (end != NULL);
if (*end == 0)
{
/* We need more data. */
break;
}
if (end > start + 64 || *end != ']' || end == &start[1])
error (EXIT_FAILURE, 0,
_("Invalid TID in the internal -ff stream: %s"), start);
start = end + 1;
start_nl = 0;
xsnprintf (fname, sizeof (fname), "%s.%lu", opt_o, ul);
if (fd_write != STDOUT_FILENO)
xclose (fd_write);
fd_write = xopen (fname, O_WRONLY | O_APPEND | O_CREAT, 0644);
continue;
}
want = &buf[buf_len] - start;
memmove (buf, start, want);
buf_len = want;
buf_nl = start_nl;
continue;
}
if (pollfd.revents & POLLHUP)
break;
assert (0);
}
if (fd_write != STDOUT_FILENO)
xclose (fd_write);
}
int
main (int argc, char **argv)
{
char **args, **argp;
args = xmalloc (sizeof (*args) * argc * 2 + 32);
argp = args;
*argp++ = "stap";
for (;;)
{
int i;
i = getopt (argc, argv, "+dfqo:p:hZ:");
if (i == EOF)
break;
switch (i)
{
case 'd':
opt_d++;
break;
case 'f':
opt_f++;
break;
case 'q':
*argp++ = "-G";
*argp++ = "opt_q=1";
opt_q = 1;
break;
case 'o':
opt_o = optarg;
opt_o_len = strlen (opt_o);
break;
case 'p':
*argp++ = "-x";
*argp++ = optarg;
opt_p = 1;
break;
case 'Z':
opt_Z = optarg;
break;
case 'h':
usage (EXIT_SUCCESS);
default:
usage (EXIT_FAILURE);
}
}
if ((optind == argc) == !opt_p)
usage (EXIT_FAILURE);
if (optind < argc)
{
pid_t child;
static char kill_s[256];
static char option_s[256];
child = xfork ();
if (!child)
{
xraise (SIGSTOP);
execvp (argv[optind], &argv[optind]);
error (EXIT_FAILURE, errno, _("Error executing child command"));
}
/* FIXME: Call `kill -CONT' from the systamtap script. How? */
xsnprintf (kill_s, sizeof (kill_s),
"kill -CONT %lu; "
"while [ -e /proc/%lu/root ]; do sleep 0.1; done",
(unsigned long) child, (unsigned long) child);
*argp++ = "-c";
*argp++ = kill_s;
xsnprintf (option_s, sizeof (option_s), "opt_child=%lu",
(unsigned long) child);
*argp++ = "-G";
*argp++ = option_s;
}
xfflush (stdout);
xdup2 (STDERR_FILENO, STDOUT_FILENO);
switch (opt_f)
{
case 1:
*argp++ = "-G";
*argp++ = "opt_f=1";
/* FALLTHRU */
case 0:
if (opt_o)
{
*argp++ = "-o";
*argp++ = opt_o;
}
break;
default:
*argp++ = "-G";
*argp++ = "opt_f=1";
if (opt_o)
{
*argp++ = "-G";
*argp++ = "opt_ff=1";
opt_ff = 1;
}
else
{
/* -ff is the same like -f when no -o is specified. */
}
break;
}
*argp++ = opt_Z;
*argp = NULL;
if (opt_d)
dump_args (args);
if (opt_ff)
{
int pipefd[2];
xpipe (pipefd);
if (xfork ())
{
xclose (pipefd[1]);
ff_filter (pipefd[0]);
exit (EXIT_SUCCESS);
}
xclose (pipefd[0]);
xdup2 (pipefd[1], STDOUT_FILENO);
xclose (pipefd[1]);
}
execvp ("stap", args);
if (!opt_d)
dump_args (args);
error (EXIT_FAILURE, errno, _("Error executing stap"));
}