+/* 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 <http://www.gnu.org/licenses/>. */
+
#include <error.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <assert.h>
#ifndef STAP_SCRIPT_FILENAME
#error "STAP_SCRIPT_FILENAME is required"
#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);
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)
{
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)
{
switch (i)
{
case 'd':
- opt_d = 1;
+ opt_d++;
break;
case 'f':
- if (opt_f)
- error (EXIT_FAILURE, 0, _("-ff is unsupported"));
- *argp++ = "-G";
- *argp++ = "opt_f=1";
- opt_f = 1;
+ opt_f++;
break;
case 'q':
*argp++ = "-G";
opt_q = 1;
break;
case 'o':
- *argp++ = "-o";
- *argp++ = optarg;
+ opt_o = optarg;
+ opt_o_len = strlen (opt_o);
break;
case 'p':
*argp++ = "-x";
if (optind < argc)
{
pid_t child;
- int i;
- static char kill_s[64];
- static char opt_s[64];
+ static char kill_s[256];
+ static char option_s[256];
- child = fork ();
- switch (child)
+ child = xfork ();
+ if (!child)
{
- case -1:
- error (EXIT_FAILURE, errno, _("Error calling fork"));
- case 0:
- i = raise (SIGSTOP);
- if (i != 0)
- error (EXIT_FAILURE, errno, _("Error calling raise (SIGSTOP)"));
+ xraise (SIGSTOP);
execvp (argv[optind], &argv[optind]);
- error (EXIT_FAILURE, errno, "Error executing child command");
- default:
- break;
+ error (EXIT_FAILURE, errno, _("Error executing child command"));
}
/* FIXME: Call `kill -CONT' from the systamtap script. How? */
- i = sprintf (kill_s, "kill -CONT %lu; "
- "while [ -e /proc/%lu/root ]; do sleep 0.1; done",
- (unsigned long) child, (unsigned long) child);
- if (i <= 0)
- error (EXIT_FAILURE, errno, _("Error calling sprintf"));
+ 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;
- i = sprintf (opt_s, "opt_child=%lu", (unsigned long) child);
- if (i <= 0)
- error (EXIT_FAILURE, errno, _("Error calling sprintf"));
+ 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_s;
+ *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");
+ error (EXIT_FAILURE, errno, _("Error executing stap"));
}
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ` = 0' is there to prevent: WARNING: never-assigned global variable
+
+# `opt_f' is a boolean for both -f and -ff.
global opt_f = 0
+# Modify the [pid %5d] output to [%d] for the -ff postprocessor.
+global opt_ff = 0
global opt_q = 0
# Override target().
global opt_child = 0
last_tid = tid
}
-function heading:string (tid:long)
+function heading (tid:long)
{
+ if (opt_ff)
+ return printf ("[%d]", tid)
+
foreach (tidi in trace limit 2)
tid_count++
if (tid_count > 1)
{
/* strace really calls TID a "pid". */
- return sprintf ("[pid %5d] ", tid)
+ return printf ("[pid %5d] ", tid)
}
- else
- return ""
}
global funcname
global nest
+function realnl ()
+{
+ if (opt_ff)
+ print ("n\n")
+ else
+ print ("\n")
+}
+
probe end
{
if (nest[last_tid])
- printf (") = ? <tracer terminated>\n")
+ {
+ printf (") = ? <tracer terminated>")
+ realnl ()
+ }
foreach (tid in nest)
if (tid != last_tid)
- printf ("%s<... %s resumed> ) = ? <tracer terminated>\n", heading (tid),
- funcname[tid] != "" ? funcname[tid] : "?");
+ {
+ heading (tid)
+ printf ("<... %s resumed> ) = ? <tracer terminated>",
+ funcname[tid] != "" ? funcname[tid] : "?");
+ realnl ()
+ }
}
/* BUG: sleeping function called from invalid context at kernel/mutex.c:94
new_tid = task_tid (task)
trace[new_tid] = 1
if (!opt_q)
- printf ("Process %d attached\n", new_tid);
+ {
+ printf ("Process %d attached", new_tid);
+ realnl ()
+ }
}
}
{
if (nest[tid])
{
+ heading (tid)
/* FIXME: Do not assume "exit" for the nested calls. */
- printf ("%s<... %s resumed> = ?\n", heading (tid),
+ printf ("<... %s resumed> = ?",
funcname[tid] != "" ? funcname[tid] : "exit")
+ realnl ()
delete nest[tid]
delete funcname[tid]
}
delete trace[tid]
if (!opt_q)
- printf ("Process %d detached\n", tid);
+ {
+ printf ("Process %d detached", tid);
+ realnl ()
+ }
}
}
probe syscall.*
lock ()
/* Why is mmap() called recursively twice? */
if (nest[last_tid])
- printf (" <unfinished ...>\n")
+ {
+ if (last_tid != tid ())
+ {
+ if (opt_ff)
+ print ("c\n")
+ else
+ print (" <unfinished ...>\n")
+ }
+ else
+ {
+ print (" <unfinished ...>")
+ realnl ()
+ }
+ }
last_tid = tid ();
funcname[tid ()] = name
nest[tid ()]++
- printf ("%s%s(%s", heading (tid ()), name, argstr)
+ heading (tid ())
+ printf ("%s(%s",name, argstr)
unlock ()
}
}
{
lock ()
if (last_tid != tid () && nest[last_tid])
- printf (" <unfinished ...>\n")
- if (last_tid != tid () || (last_tid == tid () && !nest[last_tid]))
- printf ("%s<... %s resumed> ", heading (tid ()), name);
+ {
+ if (opt_ff)
+ print ("c\n")
+ else
+ print (" <unfinished ...>\n")
+ }
+ if (last_tid != tid ())
+ {
+ heading (tid ())
+ if (!opt_ff)
+ printf ("<... %s resumed> ", name);
+ }
+ if (last_tid == tid () && funcname[last_tid] == "")
+ {
+ heading (tid ())
+ printf ("<... %s resumed> ", name);
+ }
last_tid = tid ();
- printf (") = %s\n", retstr)
+ printf (") = %s", retstr)
+ realnl ()
if (!--nest[tid ()])
delete nest[tid ()]
delete funcname[tid ()]