]> Zhao Yanbai Git Server - minix.git/commitdiff
Add trace(1): the MINIX3 system call tracer 65/2865/1
authorDavid van Moolenbroek <david@minix3.org>
Tue, 4 Nov 2014 21:33:04 +0000 (21:33 +0000)
committerDavid van Moolenbroek <david@minix3.org>
Tue, 4 Nov 2014 21:46:31 +0000 (21:46 +0000)
Change-Id: Ib970c8647409196902ed53d6e9631a1673a4ab2e

29 files changed:
distrib/sets/lists/minix/mi
minix/usr.bin/Makefile
minix/usr.bin/trace/Makefile [new file with mode: 0644]
minix/usr.bin/trace/NOTES [new file with mode: 0644]
minix/usr.bin/trace/call.c [new file with mode: 0644]
minix/usr.bin/trace/error.awk [new file with mode: 0644]
minix/usr.bin/trace/escape.c [new file with mode: 0644]
minix/usr.bin/trace/format.c [new file with mode: 0644]
minix/usr.bin/trace/inc.h [new file with mode: 0644]
minix/usr.bin/trace/ioctl.c [new file with mode: 0644]
minix/usr.bin/trace/ioctl/block.c [new file with mode: 0644]
minix/usr.bin/trace/ioctl/char.c [new file with mode: 0644]
minix/usr.bin/trace/ioctl/net.c [new file with mode: 0644]
minix/usr.bin/trace/ioctl/svrctl.c [new file with mode: 0644]
minix/usr.bin/trace/kernel.c [new file with mode: 0644]
minix/usr.bin/trace/mem.c [new file with mode: 0644]
minix/usr.bin/trace/output.c [new file with mode: 0644]
minix/usr.bin/trace/proc.c [new file with mode: 0644]
minix/usr.bin/trace/proc.h [new file with mode: 0644]
minix/usr.bin/trace/proto.h [new file with mode: 0644]
minix/usr.bin/trace/service/ipc.c [new file with mode: 0644]
minix/usr.bin/trace/service/pm.c [new file with mode: 0644]
minix/usr.bin/trace/service/rs.c [new file with mode: 0644]
minix/usr.bin/trace/service/vfs.c [new file with mode: 0644]
minix/usr.bin/trace/service/vm.c [new file with mode: 0644]
minix/usr.bin/trace/signal.awk [new file with mode: 0644]
minix/usr.bin/trace/trace.1 [new file with mode: 0644]
minix/usr.bin/trace/trace.c [new file with mode: 0644]
minix/usr.bin/trace/type.h [new file with mode: 0644]

index 792effd3f383bcd6ef5d691a6dedd5f40e0bfaa5..39570edcb82bcdee667b6dc085b2a183c29bbc32 100644 (file)
 ./usr/bin/touch                                minix-sys
 ./usr/bin/tput                         minix-sys
 ./usr/bin/tr                           minix-sys
+./usr/bin/trace                                minix-sys
 ./usr/bin/true                         minix-sys
 ./usr/bin/truncate                     minix-sys
 ./usr/bin/tsort                                minix-sys
 ./usr/man/man1/touch.1                 minix-sys
 ./usr/man/man1/tput.1                  minix-sys
 ./usr/man/man1/tr.1                    minix-sys
+./usr/man/man1/trace.1                 minix-sys
 ./usr/man/man1/trap.1                  minix-sys       obsolete
 ./usr/man/man1/true.1                  minix-sys
 ./usr/man/man1/truncate.1              minix-sys
index 60d19bf339683d392b62353e421d60ba38abe7f3..660309d14ff56d990ac694a55d51a4c4eda2d919 100644 (file)
@@ -8,5 +8,6 @@ SUBDIR+=        grep
 SUBDIR+=       ministat
 SUBDIR+=       top
 SUBDIR+=       toproto
+SUBDIR+=       trace
 
 .include <bsd.subdir.mk>
diff --git a/minix/usr.bin/trace/Makefile b/minix/usr.bin/trace/Makefile
new file mode 100644 (file)
index 0000000..af2d63b
--- /dev/null
@@ -0,0 +1,21 @@
+.include <bsd.own.mk>
+
+PROG=  trace
+SRCS=  call.o error.o escape.o format.o ioctl.o kernel.o mem.o output.o \
+       proc.o signal.o trace.o
+.PATH: ${.CURDIR}/service
+SRCS+= pm.o vfs.o rs.o vm.o ipc.o
+.PATH: ${.CURDIR}/ioctl
+SRCS+= block.o char.o net.o svrctl.o
+
+CPPFLAGS+=     -D_MINIX_SYSTEM=1 -I${.CURDIR} -I${NETBSDSRCDIR}/minix
+
+error.c: error.awk ${NETBSDSRCDIR}/sys/sys/errno.h
+       ${TOOL_AWK} -f ${.ALLSRC} > ${.TARGET}
+
+signal.c: signal.awk ${NETBSDSRCDIR}/sys/sys/signal.h
+       ${TOOL_AWK} -f ${.ALLSRC} > ${.TARGET}
+
+CLEANFILES+=   error.c signal.c
+
+.include <bsd.prog.mk>
diff --git a/minix/usr.bin/trace/NOTES b/minix/usr.bin/trace/NOTES
new file mode 100644 (file)
index 0000000..91d8cb7
--- /dev/null
@@ -0,0 +1,255 @@
+Developer notes regarding trace(1), by David van Moolenbroek.
+
+
+OVERALL CODE STRUCTURE
+
+The general tracing engine is in trace.c.  It passes IPC-level system call
+enter and leave events off to call.c, which handles IPC-level system call
+printing and passes off system calls to be interpreted by a service-specific
+system call handler whenever possible.  All the service-specific code is in the
+service/ subdirectory, grouped by destination service.  IOCTLs are a special
+case, which are handled in ioctl.c and passed on to driver-type-grouped IOCTL
+handlers in the ioctl/ subdirectory (this grouping is not strict).  Some of the
+generated output goes through the formatting code in format.c, and all of it
+ends up in output.c.  The remaining source files contain support code.
+
+
+ADDING A SYSTEM CALL HANDLER
+
+In principle, every system call stops the traced process twice: once when the
+system call is started (the call-enter event) and once when the system call
+returns (the call-leave event).  The tracer uses the call-enter event to print
+the request being made, and the call-leave event to print the result of the
+call.  The output format is supposed to mimic largely what the system call
+looks like from a C program, although with additional information where that
+makes sense.  The general output format for system calls is:
+
+  name(parameters) = result
+
+..where "name" is the name of the system call, "parameters" is a list of system
+call parameters, and "result" is the result of the system call.  If possible,
+the part up to and including the equals sign is printed from the call-enter
+event, and the result is printed from the call-leave event.  However, many
+system calls actually pass a pointer to a block of memory that is filled with
+meaningful content as part of the system call.  For that reason, it is also
+possible that the call-enter event stops printing somewhere inside the
+parameters block, and the call-leave event prints the rest of the parameters,
+as well as the equals sign and the result after it.  The place in the printed
+system call where the call-enter printer stops and the call-leave printer is
+supposed to pick up again, is referred to as the "call split".
+
+The tracer has to a handler structure for every system call that can be made by
+a user program to any of the the MINIX3 services.  This handler structure
+provides three elements: the name of the system call, an "out" function that
+handles printing of the call-enter part of the system call, and an "in"
+function that handles printing of the call-leave part of the system call.  The
+"out" function is expected to print zero or more call parameters, and then
+return a call type, which indicates whether all parameters have been printed
+yet, or not.  In fact, there are three call types, shown here with an example
+which has a "|" pipe symbol added to indicate the call split:
+
+  CT_DONE:       write(5, "foo", 3) = |3
+  CT_NOTDONE:    read(5, |"foo", 1024) = 3
+  CT_NORETURN:   execve("foo", ["foo"], []")| = -1 [ENOENT]
+
+The CT_DONE call type indicates that the handler is done printing all the
+parameters during the call-enter event, and the call split will be after the
+equals sign.  The CT_NOTDONE call type indicates that the handler is not done
+printing all parameters yet, thus yielding a call split in the middle of the
+parameters block (or even right after the opening parenthesis).  The no-return
+(CT_NORETURN) call type is used for a small number of functions that do not
+return on success.  Currently, these are the exit(), execve(), and sigreturn()
+system calls.  For these calls, no result will be printed at all, unless such
+a call fails, in which case a failure result is printed after all.  The call
+split is such that the entire parameters block is printed upon entering the
+call, but the equals sign and result are printed only if the call does return.
+
+Now more about the handler structure for the system call.  First of all, each
+system call has a name, which must be a static string.  It may be supplied
+either as a string, or as a function that returns a name string.  The latter is
+for cases where one message-level system call is used to implement multiple
+C-level system calls (such as setitimer() and getitimer() both going through
+PM_ITIMER).  The name function has the following prototype:
+
+  const char *svc_syscall_name(const message *m_out);
+
+..where "m_out" is a local copy of the request message, which the name function
+can use to decide what string to return for the system call.  As a sidenote,
+in the future, the system call name will be used to implement call filtering.
+
+An "out" printer function has the following prototype:
+
+  int svc_syscall_out(struct trace_proc *proc, const message *m_out);
+
+Here, "proc" is a pointer to the process structure containing information about
+the process making the system call; proc->pid returns the process PID, but the
+function should not access any other fields of this structure directly.
+Instead, many of the output primitive and helper functions (which are all
+prefixed with "put_") take this pointer as part of the call.  "m_out" is a
+local copy of the request message, and the printer may access its fields as it
+sees fit.
+
+The printer function should simply print parameters.  The call name and the
+opening parenthesis are printed by the main output routine.
+
+All simple call parameters should be printed using the put_field() and
+put_value() functions.  The former prints a parameter or field name as flat
+text; the latter is a printf-like interface to the former.  By default, call
+paramaters are simply printed as "value", but if printing all names is enabled,
+call parameters are printed as "name=value".  Thus, all parameters should be
+given a name, even if this name does not show up by default.  Either way, these
+two functions take care of deciding whether to print the name, as well as of
+printing separators between the parameters.  More about printing more complex
+parameters (such as structures) in a bit.
+
+The out printer function must return one of the three CT_ call type values.  If
+it returns CT_DONE, the main output routine will immediately print the closing
+parenthesis and equals sign.  If it returns CF_NORETURN, a closing parenthesis
+will be printed.  If it return CF_NOTDONE, only a parameter field separator
+(that is, a comma and a space) will be printed--after all, it can be assumed
+that more parameters will be printed later.
+
+An "in" printer function has the following prototype:
+
+  void svc_syscall_in(struct trace_proc *proc, const message *m_out,
+          const message *m_in, int failed);
+
+Again, "proc" is the traced process of which its current system call has now
+returned.  "m_out" is again the request message, guaranteed to be unchanged
+since the "out" call.  "m_in" is the reply message from the service.  "failed"
+is either 0 to indicate that the call appears to have succeeded, or PF_FAILED
+to indicate that the call definitely failed.  If PF_FAILED is set, the call
+has failed either at the IPC level or at the system call level (or for another,
+less common reason).  In that case, the contents of "m_in" may be garbage and
+"m_in" must not be used at all.
+
+For CF_NOTDONE type calls, the in printer function should first print the
+remaining parameters.  Here especially, it is important to consider that the
+entire call may fail.  In that case, the parameters of which the contents were
+still going to be printed may also contain garbage, since they were never
+filled.  The expected behavior is to print such parameters as pointer or "&.."
+or something else to indicate that their actual contents are not valid.
+
+Either way, once a CF_NOTDONE type call function is done printing the remaining
+parameters, it must call put_equals(proc) to print the closing parenthesis of
+the call and the equals sign.  CF_NORETURN calls must also use put_equals(proc)
+to print the equals sign.
+
+Then comes the result part.  If the call failed, the in printer function *must*
+use put_result(proc) to print the failure result.  This call not only takes
+care of converting negative error codes from m_in->m_type into "-1 [ECODE]" but
+also prints appropriate failure codes for IPC-level and other exceptional
+failures.  Only if the system call did not fail, may the in printer function
+choose to not call put_result(proc), which on success simply prints
+m_in->m_type as an integer.  Similarly, if the system call succeeded, the in
+printer function may print extended results after the primary result, generally
+in parentheses.  For example, getpid() and getppid() share the same system call
+and thus the tracer prints both return values, one as the primary result of the
+actual call and one in parentheses with a clarifying name as extended result:
+
+  getpid() = 3 (ppid=1)
+
+It should now be clear that printing extended results makes no sense if the
+system call failed.
+
+Besidse put_equals and put_result, the following more or less generic support
+functions are available to print the various parts of the requests and replies.
+
+  put_field - output a parameter, structure field, and so on; this function
+              should be used for just about every actual value
+  put_value - printf-like version of put_field
+  put_text  - output plain text; for call handlers, this should be used only to
+              to add things right after a put_field call, never on its own
+  put_fmt   - printf-like version of put_text, should generally not be used
+              from call handlers at all
+  put_open  - open a nested block of fields, surrounded by parentheses,
+              brackets, or something like that; this is used for structures,
+              arrays, and any other similar nontrivial case of nesting
+  put_close - close a previously opened block of fields; the nesting depth is
+              actually tracked (to keep per-level separators etc), so each
+              put_open call must have a corresponding put_close call
+  put_open_struct  - perform several tasks necessary to start printing the
+                     fields of a structure; note that this function may fail!
+  put_close_struct - end successful printing of a structure
+  put_ptr   - print a pointer in the traced process
+  put_buf   - print a buffer or string
+  put_flags - print a bitwise flags field
+  put_tail  - helper function for printing the continuation part of an array
+
+Many of these support functions take a flags field which takes PF_-prefixed
+flags to modify the output they generate.  The value of 'failed' in the in
+printer function may actually be passed (bitwise-OR'ed in) as the PF_FAILED
+flag to these support functions, and they will do the right thing.  For
+example, a call to put_open_struct with the PF_FAILED flag will end up simply
+printing the pointer to the structure, and not allow printing of the contents
+of the structure.
+
+The above support functions are documented (at a basic level) within the code,
+but in many cases, it may be useful to look up how they are used in practice by
+the existing handlers.  The same goes for various less clear cases; while there
+is basic support for printing structures, support for printing arrays must be
+coded fully by hand, as has been done for many places.  A serious attempt has
+been made to make the output consistent across the board (mainly thanks to the
+output format of strace, on which the output of this tracer has been based,
+sometimes very strictly and sometimes more loosely, but that aside) so it is
+always advisable to follow the ways of the existing handlers.  Also keep in
+mind that there are already printer functions for several generic structures,
+and these should be used whenever possible (e.g., see the put_fd() comment).
+
+Finally, the default_out and default_in functions may be used as printer
+functions for call with no parameters, and for functions which need no more
+than put_result() to print their system call result, respectively.
+
+
+INTERNALS: MULTIPROCESS OUTPUT AND PREEMPTION
+
+Things get interesting when multiple processes are traced at once.  Due to the
+nature of process scheduling, system calls may end up being preempted between
+the call-enter and call-leave phases.  This means that the output of a system
+call has to be suspended to give way to an event from another traced process.
+Such preemption may occur with literally all calls; not just "blocking" calls.
+
+The tracer goes through some lengths to aid the user in following the output in
+the light of preemtion.  The most important aspect is that the output of the
+call-enter phase is recorded, so that in the case of preemption, the call-leave
+phase can start by replaying the record.  As a result, the user gets to see the
+whole system call on a single line, instead of just the second half.  Such
+system call resumptions are marked with a "*" in their prefix, to show that
+the call was not just entered.  The output therefore looks like this:
+
+      2| syscall() = <..>
+      3| othercall() = 0
+      2|*syscall() = 0
+
+Signals that arrive during a call will cause a resumption of the call as well.
+As a result, a call may be resumed multiple times:
+
+      2| syscall() = <..>
+      3| othercall() = 0
+      2|*syscall() = ** SIGUSR1 ** ** SIGUSR2 ** <..>
+      3| othercall() = -1 [EBUSY]
+      2|*syscall() = ** SIGHUP ** <..>
+      3| othercall() = 0
+      2|*syscall() = 0
+
+This entire scenario shows one single system call from process 2.
+
+In the current implementation, the output that should be recorded and/or cause
+the "<..>" preemption marker, as well as the cases where the recorded text must
+be replayed, are marked by the code explicitly.  Replay takes place in three
+cases: upon the call-leave event (obviously), upon receiving a signal (as shown
+above), and when it is required that a suspended no-return call is shown as
+completed before continuing with other output.  The last case applies to exit()
+and execve(), and both are documented in the code quite extensively.  Generally
+speaking, in all output lines where no recording or replay actions are
+performed, the recording will not be replayed but also not removed.  This
+allows for intermediate lines for that process in the output.  Practically
+speaking, future support for job control could even print when a process get
+stopped and continued, for that process, while preempting the output for the
+ongoing system call for that same process.
+
+It is possible that the output of the call-enter phase exhausts the recording
+buffer for its process.  In this case, a new, shorter text is generated upon
+process resumption.  There are many other aspects to proper output formatting
+in the light of preemption, but most of them should be documented as part of
+the code reasonably well.
diff --git a/minix/usr.bin/trace/call.c b/minix/usr.bin/trace/call.c
new file mode 100644 (file)
index 0000000..4414329
--- /dev/null
@@ -0,0 +1,686 @@
+
+#include "inc.h"
+
+#include <minix/com.h>
+#include <minix/callnr.h>
+#include <minix/endpoint.h>
+
+static const struct calls *call_table[] = {
+       &pm_calls,
+       &vfs_calls,
+       &rs_calls,
+       &vm_calls,
+       &ipc_calls,
+};
+
+/*
+ * Find a call handler for the given endpoint, call number pair.  Return NULL
+ * if no call handler for this call exists.
+ */
+static const struct call_handler *
+find_handler(endpoint_t endpt, int call_nr)
+{
+       int i, index;
+
+       for (i = 0; i < COUNT(call_table); i++) {
+               if (call_table[i]->endpt != ANY &&
+                   call_table[i]->endpt != endpt)
+                       continue;
+
+               if (call_nr < call_table[i]->base)
+                       continue;
+
+               index = call_nr - call_table[i]->base;
+
+               if (index >= call_table[i]->count)
+                       continue;
+
+               if (call_table[i]->map[index].outfunc == NULL)
+                       continue;
+
+               return &call_table[i]->map[index];
+       }
+
+       return NULL;
+}
+
+/*
+ * Print an endpoint.
+ */
+void
+put_endpoint(struct trace_proc * proc, const char * name, endpoint_t endpt)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (endpt) {
+               TEXT(ASYNCM);
+               TEXT(IDLE);
+               TEXT(CLOCK);
+               TEXT(SYSTEM);
+               TEXT(KERNEL);
+               TEXT(PM_PROC_NR);
+               TEXT(VFS_PROC_NR);
+               TEXT(RS_PROC_NR);
+               TEXT(MEM_PROC_NR);
+               TEXT(SCHED_PROC_NR);
+               TEXT(TTY_PROC_NR);
+               TEXT(DS_PROC_NR);
+               TEXT(VM_PROC_NR);
+               TEXT(PFS_PROC_NR);
+               TEXT(ANY);
+               TEXT(NONE);
+               TEXT(SELF);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", endpt);
+}
+
+/*
+ * Print a message structure.  The source field will be printed only if the
+ * PF_ALT flag is given.
+ */
+static void
+put_message(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr)
+{
+       message m;
+
+       if (!put_open_struct(proc, name, flags, addr, &m, sizeof(m)))
+               return;
+
+       if (flags & PF_ALT)
+               put_endpoint(proc, "m_source", m.m_source);
+
+       put_value(proc, "m_type", "%x", m.m_type);
+
+       put_close_struct(proc, FALSE /*all*/);
+}
+
+/*
+ * Print the call's equals sign, which also implies that the parameters part of
+ * the call has been fully printed and the corresponding closing parenthesis
+ * may have to be printed, if it has not been printed already.
+ */
+void
+put_equals(struct trace_proc * proc)
+{
+
+       /*
+        * Do not allow multiple equals signs on a single line.  This check is
+        * protection against badly written handlers.  It does not work for the
+        * no-return type, but such calls are rare and less error prone anyway.
+        */
+       assert((proc->call_flags & (CF_DONE | CF_NORETURN)) != CF_DONE);
+
+       /*
+        * We allow (and in fact force) handlers to call put_equals in order to
+        * indicate that the call's parameters block has ended, so we must end
+        * the block here, if we hadn't done so before.
+        */
+       if (!(proc->call_flags & CF_DONE)) {
+               put_close(proc, ") ");
+
+               proc->call_flags |= CF_DONE;
+       }
+
+       put_align(proc);
+       put_text(proc, "= ");
+
+       format_set_sep(proc, NULL);
+}
+
+/*
+ * Print the primary result of a call, after the equals sign.  It is always
+ * possible that this is an IPC-level or other low-level error, in which case
+ * this takes precedence, which is why this function must be called to print
+ * the result if the call failed in any way at all; it may or may not be used
+ * if the call succeeded.  For regular call results, default MINIX3/POSIX
+ * semantics are used: if the return value is negative, the actual call failed
+ * with -1 and the negative return value is the call's error code.  The caller
+ * may consider other cases a failure (e.g., waitpid() returning 0), but
+ * negative return values *not* signifying an error are currently not supported
+ * since they are not present in MINIX3.
+ */
+void
+put_result(struct trace_proc * proc)
+{
+       const char *errname;
+       int value;
+
+       /* This call should always be preceded by a put_equals call. */
+       assert(proc->call_flags & CF_DONE);
+
+       /*
+        * If we failed to copy in the result register or message, print a
+        * basic error and nothing else.
+        */
+       if (proc->call_flags & (CF_REG_ERR | CF_MSG_ERR)) {
+               put_text(proc, "<fault>");
+
+               return;
+       }
+
+       /*
+        * If we are printing a system call rather than an IPC call, and an
+        * error occurred at the IPC level, prefix the output with "<ipc>" to
+        * indicate the IPC failure.  If we are printing an IPC call, an IPC-
+        * level result is implied, so we do not print this.
+        */
+       if (proc->call_handler != NULL && (proc->call_flags & CF_IPC_ERR))
+               put_text(proc, "<ipc> ");
+
+       value = proc->call_result;
+
+       if (value >= 0)
+               put_fmt(proc, "%d", value);
+       else if (!valuesonly && (errname = get_error_name(-value)) != NULL)
+               put_fmt(proc, "-1 [%s]", errname);
+       else
+               put_fmt(proc, "-1 [%d]", -value);
+
+       format_set_sep(proc, " ");
+}
+
+/*
+ * The default enter-call (out) printer, which prints no parameters and is thus
+ * immediately done with printing parameters.
+ */
+int
+default_out(struct trace_proc * __unused proc, const message * __unused m_out)
+{
+
+       return CT_DONE;
+}
+
+/*
+ * The default leave-call (in) printer, which simply prints the call result,
+ * possibly preceded by an equals sign if none was printed yet.  For obvious
+ * reasons, if the handler's out printer returned CT_NOTDONE, this default
+ * printer must not be used.
+ */
+void
+default_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * __unused m_in, int __unused failed)
+{
+
+       if ((proc->call_flags & (CF_DONE | CF_NORETURN)) != CF_DONE)
+               put_equals(proc);
+       put_result(proc);
+}
+
+/*
+ * Prepare a sendrec call, by copying in the request message, determining
+ * whether it is one of the calls that the tracing engine should know about,
+ * searching for a handler for the call, and returning a name for the call.
+ */
+static const char *
+sendrec_prepare(struct trace_proc * proc, endpoint_t endpt, vir_bytes addr,
+       int * trace_class)
+{
+       const char *name;
+       int r;
+
+       r = mem_get_data(proc->pid, addr, &proc->m_out, sizeof(proc->m_out));
+
+       if (r == 0) {
+               if (endpt == PM_PROC_NR) {
+                       if (proc->m_out.m_type == PM_EXEC)
+                               *trace_class = TC_EXEC;
+                       else if (proc->m_out.m_type == PM_SIGRETURN)
+                               *trace_class = TC_SIGRET;
+               }
+
+               proc->call_handler = find_handler(endpt, proc->m_out.m_type);
+       } else
+               proc->call_handler = NULL;
+
+       if (proc->call_handler != NULL) {
+               if (proc->call_handler->namefunc != NULL)
+                       name = proc->call_handler->namefunc(&proc->m_out);
+               else
+                       name = proc->call_handler->name;
+
+               assert(name != NULL);
+       } else
+               name = "ipc_sendrec";
+
+       return name;
+}
+
+/*
+ * Print the outgoing (request) part of a sendrec call.  If we found a call
+ * handler for the call, let the handler generate output.  Otherwise, print the
+ * sendrec call at the kernel IPC level.  Return the resulting call flags.
+ */
+static unsigned int
+sendrec_out(struct trace_proc * proc, endpoint_t endpt, vir_bytes addr)
+{
+
+       if (proc->call_handler != NULL) {
+               return proc->call_handler->outfunc(proc, &proc->m_out);
+       } else {
+               put_endpoint(proc, "src_dest", endpt);
+               /*
+                * We have already copied in the message, but if we used m_out
+                * and PF_LOCADDR here, a copy failure would cause "&.." to be
+                * printed rather than the actual message address.
+                */
+               put_message(proc, "m_ptr", 0, addr);
+
+               return CT_DONE;
+       }
+}
+
+/*
+ * Print the incoming (reply) part of a sendrec call.  Copy in the reply
+ * message, determine whether the call is considered to have failed, and let
+ * the call handler do the rest.  If no call handler was found, print an
+ * IPC-level result.
+ */
+static void
+sendrec_in(struct trace_proc * proc, int failed)
+{
+       message m_in;
+
+       if (failed) {
+               /* The call failed at the IPC level. */
+               memset(&m_in, 0, sizeof(m_in)); /* not supposed to be used */
+               assert(proc->call_flags & CF_IPC_ERR);
+       } else if (mem_get_data(proc->pid, proc->m_addr, &m_in,
+           sizeof(m_in)) != 0) {
+               /* The reply message is somehow unavailable to us. */
+               memset(&m_in, 0, sizeof(m_in)); /* not supposed to be used */
+               proc->call_result = EGENERIC; /* not supposed to be used */
+               proc->call_flags |= CF_MSG_ERR;
+               failed = PF_FAILED;
+       } else {
+               /* The result is for the actual call. */
+               proc->call_result = m_in.m_type;
+               failed = (proc->call_result < 0) ? PF_FAILED : 0;
+       }
+
+       if (proc->call_handler != NULL)
+               proc->call_handler->infunc(proc, &proc->m_out, &m_in, failed);
+       else
+               put_result(proc);
+}
+
+/*
+ * Perform preparations for printing a system call.  Return two things: the
+ * name to use for the call, and the trace class of the call.
+ * special treatment).
+ */
+static const char *
+call_prepare(struct trace_proc * proc, reg_t reg[3], int * trace_class)
+{
+
+       switch (proc->call_type) {
+       case SENDREC:
+               return sendrec_prepare(proc, (endpoint_t)reg[1],
+                   (vir_bytes)reg[2], trace_class);
+
+       case SEND:
+               return "ipc_send";
+
+       case SENDNB:
+               return "ipc_sendnb";
+
+       case RECEIVE:
+               return "ipc_receive";
+
+       case NOTIFY:
+               return "ipc_notify";
+
+       case SENDA:
+               return "ipc_senda";
+
+       case MINIX_KERNINFO:
+               return "minix_kerninfo";
+
+       default:
+               /*
+                * It would be nice to include the call number here, but we
+                * must return a string that will last until the entire call is
+                * finished.  Adding another buffer to the trace_proc structure
+                * is an option, but it seems overkill..
+                */
+               return "ipc_unknown";
+       }
+}
+
+/*
+ * Print the outgoing (request) part of a system call.  Return the resulting
+ * call flags.
+ */
+static unsigned int
+call_out(struct trace_proc * proc, reg_t reg[3])
+{
+
+       switch (proc->call_type) {
+       case SENDREC:
+               proc->m_addr = (vir_bytes)reg[2];
+
+               return sendrec_out(proc, (endpoint_t)reg[1],
+                   (vir_bytes)reg[2]);
+
+       case SEND:
+       case SENDNB:
+               put_endpoint(proc, "dest", (endpoint_t)reg[1]);
+               put_message(proc, "m_ptr", 0, (vir_bytes)reg[2]);
+
+               return CT_DONE;
+
+       case RECEIVE:
+               proc->m_addr = (vir_bytes)reg[2];
+
+               put_endpoint(proc, "src", (endpoint_t)reg[1]);
+
+               return CT_NOTDONE;
+
+       case NOTIFY:
+               put_endpoint(proc, "dest", (endpoint_t)reg[1]);
+
+               return CT_DONE;
+
+       case SENDA:
+               put_ptr(proc, "table", (vir_bytes)reg[2]);
+               put_value(proc, "count", "%zu", (size_t)reg[1]);
+
+               return CT_DONE;
+
+       case MINIX_KERNINFO:
+       default:
+               return CT_DONE;
+       }
+}
+
+/*
+ * Print the incoming (reply) part of a call.
+ */
+static void
+call_in(struct trace_proc * proc, int failed)
+{
+
+       switch (proc->call_type) {
+       case SENDREC:
+               sendrec_in(proc, failed);
+
+               break;
+
+       case RECEIVE:
+               /* Print the source as well. */
+               put_message(proc, "m_ptr", failed | PF_ALT, proc->m_addr);
+               put_equals(proc);
+               put_result(proc);
+
+               break;
+
+       case MINIX_KERNINFO:
+               /*
+                * We do not have a platform-independent means to access the
+                * secondary IPC return value, so we cannot print the receive
+                * status or minix_kerninfo address.
+                */
+               /* FALLTHROUGH */
+       default:
+               put_result(proc);
+
+               break;
+       }
+}
+
+/*
+ * Determine whether to skip printing the given call, based on its name.
+ */
+static int
+call_hide(const char * __unused name)
+{
+
+       /*
+        * TODO: add support for such filtering, with an strace-like -e command
+        * line option.  For now, we filter nothing, although calls may still
+        * be hidden as the result of a register retrieval error.
+        */
+       return FALSE;
+}
+
+/*
+ * The given process entered a system call.  Return the trace class of the
+ * call: TC_EXEC for an execve() call, TC_SIGRET for a sigreturn() call, or
+ * TC_NORMAL for a call that requires no exceptions in the trace engine.
+ */
+int
+call_enter(struct trace_proc * proc, int show_stack)
+{
+       const char *name;
+       reg_t reg[3];
+       int trace_class, type;
+
+       /* Get the IPC-level type and parameters of the system call. */
+       if (kernel_get_syscall(proc->pid, reg) < 0) {
+               /*
+                * If obtaining the details of the system call failed, even
+                * though we know the process is stopped on a system call, we
+                * are going to assume that the process got killed somehow.
+                * Thus, the best we can do is ignore the system call entirely,
+                * and hope that the next thing we hear about this process is
+                * its termination.  At worst, we ignore a serious error..
+                */
+               proc->call_flags = CF_HIDE;
+
+               return FALSE;
+       }
+
+       /*
+        * Obtain the call name that is to be used for this call, and decide
+        * whether we want to print this call at all.
+        */
+       proc->call_type = (int)reg[0];
+       trace_class = TC_NORMAL;
+
+       name = call_prepare(proc, reg, &trace_class);
+
+       proc->call_name = name;
+
+       if (call_hide(name)) {
+               proc->call_flags = CF_HIDE;
+
+               return trace_class;
+       }
+
+       /* Only print a stack trace if we are printing the call itself. */
+       if (show_stack)
+               kernel_put_stacktrace(proc);
+
+       /*
+        * Start a new line, start recording, and print the call name and
+        * opening parenthesis.
+        */
+       put_newline();
+
+       format_reset(proc);
+
+       record_start(proc);
+
+       put_text(proc, name);
+       put_open(proc, NULL, PF_NONAME, "(", ", ");
+
+       /*
+        * Print the outgoing part of the call, that is, some or all of its
+        * parameters.  This call returns flags indicating how far printing
+        * got, and may be one of the following combinations:
+        * - CT_NOTDONE (0) if printing parameters is not yet complete; after
+        *   the call split, the in handler must print the rest itself;
+        * - CT_DONE (CF_DONE) if printing parameters is complete, and we
+        *   should now print the closing parenthesis and equals sign;
+        * - CT_NORETURN (CF_DONE|CF_NORETURN) if printing parameters is
+        *   complete, but we should not print the equals sign, because the
+        *   call is expected not to return (the no-return call type).
+        */
+       type = call_out(proc, reg);
+       assert(type == CT_NOTDONE || type == CT_DONE || type == CT_NORETURN);
+
+       /*
+        * Print whatever the handler told us to print for now.
+        */
+       if (type & CF_DONE) {
+               if (type & CF_NORETURN) {
+                       put_close(proc, ")");
+
+                       put_space(proc);
+
+                       proc->call_flags |= type;
+               } else {
+                       /*
+                        * The equals sign is printed implicitly for the
+                        * CT_DONE type only.  For CT_NORETURN and CT_NOTDONE,
+                        * the "in" handler has to do it explicitly.
+                        */
+                       put_equals(proc);
+               }
+       } else {
+               /*
+                * If at least one parameter was printed, print the separator
+                * now.  We know that another parameter will follow (otherwise
+                * the caller would have returned CT_DONE), and this way the
+                * output looks better.
+                */
+               format_push_sep(proc);
+       }
+
+       /*
+        * We are now at the call split; further printing will be done once the
+        * call returns, through call_leave.  Stop recording; if the call gets
+        * suspended and later resumed, we should replay everything up to here.
+        */
+#if DEBUG
+       put_text(proc, "|"); /* warning, this may push a space */
+#endif
+
+       record_stop(proc);
+
+       output_flush();
+
+       return trace_class;
+}
+
+/*
+ * The given process left a system call, or if skip is set, the leave phase of
+ * the current system call should be ended.
+ */
+void
+call_leave(struct trace_proc * proc, int skip)
+{
+       reg_t retreg;
+       int hide, failed;
+
+       /* If the call is skipped, it must be a no-return type call. */
+       assert(!skip || (proc->call_flags & (CF_NORETURN | CF_HIDE)));
+
+       /*
+        * Start by replaying the current call, if necessary.  If the call was
+        * suspended and we are about to print the "in" part, this is obviously
+        * needed.  If the call is hidden, replaying will be a no-op, since
+        * nothing was recorded for this call.  The special case is a skipped
+        * call (which, as established above, must be a no-return call, e.g.
+        * exec), for which replaying has the effect that if the call was
+        * previously suspended, it will now be replayed, without suspension:
+        *
+        *       2| execve("./test", ["./test"], [..(12)]) <..>
+        *       3| sigsuspend([]) = <..>
+        * [A]   2| execve("./test", ["./test"], [..(12)])
+        *       2| ---
+        *       2| Tracing test (pid 2)
+        *
+        * The [A] line is the result of replaying the skipped call.
+        */
+       call_replay(proc);
+
+       hide = (proc->call_flags & CF_HIDE);
+
+       if (!hide && !skip) {
+               /* Get the IPC-level result of the call. */
+               if (kernel_get_retreg(proc->pid, &retreg) < 0) {
+                       /* This should never happen.  Deal with it anyway. */
+                       proc->call_flags |= CF_REG_ERR;
+                       failed = PF_FAILED;
+               } else if ((proc->call_result = (int)retreg) < 0) {
+                       proc->call_flags |= CF_IPC_ERR;
+                       failed = PF_FAILED;
+               } else
+                       failed = 0;
+
+               /*
+                * Print the incoming part of the call, that is, possibly some
+                * or all of its parameters and the call's closing parenthesis
+                * (if CT_NOTDONE), and the equals sign (if not CT_DONE), then
+                * the call result.
+                */
+               call_in(proc, failed);
+       }
+
+       if (!hide) {
+               /*
+                * The call is complete now, so clear the recording.  This also
+                * implies that no suspension marker will be printed anymore.
+                */
+               record_clear(proc);
+
+               put_newline();
+       }
+
+       /*
+        * For calls not of the no-return type, an equals sign must have been
+        * printed by now.  This is protection against badly written handlers.
+        */
+       assert(proc->call_flags & CF_DONE);
+
+       proc->call_name = NULL;
+       proc->call_flags = 0;
+}
+
+/*
+ * Replay the recorded text, if any, for the enter phase of the given process.
+ * If there is no recorded text, start a new line anyway.
+ */
+void
+call_replay(struct trace_proc * proc)
+{
+
+       /*
+        * We get TRUE if the recorded call should be replayed, but the
+        * recorded text for the call did not fit in the recording buffer.
+        * In that case, we have to come up with a replacement text for the
+        * call up to the call split.
+        */
+       if (record_replay(proc) == TRUE) {
+               /*
+                * We basically place a "<..>" suspension marker in the
+                * parameters part of the call, and use its call name and flags
+                * for the rest.  There is a trailing space in all cases.
+                */
+               put_fmt(proc, "%s(<..>%s", proc->call_name,
+                   !(proc->call_flags & CF_DONE) ? "," :
+                   ((proc->call_flags & CF_NORETURN) ? ")" : ") ="));
+               put_space(proc);
+       }
+}
+
+/*
+ * Return the human-readable name of the call currently being made by the given
+ * process.  The process is guaranteed to be in a call, although the call may
+ * be hidden.  Under no circumstances may this function return a NULL pointer.
+ */
+const char *
+call_name(struct trace_proc * proc)
+{
+
+       assert(proc->call_name != NULL);
+
+       return proc->call_name;
+}
diff --git a/minix/usr.bin/trace/error.awk b/minix/usr.bin/trace/error.awk
new file mode 100644 (file)
index 0000000..7476162
--- /dev/null
@@ -0,0 +1,28 @@
+# Derived from libc errlist.awk
+
+BEGIN {
+       printf("/* This file is automatically generated by error.awk */\n\n");
+       printf("#include \"inc.h\"\n\n");
+       printf("static const char *const errors[] = {\n");
+}
+/^#define/ {
+       name = $2;
+       if (name == "ELAST")
+               next;
+       number = $3;
+       if (number == "(_SIGN")
+               number = $4;
+       if (number < 0 || number == "EAGAIN")
+               next;
+       printf("\t[%s] = \"%s\",\n", name, name);
+}
+END {
+       printf("};\n\n");
+       printf("const char *\nget_error_name(int err)\n{\n\n");
+       printf("\tif (err >= 0 && err < sizeof(errors) / sizeof(errors[0]) &&\n");
+       printf("\t    errors[err] != NULL)\n");
+       printf("\t\treturn errors[err];\n");
+       printf("\telse\n");
+       printf("\t\treturn NULL;\n");
+       printf("}\n");
+}
diff --git a/minix/usr.bin/trace/escape.c b/minix/usr.bin/trace/escape.c
new file mode 100644 (file)
index 0000000..7f00ab8
--- /dev/null
@@ -0,0 +1,48 @@
+
+#include "inc.h"
+
+static const char *const escape[256] = {
+       "\\0",   "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\x07",
+       "\\x08", "\\t",   "\\n",   "\\x0B", "\\x0C", "\\r",   "\\x0E", "\\x0F",
+       "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
+       "\\x18", "\\x19", "\\x1A", "\\x1B", "\\x1C", "\\x1D", "\\x1E", "\\x1F",
+       " ",     "!",     "\\\"",  "#",     "$",     "%",     "&",     "'",
+       "(",     ")",     "*",     "+",     ",",     "-",     ".",     "/",
+       "0",     "1",     "2",     "3",     "4",     "5",     "6",     "7",
+       "8",     "9",     ":",     ";",     "<",     "=",     ">",     "?",
+       "@",     "A",     "B",     "C",     "D",     "E",     "F",     "G",
+       "H",     "I",     "J",     "K",     "L",     "M",     "N",     "O",
+       "P",     "Q",     "R",     "S",     "T",     "U",     "V",     "W",
+       "X",     "Y",     "Z",     "[",     "\\",    "]",     "^",     "_",
+       "`",     "a",     "b",     "c",     "d",     "e",     "f",     "g",
+       "h",     "i",     "j",     "k",     "l",     "m",     "n",     "o",
+       "p",     "q",     "r",     "s",     "t",     "u",     "v",     "w",
+       "x",     "y",     "z",     "{",     "|",     "}",     "~",     "\\x7F",
+       "\\x80", "\\x81", "\\x82", "\\x83", "\\x84", "\\x85", "\\x86", "\\x87",
+       "\\x88", "\\x89", "\\x8A", "\\x8B", "\\x8C", "\\x8D", "\\x8E", "\\x8F",
+       "\\x90", "\\x91", "\\x92", "\\x93", "\\x94", "\\x95", "\\x96", "\\x97",
+       "\\x98", "\\x99", "\\x9A", "\\x9B", "\\x9C", "\\x9D", "\\x9E", "\\x9F",
+       "\\xA0", "\\xA1", "\\xA2", "\\xA3", "\\xA4", "\\xA5", "\\xA6", "\\xA7",
+       "\\xA8", "\\xA9", "\\xAA", "\\xAB", "\\xAC", "\\xAD", "\\xAE", "\\xAF",
+       "\\xB0", "\\xB1", "\\xB2", "\\xB3", "\\xB4", "\\xB5", "\\xB6", "\\xB7",
+       "\\xB8", "\\xB9", "\\xBA", "\\xBB", "\\xBC", "\\xBD", "\\xBE", "\\xBF",
+       "\\xC0", "\\xC1", "\\xC2", "\\xC3", "\\xC4", "\\xC5", "\\xC6", "\\xC7",
+       "\\xC8", "\\xC9", "\\xCA", "\\xCB", "\\xCC", "\\xCD", "\\xCE", "\\xCF",
+       "\\xD0", "\\xD1", "\\xD2", "\\xD3", "\\xD4", "\\xD5", "\\xD6", "\\xD7",
+       "\\xD8", "\\xD9", "\\xDA", "\\xDB", "\\xDC", "\\xDD", "\\xDE", "\\xDF",
+       "\\xE0", "\\xE1", "\\xE2", "\\xE3", "\\xE4", "\\xE5", "\\xE6", "\\xE7",
+       "\\xE8", "\\xE9", "\\xEA", "\\xEB", "\\xEC", "\\xED", "\\xEE", "\\xEF",
+       "\\xF0", "\\xF1", "\\xF2", "\\xF3", "\\xF4", "\\xF5", "\\xF6", "\\xF7",
+       "\\xF8", "\\xF9", "\\xFA", "\\xFB", "\\xFC", "\\xFD", "\\xFE", "\\xFF",
+};
+
+/*
+ * For the given character, return a string representing an escaped version of
+ * the character.
+ */
+const char *
+get_escape(char c)
+{
+
+       return escape[(unsigned int)(unsigned char)c];
+}
diff --git a/minix/usr.bin/trace/format.c b/minix/usr.bin/trace/format.c
new file mode 100644 (file)
index 0000000..10a6b3e
--- /dev/null
@@ -0,0 +1,426 @@
+
+#include "inc.h"
+
+#include <stdarg.h>
+
+/*
+ * The size of the formatting buffer, which in particular limits the maximum
+ * size of the output from the variadic functions.  All printer functions which
+ * are dealing with potentially large or even unbounded output, should be able
+ * to generate their output in smaller chunks.  In the end, nothing that is
+ * being printed as a unit should even come close to reaching this limit.
+ */
+#define FORMAT_BUFSZ   4096
+
+/*
+ * The buffer which is used for all intermediate copying and/or formatting.
+ * Care must be taken that only one function uses this buffer at any time.
+ */
+static char formatbuf[FORMAT_BUFSZ];
+
+/*
+ * Reset the line formatting for the given process.
+ */
+void
+format_reset(struct trace_proc * proc)
+{
+
+       proc->next_sep = NULL;
+       proc->depth = -1;
+}
+
+/*
+ * Set the next separator for the given process.  The given separator may be
+ * NULL.
+ */
+void
+format_set_sep(struct trace_proc * proc, const char * sep)
+{
+
+       proc->next_sep = sep;
+}
+
+/*
+ * Print and clear the next separator for the process, if any.
+ */
+void
+format_push_sep(struct trace_proc * proc)
+{
+
+       if (proc->next_sep != NULL) {
+               put_text(proc, proc->next_sep);
+
+               proc->next_sep = NULL;
+       }
+}
+
+/*
+ * Print a field, e.g. a parameter or a field from a structure, separated from
+ * other fields at the same nesting depth as appropriate.  If the given field
+ * name is not NULL, it may or may not be printed.  The given text is what will
+ * be printed for this field so far, but the caller is allowed to continue
+ * printing text for the same field with e.g. put_text().  As such, the given
+ * text may even be an empty string.
+ */
+void
+put_field(struct trace_proc * proc, const char * name, const char * text)
+{
+
+       /*
+        * At depth -1 (the basic line level), names are not used.  A name
+        * should not be supplied by the caller in that case, but, it happens.
+        */
+       if (proc->depth < 0)
+               name = NULL;
+
+       format_push_sep(proc);
+
+       if (name != NULL && (proc->depths[proc->depth].name || allnames)) {
+               put_text(proc, name);
+               put_text(proc, "=");
+       }
+
+       put_text(proc, text);
+
+       format_set_sep(proc, proc->depths[proc->depth].sep);
+}
+
+/*
+ * Increase the nesting depth with a new block of fields, enclosed within
+ * parentheses, brackets, etcetera.  The given name, which may be NULL, is the
+ * name of the entire nested block.  In the flags field, PF_NONAME indicates
+ * that the fields within the block should have their names printed or not,
+ * although this may be overridden by setting the allnames variable.  The given
+ * string is the block opening string (e.g., an opening parenthesis).  The
+ * given separator is used to separate the fields within the nested block, and
+ * should generally be ", " to maintain output consistency.
+ */
+void
+put_open(struct trace_proc * proc, const char * name, int flags,
+       const char * string, const char * sep)
+{
+
+       put_field(proc, name, string);
+
+       proc->depth++;
+
+       assert(proc->depth < MAX_DEPTH);
+
+       proc->depths[proc->depth].sep = sep;
+       proc->depths[proc->depth].name = !(flags & PF_NONAME);
+
+       format_set_sep(proc, NULL);
+}
+
+/*
+ * Decrease the nesting depth by ending a nested block of fields.  The given
+ * string is the closing parenthesis, bracket, etcetera.
+ */
+void
+put_close(struct trace_proc * proc, const char * string)
+{
+
+       assert(proc->depth >= 0);
+
+       put_text(proc, string);
+
+       proc->depth--;
+
+       if (proc->depth >= 0)
+               format_set_sep(proc, proc->depths[proc->depth].sep);
+       else
+               format_set_sep(proc, NULL);
+}
+
+/*
+ * Version of put_text with variadic arguments.  The given process may be NULL.
+ */
+void
+put_fmt(struct trace_proc * proc, const char * fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       (void)vsnprintf(formatbuf, sizeof(formatbuf), fmt, ap);
+       va_end(ap);
+
+       put_text(proc, formatbuf);
+}
+
+/*
+ * Version of put_field with variadic arguments.
+ */
+void
+put_value(struct trace_proc * proc, const char * name, const char * fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       (void)vsnprintf(formatbuf, sizeof(formatbuf), fmt, ap);
+       va_end(ap);
+
+       put_field(proc, name, formatbuf);
+}
+
+/*
+ * Start printing a structure.  In general, the function copies the contents of
+ * the structure of size 'size' from the traced process at 'addr' into the
+ * local 'ptr' structure, opens a nested block with name 'name' (which may
+ * be NULL) using an opening bracket, and returns TRUE to indicate that the
+ * caller should print fields from the structure.  However, if 'flags' contains
+ * PF_FAILED, the structure will be printed as a pointer, no copy will be made,
+ * and the call will return FALSE.  Similarly, if the remote copy fails, a
+ * pointer will be printed and the call will return FALSE.  If PF_LOCADDR is
+ * given, 'addr' is a local address, and an intraprocess copy will be made.
+ */
+int
+put_open_struct(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr, void * ptr, size_t size)
+{
+
+       if ((flags & PF_FAILED) || valuesonly > 1 || addr == 0) {
+               if (flags & PF_LOCADDR)
+                       put_field(proc, name, "&..");
+               else
+                       put_ptr(proc, name, addr);
+
+               return FALSE;
+       }
+
+       if (!(flags & PF_LOCADDR)) {
+               if (mem_get_data(proc->pid, addr, ptr, size) < 0) {
+                       put_ptr(proc, name, addr);
+
+                       return FALSE;
+               }
+       } else
+               memcpy(ptr, (void *) addr, size);
+
+       put_open(proc, name, flags, "{", ", ");
+
+       return TRUE;
+}
+
+/*
+ * End printing a structure.  This must be called only to match a successful
+ * call to put_open_struct.  The given 'all' flag indicates whether all fields
+ * of the structure have been printed; if not, a ".." continuation text is
+ * printed to show the user that some structure fields have not been printed.
+ */
+void
+put_close_struct(struct trace_proc * proc, int all)
+{
+
+       if (!all)
+               put_field(proc, NULL, "..");
+
+       put_close(proc, "}");
+}
+
+/*
+ * Print a pointer.  NULL is treated as a special case.
+ */
+void
+put_ptr(struct trace_proc * proc, const char * name, vir_bytes addr)
+{
+
+       if (addr == 0 && !valuesonly)
+               put_field(proc, name, "NULL");
+       else
+               put_value(proc, name, "&0x%lx", addr);
+}
+
+/*
+ * Print the contents of a buffer, at remote address 'addr' and of 'bytes'
+ * size, as a field using name 'name' (which may be NULL).  If the PF_FAILED
+ * flag is given, the buffer address is printed instead, since it is assumed
+ * that the actual buffer contains garbage.  If the PF_LOCADDR flag is given,
+ * the given address is a local address and no intraprocess copies are
+ * performed.  If the PF_STRING flag is given, the buffer is expected to
+ * contain a null terminator within its size, and the string will be printed
+ * only up to there.  Normally, the string is cut off beyond a number of bytes
+ * which depends on the verbosity level; if the PF_FULL flag is given, the full
+ * string will be printed no matter its size (used mainly for path names, which
+ * typically become useless once cut off).
+ */
+void
+put_buf(struct trace_proc * proc, const char * name, int flags, vir_bytes addr,
+       ssize_t size)
+{
+       const char *escaped;
+       size_t len, off, max, chunk;
+       int i, cutoff;
+       char *p;
+
+       if ((flags & PF_FAILED) || valuesonly || addr == 0 || size < 0) {
+               if (flags & PF_LOCADDR)
+                       put_field(proc, name, "&..");
+               else
+                       put_ptr(proc, name, addr);
+
+               return;
+       }
+
+       if (size == 0) {
+               put_field(proc, name, "\"\"");
+
+               return;
+       }
+
+       /*
+        * TODO: the maximum says nothing about the size of the printed text.
+        * Escaped-character printing can make the output much longer.  Does it
+        * make more sense to apply a limit after the escape transformation?
+        */
+       if (verbose == 0) max = 32;
+       else if (verbose == 1) max = 256;
+       else max = SIZE_MAX;
+
+       /*
+        * If the output is cut off, we put two dots after the closing quote.
+        * For non-string buffers, the output is cut off if the size exceeds
+        * our limit or we run into a copying error somewhere in the middle.
+        * For strings, the output is cut off unless we find a null terminator.
+        */
+       cutoff = !!(flags & PF_STRING);
+       len = (size_t)size;
+       if (!(flags & PF_FULL) && len > max) {
+               len = max;
+               cutoff = TRUE;
+       }
+
+       for (off = 0; off < len; off += chunk) {
+               chunk = len - off;
+               if (chunk > sizeof(formatbuf) - 1)
+                       chunk = sizeof(formatbuf) - 1;
+
+               if (!(flags & PF_LOCADDR)) {
+                       if (mem_get_data(proc->pid, addr + off, formatbuf,
+                           chunk) < 0) {
+                               if (off == 0) {
+                                       put_ptr(proc, name, addr);
+
+                                       return;
+                               }
+
+                               cutoff = TRUE;
+                               break;
+                       }
+               } else
+                       memcpy(formatbuf, (void *)addr, chunk);
+
+               if (off == 0)
+                       put_field(proc, name, "\"");
+
+               /* In strings, look for the terminating null character. */
+               if ((flags & PF_STRING) &&
+                   (p = memchr(formatbuf, '\0', chunk)) != NULL) {
+                       chunk = (size_t)(p - formatbuf);
+                       cutoff = FALSE;
+               }
+
+               /* Print the buffer contents using escaped characters. */
+               for (i = 0; i < chunk; i++) {
+                       escaped = get_escape(formatbuf[i]);
+
+                       put_text(proc, escaped);
+               }
+
+               /* Stop if we found the end of the string. */
+               if ((flags & PF_STRING) && !cutoff)
+                       break;
+       }
+
+       if (cutoff)
+               put_text(proc, "\"..");
+       else
+               put_text(proc, "\"");
+}
+
+/*
+ * Print a flags field, using known flag names.  The name of the whole field is
+ * given as 'name' and may be NULL.  The caller must supply an array of known
+ * flags as 'fp' (with 'num' entries).  Each entry in the array has a mask, a
+ * value, and a name.  If the given flags 'value', bitwise-ANDed with the mask
+ * of an entry, yields the value of that entry, then the name is printed.  This
+ * means that certain zero bits may also be printed as actual flags, and that
+ * by supplying an all-bits-set mask can print a flag name for a zero value,
+ * for example F_OK for access().  See the FLAG macros and their usage for
+ * examples.  All matching flag names are printed with a "|" separator, and if
+ * after evaluating all 'num' entries in 'fp' there are still bits in 'value'
+ * for which nothing has been printed, the remaining bits will be printed with
+ * the 'fmt' format string for an integer (generally "%d" should be used).
+ */
+void
+put_flags(struct trace_proc * proc, const char * name, const struct flags * fp,
+       unsigned int num, const char * fmt, unsigned int value)
+{
+       unsigned int left;
+       int first;
+
+       if (valuesonly) {
+               put_value(proc, name, fmt, value);
+
+               return;
+       }
+
+       put_field(proc, name, "");
+
+       for (first = TRUE, left = value; num > 0; fp++, num--) {
+               if ((value & fp->mask) == fp->value) {
+                       if (first)
+                               first = FALSE;
+                       else
+                               put_text(proc, "|");
+                       put_text(proc, fp->name);
+
+                       left -= fp->value;
+               }
+       }
+
+       if (left != 0) {
+               if (first)
+                       first = FALSE;
+               else
+                       put_text(proc, "|");
+
+               put_fmt(proc, fmt, left);
+       }
+
+       /*
+        * If nothing has been printed so far, simply print a zero.  Ignoring
+        * the given format in this case is intentional: a simple 0 looks
+        * better than 0x0 or 00 etc.
+        */
+       if (first)
+               put_text(proc, "0");
+}
+
+/*
+ * Print a tail field at the end of an array.  The given 'count' value is the
+ * total number of elements in the array, or 0 to indicate that an error
+ * occurred.  The given 'printed' value is the number of fields printed so far.
+ * If some fields have been printed already, the number of fields not printed
+ * will be shown as "..(+N)".  If no fields have been printed already, the
+ * (total) number of fields not printed will be shown as "..(N)".  An error
+ * will print "..(?)".
+ *
+ * The rules for printing an array are as follows.  In principle, arrays should
+ * be enclosed in "[]".  However, if a copy error occurs immediately, a pointer
+ * to the array should be printed instead.  An empty array should be printed as
+ * "[]" (not "[..(0)]").  If a copy error occurs in the middle of the array,
+ * put_tail should be used with count == 0.  Only if not all fields in the
+ * array are printed, put_tail should be used with count > 0.  The value of
+ * 'printed' is typically the result of an arbitrary limit set based on the
+ * verbosity level.
+ */
+void
+put_tail(struct trace_proc * proc, unsigned int count, unsigned int printed)
+{
+
+       if (count == 0)
+               put_field(proc, NULL, "..(?)");
+       else
+               put_value(proc, NULL, "..(%s%u)",
+                   (printed > 0) ? "+" : "", count - printed);
+}
diff --git a/minix/usr.bin/trace/inc.h b/minix/usr.bin/trace/inc.h
new file mode 100644 (file)
index 0000000..2c84069
--- /dev/null
@@ -0,0 +1,22 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/ptrace.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <minix/config.h>
+#include <minix/const.h>
+#include <minix/type.h>
+#include <minix/ipc.h>
+#include <minix/com.h>
+#include <minix/callnr.h>
+#include <minix/endpoint.h>
+#include <machine/stackframe.h>
+
+#include "proc.h"
+#include "type.h"
+#include "proto.h"
diff --git a/minix/usr.bin/trace/ioctl.c b/minix/usr.bin/trace/ioctl.c
new file mode 100644 (file)
index 0000000..ed8931b
--- /dev/null
@@ -0,0 +1,226 @@
+
+#include "inc.h"
+
+#include <sys/ioctl.h>
+
+static char ioctlbuf[IOCPARM_MASK];
+
+static const struct {
+       const char *(*name)(unsigned long);
+       int (*arg)(struct trace_proc *, unsigned long, void *, int);
+       int is_svrctl;
+} ioctl_table[] = {
+       { block_ioctl_name,     block_ioctl_arg,        FALSE   },
+       { char_ioctl_name,      char_ioctl_arg,         FALSE   },
+       { net_ioctl_name,       net_ioctl_arg,          FALSE   },
+       { svrctl_name,          svrctl_arg,             TRUE    },
+};
+
+/*
+ * Print an IOCTL request code, and save certain values in the corresponding
+ * process structure in order to be able to print the IOCTL argument.
+ */
+void
+put_ioctl_req(struct trace_proc * proc, const char * name, unsigned long req,
+       int is_svrctl)
+{
+       const char *text;
+       size_t size;
+       unsigned int group, cmd;
+       int i, r, w, big;
+
+       proc->ioctl_index = -1;
+
+       if (valuesonly > 1) {
+               put_value(proc, name, "0x%lx", req);
+
+               return;
+       }
+
+       /*
+        * Lookups are bruteforce across the IOCTL submodules; they're all
+        * checked.  We could use the group letter but that would create more
+        * issues than it solves.  Our hope is that at least the compiler is
+        * smart about looking up particular codes in each switch statement,
+        * although in the worst case, it's a full O(n) lookup.
+        */
+       for (i = 0; !valuesonly && i < COUNT(ioctl_table); i++) {
+               /* IOCTLs and SVRCTLs are considered different name spaces. */
+               if (ioctl_table[i].is_svrctl != is_svrctl)
+                       continue;
+
+               if ((text = ioctl_table[i].name(req)) != NULL) {
+                       put_field(proc, name, text);
+
+                       proc->ioctl_index = i;
+
+                       return;
+               }
+       }
+
+       r = _MINIX_IOCTL_IOR(req);
+       w = _MINIX_IOCTL_IOW(req);
+       big = _MINIX_IOCTL_BIG(req);
+       size = (size_t)(big ? _MINIX_IOCTL_SIZE_BIG(req) : IOCPARM_LEN(req));
+       group = big ? 0 : IOCGROUP(req);
+       cmd = req & 0xff; /* shockingly there is no macro for this.. */
+
+       /*
+        * Not sure why an entire bit is wasted on IOC_VOID (legacy reasons?),
+        * but since the redundancy is there, we might as well check whether
+        * this is a valid IOCTL request.  Also, we expect the group to be a
+        * printable character.  If either check fails, print just a number.
+        */
+       if (((req & IOC_VOID) && (r || w || big || size > 0)) ||
+           (!(req & IOC_VOID) && ((!r && !w) || size == 0)) ||
+           (!big && (group < 32 || group > 127))) {
+               put_value(proc, name, "0x%lx", req);
+
+               return;
+       }
+
+       if (big) {
+               /* For big IOCTLs, "R" becomes before "W" (old MINIX style). */
+               put_value(proc, name, "_IO%s%s_BIG(%u,%zu)",
+                   r ? "R" : "", w ? "W" : "", cmd, size);
+       } else if (IOCGROUP(req) >= 32 && IOCGROUP(req) < 127) {
+               /* For normal IOCTLs, "W" comes before "R" (NetBSD style). */
+               put_value(proc, name, "_IO%s%s('%c',%u,%zu)",
+                   w ? "W" : "", r ? "R" : "", group, cmd, size);
+       }
+}
+
+/*
+ * Print the supplied (out) part of an IOCTL argument, as applicable.  For
+ * efficiency reasons, this function assumes that put_ioctl_req() has been
+ * called for the corresponding IOCTL already, so that the necessary fields in
+ * the given proc structure are set as expected.
+ */
+int
+put_ioctl_arg_out(struct trace_proc * proc, const char * name,
+       unsigned long req, vir_bytes addr, int is_svrctl)
+{
+       size_t size;
+       int dir, all;
+
+       dir = (_MINIX_IOCTL_IOW(req) ? IF_OUT : 0) |
+           (_MINIX_IOCTL_IOR(req) ? IF_IN : 0);
+
+       if (dir == 0)
+               proc->ioctl_index = -1; /* no argument to print at all */
+
+       /* No support for printing big-IOCTL contents just yet. */
+       if (valuesonly > 1 || _MINIX_IOCTL_BIG(req) ||
+           proc->ioctl_index == -1) {
+               put_ptr(proc, name, addr);
+
+               return CT_DONE;
+       }
+
+       assert(proc->ioctl_index >= 0);
+       assert(proc->ioctl_index < COUNT(ioctl_table));
+       assert(ioctl_table[proc->ioctl_index].is_svrctl == is_svrctl);
+
+       proc->ioctl_flags =
+           ioctl_table[proc->ioctl_index].arg(proc, req, NULL, dir);
+
+       if (proc->ioctl_flags == 0) { /* no argument printing for this IOCTL */
+               put_ptr(proc, name, addr);
+
+               proc->ioctl_index = -1; /* forget about the IOCTL handler */
+
+               return CT_DONE;
+       }
+
+       /*
+        * If this triggers, the IOCTL handler returns a direction that is not
+        * part of the actual IOCTL, and the handler should be fixed.
+        */
+       if (proc->ioctl_flags & ~dir) {
+               output_flush(); /* show the IOCTL name for debugging */
+
+               assert(0);
+       }
+
+       if (!(proc->ioctl_flags & IF_OUT))
+               return CT_NOTDONE;
+
+       size = IOCPARM_LEN(req);
+
+       if (size > sizeof(ioctlbuf) ||
+           mem_get_data(proc->pid, addr, ioctlbuf, size) < 0) {
+               put_ptr(proc, name, addr);
+
+               /* There's no harm in trying the _in side later anyhow.. */
+               return CT_DONE;
+       }
+
+       put_open(proc, name, 0, "{", ", ");
+
+       all = ioctl_table[proc->ioctl_index].arg(proc, req, ioctlbuf, IF_OUT);
+
+       if (!all)
+               put_field(proc, NULL, "..");
+
+       put_close(proc, "}");
+
+       return CT_DONE;
+}
+
+/*
+ * Print the returned (in) part of an IOCTL argument, as applicable.  This
+ * function assumes that it is preceded by a call to put_ioctl_arg_out for this
+ * process.
+ */
+void
+put_ioctl_arg_in(struct trace_proc * proc, const char * name, int failed,
+       unsigned long req, vir_bytes addr, int is_svrctl)
+{
+       size_t size;
+       int all;
+
+       if (valuesonly > 1 || _MINIX_IOCTL_BIG(req) ||
+           proc->ioctl_index == -1) {
+               put_result(proc);
+
+               return;
+       }
+
+       assert(proc->ioctl_index >= 0);
+       assert(proc->ioctl_index < COUNT(ioctl_table));
+       assert(ioctl_table[proc->ioctl_index].is_svrctl == is_svrctl);
+       assert(proc->ioctl_flags != 0);
+
+       if (proc->ioctl_flags & IF_OUT)
+               put_result(proc);
+       if (!(proc->ioctl_flags & IF_IN))
+               return;
+
+       size = IOCPARM_LEN(req);
+
+       if (failed || size > sizeof(ioctlbuf) ||
+           mem_get_data(proc->pid, addr, ioctlbuf, size) < 0) {
+               if (!(proc->ioctl_flags & IF_OUT)) {
+                       put_ptr(proc, name, addr);
+                       put_equals(proc);
+                       put_result(proc);
+               } else if (!failed)
+                       put_field(proc, NULL, "{..}");
+
+               return;
+       }
+
+       put_open(proc, name, 0, "{", ", ");
+
+       all = ioctl_table[proc->ioctl_index].arg(proc, req, ioctlbuf, IF_IN);
+
+       if (!all)
+               put_field(proc, NULL, "..");
+
+       put_close(proc, "}");
+
+       if (!(proc->ioctl_flags & IF_OUT)) {
+               put_equals(proc);
+               put_result(proc);
+       }
+}
diff --git a/minix/usr.bin/trace/ioctl/block.c b/minix/usr.bin/trace/ioctl/block.c
new file mode 100644 (file)
index 0000000..bca7d32
--- /dev/null
@@ -0,0 +1,229 @@
+
+#include "inc.h"
+
+#include <sys/ioctl.h>
+#include <minix/partition.h>
+#include <sys/vm.h>
+#include <sys/mtio.h>
+
+const char *
+block_ioctl_name(unsigned long req)
+{
+
+       switch (req) {
+       NAME(BIOCTRACEBUF);
+       NAME(BIOCTRACECTL);
+       NAME(BIOCTRACEGET);     /* big IOCTL, not printing argument */
+       NAME(DIOCSETP);
+       NAME(DIOCGETP);
+       NAME(DIOCEJECT);        /* no argument */
+       NAME(DIOCTIMEOUT);
+       NAME(DIOCOPENCT);
+       NAME(DIOCFLUSH);        /* no argument */
+       NAME(DIOCGETWC);
+       NAME(DIOCSETWC);
+       NAME(FBDCADDRULE);
+       NAME(FBDCDELRULE);
+       NAME(FBDCGETRULE);
+       NAME(MIOCRAMSIZE);
+       NAME(MTIOCGET);         /* TODO: print argument */
+       NAME(MTIOCTOP);         /* TODO: print argument */
+       NAME(VNDIOCCLR);
+       NAME(VNDIOCGET);
+       NAME(VNDIOCSET);
+       }
+
+       return NULL;
+}
+
+static const struct flags fbd_flags[] = {
+       FLAG(FBD_FLAG_READ),
+       FLAG(FBD_FLAG_WRITE),
+};
+
+static void
+put_fbd_action(struct trace_proc * proc, const char * name, int action)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (action) {
+               TEXT(FBD_ACTION_CORRUPT);
+               TEXT(FBD_ACTION_ERROR);
+               TEXT(FBD_ACTION_MISDIR);
+               TEXT(FBD_ACTION_LOSTTORN);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", action);
+}
+
+static const struct flags vnd_flags[] = {
+       FLAG(VNDIOF_HASGEOM),
+       FLAG(VNDIOF_READONLY),
+       FLAG(VNDIOF_FORCE),
+};
+
+int
+block_ioctl_arg(struct trace_proc * proc, unsigned long req, void * ptr,
+       int dir)
+{
+       struct part_geom *part;
+       struct fbd_rule *rule;
+       struct vnd_ioctl *vnd;
+       struct vnd_user *vnu;
+       int i;
+
+       switch (req) {
+       case BIOCTRACEBUF:
+               if (ptr == NULL)
+                       return IF_OUT;
+
+               put_value(proc, NULL, "%zu", *(size_t *)ptr);
+               return IF_ALL;
+
+       case BIOCTRACECTL:
+               if (ptr == NULL)
+                       return IF_OUT;
+
+               i = *(int *)ptr;
+               if (!valuesonly && i == BTCTL_START)
+                       put_field(proc, NULL, "BTCTL_START");
+               else if (!valuesonly && i == BTCTL_STOP)
+                       put_field(proc, NULL, "BTCTL_STOP");
+               else
+                       put_value(proc, NULL, "%d", i);
+               return IF_ALL;
+
+       case DIOCSETP:
+               if ((part = (struct part_geom *)ptr) == NULL)
+                       return IF_OUT;
+
+               put_value(proc, "base", "%"PRIu64, part->base);
+               put_value(proc, "size", "%"PRIu64, part->size);
+               return IF_ALL;
+
+       case DIOCGETP:
+               if ((part = (struct part_geom *)ptr) == NULL)
+                       return IF_IN;
+
+               put_value(proc, "base", "%"PRIu64, part->base);
+               put_value(proc, "size", "%"PRIu64, part->size);
+               if (verbose > 0) {
+                       put_value(proc, "cylinders", "%u", part->cylinders);
+                       put_value(proc, "heads", "%u", part->heads);
+                       put_value(proc, "sectors", "%u", part->sectors);
+                       return IF_ALL;
+               } else
+                       return 0;
+
+       case DIOCTIMEOUT:
+               /* Print the old timeout only if verbosity is high enough. */
+               if (ptr == NULL)
+                       return IF_OUT | ((verbose > 0) ? IF_IN : 0);
+
+               /* Same action for out and in. */
+               put_value(proc, NULL, "%d", *(int *)ptr);
+               return IF_ALL;
+
+       case DIOCOPENCT:
+               if (ptr == NULL)
+                       return IF_IN;
+
+               put_value(proc, NULL, "%d", *(int *)ptr);
+               return IF_ALL;
+
+       case DIOCSETWC:
+       case DIOCGETWC:
+               if (ptr == NULL)
+                       return dir; /* out or in, depending on the request */
+
+               put_value(proc, NULL, "%d", *(int *)ptr);
+               return IF_ALL;
+
+       case FBDCDELRULE:
+               if (ptr == NULL)
+                       return IF_OUT;
+
+               put_value(proc, NULL, "%d", *(fbd_rulenum_t *)ptr);
+               return IF_ALL;
+
+       case FBDCGETRULE:
+               if ((rule = (struct fbd_rule *)ptr) == NULL)
+                       return IF_OUT | IF_IN;
+
+               if (dir == IF_OUT) {
+                       put_value(proc, "num", "%d", rule->num);
+                       return IF_ALL;
+               }
+
+               /*
+                * The returned result is the same as what is passed to the
+                * add request, so we can use the same code to print both.
+                */
+               /* FALLTHROUGH */
+       case FBDCADDRULE:
+               if ((rule = (struct fbd_rule *)ptr) == NULL)
+                       return IF_OUT;
+
+               if (rule->start != 0 || rule->end != 0 || verbose > 0) {
+                       put_value(proc, "start", "%"PRIu64, rule->start);
+                       put_value(proc, "end", "%"PRIu64, rule->end);
+               }
+               if (rule->flags != (FBD_FLAG_READ | FBD_FLAG_WRITE) ||
+                   verbose > 0)
+                       put_flags(proc, "flags", fbd_flags, COUNT(fbd_flags),
+                           "0x%x", rule->flags);
+               if (rule->skip != 0 || verbose > 0)
+                       put_value(proc, "skip", "%u", rule->skip);
+               if (rule->count != 0 || verbose > 0)
+                       put_value(proc, "count", "%u", rule->count);
+               put_fbd_action(proc, "action", rule->action);
+
+               return 0; /* TODO: optionally print the union fields */
+
+       case MIOCRAMSIZE:
+               if (ptr == NULL)
+                       return IF_OUT;
+
+               put_value(proc, NULL, "%"PRIu32, *(u32_t *)ptr);
+               return IF_ALL;
+
+       case VNDIOCSET:
+               if ((vnd = (struct vnd_ioctl *)ptr) == NULL)
+                       return IF_OUT | IF_IN;
+
+               if (dir == IF_OUT) {
+                       put_value(proc, "vnd_fildes", "%d", vnd->vnd_fildes);
+                       put_flags(proc, "vnd_flags", vnd_flags,
+                           COUNT(vnd_flags), "0x%x", vnd->vnd_flags);
+                       return 0; /* TODO: print geometry if given */
+               } else {
+                       put_value(proc, "vnd_size", "%"PRIu64, vnd->vnd_size);
+                       return IF_ALL;
+               }
+
+       case VNDIOCCLR:
+               if ((vnd = (struct vnd_ioctl *)ptr) == NULL)
+                       return IF_OUT;
+
+               put_flags(proc, "vnd_flags", vnd_flags, COUNT(vnd_flags),
+                   "0x%x", vnd->vnd_flags);
+               return IF_ALL;
+
+       case VNDIOCGET:
+               if ((vnu = (struct vnd_user *)ptr) == NULL)
+                       return IF_IN;
+
+               put_value(proc, "vnu_unit", "%d", vnu->vnu_unit);
+               put_dev(proc, "vnu_dev", vnu->vnu_dev);
+               put_value(proc, "vnu_ino", "%"PRId64, vnu->vnu_ino);
+               return IF_ALL;
+
+       default:
+               return 0;
+       }
+}
diff --git a/minix/usr.bin/trace/ioctl/char.c b/minix/usr.bin/trace/ioctl/char.c
new file mode 100644 (file)
index 0000000..5d94042
--- /dev/null
@@ -0,0 +1,509 @@
+
+#include "inc.h"
+
+#include <sys/ioctl.h>
+#include <minix/i2c.h>
+#include <minix/fb.h>
+#include <minix/sound.h>
+#include <sys/termios.h>
+#include <sys/time.h>
+#include <sys/kbdio.h>
+#include <minix/keymap.h>
+#include <sys/vm.h>
+#include <sys/fcntl.h>
+
+const char *
+char_ioctl_name(unsigned long req)
+{
+
+       switch (req) {
+       NAME(MINIX_I2C_IOCTL_EXEC);
+       NAME(FBIOGET_VSCREENINFO);
+       NAME(FBIOPUT_VSCREENINFO);
+       NAME(FBIOGET_FSCREENINFO);      /* TODO: print argument */
+       NAME(FBIOPAN_DISPLAY);
+       NAME(DSPIORATE);
+       NAME(DSPIOSTEREO);
+       NAME(DSPIOSIZE);
+       NAME(DSPIOBITS);
+       NAME(DSPIOSIGN);
+       NAME(DSPIOMAX);
+       NAME(DSPIORESET);               /* no argument */
+       NAME(DSPIOFREEBUF);
+       NAME(DSPIOSAMPLESINBUF);
+       NAME(DSPIOPAUSE);               /* no argument */
+       NAME(DSPIORESUME);              /* no argument */
+       NAME(MIXIOGETVOLUME);
+       NAME(MIXIOGETINPUTLEFT);
+       NAME(MIXIOGETINPUTRIGHT);
+       NAME(MIXIOGETOUTPUT);
+       NAME(MIXIOSETVOLUME);
+       NAME(MIXIOSETINPUTLEFT);
+       NAME(MIXIOSETINPUTRIGHT);
+       NAME(MIXIOSETOUTPUT);
+       NAME(TIOCEXCL);                 /* no argument */
+       NAME(TIOCNXCL);                 /* no argument */
+       NAME(TIOCFLUSH);
+       NAME(TIOCGETA);
+       NAME(TIOCSETA);
+       NAME(TIOCSETAW);
+       NAME(TIOCSETAF);
+       NAME(TIOCGETD);
+       NAME(TIOCSETD);
+       NAME(TIOCGLINED);
+       NAME(TIOCSLINED);
+       NAME(TIOCSBRK);                 /* no argument */
+       NAME(TIOCCBRK);                 /* no argument */
+       NAME(TIOCSDTR);                 /* no argument */
+       NAME(TIOCCDTR);                 /* no argument */
+       NAME(TIOCGPGRP);
+       NAME(TIOCSPGRP);
+       NAME(TIOCOUTQ);
+       NAME(TIOCSTI);
+       NAME(TIOCNOTTY);                /* no argument */
+       NAME(TIOCPKT);
+       NAME(TIOCSTOP);                 /* no argument */
+       NAME(TIOCSTART);                /* no argument */
+       NAME(TIOCMSET);                 /* TODO: print argument */
+       NAME(TIOCMBIS);                 /* TODO: print argument */
+       NAME(TIOCMBIC);                 /* TODO: print argument */
+       NAME(TIOCMGET);                 /* TODO: print argument */
+       NAME(TIOCREMOTE);
+       NAME(TIOCGWINSZ);
+       NAME(TIOCSWINSZ);
+       NAME(TIOCUCNTL);
+       NAME(TIOCSTAT);
+       NAME(TIOCGSID);
+       NAME(TIOCCONS);
+       NAME(TIOCSCTTY);                /* no argument */
+       NAME(TIOCEXT);
+       NAME(TIOCSIG);                  /* no argument */
+       NAME(TIOCDRAIN);                /* no argument */
+       NAME(TIOCGFLAGS);               /* TODO: print argument */
+       NAME(TIOCSFLAGS);               /* TODO: print argument */
+       NAME(TIOCDCDTIMESTAMP);         /* TODO: print argument */
+       NAME(TIOCRCVFRAME);             /* TODO: print argument */
+       NAME(TIOCXMTFRAME);             /* TODO: print argument */
+       NAME(TIOCPTMGET);               /* TODO: print argument */
+       NAME(TIOCGRANTPT);              /* no argument */
+       NAME(TIOCPTSNAME);              /* TODO: print argument */
+       NAME(TIOCSQSIZE);
+       NAME(TIOCGQSIZE);
+       NAME(TIOCSFON);                 /* big IOCTL, not printing argument */
+       NAME(KIOCBELL);
+       NAME(KIOCSLEDS);
+       NAME(KIOCSMAP);                 /* not worth interpreting */
+       NAME(TIOCMAPMEM);
+       NAME(TIOCUNMAPMEM);
+       }
+
+       return NULL;
+}
+
+static void
+put_i2c_op(struct trace_proc * proc, const char *name, i2c_op_t op)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (op) {
+               TEXT(I2C_OP_READ);
+               TEXT(I2C_OP_READ_WITH_STOP);
+               TEXT(I2C_OP_WRITE);
+               TEXT(I2C_OP_WRITE_WITH_STOP);
+               TEXT(I2C_OP_READ_BLOCK);
+               TEXT(I2C_OP_WRITE_BLOCK);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", op);
+}
+
+static void
+put_sound_device(struct trace_proc * proc, const char * name, int device)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (device) {
+               TEXT(Master);
+               TEXT(Dac);
+               TEXT(Fm);
+               TEXT(Cd);
+               TEXT(Line);
+               TEXT(Mic);
+               TEXT(Speaker);
+               TEXT(Treble);
+               TEXT(Bass);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", device);
+}
+
+static void
+put_sound_state(struct trace_proc * proc, const char * name, int state)
+{
+
+       if (!valuesonly && state == ON)
+               put_field(proc, name, "ON");
+       else if (!valuesonly && state == OFF)
+               put_field(proc, name, "OFF");
+       else
+               put_value(proc, name, "%d", state);
+}
+
+static const struct flags flush_flags[] = {
+       FLAG(FREAD),
+       FLAG(FWRITE),
+};
+
+static const struct flags tc_iflags[] = {
+       FLAG(IGNBRK),
+       FLAG(BRKINT),
+       FLAG(IGNPAR),
+       FLAG(PARMRK),
+       FLAG(INPCK),
+       FLAG(ISTRIP),
+       FLAG(INLCR),
+       FLAG(IGNCR),
+       FLAG(ICRNL),
+       FLAG(IXON),
+       FLAG(IXOFF),
+       FLAG(IXANY),
+       FLAG(IMAXBEL),
+};
+
+static const struct flags tc_oflags[] = {
+       FLAG(OPOST),
+       FLAG(ONLCR),
+       FLAG(OXTABS),
+       FLAG(ONOEOT),
+       FLAG(OCRNL),
+       FLAG(ONOCR),
+       FLAG(ONLRET),
+};
+
+static const struct flags tc_cflags[] = {
+       FLAG(CIGNORE),
+       FLAG_MASK(CSIZE, CS5),
+       FLAG_MASK(CSIZE, CS6),
+       FLAG_MASK(CSIZE, CS7),
+       FLAG_MASK(CSIZE, CS8),
+       FLAG(CSTOPB),
+       FLAG(CREAD),
+       FLAG(PARENB),
+       FLAG(PARODD),
+       FLAG(HUPCL),
+       FLAG(CLOCAL),
+       FLAG(CRTSCTS),
+       FLAG(CDTRCTS),
+       FLAG(MDMBUF),
+};
+
+static const struct flags tc_lflags[] = {
+       FLAG(ECHOKE),
+       FLAG(ECHOE),
+       FLAG(ECHOK),
+       FLAG(ECHO),
+       FLAG(ECHONL),
+       FLAG(ECHOPRT),
+       FLAG(ECHOCTL),
+       FLAG(ISIG),
+       FLAG(ICANON),
+       FLAG(ALTWERASE),
+       FLAG(IEXTEN),
+       FLAG(EXTPROC),
+       FLAG(TOSTOP),
+       FLAG(FLUSHO),
+       FLAG(NOKERNINFO),
+       FLAG(PENDIN),
+       FLAG(NOFLSH),
+};
+
+static void
+put_tty_disc(struct trace_proc * proc, const char * name, int disc)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (disc) {
+               TEXT(TTYDISC);
+               TEXT(TABLDISC);
+               TEXT(SLIPDISC);
+               TEXT(PPPDISC);
+               TEXT(STRIPDISC);
+               TEXT(HDLCDISC);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", disc);
+}
+
+static const struct flags kbd_leds[] = {
+       FLAG(KBD_LEDS_NUM),
+       FLAG(KBD_LEDS_CAPS),
+       FLAG(KBD_LEDS_SCROLL),
+};
+
+int
+char_ioctl_arg(struct trace_proc * proc, unsigned long req, void * ptr,
+       int dir)
+{
+       minix_i2c_ioctl_exec_t *iie;
+       struct fb_var_screeninfo *fbvs;
+       struct volume_level *level;
+       struct inout_ctrl *inout;
+       struct termios *tc;
+       struct winsize *ws;
+       struct kio_bell *bell;
+       struct kio_leds *leds;
+       struct mapreqvm *mapreq;
+
+       switch (req) {
+       case MINIX_I2C_IOCTL_EXEC:
+               if ((iie = (minix_i2c_ioctl_exec_t *)ptr) == NULL)
+                       return IF_OUT; /* we print only the request for now */
+
+               put_i2c_op(proc, "iie_op", iie->iie_op);
+               put_value(proc, "iie_addr", "0x%04x", iie->iie_addr);
+               return 0; /* TODO: print command/data/result */
+
+       case FBIOGET_VSCREENINFO:
+               if ((fbvs = (struct fb_var_screeninfo *)ptr) == NULL)
+                       return IF_IN;
+
+               put_value(proc, "xres", "%"PRIu32, fbvs->xres);
+               put_value(proc, "yres", "%"PRIu32, fbvs->yres);
+               put_value(proc, "xres_virtual", "%"PRIu32, fbvs->xres_virtual);
+               put_value(proc, "yres_virtual", "%"PRIu32, fbvs->yres_virtual);
+               put_value(proc, "xoffset", "%"PRIu32, fbvs->xoffset);
+               put_value(proc, "yoffset", "%"PRIu32, fbvs->yoffset);
+               put_value(proc, "bits_per_pixel", "%"PRIu32,
+                   fbvs->bits_per_pixel);
+               return 0;
+
+       case FBIOPUT_VSCREENINFO:
+       case FBIOPAN_DISPLAY:
+               if ((fbvs = (struct fb_var_screeninfo *)ptr) == NULL)
+                       return IF_OUT;
+
+               put_value(proc, "xoffset", "%"PRIu32, fbvs->xoffset);
+               put_value(proc, "yoffset", "%"PRIu32, fbvs->yoffset);
+               return 0;
+
+       case DSPIORATE:
+       case DSPIOSTEREO:
+       case DSPIOSIZE:
+       case DSPIOBITS:
+       case DSPIOSIGN:
+       case DSPIOMAX:
+       case DSPIOFREEBUF:
+       case DSPIOSAMPLESINBUF:
+               if (ptr == NULL)
+                       return dir;
+
+               put_value(proc, NULL, "%u", *(unsigned int *)ptr);
+               return IF_ALL;
+
+       case MIXIOGETVOLUME:
+               if ((level = (struct volume_level *)ptr) == NULL)
+                       return dir;
+
+               if (dir == IF_OUT)
+                       put_sound_device(proc, "device", level->device);
+               else {
+                       put_value(proc, "left", "%d", level->left);
+                       put_value(proc, "right", "%d", level->right);
+               }
+               return IF_ALL;
+
+       case MIXIOSETVOLUME:
+               /* Print the corrected volume levels only with verbosity on. */
+               if ((level = (struct volume_level *)ptr) == NULL)
+                       return IF_OUT | ((verbose > 0) ? IF_IN : 0);
+
+               if (dir == IF_OUT)
+                       put_sound_device(proc, "device", level->device);
+               put_value(proc, "left", "%d", level->left);
+               put_value(proc, "right", "%d", level->right);
+               return IF_ALL;
+
+       case MIXIOGETINPUTLEFT:
+       case MIXIOGETINPUTRIGHT:
+       case MIXIOGETOUTPUT:
+               if ((inout = (struct inout_ctrl *)ptr) == NULL)
+                       return dir;
+
+               if (dir == IF_OUT)
+                       put_sound_device(proc, "device", inout->device);
+               else {
+                       put_sound_state(proc, "left", inout->left);
+                       put_sound_state(proc, "right", inout->right);
+               }
+               return IF_ALL;
+
+       case MIXIOSETINPUTLEFT:
+       case MIXIOSETINPUTRIGHT:
+       case MIXIOSETOUTPUT:
+               if ((inout = (struct inout_ctrl *)ptr) == NULL)
+                       return IF_OUT;
+
+               put_sound_device(proc, "device", inout->device);
+               put_sound_state(proc, "left", inout->left);
+               put_sound_state(proc, "right", inout->right);
+               return IF_ALL;
+
+       case TIOCFLUSH:
+               if (ptr == NULL)
+                       return IF_OUT;
+
+               put_flags(proc, NULL, flush_flags, COUNT(flush_flags), "0x%x",
+                   *(int *)ptr);
+               return IF_ALL;
+
+       case TIOCGETA:
+       case TIOCSETA:
+       case TIOCSETAW:
+       case TIOCSETAF:
+               if ((tc = (struct termios *)ptr) == NULL)
+                       return dir;
+
+               /*
+                * These are fairly common IOCTLs, so printing everything by
+                * default would create a lot of noise.  By default we limit
+                * ourselves to printing the field that contains what I
+                * consider to be the most important flag: ICANON.
+                * TODO: see if we can come up with a decent format for
+                * selectively printing (relatively important) flags.
+                */
+               if (verbose > 0) {
+                       put_flags(proc, "c_iflag", tc_iflags, COUNT(tc_iflags),
+                           "0x%x", tc->c_iflag);
+                       put_flags(proc, "c_oflag", tc_oflags, COUNT(tc_oflags),
+                           "0x%x", tc->c_oflag);
+                       put_flags(proc, "c_cflag", tc_cflags, COUNT(tc_cflags),
+                           "0x%x", tc->c_cflag);
+               }
+               put_flags(proc, "c_lflag", tc_lflags, COUNT(tc_lflags), "0x%x",
+                       tc->c_lflag);
+               if (verbose > 0) {
+                       put_value(proc, "c_ispeed", "%d", tc->c_ispeed);
+                       put_value(proc, "c_ospeed", "%d", tc->c_ospeed);
+               }
+               return 0; /* TODO: print the c_cc fields */
+
+       case TIOCGETD:
+       case TIOCSETD:
+               if (ptr == NULL)
+                       return dir;
+
+               put_tty_disc(proc, NULL, *(int *)ptr);
+               return IF_ALL;
+
+       case TIOCGLINED:
+       case TIOCSLINED:
+               if (ptr == NULL)
+                       return dir;
+
+               put_buf(proc, NULL, PF_LOCADDR | PF_STRING, (vir_bytes)ptr,
+                   sizeof(linedn_t));
+               return IF_ALL;
+
+       case TIOCGPGRP:
+       case TIOCSPGRP:
+       case TIOCOUTQ:
+       case TIOCPKT:
+       case TIOCREMOTE:
+       case TIOCUCNTL:
+       case TIOCSTAT:          /* argument seems unused? */
+       case TIOCGSID:
+       case TIOCCONS:          /* argument seems unused? */
+       case TIOCEXT:
+       case TIOCSQSIZE:
+       case TIOCGQSIZE:
+               /* Print a simple integer. */
+               if (ptr == NULL)
+                       return dir;
+
+               put_value(proc, NULL, "%d", *(int *)ptr);
+               return IF_ALL;
+
+       case TIOCSTI:
+               if (ptr == NULL)
+                       return dir;
+
+               if (!valuesonly)
+                       put_value(proc, NULL, "'%s'",
+                           get_escape(*(char *)ptr));
+               else
+                       put_value(proc, NULL, "%u", *(char *)ptr);
+               return IF_ALL;
+
+       case TIOCGWINSZ:
+       case TIOCSWINSZ:
+               if ((ws = (struct winsize *)ptr) == NULL)
+                       return dir;
+
+               /* This is a stupid order, but we follow the struct layout. */
+               put_value(proc, "ws_row", "%u", ws->ws_row);
+               put_value(proc, "ws_col", "%u", ws->ws_col);
+               if (verbose > 0) {
+                       put_value(proc, "ws_xpixel", "%u", ws->ws_xpixel);
+                       put_value(proc, "ws_ypixel", "%u", ws->ws_ypixel);
+               }
+               return (verbose > 0) ? IF_ALL : 0;
+
+       case KIOCBELL:
+               if ((bell = (struct kio_bell *)ptr) == NULL)
+                       return IF_OUT;
+
+               put_value(proc, "kb_pitch", "%u", bell->kb_pitch);
+               put_value(proc, "kb_volume", "%lu", bell->kb_volume);
+               put_struct_timeval(proc, "kb_duration", PF_LOCADDR,
+                   (vir_bytes)&bell->kb_duration);
+
+               return IF_ALL;
+
+       case KIOCSLEDS:
+               if ((leds = (struct kio_leds *)ptr) == NULL)
+                       return IF_OUT;
+
+               put_flags(proc, "kl_bits", kbd_leds, COUNT(kbd_leds), "0x%x",
+                   leds->kl_bits);
+               return IF_ALL;
+
+       case TIOCMAPMEM:
+               if ((mapreq = (struct mapreqvm *)ptr) == NULL)
+                       return dir;
+
+               /* This structure has more fields, but they're all unused.. */
+               if (dir == IF_OUT) {
+                       put_value(proc, "phys_offset", "%"PRIu64,
+                           (uint64_t)mapreq->phys_offset); /* future compat */
+                       put_value(proc, "size", "%zu", mapreq->size);
+               } else
+                       put_ptr(proc, "vaddr_ret", (vir_bytes)mapreq->vaddr);
+               return IF_ALL;
+
+       case TIOCUNMAPMEM:
+               if ((mapreq = (struct mapreqvm *)ptr) == NULL)
+                       return IF_OUT;
+
+               put_ptr(proc, "vaddr", (vir_bytes)mapreq->vaddr);
+               put_value(proc, "size", "%zu", mapreq->size);
+               return IF_ALL;
+
+       default:
+               return 0;
+       }
+}
diff --git a/minix/usr.bin/trace/ioctl/net.c b/minix/usr.bin/trace/ioctl/net.c
new file mode 100644 (file)
index 0000000..3a4f428
--- /dev/null
@@ -0,0 +1,565 @@
+
+#include "inc.h"
+
+#include <sys/ioctl.h>
+#include <sys/ucred.h>
+#include <net/gen/in.h>
+#include <net/gen/ether.h>
+#include <net/gen/eth_io.h>
+#include <net/gen/arp_io.h>
+#include <net/gen/ip_io.h>
+#include <net/gen/route.h>
+#include <net/gen/tcp.h>
+#include <net/gen/tcp_io.h>
+#include <net/gen/udp.h>
+#include <net/gen/udp_io.h>
+#include <net/gen/udp_io_hdr.h>
+#include <net/gen/psip_io.h>
+#include <arpa/inet.h>
+
+const char *
+net_ioctl_name(unsigned long req)
+{
+
+       switch (req) {
+       NAME(FIONREAD);
+       NAME(NWIOSETHOPT);      /* TODO: print argument */
+       NAME(NWIOGETHOPT);      /* TODO: print argument */
+       NAME(NWIOGETHSTAT);     /* TODO: print argument */
+       NAME(NWIOARPGIP);       /* TODO: print argument */
+       NAME(NWIOARPGNEXT);     /* TODO: print argument */
+       NAME(NWIOARPSIP);       /* TODO: print argument */
+       NAME(NWIOARPDIP);       /* TODO: print argument */
+       NAME(NWIOSIPCONF2);     /* TODO: print argument */
+       NAME(NWIOSIPCONF);      /* TODO: print argument */
+       NAME(NWIOGIPCONF2);     /* TODO: print argument */
+       NAME(NWIOGIPCONF);      /* TODO: print argument */
+       NAME(NWIOSIPOPT);
+       NAME(NWIOGIPOPT);
+       NAME(NWIOGIPOROUTE);    /* TODO: print argument */
+       NAME(NWIOSIPOROUTE);    /* TODO: print argument */
+       NAME(NWIODIPOROUTE);    /* TODO: print argument */
+       NAME(NWIOGIPIROUTE);    /* TODO: print argument */
+       NAME(NWIOSIPIROUTE);    /* TODO: print argument */
+       NAME(NWIODIPIROUTE);    /* TODO: print argument */
+       NAME(NWIOSTCPCONF);
+       NAME(NWIOGTCPCONF);
+       NAME(NWIOTCPCONN);
+       NAME(NWIOTCPLISTEN);
+       NAME(NWIOTCPATTACH);    /* TODO: print argument */
+       NAME(NWIOTCPSHUTDOWN);  /* no argument */
+       NAME(NWIOSTCPOPT);
+       NAME(NWIOGTCPOPT);
+       NAME(NWIOTCPPUSH);      /* no argument */
+       NAME(NWIOTCPLISTENQ);
+       NAME(NWIOGTCPCOOKIE);
+       NAME(NWIOTCPACCEPTTO);
+       NAME(NWIOTCPGERROR);
+       NAME(NWIOSUDPOPT);
+       NAME(NWIOGUDPOPT);
+       NAME(NWIOUDPPEEK);      /* TODO: print argument */
+       NAME(NWIOSPSIPOPT);     /* TODO: print argument */
+       NAME(NWIOGPSIPOPT);     /* TODO: print argument */
+       NAME(NWIOGUDSFADDR);
+       NAME(NWIOSUDSTADDR);
+       NAME(NWIOSUDSADDR);
+       NAME(NWIOGUDSADDR);
+       NAME(NWIOGUDSPADDR);
+       NAME(NWIOSUDSTYPE);
+       NAME(NWIOSUDSBLOG);
+       NAME(NWIOSUDSCONN);
+       NAME(NWIOSUDSSHUT);
+       NAME(NWIOSUDSPAIR);
+       NAME(NWIOSUDSACCEPT);
+       NAME(NWIOSUDSCTRL);
+       NAME(NWIOGUDSCTRL);
+       NAME(NWIOGUDSSOTYPE);
+       NAME(NWIOGUDSPEERCRED);
+       NAME(NWIOGUDSSNDBUF);
+       NAME(NWIOSUDSSNDBUF);
+       NAME(NWIOGUDSRCVBUF);
+       NAME(NWIOSUDSRCVBUF);
+       }
+
+       return NULL;
+}
+
+static const struct flags ipopt_flags[] = {
+       FLAG_ZERO(NWIO_NOFLAGS),
+       FLAG_MASK(NWIO_ACC_MASK, NWIO_EXCL),
+       FLAG_MASK(NWIO_ACC_MASK, NWIO_SHARED),
+       FLAG_MASK(NWIO_ACC_MASK, NWIO_COPY),
+       FLAG(NWIO_EN_LOC),
+       FLAG(NWIO_DI_LOC),
+       FLAG(NWIO_EN_BROAD),
+       FLAG(NWIO_DI_BROAD),
+       FLAG(NWIO_REMSPEC),
+       FLAG(NWIO_REMANY),
+       FLAG(NWIO_PROTOSPEC),
+       FLAG(NWIO_PROTOANY),
+       FLAG(NWIO_HDR_O_SPEC),
+       FLAG(NWIO_HDR_O_ANY),
+       FLAG(NWIO_RWDATONLY),
+       FLAG(NWIO_RWDATALL),
+};
+
+static void
+put_ipaddr(struct trace_proc * proc, const char * name, ipaddr_t ipaddr)
+{
+       struct in_addr in;
+
+       if (!valuesonly) {
+               in.s_addr = ipaddr;
+
+               /* Is this an acceptable encapsulation? */
+               put_value(proc, name, "[%s]", inet_ntoa(in));
+       } else
+               put_value(proc, name, "0x%08x", ntohl(ipaddr));
+}
+
+static void
+put_ipproto(struct trace_proc * proc, const char * name, ipproto_t proto)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (proto) {
+               TEXT(IPPROTO_ICMP);
+               TEXT(IPPROTO_TCP);
+               TEXT(IPPROTO_UDP);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%u", proto);
+}
+
+static const struct flags tcpconf_flags[] = {
+       FLAG_ZERO(NWTC_NOFLAGS),
+       FLAG_MASK(NWTC_ACC_MASK, NWTC_EXCL),
+       FLAG_MASK(NWTC_ACC_MASK, NWTC_SHARED),
+       FLAG_MASK(NWTC_ACC_MASK, NWTC_COPY),
+       FLAG_MASK(NWTC_LOCPORT_MASK, NWTC_LP_UNSET),
+       FLAG_MASK(NWTC_LOCPORT_MASK, NWTC_LP_SET),
+       FLAG_MASK(NWTC_LOCPORT_MASK, NWTC_LP_SEL),
+       FLAG(NWTC_SET_RA),
+       FLAG(NWTC_UNSET_RA),
+       FLAG(NWTC_SET_RP),
+       FLAG(NWTC_UNSET_RP),
+};
+
+#define put_port(proc, name, port) \
+       put_value(proc, name, "%u", ntohs(port))
+
+static const struct flags tcpcl_flags[] = {
+       FLAG_ZERO(TCF_DEFAULT),
+       FLAG(TCF_ASYNCH),
+};
+
+static const struct flags tcpopt_flags[] = {
+       FLAG_ZERO(NWTO_NOFLAG),
+       FLAG(NWTO_SND_URG),
+       FLAG(NWTO_SND_NOTURG),
+       FLAG(NWTO_RCV_URG),
+       FLAG(NWTO_RCV_NOTURG),
+       FLAG(NWTO_BSD_URG),
+       FLAG(NWTO_NOTBSD_URG),
+       FLAG(NWTO_DEL_RST),
+       FLAG(NWTO_BULK),
+       FLAG(NWTO_NOBULK),
+};
+
+static const struct flags udpopt_flags[] = {
+       FLAG_ZERO(NWUO_NOFLAGS),
+       FLAG_MASK(NWUO_ACC_MASK, NWUO_EXCL),
+       FLAG_MASK(NWUO_ACC_MASK, NWUO_SHARED),
+       FLAG_MASK(NWUO_ACC_MASK, NWUO_COPY),
+       FLAG_MASK(NWUO_LOCPORT_MASK, NWUO_LP_SET),
+       FLAG_MASK(NWUO_LOCPORT_MASK, NWUO_LP_SEL),
+       FLAG_MASK(NWUO_LOCPORT_MASK, NWUO_LP_ANY),
+       FLAG(NWUO_EN_LOC),
+       FLAG(NWUO_DI_LOC),
+       FLAG(NWUO_EN_BROAD),
+       FLAG(NWUO_DI_BROAD),
+       FLAG(NWUO_RP_SET),
+       FLAG(NWUO_RP_ANY),
+       FLAG(NWUO_RA_SET),
+       FLAG(NWUO_RA_ANY),
+       FLAG(NWUO_RWDATONLY),
+       FLAG(NWUO_RWDATALL),
+       FLAG(NWUO_EN_IPOPT),
+       FLAG(NWUO_DI_IPOPT),
+};
+
+static void
+put_family(struct trace_proc * proc, const char * name, int family)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               /* TODO: add all the other protocols */
+               switch (family) {
+               TEXT(AF_UNSPEC);
+               TEXT(AF_LOCAL);
+               TEXT(AF_INET);
+               TEXT(AF_INET6);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", family);
+}
+
+static const struct flags sock_type[] = {
+       FLAG_MASK(~SOCK_FLAGS_MASK, SOCK_STREAM),
+       FLAG_MASK(~SOCK_FLAGS_MASK, SOCK_DGRAM),
+       FLAG_MASK(~SOCK_FLAGS_MASK, SOCK_RAW),
+       FLAG_MASK(~SOCK_FLAGS_MASK, SOCK_RDM),
+       FLAG_MASK(~SOCK_FLAGS_MASK, SOCK_SEQPACKET),
+       FLAG(SOCK_CLOEXEC),
+       FLAG(SOCK_NONBLOCK),
+       FLAG(SOCK_NOSIGPIPE),
+};
+
+static void
+put_shutdown_how(struct trace_proc * proc, const char * name, int how)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (how) {
+               TEXT(SHUT_RD);
+               TEXT(SHUT_WR);
+               TEXT(SHUT_RDWR);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", how);
+}
+
+static void
+put_struct_uucred(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr)
+{
+       struct uucred cred;
+
+       if (!put_open_struct(proc, name, flags, addr, &cred, sizeof(cred)))
+               return;
+
+       put_value(proc, "cr_uid", "%u", cred.cr_uid);
+       if (verbose > 0) {
+               put_value(proc, "cr_gid", "%u", cred.cr_gid);
+               if (verbose > 1)
+                       put_value(proc, "cr_ngroups", "%d", cred.cr_ngroups);
+               put_groups(proc, "cr_groups", PF_LOCADDR,
+                   (vir_bytes)&cred.cr_groups, cred.cr_ngroups);
+       }
+
+       put_close_struct(proc, verbose > 0);
+}
+
+static void
+put_cmsg_type(struct trace_proc * proc, const char * name, int type)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (type) {
+               TEXT(SCM_RIGHTS);
+               TEXT(SCM_CREDS);
+               TEXT(SCM_TIMESTAMP);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", type);
+}
+
+static void
+put_msg_control(struct trace_proc * proc, struct msg_control * ptr)
+{
+       struct msghdr msg;
+       struct cmsghdr *cmsg;
+       size_t len;
+       int i;
+
+       if (ptr->msg_controllen > sizeof(ptr->msg_control)) {
+               put_field(proc, NULL, "..");
+
+               return;
+       }
+
+       put_open(proc, NULL, PF_NONAME, "[", ", ");
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_control = ptr->msg_control;
+       msg.msg_controllen = ptr->msg_controllen;
+
+       /*
+        * TODO: decide if we need a verbosity-based limit here.  The argument
+        * in favor of printing everything is that upon receipt, SCM_RIGHTS
+        * actually creates new file descriptors, which is pretty essential in
+        * terms of figuring out what is happening in a process.  In addition,
+        * these calls should be sufficiently rare that the lengthy output is
+        * not really disruptive for the general output flow.
+        */
+       for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+           cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+               put_open(proc, NULL, 0, "{", ", ");
+
+               if (verbose > 0)
+                       put_value(proc, "cmsg_len", "%u", cmsg->cmsg_len);
+               if (!valuesonly && cmsg->cmsg_level == SOL_SOCKET)
+                       put_field(proc, "cmsg_level", "SOL_SOCKET");
+               else
+                       put_value(proc, "cmsg_level", "%d", cmsg->cmsg_level);
+               if (cmsg->cmsg_level == SOL_SOCKET)
+                       put_cmsg_type(proc, "cmsg_type", cmsg->cmsg_type);
+
+               len = cmsg->cmsg_len - CMSG_LEN(0);
+
+               /* Print the contents of the messages that we know. */
+               if (cmsg->cmsg_level == SOL_SOCKET &&
+                   cmsg->cmsg_type == SCM_RIGHTS) {
+                       put_open(proc, NULL, PF_NONAME, "[", ", ");
+                       for (i = 0; i < len / sizeof(int); i++)
+                               put_fd(proc, NULL,
+                                   ((int *)CMSG_DATA(cmsg))[i]);
+                       put_close(proc, "]");
+               } else if (cmsg->cmsg_level == SOL_SOCKET &&
+                   cmsg->cmsg_type == SCM_CREDS) {
+                       put_struct_uucred(proc, NULL, PF_LOCADDR,
+                           (vir_bytes)CMSG_DATA(cmsg));
+               } else if (len > 0)
+                       put_field(proc, NULL, "..");
+
+               put_close(proc, "}");
+       }
+
+       put_close(proc, "]");
+}
+
+int
+net_ioctl_arg(struct trace_proc * proc, unsigned long req, void * ptr, int dir)
+{
+       const char *text;
+       nwio_ipopt_t *ipopt;
+       nwio_tcpconf_t *nwtc;
+       nwio_tcpcl_t *nwtcl;
+       nwio_tcpopt_t *nwto;
+       tcp_cookie_t *cookie;
+       nwio_udpopt_t *nwuo;
+       struct sockaddr_un *sun;
+       int i;
+
+       switch (req) {
+       case FIONREAD:
+               /*
+                * Arguably this does not belong here, but as of writing, the
+                * network services are the only ones actually implementing
+                * support for this IOCTL, and we don't have a more suitable
+                * place to put it either.
+                */
+               if (ptr == NULL)
+                       return IF_IN;
+
+               put_value(proc, NULL, "%d", *(int *)ptr);
+               return IF_ALL;
+
+       case NWIOSIPOPT:
+       case NWIOGIPOPT:
+               if ((ipopt = (nwio_ipopt_t *)ptr) == NULL)
+                       return dir;
+
+               put_flags(proc, "nwio_flags", ipopt_flags, COUNT(ipopt_flags),
+                   "0x%x", ipopt->nwio_flags);
+
+               if (ipopt->nwio_flags & NWIO_REMSPEC)
+                       put_ipaddr(proc, "nwio_rem", ipopt->nwio_rem);
+               if (ipopt->nwio_flags & NWIO_PROTOSPEC)
+                       put_ipproto(proc, "nwio_proto", ipopt->nwio_proto);
+
+               return 0; /* TODO: the remaining fields */
+
+       case NWIOSTCPCONF:
+       case NWIOGTCPCONF:
+               if ((nwtc = (nwio_tcpconf_t *)ptr) == NULL)
+                       return dir;
+
+               put_flags(proc, "nwtc_flags", tcpconf_flags,
+                   COUNT(tcpconf_flags), "0x%x", nwtc->nwtc_flags);
+
+               /* The local address cannot be set, just retrieved. */
+               if (req == NWIOGTCPCONF)
+                       put_ipaddr(proc, "nwtc_locaddr", nwtc->nwtc_locaddr);
+
+               if ((nwtc->nwtc_flags & NWTC_LOCPORT_MASK) == NWTC_LP_SET)
+                       put_port(proc, "nwtc_locport", nwtc->nwtc_locport);
+
+               if (nwtc->nwtc_flags & NWTC_SET_RA)
+                       put_ipaddr(proc, "nwtc_remaddr", nwtc->nwtc_remaddr);
+
+               if (nwtc->nwtc_flags & NWTC_SET_RP)
+                       put_port(proc, "nwtc_remport", nwtc->nwtc_remport);
+
+               return IF_ALL;
+
+       case NWIOTCPCONN:
+       case NWIOTCPLISTEN:
+               if ((nwtcl = (nwio_tcpcl_t *)ptr) == NULL)
+                       return dir;
+
+               put_flags(proc, "nwtcl_flags", tcpcl_flags,
+                   COUNT(tcpcl_flags), "0x%x", nwtcl->nwtcl_flags);
+
+               /* We pretend the unused nwtcl_ttl field does not exist. */
+               return IF_ALL;
+
+       case NWIOSTCPOPT:
+       case NWIOGTCPOPT:
+               if ((nwto = (nwio_tcpopt_t *)ptr) == NULL)
+                       return dir;
+
+               put_flags(proc, "nwto_flags", tcpopt_flags,
+                   COUNT(tcpopt_flags), "0x%x", nwto->nwto_flags);
+               return IF_ALL;
+
+       case NWIOTCPLISTENQ:
+       case NWIOSUDSBLOG:
+               if (ptr == NULL)
+                       return IF_OUT;
+
+               put_value(proc, NULL, "%d", *(int *)ptr);
+               return IF_ALL;
+
+       case NWIOGTCPCOOKIE:
+       case NWIOTCPACCEPTTO:
+               if ((cookie = (tcp_cookie_t *)ptr) == NULL)
+                       return dir;
+
+               put_value(proc, "tc_ref", "%"PRIu32, cookie->tc_ref);
+               if (verbose > 0)
+                       put_buf(proc, "tc_secret", PF_LOCADDR,
+                           (vir_bytes)&cookie->tc_secret,
+                           sizeof(cookie->tc_secret));
+               return (verbose > 0) ? IF_ALL : 0;
+
+       case NWIOTCPGERROR:
+               if (ptr == NULL)
+                       return IF_IN;
+
+               i = *(int *)ptr;
+               if (!valuesonly && (text = get_error_name(i)) != NULL)
+                       put_field(proc, NULL, text);
+               else
+                       put_value(proc, NULL, "%d", i);
+               return IF_ALL;
+
+       case NWIOSUDPOPT:
+       case NWIOGUDPOPT:
+               if ((nwuo = (nwio_udpopt_t *)ptr) == NULL)
+                       return dir;
+
+               put_flags(proc, "nwuo_flags", udpopt_flags,
+                   COUNT(udpopt_flags), "0x%x", nwuo->nwuo_flags);
+
+               /* The local address cannot be set, just retrieved. */
+               if (req == NWIOGUDPOPT)
+                       put_ipaddr(proc, "nwuo_locaddr", nwuo->nwuo_locaddr);
+
+               if ((nwuo->nwuo_flags & NWUO_LOCPORT_MASK) == NWUO_LP_SET)
+                       put_port(proc, "nwuo_locport", nwuo->nwuo_locport);
+
+               if (nwuo->nwuo_flags & NWUO_RA_SET)
+                       put_ipaddr(proc, "nwuo_remaddr", nwuo->nwuo_remaddr);
+
+               if (nwuo->nwuo_flags & NWUO_RP_SET)
+                       put_port(proc, "nwuo_remport", nwuo->nwuo_remport);
+
+               return IF_ALL;
+
+       case NWIOGUDSFADDR:
+       case NWIOSUDSTADDR:
+       case NWIOSUDSADDR:
+       case NWIOGUDSADDR:
+       case NWIOGUDSPADDR:
+       case NWIOSUDSCONN:
+       case NWIOSUDSACCEPT:
+               if ((sun = (struct sockaddr_un *)ptr) == NULL)
+                       return dir;
+
+               put_family(proc, "sun_family", sun->sun_family);
+
+               /* This could be extended to a generic sockaddr printer.. */
+               if (sun->sun_family == AF_LOCAL) {
+                       put_buf(proc, "sun_path", PF_LOCADDR | PF_PATH,
+                           (vir_bytes)&sun->sun_path, sizeof(sun->sun_path));
+                       return IF_ALL; /* skipping sun_len, it's unused */
+               } else
+                       return 0;
+
+       case NWIOSUDSTYPE:
+       case NWIOGUDSSOTYPE:
+               if (ptr == NULL)
+                       return dir;
+
+               put_flags(proc, NULL, sock_type, COUNT(sock_type), "0x%x",
+                   *(int *)ptr);
+               return IF_ALL;
+
+       case NWIOSUDSSHUT:
+               if (ptr == NULL)
+                       return IF_OUT;
+
+               put_shutdown_how(proc, NULL, *(int *)ptr);
+               return IF_ALL;
+
+       case NWIOSUDSPAIR:
+               if (ptr == NULL)
+                       return IF_OUT;
+
+               put_dev(proc, NULL, *(dev_t *)ptr);
+               return IF_ALL;
+
+       case NWIOSUDSCTRL:
+               if (ptr == NULL)
+                       return IF_OUT;
+
+               /* FALLTHROUGH */
+       case NWIOGUDSCTRL:
+               if (ptr == NULL)
+                       return IF_IN;
+
+               put_msg_control(proc, (struct msg_control *)ptr);
+               return IF_ALL;
+
+       case NWIOGUDSPEERCRED:
+               if (ptr == NULL)
+                       return IF_IN;
+
+               put_struct_uucred(proc, NULL, PF_LOCADDR, (vir_bytes)ptr);
+               return IF_ALL;
+
+       case NWIOGUDSSNDBUF:
+       case NWIOSUDSSNDBUF:
+       case NWIOGUDSRCVBUF:
+       case NWIOSUDSRCVBUF:
+               if (ptr == NULL)
+                       return dir;
+
+               put_value(proc, NULL, "%zu", *(size_t *)ptr);
+               return IF_ALL;
+
+       default:
+               return 0;
+       }
+}
diff --git a/minix/usr.bin/trace/ioctl/svrctl.c b/minix/usr.bin/trace/ioctl/svrctl.c
new file mode 100644 (file)
index 0000000..8708ed2
--- /dev/null
@@ -0,0 +1,63 @@
+
+#include "inc.h"
+
+#include <sys/svrctl.h>
+
+const char *
+svrctl_name(unsigned long req)
+{
+
+       switch (req) {
+       NAME(PMSETPARAM);
+       NAME(PMGETPARAM);
+       NAME(VFSGETPARAM);
+       NAME(VFSSETPARAM);
+       }
+
+       return NULL;
+}
+
+int
+svrctl_arg(struct trace_proc * proc, unsigned long req, void * ptr, int dir)
+{
+       struct sysgetenv *env;
+
+       switch (req) {
+       case PMSETPARAM:
+       case VFSSETPARAM:
+               if ((env = (struct sysgetenv *)ptr) == NULL)
+                       return IF_OUT;
+
+               put_buf(proc, "key", PF_STRING, (vir_bytes)env->key,
+                   env->keylen);
+               put_buf(proc, "value", PF_STRING, (vir_bytes)env->val,
+                   env->vallen);
+               return IF_ALL;
+
+       case PMGETPARAM:
+       case VFSGETPARAM:
+               if ((env = (struct sysgetenv *)ptr) == NULL)
+                       return IF_OUT | IF_IN;
+
+               /*
+                * So far this is the only IOCTL case where the output depends
+                * on one of the values in the input: if the given key is NULL,
+                * PM provides the entire system environment in return, which
+                * means we cannot just print a single string.  We rely on PM
+                * not changing the key field, which (while true) is an
+                * assumption.  With the current (simple) model we would have
+                * to save the provided key pointer somewhere otherwise.
+                */
+               if (dir == IF_OUT)
+                       put_buf(proc, "key", PF_STRING, (vir_bytes)env->key,
+                           env->keylen);
+               else
+                       put_buf(proc, "value",
+                           (env->key != NULL) ? PF_STRING : 0,
+                           (vir_bytes)env->val, env->vallen);
+               return IF_ALL;
+
+       default:
+               return 0;
+       }
+}
diff --git a/minix/usr.bin/trace/kernel.c b/minix/usr.bin/trace/kernel.c
new file mode 100644 (file)
index 0000000..1951230
--- /dev/null
@@ -0,0 +1,307 @@
+/*
+ * This file, and only this file, should contain all the ugliness needed to
+ * obtain values from the kernel.  It has to be recompiled every time the
+ * layout of the kernel "struct proc" and/or "struct priv" structures changes.
+ * In addition, this file contains the platform-dependent code related to
+ * interpreting the registers exposed by the kernel.
+ *
+ * As a quick note, some functions return TRUE/FALSE, and some return 0/-1.
+ * The former convention is used for functions that return a boolean value;
+ * the latter is used for functions that set errno in all cases of failure,
+ * and where the caller may conceivably use errno as a result.
+ *
+ * On a related note, relevant here and elsewhere: we define _MINIX_SYSTEM but
+ * not _SYSTEM, which means that we should not get negative error numbers.
+ */
+
+#include "inc.h"
+
+#include <machine/archtypes.h>
+#include <minix/timers.h>
+#include "kernel/proc.h"
+#include "kernel/priv.h"
+#if defined(__i386__)
+#include "kernel/arch/i386/include/archconst.h" /* for the KTS_ constants */
+#endif
+
+#include <minix/param.h>
+
+extern struct minix_kerninfo *_minix_kerninfo;
+
+/*
+ * Working area.  By obtaining values from the kernel into these local process
+ * structures, and then returning them, we gain a little robustness against
+ * changes in data types of the fields we need.
+ */
+static struct proc kernel_proc;
+static struct priv kernel_priv;
+
+/*
+ * Check whether our notion of the kernel process structure layout matches that
+ * of the kernel, by comparing magic values.  This can be done only once we
+ * have attached to a process.  Return TRUE if everything seems alright; FALSE
+ * otherwise.
+ */
+int
+kernel_check(pid_t pid)
+{
+
+       if (mem_get_user(pid, offsetof(struct proc, p_magic),
+           &kernel_proc.p_magic, sizeof(kernel_proc.p_magic)) < 0)
+               return FALSE;
+
+       return (kernel_proc.p_magic == PMAGIC);
+}
+
+/*
+ * Obtain the kernel name for the given (stopped) process.  Return 0 on
+ * success, with the (possibly truncated) name stored in the 'name' buffer
+ * which is of 'size' bytes; the name will be null-terminated.  Note that the
+ * name may contain any suffixes as set by the kernel.  Return -1 on failure,
+ * with errno set as appropriate.
+ */
+int
+kernel_get_name(pid_t pid, char * name, size_t size)
+{
+
+       if (mem_get_user(pid, offsetof(struct proc, p_name),
+           kernel_proc.p_name, sizeof(kernel_proc.p_name)) < 0)
+               return -1;
+
+       strlcpy(name, kernel_proc.p_name, size);
+       return 0;
+}
+
+/*
+ * Check whether the given process, which we have just attached to, is a system
+ * service.  PM does not prevent us from attaching to most system services,
+ * even though this utility only supports tracing user programs.  Unlike a few
+ * other routines in this file, this function can not use ProcFS to obtain its
+ * result, because the given process may actually be VFS or ProcFS itself!
+ * Return TRUE if the given process is a system service; FALSE if not.
+ */
+int
+kernel_is_service(pid_t pid)
+{
+       size_t align, off;
+
+       /*
+        * For T_GETUSER, the priv structure follows the proc structure, but
+        * possibly with padding in between so as to align the priv structure
+        * to long boundary.
+        */
+       align = sizeof(long) - 1;
+       off = (sizeof(struct proc) + align) & ~align;
+
+       if (mem_get_user(pid, off + offsetof(struct priv, s_id),
+           &kernel_priv.s_id, sizeof(kernel_priv.s_id)) < 0)
+               return FALSE; /* process may have disappeared, so no danger */
+
+       return (kernel_priv.s_id != USER_PRIV_ID);
+}
+
+/*
+ * For the given process, which must be stopped on entering a system call,
+ * retrieve the three register values describing the system call.  Return 0 on
+ * success, or -1 on failure with errno set as appropriate.
+ */
+int
+kernel_get_syscall(pid_t pid, reg_t reg[3])
+{
+
+       assert(sizeof(kernel_proc.p_defer) == sizeof(reg_t) * 3);
+
+       if (mem_get_user(pid, offsetof(struct proc, p_defer),
+           &kernel_proc.p_defer, sizeof(kernel_proc.p_defer)) < 0)
+               return -1;
+
+       reg[0] = kernel_proc.p_defer.r1;
+       reg[1] = kernel_proc.p_defer.r2;
+       reg[2] = kernel_proc.p_defer.r3;
+       return 0;
+}
+
+/*
+ * Retrieve the value of the primary return register for the given process,
+ * which must be stopped on leaving a system call.  This register contains the
+ * IPC-level result of the system call.  Return 0 on success, or -1 on failure
+ * with errno set as appropriate.
+ */
+int
+kernel_get_retreg(pid_t pid, reg_t * retreg)
+{
+       size_t off;
+
+       /*
+        * Historically p_reg had to be the first field in the proc structure,
+        * but since this is no longer a hard requirement, getting its actual
+        * offset into the proc structure certainly doesn't hurt.
+        */
+       off = offsetof(struct proc, p_reg);
+
+       if (mem_get_user(pid, off + offsetof(struct stackframe_s, retreg),
+           &kernel_proc.p_reg.retreg, sizeof(kernel_proc.p_reg.retreg)) < 0)
+               return -1;
+
+       *retreg = kernel_proc.p_reg.retreg;
+       return 0;
+}
+
+/*
+ * Return the stack top for user processes.  This is needed for execve(), since
+ * the supplied frame contains pointers prepared for the new location of the
+ * frame, which is at the stack top of the process after the execve().
+ */
+vir_bytes
+kernel_get_stacktop(void)
+{
+
+       return _minix_kerninfo->kinfo->user_sp;
+}
+
+/*
+ * For the given stopped process, get its program counter (pc), stack pointer
+ * (sp), and optionally its frame pointer (fp).  The given fp pointer may be
+ * NULL, in which case the frame pointer is not obtained.  The given pc and sp
+ * pointers must not be NULL, and this is intentional: obtaining fp may require
+ * obtaining sp first.  Return 0 on success, or -1 on failure with errno set
+ * as appropriate.  This functionality is not essential for tracing processes,
+ * and may not be supported on all platforms, in part or full.  In particular,
+ * on some platforms, a zero (= invalid) frame pointer may be returned on
+ * success, indicating that obtaining frame pointers is not supported.
+ */
+int
+kernel_get_context(pid_t pid, reg_t * pc, reg_t * sp, reg_t * fp)
+{
+       size_t off;
+
+       off = offsetof(struct proc, p_reg); /* as above */
+
+       if (mem_get_user(pid, off + offsetof(struct stackframe_s, pc),
+           &kernel_proc.p_reg.pc, sizeof(kernel_proc.p_reg.pc)) < 0)
+               return -1;
+       if (mem_get_user(pid, off + offsetof(struct stackframe_s, sp),
+           &kernel_proc.p_reg.sp, sizeof(kernel_proc.p_reg.sp)) < 0)
+               return -1;
+
+       *pc = kernel_proc.p_reg.pc;
+       *sp = kernel_proc.p_reg.sp;
+
+       if (fp == NULL)
+               return 0;
+
+#if defined(__i386__)
+       if (mem_get_user(pid, offsetof(struct proc, p_seg) +
+           offsetof(struct segframe, p_kern_trap_style),
+           &kernel_proc.p_seg.p_kern_trap_style,
+           sizeof(kernel_proc.p_seg.p_kern_trap_style)) < 0)
+               return -1;
+
+       /* This is taken from the kernel i386 exception code. */
+       switch (kernel_proc.p_seg.p_kern_trap_style) {
+       case KTS_SYSENTER:
+       case KTS_SYSCALL:
+               if (mem_get_data(pid, *sp + 16, fp, sizeof(fp)) < 0)
+                       return -1;
+               break;
+
+       default:
+               if (mem_get_user(pid, off + offsetof(struct stackframe_s, fp),
+                   &kernel_proc.p_reg.fp, sizeof(kernel_proc.p_reg.fp)) < 0)
+                       return -1;
+
+               *fp = kernel_proc.p_reg.fp;
+       }
+#else
+       *fp = 0; /* not supported; this is not a failure (*pc is valid) */
+#endif
+       return 0;
+}
+
+/*
+ * Given a frame pointer, obtain the next program counter and frame pointer.
+ * Return 0 if successful, or -1 on failure with errno set appropriately.  The
+ * functionality is not essential for tracing processes, and may not be
+ * supported on all platforms.  Thus, on some platforms, this function may
+ * always fail.
+ */
+static int
+kernel_get_nextframe(pid_t pid, reg_t fp, reg_t * next_pc, reg_t * next_fp)
+{
+#if defined(__i386__)
+       void *p[2];
+
+       if (mem_get_data(pid, (vir_bytes)fp, &p, sizeof(p)) < 0)
+               return -1;
+
+       *next_pc = (reg_t)p[1];
+       *next_fp = (reg_t)p[0];
+       return 0;
+#else
+       /* Not supported (yet). */
+       errno = ENOSYS;
+       return -1;
+#endif
+}
+
+/*
+ * Print a stack trace for the given process, which is known to be stopped on
+ * entering a system call.  This function does not really belong here, but
+ * without a doubt it is going to have to be fully rewritten to support
+ * anything other than i386.
+ *
+ * Getting symbol names is currently an absolute nightmare.  Not just because
+ * of shared libraries, but also since ProcFS does not offer a /proc/NNN/exe,
+ * so that we cannot reliably determine the binary being executed: not for
+ * processes being attached to, and not for exec calls using a relative path.
+ */
+void
+kernel_put_stacktrace(struct trace_proc * proc)
+{
+       unsigned int count, max;
+       reg_t pc, sp, fp, low, high;
+
+       if (kernel_get_context(proc->pid, &pc, &sp, &fp) < 0)
+               return;
+
+       /*
+        * A low default limit such as 6 looks much prettier, but is simply not
+        * useful enough for moderately-sized programs in practice.  Right now,
+        * 15 is about two lines on a 80-column terminal.
+        */
+       if (verbose == 0) max = 15;
+       else if (verbose == 1) max = 31;
+       else max = UINT_MAX;
+
+       /*
+        * We keep formatting to an absolute minimum, to facilitate passing
+        * the lines straight into tools such as addr2line.
+        */
+       put_newline();
+       put_fmt(proc, "  0x%x", pc);
+
+       low = high = fp;
+
+       for (count = 1; count < max && fp != 0; count++) {
+               if (kernel_get_nextframe(proc->pid, fp, &pc, &fp) < 0)
+                       break;
+
+               put_fmt(proc, " 0x%x", pc);
+
+               /*
+                * Stop if we see a frame pointer that falls within the range
+                * of the frame pointers we have seen so far.  This also
+                * prevents getting stuck in a loop on the same frame pointer.
+                */
+               if (fp >= low && fp <= high)
+                       break;
+               if (low > fp)
+                       low = fp;
+               if (high < fp)
+                       high = fp;
+       }
+
+       if (fp != 0)
+               put_text(proc, " ..");
+       put_newline();
+}
diff --git a/minix/usr.bin/trace/mem.c b/minix/usr.bin/trace/mem.c
new file mode 100644 (file)
index 0000000..e0b6727
--- /dev/null
@@ -0,0 +1,61 @@
+
+#include "inc.h"
+
+/*
+ * Retrieve 'len' bytes from the memory of the traced process 'pid' at address
+ * 'addr' and put the result in the buffer pointed to by 'ptr'.  Return 0 on
+ * success, or otherwise -1 with errno set appropriately.
+ */
+int
+mem_get_data(pid_t pid, vir_bytes addr, void * ptr, size_t len)
+{
+       struct ptrace_range pr;
+
+       if (len == 0) return 0;
+
+       pr.pr_space = TS_DATA;
+       pr.pr_addr = addr;
+       pr.pr_size = len;
+       pr.pr_ptr = ptr;
+
+       return ptrace(T_GETRANGE, pid, &pr, 0);
+}
+
+/*
+ * Retrieve 'len' bytes from the kernel structure memory of the traced process
+ * 'pid' at offset 'addr' and put the result in the buffer pointed to by 'ptr'.
+ * Return 0 on success, or otherwise -1 with errno set appropriately.
+ */
+int
+mem_get_user(pid_t pid, vir_bytes addr, void * ptr, size_t len)
+{
+       long data;
+       char *p;
+       size_t off, chunk;
+
+       if (len == 0) return 0;
+
+       /* Align access to address. */
+       off = addr & (sizeof(data) - 1);
+       addr -= off;
+
+       p = ptr;
+
+       while (len > 0) {
+               errno = 0;
+               data = ptrace(T_GETUSER, pid, (void *)addr, 0);
+               if (errno != 0) return -1;
+
+               chunk = sizeof(data) - off;
+               if (chunk > len)
+                       chunk = len;
+
+               memcpy(p, (char *)&data + off, chunk);
+               p += chunk;
+               addr += chunk;
+               len -= chunk;
+               off = 0;
+       }
+
+       return 0;
+}
diff --git a/minix/usr.bin/trace/output.c b/minix/usr.bin/trace/output.c
new file mode 100644 (file)
index 0000000..f874163
--- /dev/null
@@ -0,0 +1,516 @@
+
+#include "inc.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+
+/*
+ * The maximum number of bytes that may be buffered before writing the buffered
+ * output to the underlying file.  This is a performance optimization only.
+ * Writing more than this number of bytes at once will be handled correctly.
+ */
+#define OUTPUT_BUFSZ   512
+
+static int out_fd;
+static char out_buf[OUTPUT_BUFSZ];
+static int out_len;
+static int out_err;
+
+static pid_t last_pid; /* not a trace_proc pointer; it could become invalid! */
+static unsigned int line_off;
+static unsigned int prefix_off;
+static int print_pid;
+static int print_susp;
+static int add_space;
+
+/*
+ * Initialize the output channel.  Called before any other output functions,
+ * but after a child process (to be traced) has already been spawned.  If the
+ * given file string is not NULL, it is the path to a file that is to be used
+ * to write output to.  If it is NULL, output is written to standard error.
+ */
+int
+output_init(const char * file)
+{
+
+       /* Initialize state. */
+       out_len = 0;
+       out_err = FALSE;
+
+       last_pid = 0;
+       line_off = 0;
+       prefix_off = 0;
+       print_pid = FALSE;
+       print_susp = FALSE;
+       add_space = FALSE;
+
+       /*
+        * Ignore signals resulting from writing to a closed pipe.  We can
+        * handle write errors properly ourselves.  Setting O_NOSIGPIPE is an
+        * alternative, but that would affect other processes writing to the
+        * same file object, even after we have terminated.
+        */
+       signal(SIGPIPE, SIG_IGN);
+
+       /* Initialize the output file descriptor. */
+       if (file == NULL) {
+               /* No output file given?  Use standard error. */
+               out_fd = STDERR_FILENO;
+
+               return 0;
+       } else {
+               /*
+                * Use a restrictive mask for the output file.  Traces may
+                * contain sensitive information (for security and otherwise),
+                * and the user might not always be careful about the location
+                * of the file.
+                */
+               /* The file descriptor is not closed explicitly. */
+               out_fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_APPEND,
+                   0600);
+
+               return (out_fd < 0) ? -1 : 0;
+       }
+}
+
+/*
+ * Write the given data to the given file descriptor, taking into account the
+ * possibility of partial writes and write errors.
+ */
+static void
+write_fd(int fd, const char *buf, size_t len)
+{
+       ssize_t r;
+
+       /* If we got a write error before, do not try to write more. */
+       if (out_err)
+               return;
+
+       /* Write all output, in chunks if we have to. */
+       while (len > 0) {
+               r = write(fd, buf, len);
+
+               /*
+                * A write error (and that includes EOF) causes the program to
+                * terminate with an error code.  For obvious reasons we cannot
+                * print an error about this.  Do not even report to standard
+                * error if the output was redirected, because that may mess
+                * with the actual programs being run right now.
+                */
+               if (r <= 0) {
+                       out_err = TRUE;
+
+                       break;
+               }
+
+               len -= r;
+       }
+}
+
+/*
+ * Return TRUE iff an output error occurred and the program should terminate.
+ */
+int
+output_error(void)
+{
+
+       return out_err;
+}
+
+/*
+ * Print the given null-terminated string to the output channel.  Return the
+ * number of characters printed, for alignment purposes.  In the future, this
+ * number may end up being different from the number of bytes given to print,
+ * due to multibyte encoding or colors or whatnot.
+ */
+static unsigned int
+output_write(const char * text)
+{
+       size_t len;
+
+       len = strlen(text);
+
+       if (out_len + len > sizeof(out_buf)) {
+               write_fd(out_fd, out_buf, out_len);
+
+               out_len = 0;
+
+               /* Write large buffers right away. */
+               if (len > sizeof(out_buf)) {
+                       write_fd(out_fd, text, len);
+
+                       return len;
+               }
+       }
+
+       memcpy(&out_buf[out_len], text, len);
+
+       out_len += len;
+
+       return len;
+}
+
+/*
+ * Flush any pending output to the output channel.
+ */
+void
+output_flush(void)
+{
+
+       if (out_len > 0) {
+               write_fd(out_fd, out_buf, out_len);
+
+               out_len = 0;
+       }
+}
+
+/*
+ * Print a PID prefix for the given process, or an info prefix if no process
+ * (NULL) is given.  Prefixes are only relevant when multiple processes are
+ * traced.  As long as there are multiple processes, each line is prefixed with
+ * the PID of the process.  As soon as the number of processes has been reduced
+ * back to one, one more line is prefixed with the PID of the remaining process
+ * (with a "'" instead of a "|") to help the user identify which process is
+ * left.  In addition, whenever a preempted call is about to be resumed, a "*"
+ * is printed instead of a space, so as to show that it is a continuation of a
+ * previous line.  An example of all these cases:
+ *
+ *   fork() = 3
+ *       3| Tracing test (pid 3)
+ *       3| fork() = 0
+ *       3| read(0, <..>
+ *       2| waitpid(-1, <..>
+ *    INFO| This is an example info line.
+ *       3|*read(0, "", 1024) = 0
+ *       3| exit(1)
+ *       3| Process exited normally with code 1
+ *       2'*waitpid(-1, W_EXITED(1), 0) = 3
+ *   exit(0)
+ *   Process exited normally with code 0
+ */
+static void
+put_prefix(struct trace_proc * proc, int resuming)
+{
+       char prefix[32];
+       unsigned int count;
+
+       assert(line_off == 0);
+
+       count = proc_count();
+
+       /* TODO: add a command line option for always printing the pid. */
+       if (print_pid || count > 1 || proc == NULL) {
+               /*
+                * TODO: we currently rely on the highest PID having at most
+                * five digits, but this will eventually change.  There are
+                * several ways to deal with that, but none are great.
+                */
+               if (proc == NULL)
+                       snprintf(prefix, sizeof(prefix), "%5s| ", "INFO");
+               else
+                       snprintf(prefix, sizeof(prefix), "%5d%c%c",
+                           proc->pid, (count > 1) ? '|' : '\'',
+                           resuming ? '*' : ' ');
+
+               prefix_off = line_off = output_write(prefix);
+
+               last_pid = (proc != NULL ? proc->pid : 0);
+       } else {
+               assert(!resuming);
+
+               prefix_off = 0;
+       }
+
+       /* Remember whether the next line should get prefixed regardless. */
+       print_pid = (count > 1 || proc == NULL);
+}
+
+/*
+ * Add a string to the end of the text recording for the given process.
+ * This is used only to record the call-enter output of system calls.
+ */
+static void
+record_add(struct trace_proc * proc, const char * text)
+{
+       size_t len;
+
+       assert(proc->recording);
+
+       /* If the recording buffer is already full, do not record more. */
+       if (proc->outlen == sizeof(proc->outbuf))
+               return;
+
+       len = strlen(text);
+
+       /* If nonempty, the recording buffer is always null terminated. */
+       if (len < sizeof(proc->outbuf) - proc->outlen - 1) {
+               strcpy(&proc->outbuf[proc->outlen], text);
+
+               proc->outlen += len;
+       } else
+               proc->outlen = sizeof(proc->outbuf); /* buffer exhausted */
+}
+
+/*
+ * Start recording text for the given process.  Since this marks the start of
+ * a call, remember to print a preemption marker when the call gets preempted.
+ */
+void
+record_start(struct trace_proc * proc)
+{
+
+       proc->recording = TRUE;
+
+       print_susp = TRUE;
+}
+
+/*
+ * Stop recording text for the given process.
+ */
+void
+record_stop(struct trace_proc * proc)
+{
+
+       proc->recording = FALSE;
+}
+
+/*
+ * Clear recorded text for the given process.  Since this also marks the end of
+ * the entire call, no longer print a supension marker before the next newline.
+ */
+void
+record_clear(struct trace_proc * proc)
+{
+
+       assert(!proc->recording);
+       proc->outlen = 0;
+
+       if (proc->pid == last_pid)
+               print_susp = FALSE;
+}
+
+/*
+ * Replay the record for the given process on a new line, if the current line
+ * does not already have output for this process.  If it does, do nothing.
+ * If the process has no recorded output, just start a new line.  Return TRUE
+ * iff the caller must print its own replay text due to a recording overflow.
+ */
+int
+record_replay(struct trace_proc * proc)
+{
+       int space;
+
+       assert(!proc->recording);
+
+       /*
+        * If there is output on the current line, and it is for the current
+        * process, we must assume that it is the original, recorded text, and
+        * thus, we should do nothing.  If output on the current line is for
+        * another process, we must force a new line before replaying.
+        */
+       if (line_off > 0) {
+               if (proc->pid == last_pid)
+                       return FALSE;
+
+               put_newline();
+       }
+
+       /*
+        * If there is nothing to replay, do nothing further.  This case may
+        * occur when printing signals, in which case the caller still expects
+        * a new line to be started.  This line must not be prefixed with a
+        * "resuming" marker though--after all, nothing is being resumed here.
+        */
+       if (proc->outlen == 0)
+               return FALSE;
+
+       /*
+        * If there is text to replay, then this does mean we are in effect
+        * resuming the recorded call, even if it is just to print a signal.
+        * Thus, we must print a prefix that shows the call is being resumed.
+        * Similarly, unless the recording is cleared before a newline, we must
+        * suspend the line again, too.
+        */
+       put_prefix(proc, TRUE /*resuming*/);
+
+       print_susp = TRUE;
+
+       /*
+        * If the recording buffer was exhausted during recording, the caller
+        * must generate the replay text instead.
+        */
+       if (proc->outlen == sizeof(proc->outbuf))
+               return TRUE;
+
+       /*
+        * Replay the recording.  If it ends with a space, turn it into a soft
+        * space, because the recording may be followed immediately by a
+        * newline; an example of this is the exit() exception.
+        */
+       space = proc->outbuf[proc->outlen - 1] == ' ';
+       if (space)
+               proc->outbuf[proc->outlen - 1] = 0;
+
+       put_text(proc, proc->outbuf);
+
+       if (space) {
+               put_space(proc);
+
+               /* Restore the space, in case another replay takes place. */
+               proc->outbuf[proc->outlen - 1] = ' ';
+       }
+
+       return FALSE;
+}
+
+/*
+ * Start a new line, and adjust the local state accordingly.  If nothing has
+ * been printed on the current line yet, this function is a no-op.  Otherwise,
+ * the output so far may have to be marked as preempted with the "<..>"
+ * preemption marker.
+ */
+void
+put_newline(void)
+{
+
+       if (line_off == 0)
+               return;
+
+       if (print_susp) {
+               if (add_space)
+                       (void)output_write(" ");
+
+               (void)output_write("<..>");
+       }
+
+#if DEBUG
+       (void)output_write("|");
+#endif
+
+       (void)output_write("\n");
+       output_flush();
+
+       line_off = 0;
+       add_space = FALSE;
+       print_susp = FALSE;
+       last_pid = 0;
+}
+
+/*
+ * Print a string as part of the output associated with a process.  If the
+ * current line contains output for another process, a newline will be printed
+ * first.  If the current line contains output for the same process, then the
+ * text will simply continue on the same line.  If the current line is empty,
+ * a process PID prefix may have to be printed first.  Either way, after this
+ * operation, the current line will contain text for the given process.  If
+ * requested, the text may also be recorded for the process, for later replay.
+ * As an exception, proc may be NULL when printing general information lines.
+ */
+void
+put_text(struct trace_proc * proc, const char * text)
+{
+
+       if (line_off > 0 && (proc == NULL || proc->pid != last_pid)) {
+               /*
+                * The current line has not been terminated with a newline yet.
+                * Start a new line.  Note that this means that for lines not
+                * associated to a process, the whole line must be printed at
+                * once.  This can be fixed but is currently not an issue.
+                */
+               put_newline();
+       }
+
+       /* See if we must add a prefix at the start of the line. */
+       if (line_off == 0)
+               put_prefix(proc, FALSE /*resuming*/);
+
+       /* If needed, record the given text. */
+       if (proc != NULL && proc->recording)
+               record_add(proc, text);
+
+       /*
+        * If we delayed printing a space, print one now.  This is never part
+        * of text that must be saved.  In fact, we support these soft spaces
+        * for exactly one case; see put_space() for details.
+        */
+       if (add_space) {
+               line_off += output_write(" ");
+
+               add_space = FALSE;
+       }
+
+       /* Finally, print the actual text. */
+       line_off += output_write(text);
+
+       last_pid = (proc != NULL) ? proc->pid : 0;
+}
+
+/*
+ * Add a space to the output for the given process, but only if and once more
+ * text is printed for the process afterwards.  The aim is to ensure that no
+ * lines ever end with a space, to prevent needless line wrapping on terminals.
+ * The space may have to be remembered for the current line (for preemption,
+ * which does not have a process pointer to work with) as well as recorded for
+ * later replay, if recording is enabled.  Consider the following example:
+ *
+ * [A]   3| execve(..) <..>
+ *       2| getpid(0) = 2 (ppid=1)
+ * [B]   3| execve(..) = -1 [ENOENT]
+ * [A]   3| exit(1) <..>
+ *       2| getpid(0) = 2 (ppid=1)
+ *       3| exit(1)
+ *       3| Process exited normally with code 1
+ *
+ * On the [A] lines, the space between the call's closing parenthesis and the
+ * "<..>" preemption marker is the result of add_space being set to TRUE; on
+ * the [B] line, the space between the closing parenthesis and the equals sign
+ * is the result of the space being recorded.
+ */
+void
+put_space(struct trace_proc * proc)
+{
+
+       /* This call must only be used after output for the given process. */
+       assert(last_pid == proc->pid);
+
+       /* In case the call does not get preempted. */
+       add_space = TRUE;
+
+       /* In case the call does get preempted. */
+       if (proc->recording)
+               record_add(proc, " ");
+}
+
+/*
+ * Indent the remainders of the text on the line for this process, such that
+ * similar remainders are similarly aligned.  In particular, the remainder is
+ * the equals sign of a call, and everything after it.  Of course, alignment
+ * can only be used if the call has not already printed beyond the alignment
+ * position.  Also, the prefix must not be counted toward the alignment, as it
+ * is possible that a line without prefix may be preempted and later continued
+ * with prefix.  All things considered, the result would look like this:
+ *
+ *   getuid()                      = 1 (euid=1)
+ *   setuid(0)                     = -1 [EPERM]
+ *   write(2, "Permission denied\n", 18) = 18
+ *   fork()                        = 3
+ *       3| Tracing test (pid 3)
+ *       3| fork()                        = 0
+ *       3| exit(0)
+ *       3| Process exited normally with code 0
+ *       2' waitpid(-1, W_EXITED(0), 0)   = 3
+ *
+ */
+void put_align(struct trace_proc * __unused proc)
+{
+
+       /*
+        * TODO: add actual support for this.  The following code works,
+        * although not so efficiently.  The difficulty is the default
+        * configuration and corresponding options.
+
+       while (line_off - prefix_off < 20)
+               put_text(proc, " ");
+
+        */
+}
diff --git a/minix/usr.bin/trace/proc.c b/minix/usr.bin/trace/proc.c
new file mode 100644 (file)
index 0000000..665ea9f
--- /dev/null
@@ -0,0 +1,97 @@
+
+#include "inc.h"
+
+static TAILQ_HEAD(, trace_proc) proc_root;
+static unsigned int nr_procs;
+
+/*
+ * Initialize the list of traced processes.
+ */
+void
+proc_init(void)
+{
+
+       TAILQ_INIT(&proc_root);
+       nr_procs = 0;
+}
+
+/*
+ * Add a new process to the list of traced processes, allocating memory for it
+ * first.  Return the new process structure with its PID assigned and the rest
+ * zeroed out, or NULL upon allocation failure (with errno set appropriately).
+ */
+struct trace_proc *
+proc_add(pid_t pid)
+{
+       struct trace_proc *proc;
+
+       proc = (struct trace_proc *)malloc(sizeof(struct trace_proc));
+
+       if (proc == NULL)
+               return NULL;
+
+       memset(proc, 0, sizeof(*proc));
+
+       proc->pid = pid;
+
+       TAILQ_INSERT_TAIL(&proc_root, proc, next);
+       nr_procs++;
+
+       return proc;
+}
+
+/*
+ * Retrieve the data structure for a traced process based on its PID.  Return
+ * a pointer to the structure, or NULL if no structure exists for this process.
+ */
+struct trace_proc *
+proc_get(pid_t pid)
+{
+       struct trace_proc *proc;
+
+       /* Linear search for now; se we can easily add a hashtable later.. */
+       TAILQ_FOREACH(proc, &proc_root, next) {
+               if (proc->pid == pid)
+                       return proc;
+       }
+
+       return NULL;
+}
+
+/*
+ * Remove a process from the list of traced processes.
+ */
+void
+proc_del(struct trace_proc * proc)
+{
+
+       TAILQ_REMOVE(&proc_root, proc, next);
+       nr_procs--;
+
+       free(proc);
+}
+
+/*
+ * Iterator for the list of traced processes.  If a NULL pointer is given,
+ * return the first process in the list; otherwise, return the next process in
+ * the list.  Not stable with respect to list modifications.
+ */
+struct trace_proc *
+proc_next(struct trace_proc * proc)
+{
+
+       if (proc == NULL)
+               return TAILQ_FIRST(&proc_root);
+       else
+               return TAILQ_NEXT(proc, next);
+}
+
+/*
+ * Return the number of processes in the list of traced processes.
+ */
+unsigned int
+proc_count(void)
+{
+
+       return nr_procs;
+}
diff --git a/minix/usr.bin/trace/proc.h b/minix/usr.bin/trace/proc.h
new file mode 100644 (file)
index 0000000..509f523
--- /dev/null
@@ -0,0 +1,99 @@
+
+#include <sys/queue.h>
+
+/*
+ * The maximum nesting depth of parentheses/brackets.  The current maximum
+ * depth is something like six, for UDS control messages.  This constant can be
+ * increased as necessary without any problem.
+ */
+#define MAX_DEPTH      10
+
+/*
+ * The maximum size of text that may be recorded, including null terminator.
+ * Increasing this allows longer lines to be recorded and replayed without
+ * being cut short (see call_replay), but also increases memory usage.
+ */
+#define RECORD_BUFSZ   256
+
+struct trace_proc {
+       /* identity (public) */
+       pid_t pid;
+
+       /* data structure management (proc.c) */
+       TAILQ_ENTRY(trace_proc) next;
+
+       /* general process state (trace.c) */
+       char name[PROC_NAME_LEN];
+       unsigned int trace_flags;
+       reg_t last_pc;
+       reg_t last_sp;
+
+       /* call enter-to-leave state (call.c) */
+       int call_type;
+       vir_bytes m_addr;
+       message m_out;
+       const char *call_name;
+       unsigned int call_flags;
+       const struct call_handler *call_handler;
+       int call_result;
+
+       /* output state (output.c) */
+       int recording;
+       char outbuf[RECORD_BUFSZ];
+       size_t outlen;
+
+       /* formatting state (format.c) */
+       const char *next_sep;
+       int depth;
+       struct {
+               const char *sep;
+               int name;
+       } depths[MAX_DEPTH];
+
+       /* ioctl state (ioctl.c) */
+       int ioctl_index;
+       unsigned int ioctl_flags;
+};
+
+/* Trace flags. */
+#define TF_INCALL      0x01    /* the process has entered a system call */
+#define TF_SKIP                0x02    /* the system call result is to be skipped */
+#define TF_CTX_SKIP    0x04    /* skip call result only if context changes */
+#define TF_STOPPING    0x08    /* the process is expecting a SIGSTOP */
+#define TF_ATTACH      0x10    /* we have not started this process */
+#define TF_DETACH      0x20    /* detach from the process as soon as we can */
+#define TF_EXEC                0x40    /* the process may be performing an execve() */
+#define TF_NOCALL      0x80    /* no system call seen yet (for info only) */
+
+/* Trace classes, determining how the tracer engine should handle a call. */
+#define TC_NORMAL      0       /* normal call, no exceptions required */
+#define TC_EXEC                1       /* exec call, success on subsequent SIGSTOP */
+#define TC_SIGRET      2       /* sigreturn call, success on context change */
+
+/* Call flags. */
+#define CF_DONE                0x01    /* printing the call parameters is done */
+#define CF_NORETURN    0x02    /* the call does not return on success */
+#define CF_HIDE                0x04    /* do not print the current call */
+#define CF_IPC_ERR     0x08    /* a failure occurred at the IPC level */
+#define CF_REG_ERR     0x10    /* unable to retrieve the result register */
+#define CF_MSG_ERR     0x20    /* unable to copy in the reply message */
+
+/* Call types, determining how much has been printed up to the call split. */
+#define CT_NOTDONE     (0)     /* not all parameters have been printed yet */
+#define CT_DONE                (CF_DONE)       /* all parameters have been printed */
+#define CT_NORETURN    (CF_DONE | CF_NORETURN) /* the no-return call type */
+
+/* Put flags. */
+#define PF_FAILED      0x01    /* call failed, results may be invalid */
+#define PF_LOCADDR     0x02    /* pointer is into local address space */
+/* Yes, PF_LOCAL would conflict with the packet family definition.  Bah. */
+#define PF_ALT         0x04    /* alternative output (callee specific) */
+#define PF_STRING      PF_ALT  /* buffer is string (put_buf only) */
+#define PF_FULL                0x08    /* print full format (callee specific) */
+#define PF_PATH                (PF_STRING | PF_FULL)   /* flags for path names */
+#define PF_NONAME      0x10    /* default to no field names at this depth */
+
+/* I/O control flags. */
+#define IF_OUT         0x1     /* call to print outgoing (written) data */
+#define IF_IN          0x2     /* call to print incoming (read) data */
+#define IF_ALL         0x4     /* all fields printed (not really a bit) */
diff --git a/minix/usr.bin/trace/proto.h b/minix/usr.bin/trace/proto.h
new file mode 100644 (file)
index 0000000..27a8894
--- /dev/null
@@ -0,0 +1,130 @@
+
+/* call.c */
+void put_endpoint(struct trace_proc *proc, const char *name, endpoint_t endpt);
+void put_equals(struct trace_proc *proc);
+void put_result(struct trace_proc *proc);
+int default_out(struct trace_proc *proc, const message *m_out);
+void default_in(struct trace_proc *proc, const message *m_out,
+       const message *m_in, int failed);
+int call_enter(struct trace_proc *proc, int show_stack);
+void call_leave(struct trace_proc *proc, int skip);
+void call_replay(struct trace_proc *proc);
+const char *call_name(struct trace_proc *proc);
+
+/* error.c */
+const char *get_error_name(int err);
+
+/* escape.c */
+const char *get_escape(char c);
+
+/* format.c */
+void format_reset(struct trace_proc *proc);
+void format_set_sep(struct trace_proc *proc, const char *sep);
+void format_push_sep(struct trace_proc *proc);
+void put_field(struct trace_proc *proc, const char *name, const char *text);
+void put_open(struct trace_proc *proc, const char *name, int flags,
+       const char *string, const char *separator);
+void put_close(struct trace_proc *proc, const char *string);
+void put_fmt(struct trace_proc *proc, const char *fmt, ...)
+       __attribute__((__format__(__printf__, 2, 3)));
+void put_value(struct trace_proc *proc, const char *name, const char *fmt, ...)
+       __attribute__((__format__(__printf__, 3, 4)));
+int put_open_struct(struct trace_proc *proc, const char *name, int flags,
+       vir_bytes addr, void *ptr, size_t size);
+void put_close_struct(struct trace_proc *proc, int all);
+void put_ptr(struct trace_proc *proc, const char *name, vir_bytes addr);
+void put_buf(struct trace_proc *proc, const char *name, int flags,
+       vir_bytes addr, ssize_t size);
+void put_flags(struct trace_proc *proc, const char *name,
+       const struct flags *fp, unsigned int num, const char *fmt,
+       unsigned int value);
+void put_tail(struct trace_proc * proc, unsigned int count,
+       unsigned int printed);
+
+/* ioctl.c */
+void put_ioctl_req(struct trace_proc *proc, const char *name,
+       unsigned long req, int is_svrctl);
+int put_ioctl_arg_out(struct trace_proc *proc, const char *name,
+       unsigned long req, vir_bytes addr, int is_svrctl);
+void put_ioctl_arg_in(struct trace_proc *proc, const char *name, int failed,
+       unsigned long req, vir_bytes addr, int is_svrctl);
+
+/* kernel.c */
+int kernel_check(pid_t pid);
+int kernel_get_name(pid_t pid, char *name, size_t size);
+int kernel_is_service(pid_t pid);
+int kernel_get_syscall(pid_t pid, reg_t reg[3]);
+int kernel_get_retreg(pid_t pid, reg_t *retreg);
+vir_bytes kernel_get_stacktop(void);
+int kernel_get_context(pid_t pid, reg_t *pc, reg_t *sp, reg_t *fp);
+void kernel_put_stacktrace(struct trace_proc * proc);
+
+/* mem.c */
+int mem_get_data(pid_t pid, vir_bytes addr, void *ptr, size_t len);
+int mem_get_user(pid_t pid, vir_bytes addr, void *ptr, size_t len);
+
+/* pm.c */
+void put_struct_timeval(struct trace_proc *proc, const char *name, int flags,
+       vir_bytes addr);
+void put_time(struct trace_proc *proc, const char *name, time_t time);
+void put_groups(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr, int count);
+
+/* output.c */
+int output_init(const char *file);
+int output_error(void);
+void output_flush(void);
+void record_start(struct trace_proc *proc);
+void record_stop(struct trace_proc *proc);
+void record_clear(struct trace_proc *proc);
+int record_replay(struct trace_proc *proc);
+void put_newline(void);
+void put_text(struct trace_proc *proc, const char *text);
+void put_space(struct trace_proc *proc);
+void put_align(struct trace_proc *proc);
+
+/* proc.c */
+void proc_init(void);
+struct trace_proc *proc_add(pid_t pid);
+struct trace_proc *proc_get(pid_t pid);
+void proc_del(struct trace_proc *proc);
+struct trace_proc *proc_next(struct trace_proc *last);
+unsigned int proc_count(void);
+
+/* signal.c */
+const char *get_signal_name(int sig);
+
+/* trace.c */
+extern int allnames;
+extern unsigned int verbose;
+extern unsigned int valuesonly;
+
+/* vfs.c */
+void put_fd(struct trace_proc *proc, const char *name, int fd);
+void put_dev(struct trace_proc *proc, const char *name, dev_t dev);
+
+/* service */
+const struct calls pm_calls;
+const struct calls vfs_calls;
+const struct calls rs_calls;
+const struct calls vm_calls;
+const struct calls ipc_calls;
+
+/* ioctl/block.c */
+const char *block_ioctl_name(unsigned long req);
+int block_ioctl_arg(struct trace_proc *proc, unsigned long req, void *ptr,
+       int dir);
+
+/* ioctl/char.c */
+const char *char_ioctl_name(unsigned long req);
+int char_ioctl_arg(struct trace_proc *proc, unsigned long req, void *ptr,
+       int dir);
+
+/* ioctl/net.c */
+const char *net_ioctl_name(unsigned long req);
+int net_ioctl_arg(struct trace_proc *proc, unsigned long req, void *ptr,
+       int dir);
+
+/* ioctl/svrctl.c */
+const char *svrctl_name(unsigned long req);
+int svrctl_arg(struct trace_proc *proc, unsigned long req, void *ptr, int dir);
diff --git a/minix/usr.bin/trace/service/ipc.c b/minix/usr.bin/trace/service/ipc.c
new file mode 100644 (file)
index 0000000..21368d9
--- /dev/null
@@ -0,0 +1,445 @@
+/* This file is concerned with the IPC server, not with kernel-level IPC. */
+
+#include "inc.h"
+
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <sys/sem.h>
+
+static void
+put_key(struct trace_proc * proc, const char * name, key_t key)
+{
+
+       if (!valuesonly && key == IPC_PRIVATE)
+               put_field(proc, name, "IPC_PRIVATE");
+       else
+               put_value(proc, name, "%ld", key);
+}
+
+static const struct flags ipcget_flags[] = {
+       FLAG(IPC_CREAT),
+       FLAG(IPC_EXCL),
+};
+
+static int
+ipc_shmget_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_key(proc, "key", m_out->m_lc_ipc_shmget.key);
+       put_value(proc, "size", "%zu", m_out->m_lc_ipc_shmget.size);
+       put_flags(proc, "shmflg", ipcget_flags, COUNT(ipcget_flags), "0%o",
+           m_out->m_lc_ipc_shmget.flag);
+
+       return CT_DONE;
+}
+
+static void
+ipc_shmget_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       if (!failed)
+               put_value(proc, NULL, "%d", m_in->m_lc_ipc_shmget.retid);
+       else
+               put_result(proc);
+}
+
+static const struct flags shmat_flags[] = {
+       FLAG(SHM_RDONLY),
+       FLAG(SHM_RND),
+};
+
+static int
+ipc_shmat_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_value(proc, "shmid", "%d", m_out->m_lc_ipc_shmat.id);
+       put_ptr(proc, "shmaddr", (vir_bytes)m_out->m_lc_ipc_shmat.addr);
+       put_flags(proc, "shmflg", shmat_flags, COUNT(shmat_flags), "0x%x",
+           m_out->m_lc_ipc_shmat.flag);
+
+       return CT_DONE;
+}
+
+static void
+ipc_shmat_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       if (!failed)
+               put_ptr(proc, NULL, (vir_bytes)m_in->m_lc_ipc_shmat.retaddr);
+       else
+               put_result(proc);
+}
+
+static int
+ipc_shmdt_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_ptr(proc, "shmaddr", (vir_bytes)m_out->m_lc_ipc_shmdt.addr);
+
+       return CT_DONE;
+}
+
+static void
+put_shmctl_cmd(struct trace_proc * proc, const char * name, int cmd)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (cmd) {
+               TEXT(IPC_RMID);
+               TEXT(IPC_SET);
+               TEXT(IPC_STAT);
+               TEXT(SHM_STAT);
+               TEXT(SHM_INFO);
+               TEXT(IPC_INFO);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", cmd);
+}
+
+static const struct flags shm_mode_flags[] = {
+       FLAG(SHM_DEST),
+       FLAG(SHM_LOCKED),
+};
+
+static void
+put_struct_shmid_ds(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr)
+{
+       struct shmid_ds buf;
+       int set;
+
+       if (!put_open_struct(proc, name, flags, addr, &buf, sizeof(buf)))
+               return;
+
+       /* Is this an IPC_SET call?  Then print a small subset of fields.. */
+       set = (flags & PF_ALT);
+
+       put_open(proc, "shm_perm", 0, "{", ", ");
+
+       put_value(proc, "uid", "%u", buf.shm_perm.uid);
+       put_value(proc, "gid", "%u", buf.shm_perm.gid);
+       if (!set && verbose > 0) {
+               put_value(proc, "cuid", "%u", buf.shm_perm.cuid);
+               put_value(proc, "cgid", "%u", buf.shm_perm.cgid);
+       }
+       put_flags(proc, "mode", shm_mode_flags, COUNT(shm_mode_flags),
+           "0%03o", buf.shm_perm.mode);
+
+       put_close(proc, "}");
+
+       if (!set) {
+               put_value(proc, "shm_segsz", "%zu", buf.shm_segsz);
+               if (verbose > 0) {
+                       put_value(proc, "shm_lpid", "%d", buf.shm_lpid);
+                       put_value(proc, "shm_cpid", "%d", buf.shm_cpid);
+                       put_time(proc, "shm_atime", buf.shm_atime);
+                       put_time(proc, "shm_dtime", buf.shm_dtime);
+                       put_time(proc, "shm_ctime", buf.shm_ctime);
+               }
+               put_value(proc, "shm_nattch", "%u", buf.shm_nattch);
+       }
+
+       put_close_struct(proc, set || verbose > 0);
+}
+
+static int
+ipc_shmctl_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_value(proc, "shmid", "%d", m_out->m_lc_ipc_shmctl.id);
+       put_shmctl_cmd(proc, "cmd", m_out->m_lc_ipc_shmctl.cmd);
+
+       /* TODO: add support for the IPC_INFO and SHM_INFO structures.. */
+       switch (m_out->m_lc_ipc_shmctl.cmd) {
+       case IPC_STAT:
+       case SHM_STAT:
+               return CT_NOTDONE;
+
+       case IPC_SET:
+               put_struct_shmid_ds(proc, "buf", PF_ALT,
+                   (vir_bytes)m_out->m_lc_ipc_shmctl.buf);
+
+               return CT_DONE;
+
+       default:
+               put_ptr(proc, "buf", (vir_bytes)m_out->m_lc_ipc_shmctl.buf);
+
+               return CT_DONE;
+       }
+}
+
+static void
+ipc_shmctl_in(struct trace_proc * proc, const message * m_out,
+       const message * m_in, int failed)
+{
+
+       switch (m_out->m_lc_ipc_shmctl.cmd) {
+       case IPC_STAT:
+       case SHM_STAT:
+               put_struct_shmid_ds(proc, "buf", failed,
+                   (vir_bytes)m_out->m_lc_ipc_shmctl.buf);
+               put_equals(proc);
+
+               break;
+       }
+
+       if (!failed) {
+               switch (m_out->m_lc_ipc_shmctl.cmd) {
+               case SHM_INFO:
+               case SHM_STAT:
+               case IPC_INFO:
+                       put_value(proc, NULL, "%d", m_in->m_lc_ipc_shmctl.ret);
+
+                       return;
+               }
+       }
+
+       put_result(proc);
+}
+
+static int
+ipc_semget_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_key(proc, "key", m_out->m_lc_ipc_semget.key);
+       put_value(proc, "nsems", "%d", m_out->m_lc_ipc_semget.nr);
+       put_flags(proc, "semflg", ipcget_flags, COUNT(ipcget_flags), "0%o",
+           m_out->m_lc_ipc_semget.flag);
+
+       return CT_DONE;
+}
+
+static void
+ipc_semget_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       if (!failed)
+               put_value(proc, NULL, "%d", m_in->m_lc_ipc_semget.retid);
+       else
+               put_result(proc);
+}
+
+static void
+put_semctl_cmd(struct trace_proc * proc, const char * name, int cmd)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (cmd) {
+               TEXT(IPC_RMID);
+               TEXT(IPC_SET);
+               TEXT(IPC_STAT);
+               TEXT(GETNCNT);
+               TEXT(GETPID);
+               TEXT(GETVAL);
+               TEXT(GETALL);
+               TEXT(GETZCNT);
+               TEXT(SETVAL);
+               TEXT(SETALL);
+               TEXT(SEM_STAT);
+               TEXT(SEM_INFO);
+               TEXT(IPC_INFO);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", cmd);
+}
+
+static void
+put_struct_semid_ds(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr)
+{
+       struct semid_ds buf;
+       int set;
+
+       if (!put_open_struct(proc, name, flags, addr, &buf, sizeof(buf)))
+               return;
+
+       /* Is this an IPC_SET call?  Then print a small subset of fields.. */
+       set = (flags & PF_ALT);
+
+       put_open(proc, "sem_perm", 0, "{", ", ");
+
+       put_value(proc, "uid", "%u", buf.sem_perm.uid);
+       put_value(proc, "gid", "%u", buf.sem_perm.gid);
+       if (!set && verbose > 0) {
+               put_value(proc, "cuid", "%u", buf.sem_perm.cuid);
+               put_value(proc, "cgid", "%u", buf.sem_perm.cgid);
+       }
+       put_value(proc, "mode", "0%03o", buf.sem_perm.mode);
+
+       put_close(proc, "}");
+
+       if (!set) {
+               if (verbose > 0) {
+                       put_time(proc, "sem_otime", buf.sem_otime);
+                       put_time(proc, "sem_ctime", buf.sem_ctime);
+               }
+               put_value(proc, "sem_nsems", "%u", buf.sem_nsems);
+       }
+
+       put_close_struct(proc, set || verbose > 0);
+}
+
+
+static int
+ipc_semctl_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_value(proc, "semid", "%d", m_out->m_lc_ipc_semctl.id);
+       put_value(proc, "semnum", "%d", m_out->m_lc_ipc_semctl.num);
+       put_semctl_cmd(proc, "cmd", m_out->m_lc_ipc_semctl.cmd);
+
+       /* TODO: add support for the IPC_INFO and SEM_INFO structures.. */
+       switch (m_out->m_lc_ipc_semctl.cmd) {
+       case IPC_STAT:
+       case SEM_STAT:
+               return CT_NOTDONE;
+
+       case IPC_SET:
+               put_struct_semid_ds(proc, "buf", PF_ALT,
+                   (vir_bytes)m_out->m_lc_ipc_semctl.opt);
+
+               return CT_DONE;
+
+       case IPC_INFO:
+       case SEM_INFO:
+               put_ptr(proc, "buf", (vir_bytes)m_out->m_lc_ipc_semctl.opt);
+
+               return CT_DONE;
+
+       case GETALL:
+       case SETALL:
+               put_ptr(proc, "array", (vir_bytes)m_out->m_lc_ipc_semctl.opt);
+
+               return CT_DONE;
+
+       case SETVAL:
+               put_value(proc, "val", "%d", m_out->m_lc_ipc_semctl.opt);
+
+               return CT_DONE;
+
+       default:
+               return CT_DONE;
+       }
+}
+
+static void
+ipc_semctl_in(struct trace_proc * proc, const message * m_out,
+       const message * m_in, int failed)
+{
+
+       switch (m_out->m_lc_ipc_semctl.cmd) {
+       case IPC_STAT:
+       case SEM_STAT:
+               put_struct_semid_ds(proc, "buf", failed,
+                   (vir_bytes)m_out->m_lc_ipc_semctl.opt);
+               put_equals(proc);
+
+               break;
+       }
+
+       if (!failed) {
+               switch (m_out->m_lc_ipc_semctl.cmd) {
+               case GETNCNT:
+               case GETPID:
+               case GETVAL:
+               case GETZCNT:
+               case SEM_INFO:
+               case SEM_STAT:
+               case IPC_INFO:
+                       put_value(proc, NULL, "%d", m_in->m_lc_ipc_semctl.ret);
+                       return;
+               }
+       }
+       put_result(proc);
+}
+
+static const struct flags sem_flags[] = {
+       FLAG(IPC_NOWAIT),
+       FLAG(SEM_UNDO),
+};
+
+static void
+put_struct_sembuf(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr)
+{
+       struct sembuf buf;
+       int all;
+
+       if (!put_open_struct(proc, name, flags, addr, &buf, sizeof(buf)))
+               return;
+
+       all = FALSE;
+       put_value(proc, "sem_num", "%u", buf.sem_num);
+       put_value(proc, "sem_op", "%d", buf.sem_op);
+       if (verbose > 0 || (buf.sem_flg & ~SEM_UNDO) != 0) {
+               put_flags(proc, "sem_flg", sem_flags, COUNT(sem_flags), "0x%x",
+                  buf.sem_flg);
+               all = TRUE;
+       }
+
+       put_close_struct(proc, all);
+}
+
+static void
+put_sembuf_array(struct trace_proc * proc, const char * name, vir_bytes addr,
+       size_t count)
+{
+       struct sembuf buf[SEMOPM]; /* about 600 bytes, so OK for the stack */
+       size_t i;
+
+       if (valuesonly > 1 || count > SEMOPM ||
+           mem_get_data(proc->pid, addr, &buf, count * sizeof(buf[0])) != 0) {
+               put_ptr(proc, name, addr);
+
+               return;
+       }
+
+       put_open(proc, name, PF_NONAME, "[", ", ");
+       for (i = 0; i < count; i++)
+               put_struct_sembuf(proc, NULL, PF_LOCADDR, (vir_bytes)&buf[i]);
+       put_close(proc, "]");
+}
+
+static int
+ipc_semop_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_value(proc, "semid", "%d", m_out->m_lc_ipc_semop.id);
+       put_sembuf_array(proc, "sops", (vir_bytes)m_out->m_lc_ipc_semop.ops,
+           m_out->m_lc_ipc_semop.size);
+       put_value(proc, "nsops", "%zu", m_out->m_lc_ipc_semop.size);
+
+       return CT_DONE;
+}
+
+#define IPC_CALL(c) [((IPC_ ## c) - IPC_BASE)]
+
+static const struct call_handler ipc_map[] = {
+       IPC_CALL(SHMGET) = HANDLER("shmget", ipc_shmget_out, ipc_shmget_in),
+       IPC_CALL(SHMAT) = HANDLER("shmat", ipc_shmat_out, ipc_shmat_in),
+       IPC_CALL(SHMDT) = HANDLER("shmdt", ipc_shmdt_out, default_in),
+       IPC_CALL(SHMCTL) = HANDLER("shmctl", ipc_shmctl_out, ipc_shmctl_in),
+       IPC_CALL(SEMGET) = HANDLER("semget", ipc_semget_out, ipc_semget_in),
+       IPC_CALL(SEMCTL) = HANDLER("semctl", ipc_semctl_out, ipc_semctl_in),
+       IPC_CALL(SEMOP) = HANDLER("semop", ipc_semop_out, default_in),
+};
+
+const struct calls ipc_calls = {
+       .endpt = ANY,
+       .base = IPC_BASE,
+       .map = ipc_map,
+       .count = COUNT(ipc_map)
+};
diff --git a/minix/usr.bin/trace/service/pm.c b/minix/usr.bin/trace/service/pm.c
new file mode 100644 (file)
index 0000000..1561123
--- /dev/null
@@ -0,0 +1,1396 @@
+
+#include "inc.h"
+
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
+#include <sys/utsname.h>
+#include <sys/reboot.h>
+#include <minix/profile.h>
+
+static int
+pm_exit_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_value(proc, "status", "%d", m_out->m_lc_pm_exit.status);
+
+       return CT_NORETURN;
+}
+
+static const struct flags waitpid_options[] = {
+       FLAG(WNOHANG),
+       FLAG(WUNTRACED),
+       FLAG(WALTSIG),
+       FLAG(WALLSIG),
+       FLAG(WNOWAIT),
+       FLAG(WNOZOMBIE),
+       FLAG(WOPTSCHECKED),
+};
+
+static void
+put_waitpid_status(struct trace_proc * proc, const char * name, int status)
+{
+       const char *signame;
+       int sig;
+
+       /*
+        * There is no suitable set of macros to be used here, so we're going
+        * to invent our own: W_EXITED, W_SIGNALED, and W_STOPPED.  Hopefully
+        * they are sufficiently clear even though they don't actually exist.
+        * The code below is downright messy, but it also ensures that no bits
+        * are set unexpectedly in the status.
+        */
+       if (!valuesonly && WIFEXITED(status) &&
+           status == W_EXITCODE(WEXITSTATUS(status), 0)) {
+               put_value(proc, name, "W_EXITED(%d)",
+                   WEXITSTATUS(status));
+
+               return;
+       }
+
+       /* WCOREDUMP() actually returns WCOREFLAG or 0, but better safe.. */
+       if (!valuesonly && WIFSIGNALED(status) && status == (W_EXITCODE(0,
+           WTERMSIG(status)) | (WCOREDUMP(status) ? WCOREFLAG : 0))) {
+               sig = WTERMSIG(status);
+
+               if ((signame = get_signal_name(sig)) != NULL)
+                       put_value(proc, name, "W_SIGNALED(%s)", signame);
+               else
+                       put_value(proc, name, "W_SIGNALED(%u)", sig);
+
+               if (WCOREDUMP(status))
+                       put_text(proc, "|WCOREDUMP");
+
+               return;
+       }
+
+       if (!valuesonly && WIFSTOPPED(status) &&
+           status == W_STOPCODE(WSTOPSIG(status))) {
+               sig = WSTOPSIG(status);
+
+               if ((signame = get_signal_name(sig)) != NULL)
+                       put_value(proc, name, "W_STOPPED(%s)", signame);
+               else
+                       put_value(proc, name, "W_STOPPED(%u)", sig);
+
+               return;
+       }
+
+       /*
+        * If we get here, either valuesonly is enabled or the resulting status
+        * is not one we recognize, for example because extra bits are set.
+        */
+       put_value(proc, name, "0x%04x", status);
+}
+
+static int
+pm_waitpid_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_value(proc, "pid", "%d", m_out->m_lc_pm_waitpid.pid);
+
+       return CT_NOTDONE;
+}
+
+static void
+pm_waitpid_in(struct trace_proc * proc, const message * m_out,
+       const message * m_in, int failed)
+{
+
+       /*
+        * If the result is zero, there is no status to show.  Also, since the
+        * status is returned in the result message, we cannot print the user-
+        * given pointer.  Instead, upon failure we show "&.." to indicate an
+        * unknown pointer.
+        */
+       if (!failed && m_in->m_type > 0)
+               put_waitpid_status(proc, "status",
+                   m_in->m_pm_lc_waitpid.status);
+       else
+               put_field(proc, "status", "&..");
+       put_flags(proc, "options", waitpid_options, COUNT(waitpid_options),
+           "0x%x", m_out->m_lc_pm_waitpid.options);
+       put_equals(proc);
+       put_result(proc);
+}
+
+static void
+pm_getpid_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       put_result(proc);
+       if (!failed) {
+               put_open(proc, NULL, 0, "(", ", ");
+               put_value(proc, "ppid", "%d", m_in->m_pm_lc_getpid.parent_pid);
+               put_close(proc, ")");
+       }
+}
+
+/* This function is shared between setuid and seteuid. */
+static int
+pm_setuid_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_value(proc, "uid", "%u", m_out->m_lc_pm_setuid.uid);
+
+       return CT_DONE;
+}
+
+static void
+pm_getuid_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       put_result(proc);
+       if (!failed) {
+               put_open(proc, NULL, 0, "(", ", ");
+               put_value(proc, "euid", "%u", m_in->m_pm_lc_getuid.euid);
+               put_close(proc, ")");
+       }
+}
+
+static int
+pm_stime_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_time(proc, "time", m_out->m_lc_pm_time.sec);
+
+       return CT_DONE;
+}
+
+static void
+put_signal(struct trace_proc * proc, const char * name, int sig)
+{
+       const char *signame;
+
+       if (!valuesonly && (signame = get_signal_name(sig)) != NULL)
+               put_field(proc, name, signame);
+       else
+               put_value(proc, name, "%d", sig);
+}
+
+static void
+put_ptrace_req(struct trace_proc * proc, const char * name, int req)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (req) {
+               TEXT(T_STOP);
+               TEXT(T_OK);
+               TEXT(T_ATTACH);
+               TEXT(T_DETACH);
+               TEXT(T_RESUME);
+               TEXT(T_STEP);
+               TEXT(T_SYSCALL);
+               TEXT(T_EXIT);
+               TEXT(T_GETINS);
+               TEXT(T_GETDATA);
+               TEXT(T_GETUSER);
+               TEXT(T_SETINS);
+               TEXT(T_SETDATA);
+               TEXT(T_SETUSER);
+               TEXT(T_SETOPT);
+               TEXT(T_GETRANGE);
+               TEXT(T_SETRANGE);
+               TEXT(T_READB_INS);
+               TEXT(T_WRITEB_INS);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", req);
+}
+
+static void
+put_struct_ptrace_range(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr)
+{
+       struct ptrace_range pr;
+
+       if (!put_open_struct(proc, name, flags, addr, &pr, sizeof(pr)))
+               return;
+
+       if (!valuesonly && pr.pr_space == TS_INS)
+               put_field(proc, "pr_space", "TS_INS");
+       else if (!valuesonly && pr.pr_space == TS_DATA)
+               put_field(proc, "pr_space", "TS_DATA");
+       else
+               put_value(proc, "pr_space", "%d", pr.pr_space);
+       put_value(proc, "pr_addr", "0x%lx", pr.pr_addr);
+       put_ptr(proc, "pr_ptr", (vir_bytes)pr.pr_ptr);
+       put_value(proc, "pr_size", "%zu", pr.pr_size);
+
+       put_close_struct(proc, TRUE /*all*/);
+}
+
+static int
+pm_ptrace_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_ptrace_req(proc, "req", m_out->m_lc_pm_ptrace.req);
+       put_value(proc, "pid", "%d", m_out->m_lc_pm_ptrace.pid);
+
+       switch (m_out->m_lc_pm_ptrace.req) {
+       case T_GETINS:
+       case T_GETDATA:
+       case T_GETUSER:
+       case T_READB_INS:
+               put_value(proc, "addr", "0x%lx", m_out->m_lc_pm_ptrace.addr);
+               put_value(proc, "data", "%ld", m_out->m_lc_pm_ptrace.data);
+               break;
+       case T_SETINS:
+       case T_SETDATA:
+       case T_SETUSER:
+       case T_WRITEB_INS:
+               put_value(proc, "addr", "0x%lx", m_out->m_lc_pm_ptrace.addr);
+               put_value(proc, "data", "0x%lx", m_out->m_lc_pm_ptrace.data);
+               break;
+       case T_RESUME:
+       case T_STEP:
+       case T_SYSCALL:
+               put_value(proc, "addr", "%ld", m_out->m_lc_pm_ptrace.addr);
+               put_signal(proc, "data", m_out->m_lc_pm_ptrace.data);
+               break;
+       case T_GETRANGE:
+       case T_SETRANGE:
+               put_struct_ptrace_range(proc, "addr", 0,
+                   m_out->m_lc_pm_ptrace.addr);
+               put_value(proc, "data", "%ld", m_out->m_lc_pm_ptrace.data);
+               break;
+       default:
+               put_value(proc, "addr", "%ld", m_out->m_lc_pm_ptrace.addr);
+               put_value(proc, "data", "%ld", m_out->m_lc_pm_ptrace.data);
+               break;
+       }
+
+       return CT_DONE;
+}
+
+static void
+pm_ptrace_in(struct trace_proc * proc, const message * m_out,
+       const message * m_in, int failed)
+{
+
+       if (!failed) {
+               switch (m_out->m_lc_pm_ptrace.req) {
+               case T_GETINS:
+               case T_GETDATA:
+               case T_GETUSER:
+               case T_READB_INS:
+                       put_value(proc, NULL, "0x%lx",
+                           m_in->m_pm_lc_ptrace.data);
+                       return;
+               }
+       }
+
+       put_result(proc);
+}
+
+void
+put_groups(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr, int count)
+{
+       gid_t groups[NGROUPS_MAX];
+       int i;
+
+       if ((flags & PF_FAILED) || valuesonly || count < 0 ||
+           count > NGROUPS_MAX || (count > 0 && mem_get_data(proc->pid, addr,
+           groups, count * sizeof(groups[0])) < 0)) {
+               if (flags & PF_LOCADDR)
+                       put_field(proc, name, "&..");
+               else
+                       put_ptr(proc, name, addr);
+
+               return;
+       }
+
+       put_open(proc, name, PF_NONAME, "[", ", ");
+       for (i = 0; i < count; i++)
+               put_value(proc, NULL, "%u", groups[i]);
+       put_close(proc, "]");
+}
+
+static int
+pm_setgroups_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_value(proc, "ngroups", "%d", m_out->m_lc_pm_groups.num);
+       put_groups(proc, "grouplist", 0, m_out->m_lc_pm_groups.ptr,
+           m_out->m_lc_pm_groups.num);
+
+       return CT_DONE;
+}
+
+static int
+pm_getgroups_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_value(proc, "ngroups", "%d", m_out->m_lc_pm_groups.num);
+
+       return CT_NOTDONE;
+}
+
+static void
+pm_getgroups_in(struct trace_proc * proc, const message * m_out,
+       const message * m_in, int failed)
+{
+
+       put_groups(proc, "grouplist", failed, m_out->m_lc_pm_groups.ptr,
+           m_in->m_type);
+       put_equals(proc);
+       put_result(proc);
+}
+
+static int
+pm_kill_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_value(proc, "pid", "%d", m_out->m_lc_pm_sig.pid);
+       put_signal(proc, "sig", m_out->m_lc_pm_sig.nr);
+
+       return CT_DONE;
+}
+
+/* This function is shared between setgid and setegid. */
+static int
+pm_setgid_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_value(proc, "gid", "%u", m_out->m_lc_pm_setgid.gid);
+
+       return CT_DONE;
+}
+
+static void
+pm_getgid_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       put_result(proc);
+       if (!failed) {
+               put_open(proc, NULL, 0, "(", ", ");
+               put_value(proc, "egid", "%u", m_in->m_pm_lc_getgid.egid);
+               put_close(proc, ")");
+       }
+}
+
+static int
+put_frame_string(struct trace_proc * proc, vir_bytes frame, size_t len,
+       vir_bytes addr)
+{
+       vir_bytes stacktop, offset;
+
+       /*
+        * The addresses in the frame assume that the process has already been
+        * changed, and the top of the frame is now located at the new process
+        * stack top, which is a hardcoded system-global value.  In order to
+        * print the strings, we must convert back each address to its location
+        * within the given frame.
+        */
+       stacktop = kernel_get_stacktop();
+
+       if (addr >= stacktop)
+               return FALSE;
+       offset = stacktop - addr;
+       if (offset >= len)
+               return FALSE;
+       addr = frame + len - offset;
+
+       /*
+        * TODO: while using put_buf() is highly convenient, it does require at
+        * least one copy operation per printed string.  The strings are very
+        * likely to be consecutive in memory, so copying in larger chunks at
+        * once would be preferable.  Also, if copying from the frame fails,
+        * put_buf() will print the string address as we corrected it above,
+        * rather than the address as found in the frame.  A copy failure would
+        * always be a case of malice on the traced process's behalf, though.
+        */
+       put_buf(proc, NULL, PF_STRING, addr, len - offset);
+
+       return TRUE;
+}
+
+/*
+ * Print the contents of the exec frame, which includes both pointers and
+ * actual string data for the arguments and environment variables to be used.
+ * Even though we know that the entire frame is not going to exceed ARG_MAX
+ * bytes, this is too large a size for a static buffer, and we'd like to avoid
+ * allocating large dynamic buffers as well.  The situation is complicated by
+ * the fact that any string in the frame may run up to the end of the frame.
+ */
+static void
+put_exec_frame(struct trace_proc * proc, vir_bytes addr, size_t len)
+{
+       void *argv[64];
+       size_t off, chunk;
+       unsigned int i, count, max, argv_max, envp_max;
+       int first, ok, nulls;
+
+       if (valuesonly) {
+               put_ptr(proc, "frame", addr);
+               put_value(proc, "framelen", "%zu", len);
+
+               return;
+       }
+
+       if (verbose == 0) {
+               argv_max = 16;
+               envp_max = 0;
+       } else if (verbose == 1)
+               argv_max = envp_max = 64;
+       else
+               argv_max = envp_max = INT_MAX;
+
+       off = sizeof(int); /* skip 'argc' at the start of the frame */
+       first = TRUE;
+       ok = TRUE;
+       nulls = 0;
+       count = 0;
+       max = argv_max;
+
+       do {
+               chunk = sizeof(argv);
+               if (chunk > len - off)
+                       chunk = len - off;
+
+               if (mem_get_data(proc->pid, addr + off, argv, chunk) != 0)
+                       break;
+
+               if (first) {
+                       put_open(proc, "argv", PF_NONAME, "[", ", ");
+
+                       first = FALSE;
+               }
+
+               for (i = 0; i < chunk / sizeof(void *) && ok; i++) {
+                       if (argv[i] == NULL) {
+                               if (count > max)
+                                       put_tail(proc, count, max);
+                               put_close(proc, "]");
+                               if (nulls++ == 0) {
+                                       put_open(proc, "envp", PF_NONAME, "[",
+                                           ", ");
+                                       count = 0;
+                                       max = envp_max;
+                               } else
+                                       break; /* two NULL pointers: done! */
+                       } else if (count++ < max)
+                               ok = put_frame_string(proc, addr, len,
+                                   (vir_bytes)argv[i]);
+               }
+
+               off += chunk;
+       } while (nulls < 2 && ok);
+
+       /*
+        * Handle failure cases, implied by not reaching the second NULL
+        * in the array.  Successful completion is handled in the loop above.
+        * Note that 'ok' is not always cleared on failure, as it is used only
+        * to break out of the outer loop.
+        */
+       if (first) {
+               put_ptr(proc, "argv", addr + off);
+               put_field(proc, "envp", "&..");
+       } else if (nulls < 2) {
+               put_tail(proc, 0, 0);
+               put_close(proc, "]");
+               if (nulls < 1) {
+                       put_open(proc, "envp", PF_NONAME, "[", ", ");
+                       put_tail(proc, 0, 0);
+                       put_close(proc, "]");
+               }
+       }
+}
+
+static int
+pm_exec_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_buf(proc, "path", PF_PATH, m_out->m_lc_pm_exec.name,
+           m_out->m_lc_pm_exec.namelen);
+       put_exec_frame(proc, m_out->m_lc_pm_exec.frame,
+           m_out->m_lc_pm_exec.framelen);
+
+       return CT_NORETURN;
+}
+
+/* The idea is that this function may one day print a human-readable time. */
+void
+put_time(struct trace_proc * proc, const char * name, time_t time)
+{
+
+       put_value(proc, name, "%"PRId64, time);
+}
+
+void
+put_struct_timeval(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr)
+{
+       struct timeval tv;
+
+       /* No field names; they just make things harder to read. */
+       if (!put_open_struct(proc, name, flags | PF_NONAME, addr, &tv,
+           sizeof(tv)))
+               return;
+
+       if (flags & PF_ALT)
+               put_time(proc, "tv_sec", tv.tv_sec);
+       else
+               put_value(proc, "tv_sec", "%"PRId64, tv.tv_sec);
+       put_value(proc, "tv_usec", "%d", tv.tv_usec);
+
+       put_close_struct(proc, TRUE /*all*/);
+}
+
+static void
+put_struct_itimerval(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr)
+{
+       struct itimerval it;
+
+       /*
+        * This used to pass PF_NONAME, but the layout may not be clear enough
+        * without names.  It does turn simple alarm(1) calls into rather
+        * lengthy output, though.
+        */
+       if (!put_open_struct(proc, name, flags, addr, &it, sizeof(it)))
+               return;
+
+       put_struct_timeval(proc, "it_interval", PF_LOCADDR,
+           (vir_bytes)&it.it_interval);
+       put_struct_timeval(proc, "it_value", PF_LOCADDR,
+           (vir_bytes)&it.it_value);
+
+       put_close_struct(proc, TRUE /*all*/);
+}
+
+static void
+put_itimer_which(struct trace_proc * proc, const char * name, int which)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (which) {
+               TEXT(ITIMER_REAL);
+               TEXT(ITIMER_VIRTUAL);
+               TEXT(ITIMER_PROF);
+               TEXT(ITIMER_MONOTONIC);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", which);
+}
+
+static const char *
+pm_itimer_name(const message * m_out)
+{
+
+       return (m_out->m_lc_pm_itimer.value != 0) ? "setitimer" : "getitimer";
+}
+
+static int
+pm_itimer_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_itimer_which(proc, "which", m_out->m_lc_pm_itimer.which);
+       if (m_out->m_lc_pm_itimer.value != 0) {
+               put_struct_itimerval(proc, "value", 0,
+                   m_out->m_lc_pm_itimer.value);
+
+               /*
+                * If there will be no old values to print, finish the call
+                * now.  For setitimer only; getitimer may not pass NULL.
+                */
+               if (m_out->m_lc_pm_itimer.ovalue == 0) {
+                       put_ptr(proc, "ovalue", 0);
+
+                       return CT_DONE;
+               }
+       }
+
+       return CT_NOTDONE;
+}
+
+static void
+pm_itimer_in(struct trace_proc * proc, const message * m_out,
+       const message * __unused m_in, int failed)
+{
+
+       if (m_out->m_lc_pm_itimer.value == 0 ||
+           m_out->m_lc_pm_itimer.ovalue != 0) {
+               put_struct_itimerval(proc,
+                   (m_out->m_lc_pm_itimer.value != 0) ? "ovalue" : "value",
+                   failed, m_out->m_lc_pm_itimer.ovalue);
+               put_equals(proc);
+       }
+       put_result(proc);
+}
+
+static void
+put_struct_mcontext(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr)
+{
+       mcontext_t ctx;
+
+       if (!put_open_struct(proc, name, flags, addr, &ctx, sizeof(ctx)))
+               return;
+
+       /*
+        * TODO: print actual fields.  Then again, the ones that are saved and
+        * restored (FPU state) are hardly interesting enough to print..
+        */
+
+       put_close_struct(proc, FALSE /*all*/);
+}
+
+static int
+pm_getmcontext_out(struct trace_proc * proc, const message * m_out)
+{
+
+       return CT_NOTDONE;
+}
+
+static void
+pm_getmcontext_in(struct trace_proc * proc, const message * m_out,
+       const message * m_in, int failed)
+{
+
+       put_struct_mcontext(proc, "mcp", failed, m_out->m_lc_pm_mcontext.ctx);
+       put_equals(proc);
+       put_result(proc);
+}
+
+static int
+pm_setmcontext_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_struct_mcontext(proc, "mcp", 0, m_out->m_lc_pm_mcontext.ctx);
+
+       return CT_DONE;
+}
+
+static void
+put_sigset(struct trace_proc * proc, const char * name, sigset_t set)
+{
+       const char *signame;
+       unsigned int count, unknown;
+       int sig, invert;
+
+       /*
+        * First decide whether we should print a normal or an inverted mask.
+        * Unfortunately, depending on the place, a filled set may or may not
+        * have bits outside the 1..NSIG range set.  Therefore, we ignore the
+        * bits outside this range entirely, and use simple heuristics to
+        * decide whether to show an inverted set.  If we know all the signal
+        * names for either set and not the other, show that one; otherwise,
+        * show an inverted mask if at least 3/4th of the bits are set.
+        */
+       count = 0;
+       unknown = 0;
+       for (sig = 1; sig < NSIG; sig++) {
+               if (sigismember(&set, sig))
+                       count++;
+               if (get_signal_name(sig) == NULL)
+                       unknown |= 1 << !!sigismember(&set, sig);
+       }
+       if (unknown == 1 /*for unset bit*/ || unknown == 2 /*for set bit*/)
+               invert = unknown - 1;
+       else
+               invert = (count >= (NSIG - 1) * 3 / 4);
+
+       put_open(proc, name, PF_NONAME, invert ? "~[" : "[", " ");
+
+       for (sig = 1; sig < NSIG; sig++) {
+               /* Note that sigismember() may not strictly return 0 or 1.. */
+               if (!sigismember(&set, sig) != invert)
+                       continue;
+
+               if ((signame = get_signal_name(sig)) != NULL) {
+                       /* Skip the "SIG" prefix for brevity. */
+                       if (!strncmp(signame, "SIG", 3))
+                               put_field(proc, NULL, &signame[3]);
+                       else
+                               put_field(proc, NULL, signame);
+               } else
+                       put_value(proc, NULL, "%d", sig);
+       }
+
+       put_close(proc, "]");
+}
+
+static const struct flags sa_flags[] = {
+       FLAG(SA_ONSTACK),
+       FLAG(SA_RESTART),
+       FLAG(SA_RESETHAND),
+       FLAG(SA_NODEFER),
+       FLAG(SA_NOCLDSTOP),
+       FLAG(SA_NOCLDWAIT),
+#ifdef SA_SIGINFO
+       FLAG(SA_SIGINFO),
+#endif
+       FLAG(SA_NOKERNINFO)
+};
+
+static void
+put_sa_handler(struct trace_proc * proc, const char * name, vir_bytes handler)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch ((int)handler) {
+               case (int)SIG_DFL: text = "SIG_DFL"; break;
+               case (int)SIG_IGN: text = "SIG_IGN"; break;
+               case (int)SIG_HOLD: text = "SIG_HOLD"; break;
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_ptr(proc, name, handler);
+}
+
+static void
+put_struct_sigaction(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr)
+{
+       struct sigaction sa;
+
+       if (!put_open_struct(proc, name, flags, addr, &sa, sizeof(sa)))
+               return;
+
+       put_sa_handler(proc, "sa_handler", (vir_bytes)sa.sa_handler);
+
+       if (verbose > 1)
+               put_sigset(proc, "sa_mask", sa.sa_mask);
+
+       /* A somewhat lame attempt to reduce noise a bit. */
+       if ((sa.sa_flags & ~(SA_ONSTACK | SA_RESTART | SA_RESETHAND |
+           SA_NODEFER)) != 0 || sa.sa_handler != SIG_DFL || verbose > 0)
+               put_flags(proc, "sa_flags", sa_flags, COUNT(sa_flags), "0x%x",
+                   sa.sa_flags);
+
+       put_close_struct(proc, verbose > 1);
+}
+
+static int
+pm_sigaction_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_signal(proc, "signal", m_out->m_lc_pm_sig.nr);
+       put_struct_sigaction(proc, "act", 0, m_out->m_lc_pm_sig.act);
+
+       /* If there will be no old values to print, finish the call now. */
+       if (m_out->m_lc_pm_sig.oact == 0) {
+               put_ptr(proc, "oact", 0);
+               return CT_DONE;
+       } else
+               return CT_NOTDONE;
+}
+
+static void
+pm_sigaction_in(struct trace_proc * proc, const message * m_out,
+       const message * __unused m_in, int failed)
+{
+
+       if (m_out->m_lc_pm_sig.oact != 0) {
+               put_struct_sigaction(proc, "oact", failed,
+                   m_out->m_lc_pm_sig.oact);
+               put_equals(proc);
+       }
+       put_result(proc);
+}
+
+static int
+pm_sigsuspend_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_sigset(proc, "set", m_out->m_lc_pm_sigset.set);
+
+       return CT_DONE;
+}
+
+static int
+pm_sigpending_out(struct trace_proc * __unused proc,
+       const message * __unused m_out)
+{
+
+       return CT_NOTDONE;
+}
+
+static void
+pm_sigpending_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       if (!failed)
+               put_sigset(proc, "set", m_in->m_pm_lc_sigset.set);
+       else
+               put_field(proc, "set", "&..");
+       put_equals(proc);
+       put_result(proc);
+}
+
+static void
+put_sigprocmask_how(struct trace_proc * proc, const char * name, int how)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (how) {
+               case SIG_INQUIRE: /* pseudocode, print something else */
+               TEXT(SIG_BLOCK);
+               TEXT(SIG_UNBLOCK);
+               TEXT(SIG_SETMASK);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", how);
+}
+
+static int
+pm_sigprocmask_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_sigprocmask_how(proc, "how", m_out->m_lc_pm_sigset.how);
+       if (m_out->m_lc_pm_sigset.how == SIG_INQUIRE)
+               put_ptr(proc, "set", 0);
+       else
+               put_sigset(proc, "set", m_out->m_lc_pm_sigset.set);
+
+       return CT_NOTDONE;
+}
+
+static void
+pm_sigprocmask_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       if (!failed)
+               put_sigset(proc, "oset", m_in->m_pm_lc_sigset.set);
+       else
+               put_field(proc, "oset", "&..");
+       put_equals(proc);
+       put_result(proc);
+}
+
+static int
+pm_sigreturn_out(struct trace_proc * proc, const message * m_out)
+{
+       struct sigcontext scp;
+
+       if (put_open_struct(proc, "scp", 0, m_out->m_lc_pm_sigset.ctx, &scp,
+           sizeof(scp))) {
+               if (verbose == 1) {
+#if defined(__i386__)
+                       put_ptr(proc, "sc_eip", scp.sc_eip);
+                       put_ptr(proc, "sc_esp", scp.sc_esp);
+#elif defined(__arm__)
+                       put_ptr(proc, "sc_pc", scp.sc_pc);
+                       put_ptr(proc, "sc_usr_sp", scp.sc_usr_sp);
+#endif
+               }
+
+               /*
+                * We deliberately print the signal set from the message rather
+                * than from the structure, since in theory they may be
+                * different and PM uses the one from the message only.
+                */
+               put_sigset(proc, "sc_mask", m_out->m_lc_pm_sigset.set);
+
+               /*
+                * TODO: print some other fields, although it is probably not
+                * useful to print all registers even with verbose > 1?
+                */
+               put_close_struct(proc, FALSE /*all*/);
+       }
+
+       return CT_NORETURN;
+}
+
+static void
+pm_sigreturn_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * __unused m_in, int failed)
+{
+
+       if (failed) {
+               put_equals(proc);
+               put_result(proc);
+       }
+}
+
+static void
+put_sysuname_field(struct trace_proc * proc, const char * name, int field)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (field) {
+               TEXT(_UTS_ARCH);
+               TEXT(_UTS_KERNEL);
+               TEXT(_UTS_MACHINE);
+               TEXT(_UTS_HOSTNAME);
+               TEXT(_UTS_NODENAME);
+               TEXT(_UTS_RELEASE);
+               TEXT(_UTS_VERSION);
+               TEXT(_UTS_SYSNAME);
+               TEXT(_UTS_BUS);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", field);
+}
+
+static int
+pm_sysuname_out(struct trace_proc * proc, const message * m_out)
+{
+
+       if (!valuesonly && m_out->m_lc_pm_sysuname.req == _UTS_GET)
+               put_field(proc, "req", "_UTS_GET");
+       else if (!valuesonly && m_out->m_lc_pm_sysuname.req == _UTS_SET)
+               put_field(proc, "req", "_UTS_SET");
+       else
+               put_value(proc, "req", "%d", m_out->m_lc_pm_sysuname.req);
+       put_sysuname_field(proc, "field", m_out->m_lc_pm_sysuname.field);
+
+       if (m_out->m_lc_pm_sysuname.req == _UTS_GET)
+               return CT_NOTDONE;
+
+       put_buf(proc, "value", PF_STRING, m_out->m_lc_pm_sysuname.value,
+           m_out->m_lc_pm_sysuname.len);
+       put_value(proc, "len", "%d", m_out->m_lc_pm_sysuname.len);
+       return CT_DONE;
+}
+
+static void
+pm_sysuname_in(struct trace_proc * proc, const message * m_out,
+       const message * m_in, int failed)
+{
+
+       if (m_out->m_lc_pm_sysuname.req == _UTS_GET) {
+               put_buf(proc, "value", failed | PF_STRING,
+                   m_out->m_lc_pm_sysuname.value, m_in->m_type);
+               put_value(proc, "len", "%d", m_out->m_lc_pm_sysuname.len);
+               put_equals(proc);
+       }
+       put_result(proc);
+}
+
+static void
+put_priority_which(struct trace_proc * proc, const char * name, int which)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (which) {
+               TEXT(PRIO_PROCESS);
+               TEXT(PRIO_PGRP);
+               TEXT(PRIO_USER);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", which);
+}
+
+static int
+pm_getpriority_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_priority_which(proc, "which", m_out->m_lc_pm_priority.which);
+       put_value(proc, "who", "%d", m_out->m_lc_pm_priority.who);
+
+       return CT_DONE;
+}
+
+static void
+pm_getpriority_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       if (!failed)
+               put_value(proc, NULL, "%d", m_in->m_type + PRIO_MIN);
+       else
+               put_result(proc);
+}
+
+static int
+pm_setpriority_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_priority_which(proc, "which", m_out->m_lc_pm_priority.which);
+       put_value(proc, "who", "%d", m_out->m_lc_pm_priority.who);
+       put_value(proc, "prio", "%d", m_out->m_lc_pm_priority.prio);
+
+       return CT_DONE;
+}
+
+static int
+pm_gettimeofday_out(struct trace_proc * __unused proc,
+       const message * __unused m_out)
+{
+
+       return CT_NOTDONE;
+}
+
+static void
+put_timespec_as_timeval(struct trace_proc * proc, const char * name,
+       time_t sec, long nsec)
+{
+
+       /* No field names within the structure. */
+       put_open(proc, name, PF_NONAME, "{", ", ");
+
+       put_time(proc, "tv_sec", sec);
+       put_value(proc, "tv_usec", "%ld", nsec / 1000);
+
+       put_close(proc, "}");
+}
+
+static void
+pm_gettimeofday_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       if (!failed) {
+               /*
+                * The system call returns values which do not match the call
+                * being made, so just like libc, we have to correct..
+                */
+               put_timespec_as_timeval(proc, "tp", m_in->m_pm_lc_time.sec,
+                   m_in->m_pm_lc_time.nsec);
+       } else
+               put_field(proc, "tp", "&..");
+       put_ptr(proc, "tzp", 0); /* not part of the system call (yet) */
+
+       put_equals(proc);
+       put_result(proc);
+}
+
+static int
+pm_getsid_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_value(proc, "pid", "%d", m_out->m_lc_pm_getsid.pid);
+
+       return CT_DONE;
+}
+
+static void
+put_clockid(struct trace_proc * proc, const char * name, clockid_t clock_id)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (clock_id) {
+               TEXT(CLOCK_REALTIME);
+#ifdef CLOCK_VIRTUAL
+               TEXT(CLOCK_VIRTUAL);
+#endif
+#ifdef CLOCK_PROF
+               TEXT(CLOCK_PROF);
+#endif
+               TEXT(CLOCK_MONOTONIC);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", clock_id);
+}
+
+static void
+put_clock_timespec(struct trace_proc * proc, const char * name, int flags,
+       time_t sec, long nsec)
+{
+
+       if (flags & PF_FAILED) {
+               put_field(proc, name, "&..");
+
+               return;
+       }
+
+       /* No field names within the structure. */
+       put_open(proc, name, PF_NONAME, "{", ", ");
+
+       if (flags & PF_ALT)
+               put_time(proc, "tv_sec", sec);
+       else
+               put_value(proc, "tv_sec", "%"PRId64, sec);
+       put_value(proc, "tv_nsec", "%ld", nsec);
+
+       put_close(proc, "}");
+}
+
+/* This function is shared between clock_getres and clock_gettime. */
+static int
+pm_clock_get_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_clockid(proc, "clock_id", m_out->m_lc_pm_time.clk_id);
+
+       return CT_NOTDONE;
+}
+
+static void
+pm_clock_getres_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       put_clock_timespec(proc, "res", failed, m_in->m_pm_lc_time.sec,
+           m_in->m_pm_lc_time.nsec);
+       put_equals(proc);
+       put_result(proc);
+}
+
+/*
+ * Same as pm_clock_getres_in, but different field name and the option to print
+ * at least some results as time strings (in the future).
+ */
+static void
+pm_clock_gettime_in(struct trace_proc * proc, const message * m_out,
+       const message * m_in, int failed)
+{
+       int flags;
+
+       flags = failed;
+       if (m_out->m_lc_pm_time.clk_id == CLOCK_REALTIME)
+               flags |= PF_ALT; /* TODO: make this print a time string. */
+
+       put_clock_timespec(proc, "tp", flags, m_in->m_pm_lc_time.sec,
+           m_in->m_pm_lc_time.nsec);
+       put_equals(proc);
+       put_result(proc);
+}
+
+static const char *
+pm_clock_settime_name(const message * m_out)
+{
+
+       if (m_out->m_lc_pm_time.now == 0)
+               return "adjtime";
+       else
+               return "clock_settime";
+}
+
+static int
+pm_clock_settime_out(struct trace_proc * proc, const message * m_out)
+{
+       int flags;
+
+       /* These two calls just look completely different.. */
+       if (m_out->m_lc_pm_time.now == 0) {
+               put_timespec_as_timeval(proc, "delta", m_out->m_lc_pm_time.sec,
+                   m_out->m_lc_pm_time.nsec);
+               put_ptr(proc, "odelta", 0); /* not supported on MINIX3 */
+       } else {
+               flags = 0;
+               if (m_out->m_lc_pm_time.clk_id == CLOCK_REALTIME)
+                       flags |= PF_ALT;
+               put_clockid(proc, "clock_id", m_out->m_lc_pm_time.clk_id);
+               put_clock_timespec(proc, "tp", flags, m_out->m_lc_pm_time.sec,
+                   m_out->m_lc_pm_time.nsec);
+       }
+
+       return CT_DONE;
+}
+
+static int
+pm_getrusage_out(struct trace_proc * proc, const message * m_out)
+{
+
+       if (!valuesonly && m_out->m_lc_pm_rusage.who == RUSAGE_SELF)
+               put_field(proc, "who", "RUSAGE_SELF");
+       else if (!valuesonly && m_out->m_lc_pm_rusage.who == RUSAGE_CHILDREN)
+               put_field(proc, "who", "RUSAGE_CHILDREN");
+       else
+               put_value(proc, "who", "%d", m_out->m_lc_pm_rusage.who);
+
+       return CT_NOTDONE;
+}
+
+static void
+pm_getrusage_in(struct trace_proc * proc, const message * m_out,
+       const message * __unused m_in, int failed)
+{
+       struct rusage buf;
+
+       /* Inline; we will certainly not be reusing this anywhere else. */
+       if (put_open_struct(proc, "rusage", failed, m_out->m_lc_pm_rusage.addr,
+           &buf, sizeof(buf))) {
+               put_struct_timeval(proc, "ru_utime", PF_LOCADDR,
+                   (vir_bytes)&buf.ru_utime);
+               put_struct_timeval(proc, "ru_stime", PF_LOCADDR,
+                   (vir_bytes)&buf.ru_stime);
+
+               if (verbose > 0)
+                       put_value(proc, "ru_nsignals", "%ld", buf.ru_nsignals);
+               put_close_struct(proc, verbose > 0);
+       }
+       put_equals(proc);
+       put_result(proc);
+}
+
+static const struct flags reboot_flags[] = {
+       FLAG_ZERO(RB_AUTOBOOT),
+       FLAG(RB_ASKNAME),
+       FLAG(RB_DUMP),
+       FLAG_MASK(RB_POWERDOWN, RB_HALT),
+       FLAG(RB_POWERDOWN),
+       FLAG(RB_INITNAME),
+       FLAG(RB_KDB),
+       FLAG(RB_NOSYNC),
+       FLAG(RB_RDONLY),
+       FLAG(RB_SINGLE),
+       FLAG(RB_STRING),
+       FLAG(RB_USERCONF),
+};
+
+static int
+pm_reboot_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_flags(proc, "how", reboot_flags, COUNT(reboot_flags), "0x%x",
+           m_out->m_lc_pm_reboot.how);
+       put_ptr(proc, "bootstr", 0); /* not supported on MINIX3 */
+
+       return CT_DONE;
+}
+
+static int
+pm_svrctl_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_ioctl_req(proc, "request", m_out->m_lc_svrctl.request,
+           TRUE /*is_svrctl*/);
+       return put_ioctl_arg_out(proc, "arg", m_out->m_lc_svrctl.request,
+           m_out->m_lc_svrctl.arg, TRUE /*is_svrctl*/);
+}
+
+static void
+pm_svrctl_in(struct trace_proc * proc, const message * m_out,
+       const message * __unused m_in, int failed)
+{
+
+       put_ioctl_arg_in(proc, "arg", failed, m_out->m_lc_svrctl.request,
+           m_out->m_lc_svrctl.arg, TRUE /*is_svrctl*/);
+}
+
+static int
+pm_sprof_out(struct trace_proc * proc, const message * m_out)
+{
+       int freq;
+
+       if (!valuesonly && m_out->m_lc_pm_sprof.action == PROF_START)
+               put_field(proc, "action", "PROF_START");
+       else if (!valuesonly && m_out->m_lc_pm_sprof.action == PROF_STOP)
+               put_field(proc, "action", "PROF_STOP");
+       else
+               put_value(proc, "action", "%d", m_out->m_lc_pm_sprof.action);
+
+       put_value(proc, "size", "%zu", m_out->m_lc_pm_sprof.mem_size);
+
+       freq = m_out->m_lc_pm_sprof.freq;
+       if (!valuesonly && freq >= 3 && freq <= 15) /* no constants.. */
+               put_value(proc, "freq", "%u /*%uHz*/", freq, 1 << (16 - freq));
+       else
+               put_value(proc, "freq", "%u", freq);
+
+       if (!valuesonly && m_out->m_lc_pm_sprof.intr_type == PROF_RTC)
+               put_field(proc, "type", "PROF_RTC");
+       else if (!valuesonly && m_out->m_lc_pm_sprof.intr_type == PROF_NMI)
+               put_field(proc, "type", "PROF_NMI");
+       else
+               put_value(proc, "type", "%d", m_out->m_lc_pm_sprof.intr_type);
+
+       put_ptr(proc, "ctl_ptr", m_out->m_lc_pm_sprof.ctl_ptr);
+       put_ptr(proc, "mem_ptr", m_out->m_lc_pm_sprof.mem_ptr);
+
+       return CT_DONE;
+}
+
+#define PM_CALL(c) [((PM_ ## c) - PM_BASE)]
+
+static const struct call_handler pm_map[] = {
+       PM_CALL(EXIT) = HANDLER("exit", pm_exit_out, default_in),
+       PM_CALL(FORK) = HANDLER("fork", default_out, default_in),
+       PM_CALL(WAITPID) = HANDLER("waitpid", pm_waitpid_out, pm_waitpid_in),
+       PM_CALL(GETPID) = HANDLER("getpid", default_out, pm_getpid_in),
+       PM_CALL(SETUID) = HANDLER("setuid", pm_setuid_out, default_in),
+       PM_CALL(GETUID) = HANDLER("getuid", default_out, pm_getuid_in),
+       PM_CALL(STIME) = HANDLER("stime", pm_stime_out, default_in),
+       PM_CALL(PTRACE) = HANDLER("ptrace", pm_ptrace_out, pm_ptrace_in),
+       PM_CALL(SETGROUPS) = HANDLER("setgroups", pm_setgroups_out,
+           default_in),
+       PM_CALL(GETGROUPS) = HANDLER("getgroups", pm_getgroups_out,
+           pm_getgroups_in),
+       PM_CALL(KILL) = HANDLER("kill", pm_kill_out, default_in),
+       PM_CALL(SETGID) = HANDLER("setgid", pm_setgid_out, default_in),
+       PM_CALL(GETGID) = HANDLER("getgid", default_out, pm_getgid_in),
+       PM_CALL(EXEC) = HANDLER("execve", pm_exec_out, default_in),
+       PM_CALL(SETSID) = HANDLER("setsid", default_out, default_in),
+       PM_CALL(GETPGRP) = HANDLER("getpgrp", default_out, default_in),
+       PM_CALL(ITIMER) = HANDLER_NAME(pm_itimer_name, pm_itimer_out,
+           pm_itimer_in),
+       PM_CALL(GETMCONTEXT) = HANDLER("getmcontext", pm_getmcontext_out,
+           pm_getmcontext_in),
+       PM_CALL(SETMCONTEXT) = HANDLER("setmcontext", pm_setmcontext_out,
+           default_in),
+       PM_CALL(SIGACTION) = HANDLER("sigaction", pm_sigaction_out,
+           pm_sigaction_in),
+       PM_CALL(SIGSUSPEND) = HANDLER("sigsuspend", pm_sigsuspend_out,
+           default_in),
+       PM_CALL(SIGPENDING) = HANDLER("sigpending", pm_sigpending_out,
+           pm_sigpending_in),
+       PM_CALL(SIGPROCMASK) = HANDLER("sigprocmask", pm_sigprocmask_out,
+           pm_sigprocmask_in),
+       PM_CALL(SIGRETURN) = HANDLER("sigreturn", pm_sigreturn_out,
+           pm_sigreturn_in),
+       PM_CALL(SYSUNAME) = HANDLER("sysuname", pm_sysuname_out,
+           pm_sysuname_in),
+       PM_CALL(GETPRIORITY) = HANDLER("getpriority", pm_getpriority_out,
+           pm_getpriority_in),
+       PM_CALL(SETPRIORITY) = HANDLER("setpriority", pm_setpriority_out,
+           default_in),
+       PM_CALL(GETTIMEOFDAY) = HANDLER("gettimeofday", pm_gettimeofday_out,
+           pm_gettimeofday_in),
+       PM_CALL(SETEUID) = HANDLER("seteuid", pm_setuid_out, default_in),
+       PM_CALL(SETEGID) = HANDLER("setegid", pm_setgid_out, default_in),
+       PM_CALL(ISSETUGID) = HANDLER("issetugid", default_out, default_in),
+       PM_CALL(GETSID) = HANDLER("getsid", pm_getsid_out, default_in),
+       PM_CALL(CLOCK_GETRES) = HANDLER("clock_getres", pm_clock_get_out,
+           pm_clock_getres_in),
+       PM_CALL(CLOCK_GETTIME) = HANDLER("clock_gettime", pm_clock_get_out,
+           pm_clock_gettime_in),
+       PM_CALL(CLOCK_SETTIME) = HANDLER_NAME(pm_clock_settime_name,
+           pm_clock_settime_out, default_in),
+       PM_CALL(GETRUSAGE) = HANDLER("pm_getrusage", pm_getrusage_out,
+           pm_getrusage_in),
+       PM_CALL(REBOOT) = HANDLER("reboot", pm_reboot_out, default_in),
+       PM_CALL(SVRCTL) = HANDLER("pm_svrctl", pm_svrctl_out, pm_svrctl_in),
+       PM_CALL(SPROF) = HANDLER("sprofile", pm_sprof_out, default_in),
+};
+
+const struct calls pm_calls = {
+       .endpt = PM_PROC_NR,
+       .base = PM_BASE,
+       .map = pm_map,
+       .count = COUNT(pm_map)
+};
diff --git a/minix/usr.bin/trace/service/rs.c b/minix/usr.bin/trace/service/rs.c
new file mode 100644 (file)
index 0000000..514a483
--- /dev/null
@@ -0,0 +1,140 @@
+
+#include "inc.h"
+
+#include <minix/rs.h>
+
+static const struct flags rss_flags[] = {
+       FLAG(RSS_COPY),
+       FLAG(RSS_REUSE),
+       FLAG(RSS_NOBLOCK),
+       FLAG(RSS_REPLICA),
+       FLAG(RSS_SELF_LU),
+       FLAG(RSS_SYS_BASIC_CALLS),
+       FLAG(RSS_VM_BASIC_CALLS),
+       FLAG(RSS_NO_BIN_EXP),
+};
+
+static void
+put_struct_rs_start(struct trace_proc * proc, const char * name,
+       vir_bytes addr)
+{
+       struct rs_start buf;
+
+       if (!put_open_struct(proc, name, 0, addr, &buf, sizeof(buf)))
+               return;
+
+       if (verbose > 0)
+               put_flags(proc, "rss_flags", rss_flags, COUNT(rss_flags),
+                   "0x%x", buf.rss_flags);
+       put_buf(proc, "rss_cmd", 0, (vir_bytes)buf.rss_cmd, buf.rss_cmdlen);
+       put_buf(proc, "rss_label", 0, (vir_bytes)buf.rss_label.l_addr,
+           buf.rss_label.l_len);
+       if (verbose > 0 || buf.rss_major != 0)
+               put_value(proc, "rss_major", "%d", buf.rss_major);
+       if (verbose > 0 || buf.devman_id != 0)
+               put_value(proc, "devman_id", "%d", buf.devman_id);
+       put_value(proc, "rss_uid", "%u", buf.rss_uid);
+       if (verbose > 0) {
+               put_endpoint(proc, "rss_sigmgr", buf.rss_sigmgr);
+               put_endpoint(proc, "rss_scheduler", buf.rss_sigmgr);
+       }
+       if (verbose > 1) {
+               put_value(proc, "rss_priority", "%d", buf.rss_priority);
+               put_value(proc, "rss_quantum", "%d", buf.rss_quantum);
+       }
+       if (verbose > 0) {
+               put_value(proc, "rss_period", "%ld", buf.rss_period);
+               put_buf(proc, "rss_script", 0, (vir_bytes)buf.rss_script,
+                   buf.rss_scriptlen);
+       }
+
+       put_close_struct(proc, FALSE /*all*/); /* TODO: the remaining fields */
+}
+
+/* This function is shared between rs_up and rs_edit. */
+static int
+rs_up_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_struct_rs_start(proc, "addr", (vir_bytes)m_out->m_rs_req.addr);
+
+       return CT_DONE;
+}
+
+/*
+ * This function is shared between rs_down, rs_refresh, rs_restart, and
+ * rs_clone.
+ */
+static int
+rs_label_out(struct trace_proc * proc, const message * m_out)
+{
+
+       /*
+        * We are not using PF_STRING here, because unlike in most places
+        * (including rs_lookup), the string length does not include the
+        * terminating NULL character.
+        */
+       put_buf(proc, "label", 0, (vir_bytes)m_out->m_rs_req.addr,
+           m_out->m_rs_req.len);
+
+       return CT_DONE;
+}
+
+static int
+rs_update_out(struct trace_proc * proc, const message * m_out)
+{
+
+       /*
+        * FIXME: this is a value from the wrong message union, and that is
+        * actually a minix bug.
+        */
+       put_struct_rs_start(proc, "addr", (vir_bytes)m_out->m_rs_req.addr);
+
+       /* TODO: interpret these fields */
+       put_value(proc, "state", "%d", m_out->m_rs_update.state);
+       put_value(proc, "maxtime", "%d", m_out->m_rs_update.prepare_maxtime);
+
+       return CT_DONE;
+}
+
+static int
+rs_lookup_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_buf(proc, "label", PF_STRING, (vir_bytes)m_out->m_rs_req.name,
+           m_out->m_rs_req.name_len);
+
+       return CT_DONE;
+}
+
+static void
+rs_lookup_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       if (!failed)
+               put_endpoint(proc, NULL, m_in->m_rs_req.endpoint);
+       else
+               put_result(proc);
+}
+
+#define RS_CALL(c) [((RS_ ## c) - RS_RQ_BASE)]
+
+static const struct call_handler rs_map[] = {
+       RS_CALL(UP) = HANDLER("rs_up", rs_up_out, default_in),
+       RS_CALL(DOWN) = HANDLER("rs_down", rs_label_out, default_in),
+       RS_CALL(REFRESH) = HANDLER("rs_refresh", rs_label_out, default_in),
+       RS_CALL(RESTART) = HANDLER("rs_restart", rs_label_out, default_in),
+       RS_CALL(SHUTDOWN) = HANDLER("rs_shutdown", default_out, default_in),
+       RS_CALL(CLONE) = HANDLER("rs_clone", rs_label_out, default_in),
+       RS_CALL(UPDATE) = HANDLER("rs_update", rs_update_out, default_in),
+       RS_CALL(EDIT) = HANDLER("rs_edit", rs_up_out, default_in),
+       RS_CALL(LOOKUP) = HANDLER("rs_lookup", rs_lookup_out, rs_lookup_in),
+};
+
+const struct calls rs_calls = {
+       .endpt = RS_PROC_NR,
+       .base = RS_RQ_BASE,
+       .map = rs_map,
+       .count = COUNT(rs_map)
+};
diff --git a/minix/usr.bin/trace/service/vfs.c b/minix/usr.bin/trace/service/vfs.c
new file mode 100644 (file)
index 0000000..71006c1
--- /dev/null
@@ -0,0 +1,1457 @@
+
+#include "inc.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/mount.h>
+#include <sys/resource.h>
+
+/*
+ * This function should always be used when printing a file descriptor.  It
+ * currently offers no benefit, but will in the future allow for features such
+ * as color highlighting and tracking of specific open files (TODO).
+ */
+void
+put_fd(struct trace_proc * proc, const char * name, int fd)
+{
+
+       put_value(proc, name, "%d", fd);
+}
+
+static int
+vfs_read_out(struct trace_proc * proc, const message *m_out)
+{
+
+       put_fd(proc, "fd", m_out->m_lc_vfs_readwrite.fd);
+
+       return CT_NOTDONE;
+}
+
+static void
+vfs_read_in(struct trace_proc * proc, const message *m_out,
+       const message *m_in, int failed)
+{
+
+       put_buf(proc, "buf", failed, m_out->m_lc_vfs_readwrite.buf,
+           m_in->m_type);
+       put_value(proc, "len", "%zu", m_out->m_lc_vfs_readwrite.len);
+       put_equals(proc);
+       put_result(proc);
+}
+
+static int
+vfs_write_out(struct trace_proc * proc, const message *m_out)
+{
+
+       put_fd(proc, "fd", m_out->m_lc_vfs_readwrite.fd);
+       put_buf(proc, "buf", 0, m_out->m_lc_vfs_readwrite.buf,
+           m_out->m_lc_vfs_readwrite.len);
+       put_value(proc, "len", "%zu", m_out->m_lc_vfs_readwrite.len);
+
+       return CT_DONE;
+}
+
+static void
+put_lseek_whence(struct trace_proc * proc, const char * name, int whence)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (whence) {
+               TEXT(SEEK_SET);
+               TEXT(SEEK_CUR);
+               TEXT(SEEK_END);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", whence);
+}
+
+static int
+vfs_lseek_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_fd(proc, "fd", m_out->m_lc_vfs_lseek.fd);
+       put_value(proc, "offset", "%"PRId64, m_out->m_lc_vfs_lseek.offset);
+       put_lseek_whence(proc, "whence", m_out->m_lc_vfs_lseek.whence);
+
+       return CT_DONE;
+}
+
+static void
+vfs_lseek_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       if (!failed)
+               put_value(proc, NULL, "%"PRId64, m_in->m_vfs_lc_lseek.offset);
+       else
+               put_result(proc);
+}
+
+static const struct flags open_flags[] = {
+       FLAG_MASK(O_ACCMODE, O_RDONLY),
+       FLAG_MASK(O_ACCMODE, O_WRONLY),
+       FLAG_MASK(O_ACCMODE, O_RDWR),
+#define ACCMODE_ENTRIES 3      /* the first N entries are for O_ACCMODE */
+       FLAG(O_NONBLOCK),
+       FLAG(O_APPEND),
+       FLAG(O_SHLOCK),
+       FLAG(O_EXLOCK),
+       FLAG(O_ASYNC),
+       FLAG(O_SYNC),
+       FLAG(O_NOFOLLOW),
+       FLAG(O_CREAT),
+       FLAG(O_TRUNC),
+       FLAG(O_EXCL),
+       FLAG(O_NOCTTY),
+       FLAG(O_DSYNC),
+       FLAG(O_RSYNC),
+       FLAG(O_ALT_IO),
+       FLAG(O_DIRECT),
+       FLAG(O_DIRECTORY),
+       FLAG(O_CLOEXEC),
+       FLAG(O_SEARCH),
+       FLAG(O_NOSIGPIPE),
+};
+
+static void
+put_open_flags(struct trace_proc * proc, const char * name, int value,
+       int full)
+{
+       const struct flags *fp;
+       unsigned int num;
+
+       fp = open_flags;
+       num = COUNT(open_flags);
+
+       /*
+        * If we're not printing a full open()-style set of flags, but instead
+        * just a loose set of flags, then skip the access mode altogether,
+        * otherwise we'd be printing O_RDONLY when no access mode is given.
+        */
+       if (!full) {
+               fp += ACCMODE_ENTRIES;
+               num -= ACCMODE_ENTRIES;
+       }
+
+       put_flags(proc, name, fp, num, "0x%x", value);
+}
+
+static const struct flags mode_flags[] = {
+       FLAG_MASK(S_IFMT, S_IFIFO),
+       FLAG_MASK(S_IFMT, S_IFCHR),
+       FLAG_MASK(S_IFMT, S_IFDIR),
+       FLAG_MASK(S_IFMT, S_IFBLK),
+       FLAG_MASK(S_IFMT, S_IFREG),
+       FLAG_MASK(S_IFMT, S_IFLNK),
+       FLAG_MASK(S_IFMT, S_IFSOCK),
+       FLAG_MASK(S_IFMT, S_IFWHT),
+       FLAG(S_ARCH1),
+       FLAG(S_ARCH2),
+       FLAG(S_ISUID),
+       FLAG(S_ISGID),
+       FLAG(S_ISTXT),
+};
+
+/* Do not use %04o instead of 0%03o; it is octal even if greater than 0777. */
+#define put_mode(p, n, v) \
+       put_flags(p, n, mode_flags, COUNT(mode_flags), "0%03o", v)
+
+static void
+put_path(struct trace_proc * proc, const message * m_out)
+{
+       size_t len;
+
+       if ((len = m_out->m_lc_vfs_path.len) <= M_PATH_STRING_MAX)
+               put_buf(proc, "path", PF_LOCADDR | PF_PATH,
+                   (vir_bytes)m_out->m_lc_vfs_path.buf, len);
+       else
+               put_buf(proc, "path", PF_PATH, m_out->m_lc_vfs_path.name, len);
+}
+
+static int
+vfs_open_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_path(proc, m_out);
+       put_open_flags(proc, "flags", m_out->m_lc_vfs_path.flags,
+           TRUE /*full*/);
+
+       return CT_DONE;
+}
+
+/* This function is shared between creat and open. */
+static void
+vfs_open_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       if (!failed)
+               put_fd(proc, NULL, m_in->m_type);
+       else
+               put_result(proc);
+}
+
+static int
+vfs_creat_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_buf(proc, "path", PF_PATH, m_out->m_lc_vfs_creat.name,
+           m_out->m_lc_vfs_creat.len);
+       put_open_flags(proc, "flags", m_out->m_lc_vfs_creat.flags,
+           TRUE /*full*/);
+       put_mode(proc, "mode", m_out->m_lc_vfs_creat.mode);
+
+       return CT_DONE;
+}
+
+static int
+vfs_close_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_fd(proc, "fd", m_out->m_lc_vfs_close.fd);
+
+       return CT_DONE;
+}
+
+/* This function is used for link, rename, and symlink. */
+static int
+vfs_link_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_buf(proc, "path1", PF_PATH, m_out->m_lc_vfs_link.name1,
+           m_out->m_lc_vfs_link.len1);
+       put_buf(proc, "path2", PF_PATH, m_out->m_lc_vfs_link.name2,
+           m_out->m_lc_vfs_link.len2);
+
+       return CT_DONE;
+}
+
+static int
+vfs_path_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_path(proc, m_out);
+
+       return CT_DONE;
+}
+
+static int
+vfs_path_mode_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_path(proc, m_out);
+       put_mode(proc, "mode", m_out->m_lc_vfs_path.mode);
+
+       return CT_DONE;
+}
+
+void
+put_dev(struct trace_proc * proc, const char * name, dev_t dev)
+{
+       devmajor_t major;
+       devminor_t minor;
+
+       major = major(dev);
+       minor = minor(dev);
+
+       /* The value 0 ("no device") should print as "0". */
+       if (dev != 0 && makedev(major, minor) == dev && !valuesonly)
+               put_value(proc, name, "<%d,%d>", major, minor);
+       else
+               put_value(proc, name, "%"PRIu64, dev);
+}
+
+static int
+vfs_mknod_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_buf(proc, "path", PF_PATH, m_out->m_lc_vfs_mknod.name,
+           m_out->m_lc_vfs_mknod.len);
+       put_mode(proc, "mode", m_out->m_lc_vfs_mknod.mode);
+       put_dev(proc, "dev", m_out->m_lc_vfs_mknod.device);
+
+       return CT_DONE;
+}
+
+static int
+vfs_chown_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_buf(proc, "path", PF_PATH, m_out->m_lc_vfs_chown.name,
+           m_out->m_lc_vfs_chown.len);
+       /* -1 means "keep the current value" so print as signed */
+       put_value(proc, "owner", "%d", m_out->m_lc_vfs_chown.owner);
+       put_value(proc, "group", "%d", m_out->m_lc_vfs_chown.group);
+
+       return CT_DONE;
+}
+
+/* TODO: expand this to the full ST_ set. */
+static const struct flags mount_flags[] = {
+       FLAG(MNT_RDONLY),
+};
+
+static int
+vfs_mount_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_buf(proc, "special", PF_PATH, m_out->m_lc_vfs_mount.dev,
+           m_out->m_lc_vfs_mount.devlen);
+       put_buf(proc, "path", PF_PATH, m_out->m_lc_vfs_mount.path,
+           m_out->m_lc_vfs_mount.pathlen);
+       put_flags(proc, "flags", mount_flags, COUNT(mount_flags), "0x%x",
+           m_out->m_lc_vfs_mount.flags);
+       put_buf(proc, "type", PF_STRING, m_out->m_lc_vfs_mount.type,
+           m_out->m_lc_vfs_mount.typelen);
+       put_buf(proc, "label", PF_STRING, m_out->m_lc_vfs_mount.label,
+           m_out->m_lc_vfs_mount.labellen);
+
+       return CT_DONE;
+}
+
+static int
+vfs_umount_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_buf(proc, "path", PF_PATH, m_out->m_lc_vfs_umount.name,
+           m_out->m_lc_vfs_umount.namelen);
+
+       return CT_DONE;
+}
+
+static void
+vfs_umount_in(struct trace_proc * proc, const message * m_out,
+       const message * __unused m_in, int failed)
+{
+
+       put_result(proc);
+
+       if (!failed) {
+               put_open(proc, NULL, 0, "(", ", ");
+               put_buf(proc, "label", PF_STRING, m_out->m_lc_vfs_umount.label,
+                   m_out->m_lc_vfs_umount.labellen);
+
+               put_close(proc, ")");
+       }
+}
+
+
+static const struct flags access_flags[] = {
+       FLAG_ZERO(F_OK),
+       FLAG(R_OK),
+       FLAG(W_OK),
+       FLAG(X_OK),
+};
+
+static int
+vfs_access_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_path(proc, m_out);
+       put_flags(proc, "mode", access_flags, COUNT(access_flags), "0x%x",
+           m_out->m_lc_vfs_path.mode);
+
+       return CT_DONE;
+}
+
+static int
+vfs_readlink_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_buf(proc, "path", PF_PATH, m_out->m_lc_vfs_readlink.name,
+           m_out->m_lc_vfs_readlink.namelen);
+
+       return CT_NOTDONE;
+}
+
+static void
+vfs_readlink_in(struct trace_proc * proc, const message * m_out,
+       const message * m_in, int failed)
+{
+
+       /* The call does not return a string, so do not use PF_STRING here. */
+       put_buf(proc, "buf", failed, m_out->m_lc_vfs_readlink.buf,
+           m_in->m_type);
+       put_value(proc, "bufsize", "%zd", m_out->m_lc_vfs_readlink.bufsize);
+       put_equals(proc);
+       put_result(proc);
+}
+
+static void
+put_struct_stat(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr)
+{
+       struct stat buf;
+       int is_special;
+
+       if (!put_open_struct(proc, name, flags, addr, &buf, sizeof(buf)))
+               return;
+
+       /*
+        * The combination of struct stat's frequent usage and large number of
+        * fields makes this structure a pain to print.  For now, the idea is
+        * that for verbosity level 0, we print the mode, and the target device
+        * for block/char special files or the file size for all other files.
+        * For higher verbosity levels, largely maintain the structure's own
+        * order of fields.  Violate this general structure printing rule for
+        * some fields though, because the actual field order in struct stat is
+        * downright ridiculous.  Like elsewhere, for verbosity level 1 print
+        * all fields with meaningful values, and for verbosity level 2 just
+        * print everything, including fields that are known to be not yet
+        * supported and fields that contain known values.
+        */
+       is_special = (S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode));
+
+       if (verbose > 0) {
+               put_dev(proc, "st_dev", buf.st_dev);
+               put_value(proc, "st_ino", "%"PRId64, buf.st_ino);
+       }
+       put_mode(proc, "st_mode", buf.st_mode);
+       if (verbose > 0) {
+               put_value(proc, "st_nlink", "%u", buf.st_nlink);
+               put_value(proc, "st_uid", "%u", buf.st_uid);
+               put_value(proc, "st_gid", "%u", buf.st_gid);
+       }
+       if (is_special || verbose > 1)
+               put_dev(proc, "st_rdev", buf.st_rdev);
+       if (verbose > 0) {
+               /*
+                * TODO: print the nanosecond part, but possibly only if we are
+                * not actually interpreting the time as a date (another TODO),
+                * and/or possibly only with verbose > 1 (largely unsupported).
+                */
+               put_time(proc, "st_atime", buf.st_atime);
+               put_time(proc, "st_mtime", buf.st_mtime);
+               put_time(proc, "st_ctime", buf.st_ctime);
+       }
+       if (verbose > 1) /* not yet supported on MINIX3 */
+               put_time(proc, "st_birthtime", buf.st_birthtime);
+       if (!is_special || verbose > 1)
+               put_value(proc, "st_size", "%"PRId64, buf.st_size);
+       if (verbose > 0) {
+               put_value(proc, "st_blocks", "%"PRId64, buf.st_blocks);
+               put_value(proc, "st_blksize", "%"PRId32, buf.st_blksize);
+       }
+       if (verbose > 1) {
+               put_value(proc, "st_flags", "%"PRIu32, buf.st_flags);
+               put_value(proc, "st_gen", "%"PRIu32, buf.st_gen);
+       }
+
+       put_close_struct(proc, verbose > 1);
+}
+
+static int
+vfs_stat_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_buf(proc, "path", PF_PATH, m_out->m_lc_vfs_stat.name,
+           m_out->m_lc_vfs_stat.len);
+
+       return CT_NOTDONE;
+}
+
+static void
+vfs_stat_in(struct trace_proc * proc, const message * m_out,
+       const message * __unused m_in, int failed)
+{
+
+       put_struct_stat(proc, "buf", failed, m_out->m_lc_vfs_stat.buf);
+       put_equals(proc);
+       put_result(proc);
+}
+
+static int
+vfs_fstat_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_fd(proc, "fd", m_out->m_lc_vfs_fstat.fd);
+
+       return CT_NOTDONE;
+}
+
+static void
+vfs_fstat_in(struct trace_proc * proc, const message * m_out,
+       const message * __unused m_in, int failed)
+{
+
+       put_struct_stat(proc, "buf", failed, m_out->m_lc_vfs_fstat.buf);
+       put_equals(proc);
+       put_result(proc);
+}
+
+static int
+vfs_ioctl_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_fd(proc, "fd", m_out->m_lc_vfs_ioctl.fd);
+       put_ioctl_req(proc, "req", m_out->m_lc_vfs_ioctl.req,
+           FALSE /*is_svrctl*/);
+       return put_ioctl_arg_out(proc, "arg", m_out->m_lc_vfs_ioctl.req,
+           (vir_bytes)m_out->m_lc_vfs_ioctl.arg, FALSE /*is_svrctl*/);
+}
+
+static void
+vfs_ioctl_in(struct trace_proc * proc, const message * m_out,
+       const message * __unused m_in, int failed)
+{
+
+       put_ioctl_arg_in(proc, "arg", failed, m_out->m_lc_vfs_ioctl.req,
+           (vir_bytes)m_out->m_lc_vfs_ioctl.arg, FALSE /*is_svrctl*/);
+}
+
+static void
+put_fcntl_cmd(struct trace_proc * proc, const char * name, int cmd)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (cmd) {
+               TEXT(F_DUPFD);
+               TEXT(F_GETFD);
+               TEXT(F_SETFD);
+               TEXT(F_GETFL);
+               TEXT(F_SETFL);
+               TEXT(F_GETOWN);
+               TEXT(F_SETOWN);
+               TEXT(F_GETLK);
+               TEXT(F_SETLK);
+               TEXT(F_SETLKW);
+               TEXT(F_CLOSEM);
+               TEXT(F_MAXFD);
+               TEXT(F_DUPFD_CLOEXEC);
+               TEXT(F_GETNOSIGPIPE);
+               TEXT(F_SETNOSIGPIPE);
+               TEXT(F_FREESP);
+               TEXT(F_FLUSH_FS_CACHE);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", cmd);
+}
+
+static const struct flags fd_flags[] = {
+       FLAG(FD_CLOEXEC),
+};
+
+#define put_fd_flags(p, n, v) \
+       put_flags(p, n, fd_flags, COUNT(fd_flags), "0x%x", v)
+
+static void
+put_flock_type(struct trace_proc * proc, const char * name, int type)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (type) {
+               TEXT(F_RDLCK);
+               TEXT(F_UNLCK);
+               TEXT(F_WRLCK);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%d", type);
+}
+
+/*
+ * With PF_FULL, also print l_pid, unless l_type is F_UNLCK in which case
+ * only that type is printed.   With PF_ALT, print only l_whence/l_start/l_len.
+ */
+static void
+put_struct_flock(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr)
+{
+       struct flock flock;
+       int limited;
+
+       if (!put_open_struct(proc, name, flags, addr, &flock, sizeof(flock)))
+               return;
+
+       limited = ((flags & PF_FULL) && flock.l_type == F_UNLCK);
+
+       if (!(flags & PF_ALT))
+               put_flock_type(proc, "l_type", flock.l_type);
+       if (!limited) {
+               put_lseek_whence(proc, "l_whence", flock.l_whence);
+               put_value(proc, "l_start", "%"PRId64, flock.l_start);
+               put_value(proc, "l_len", "%"PRId64, flock.l_len);
+               if (flags & PF_FULL)
+                       put_value(proc, "l_pid", "%d", flock.l_pid);
+       }
+
+       put_close_struct(proc, TRUE /*all*/);
+}
+
+static int
+vfs_fcntl_out(struct trace_proc * proc, const message * m_out)
+{
+       int full;
+
+       put_fd(proc, "fd", m_out->m_lc_vfs_fcntl.fd);
+       put_fcntl_cmd(proc, "cmd", m_out->m_lc_vfs_fcntl.cmd);
+
+       switch (m_out->m_lc_vfs_fcntl.cmd) {
+       case F_DUPFD:
+               put_fd(proc, "fd2", m_out->m_lc_vfs_fcntl.arg_int);
+               break;
+       case F_SETFD:
+               put_fd_flags(proc, "flags", m_out->m_lc_vfs_fcntl.arg_int);
+               break;
+       case F_SETFL:
+               /*
+                * One of those difficult cases: the access mode is ignored, so
+                * we don't want to print O_RDONLY if it is not given.  On the
+                * other hand, fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_..) is
+                * a fairly common construction, in which case we don't want to
+                * print eg O_..|0x2 if the access mode is O_RDWR.  Thus, we
+                * compromise: show the access mode if any of its bits are set.
+                */
+               put_open_flags(proc, "flags", m_out->m_lc_vfs_fcntl.arg_int,
+                   m_out->m_lc_vfs_fcntl.arg_int & O_ACCMODE /*full*/);
+               break;
+       case F_SETLK:
+       case F_SETLKW:
+               put_struct_flock(proc, "lkp", 0,
+                   m_out->m_lc_vfs_fcntl.arg_ptr);
+               break;
+       case F_FREESP:
+               put_struct_flock(proc, "lkp", PF_ALT,
+                   m_out->m_lc_vfs_fcntl.arg_ptr);
+               break;
+       case F_SETNOSIGPIPE:
+               put_value(proc, "arg", "%d", m_out->m_lc_vfs_fcntl.arg_int);
+               break;
+       }
+
+       return (m_out->m_lc_vfs_fcntl.cmd != F_GETLK) ? CT_DONE : CT_NOTDONE;
+}
+
+static void
+vfs_fcntl_in(struct trace_proc * proc, const message * m_out,
+       const message * m_in, int failed)
+{
+
+       switch (m_out->m_lc_vfs_fcntl.cmd) {
+       case F_GETFD:
+               if (failed)
+                       break;
+               put_fd_flags(proc, NULL, m_in->m_type);
+               return;
+       case F_GETFL:
+               if (failed)
+                       break;
+               put_open_flags(proc, NULL, m_in->m_type, TRUE /*full*/);
+               return;
+       case F_GETLK:
+               put_struct_flock(proc, "lkp", failed | PF_FULL,
+                   m_out->m_lc_vfs_fcntl.arg_ptr);
+               put_equals(proc);
+               break;
+       }
+
+       put_result(proc);
+}
+
+static int
+vfs_pipe2_out(struct trace_proc * __unused proc,
+       const message * __unused m_out)
+{
+
+       return CT_NOTDONE;
+}
+
+static void
+vfs_pipe2_in(struct trace_proc * proc, const message * m_out,
+       const message * m_in, int failed)
+{
+
+       if (!failed) {
+               put_open(proc, "fd", PF_NONAME, "[", ", ");
+               put_fd(proc, "rfd", m_in->m_lc_vfs_pipe2.fd0);
+               put_fd(proc, "wfd", m_in->m_lc_vfs_pipe2.fd1);
+               put_close(proc, "]");
+       } else
+               put_field(proc, "fd", "&..");
+       put_open_flags(proc, "flags", m_out->m_lc_vfs_pipe2.flags,
+           FALSE /*full*/);
+       put_equals(proc);
+       put_result(proc);
+}
+
+static int
+vfs_umask_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_mode(proc, NULL, m_out->m_lc_vfs_umask.mask);
+
+       return CT_DONE;
+}
+
+static void
+vfs_umask_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       if (!failed)
+               put_mode(proc, NULL, m_in->m_type);
+       else
+               put_result(proc);
+
+}
+
+static void
+put_dirent_type(struct trace_proc * proc, const char * name, unsigned int type)
+{
+       const char *text = NULL;
+
+       if (!valuesonly) {
+               switch (type) {
+               TEXT(DT_UNKNOWN);
+               TEXT(DT_FIFO);
+               TEXT(DT_CHR);
+               TEXT(DT_DIR);
+               TEXT(DT_BLK);
+               TEXT(DT_REG);
+               TEXT(DT_LNK);
+               TEXT(DT_SOCK);
+               TEXT(DT_WHT);
+               }
+       }
+
+       if (text != NULL)
+               put_field(proc, name, text);
+       else
+               put_value(proc, name, "%u", type);
+}
+
+static void
+put_struct_dirent(struct trace_proc * proc, const char *name, int flags,
+       vir_bytes addr)
+{
+       struct dirent dirent;
+
+       if (!put_open_struct(proc, name, flags, addr, &dirent, sizeof(dirent)))
+               return;
+
+       if (verbose > 0)
+               put_value(proc, "d_fileno", "%"PRIu64, dirent.d_fileno);
+       if (verbose > 1) {
+               put_value(proc, "d_reclen", "%u", dirent.d_reclen);
+               put_value(proc, "d_namlen", "%u", dirent.d_namlen);
+       }
+       if (verbose >= 1 + (dirent.d_type == DT_UNKNOWN))
+               put_dirent_type(proc, "d_type", dirent.d_type);
+       put_buf(proc, "d_name", PF_LOCADDR, (vir_bytes)dirent.d_name,
+           MIN(dirent.d_namlen, sizeof(dirent.d_name)));
+
+       put_close_struct(proc, verbose > 1);
+}
+
+static void
+put_dirent_array(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr, ssize_t size)
+{
+       struct dirent dirent;
+       unsigned count, max;
+       ssize_t off, chunk;
+
+       if ((flags & PF_FAILED) || valuesonly > 1 || size < 0) {
+               put_ptr(proc, name, addr);
+
+               return;
+       }
+
+       if (size == 0) {
+               put_field(proc, name, "[]");
+
+               return;
+       }
+
+       if (verbose == 0)
+               max = 0; /* TODO: should we set this to 1 instead? */
+       else if (verbose == 1)
+               max = 3; /* low; just to give an indication where we are */
+       else
+               max = INT_MAX;
+
+       /*
+        * TODO: as is, this is highly inefficient, as we are typically copying
+        * in the same pieces of memory in repeatedly..
+        */
+       count = 0;
+       for (off = 0; off < size; off += chunk) {
+               chunk = size - off;
+               if (chunk > sizeof(dirent))
+                       chunk = sizeof(dirent);
+               if (chunk < _DIRENT_MINSIZE(&dirent))
+                       break;
+
+               if (mem_get_data(proc->pid, addr + off, &dirent, chunk) < 0) {
+                       if (off == 0) {
+                               put_ptr(proc, name, addr);
+
+                               return;
+                       }
+
+                       break;
+               }
+
+               if (off == 0)
+                       put_open(proc, name, PF_NONAME, "[", ", ");
+
+               if (count < max)
+                       put_struct_dirent(proc, NULL, PF_LOCADDR,
+                           (vir_bytes)&dirent);
+
+               if (chunk > dirent.d_reclen)
+                       chunk = dirent.d_reclen;
+               count++;
+       }
+
+       if (off < size)
+               put_tail(proc, 0, 0);
+       else if (count > max)
+               put_tail(proc, count, max);
+       put_close(proc, "]");
+}
+
+static int
+vfs_getdents_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_fd(proc, "fd", m_out->m_lc_vfs_readwrite.fd);
+
+       return CT_NOTDONE;
+}
+
+static void
+vfs_getdents_in(struct trace_proc * proc, const message * m_out,
+       const message * m_in, int failed)
+{
+
+       put_dirent_array(proc, "buf", failed, m_out->m_lc_vfs_readwrite.buf,
+           m_in->m_type);
+       put_value(proc, "len", "%zu", m_out->m_lc_vfs_readwrite.len);
+       put_equals(proc);
+       put_result(proc);
+}
+
+static void
+put_fd_set(struct trace_proc * proc, const char * name, vir_bytes addr,
+       int nfds)
+{
+       fd_set set;
+       size_t off;
+       unsigned int i, j, words, count, max;
+
+       if (addr == 0 || nfds < 0) {
+               put_ptr(proc, name, addr);
+
+               return;
+       }
+
+       /*
+        * Each process may define its own FD_SETSIZE, so our fd_set may be of
+        * a different size than theirs.  Thus, we copy at a granularity known
+        * to be valid in any case: a single word of bits.  We make the
+        * assumption that fd_set consists purely of bits, so that we can use
+        * the second (and so on) bit word as an fd_set by itself.
+        */
+       words = (nfds + NFDBITS - 1) / NFDBITS;
+
+       count = 0;
+
+       if (verbose == 0)
+               max = 16;
+       else if (verbose == 1)
+               max = FD_SETSIZE;
+       else
+               max = INT_MAX;
+
+       /* TODO: copy in more at once, but stick to fd_mask boundaries. */
+       for (off = 0, i = 0; i < words; i++, off += sizeof(fd_mask)) {
+               if (mem_get_data(proc->pid, addr + off, &set,
+                   sizeof(fd_mask)) != 0) {
+                       if (count == 0) {
+                               put_ptr(proc, name, addr);
+
+                               return;
+                       }
+
+                       break;
+               }
+
+               for (j = 0; j < NFDBITS; j++) {
+                       if (FD_ISSET(j, &set)) {
+                               if (count == 0)
+                                       put_open(proc, name, PF_NONAME, "[",
+                                           " ");
+
+                               if (count < max)
+                                       put_fd(proc, NULL, i * NFDBITS + j);
+
+                               count++;
+                       }
+               }
+       }
+
+       /*
+        * The empty set should print as "[]".  If copying any part failed, it
+        * should print as "[x, ..(?)]" where x is the set printed so far, if
+        * any.  If copying never failed, and we did not print all fds in the
+        * set, print the remaining count n as "[x, ..(+n)]" at the end.
+        */
+       if (count == 0)
+               put_open(proc, name, PF_NONAME, "[", " ");
+
+       if (i < words)
+               put_tail(proc, 0, 0);
+       else if (count > max)
+               put_tail(proc, count, max);
+
+       put_close(proc, "]");
+}
+
+static int
+vfs_select_out(struct trace_proc * proc, const message * m_out)
+{
+       int nfds;
+
+       nfds = m_out->m_lc_vfs_select.nfds;
+
+       put_fd(proc, "nfds", nfds); /* not really a file descriptor.. */
+       put_fd_set(proc, "readfds",
+           (vir_bytes)m_out->m_lc_vfs_select.readfds, nfds);
+       put_fd_set(proc, "writefds",
+           (vir_bytes)m_out->m_lc_vfs_select.writefds, nfds);
+       put_fd_set(proc, "errorfds",
+           (vir_bytes)m_out->m_lc_vfs_select.errorfds, nfds);
+       put_struct_timeval(proc, "timeout", 0, m_out->m_lc_vfs_select.timeout);
+
+       return CT_DONE;
+}
+
+static void
+vfs_select_in(struct trace_proc * proc, const message * m_out,
+       const message * __unused m_in, int failed)
+{
+       vir_bytes readfds, writefds, errorfds;
+       int nfds;
+
+       put_result(proc);
+       if (failed)
+               return;
+
+       nfds = m_out->m_lc_vfs_select.nfds;
+
+       readfds = (vir_bytes)m_out->m_lc_vfs_select.readfds;
+       writefds = (vir_bytes)m_out->m_lc_vfs_select.writefds;
+       errorfds = (vir_bytes)m_out->m_lc_vfs_select.errorfds;
+
+       if (readfds == 0 && writefds == 0 && errorfds == 0)
+               return;
+
+       /* Omit names, because it looks weird. */
+       put_open(proc, NULL, PF_NONAME, "(", ", ");
+       if (readfds != 0)
+               put_fd_set(proc, "readfds", readfds, nfds);
+       if (writefds != 0)
+               put_fd_set(proc, "writefds", writefds, nfds);
+       if (errorfds != 0)
+               put_fd_set(proc, "errorfds", errorfds, nfds);
+       put_close(proc, ")");
+}
+
+static int
+vfs_fchdir_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_fd(proc, "fd", m_out->m_lc_vfs_fchdir.fd);
+
+       return CT_DONE;
+}
+
+static int
+vfs_fsync_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_fd(proc, "fd", m_out->m_lc_vfs_fsync.fd);
+
+       return CT_DONE;
+}
+
+static int
+vfs_truncate_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_buf(proc, "path", PF_PATH, m_out->m_lc_vfs_truncate.name,
+           m_out->m_lc_vfs_truncate.len);
+       put_value(proc, "length", "%"PRId64, m_out->m_lc_vfs_truncate.offset);
+
+       return CT_DONE;
+}
+
+static int
+vfs_ftruncate_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_fd(proc, "fd", m_out->m_lc_vfs_truncate.fd);
+       put_value(proc, "length", "%"PRId64, m_out->m_lc_vfs_truncate.offset);
+
+       return CT_DONE;
+}
+
+static int
+vfs_fchmod_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_fd(proc, "fd", m_out->m_lc_vfs_fchmod.fd);
+       put_mode(proc, "mode", m_out->m_lc_vfs_fchmod.mode);
+
+       return CT_DONE;
+}
+
+static int
+vfs_fchown_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_fd(proc, "fd", m_out->m_lc_vfs_chown.fd);
+       /* -1 means "keep the current value" so print as signed */
+       put_value(proc, "owner", "%d", m_out->m_lc_vfs_chown.owner);
+       put_value(proc, "group", "%d", m_out->m_lc_vfs_chown.group);
+
+       return CT_DONE;
+}
+
+static const char *
+vfs_utimens_name(const message * m_out)
+{
+       int has_path, has_flags;
+
+       has_path = (m_out->m_vfs_utimens.name != NULL);
+       has_flags = (m_out->m_vfs_utimens.flags != 0);
+
+       if (has_path && m_out->m_vfs_utimens.flags == AT_SYMLINK_NOFOLLOW)
+               return "lutimens";
+       if (has_path && !has_flags)
+               return "utimens";
+       else if (!has_path && !has_flags)
+               return "futimens";
+       else
+               return "utimensat";
+}
+
+static const struct flags at_flags[] = {
+       FLAG(AT_EACCESS),
+       FLAG(AT_SYMLINK_NOFOLLOW),
+       FLAG(AT_SYMLINK_FOLLOW),
+       FLAG(AT_REMOVEDIR),
+};
+
+static void
+put_utimens_timespec(struct trace_proc * proc, const char * name,
+       time_t sec, long nsec)
+{
+
+       /* No field names. */
+       put_open(proc, name, PF_NONAME, "{", ", ");
+
+       put_time(proc, "tv_sec", sec);
+
+       if (!valuesonly && nsec == UTIME_NOW)
+               put_field(proc, "tv_nsec", "UTIME_NOW");
+       else if (!valuesonly && nsec == UTIME_OMIT)
+               put_field(proc, "tv_nsec", "UTIME_OMIT");
+       else
+               put_value(proc, "tv_nsec", "%ld", nsec);
+
+       put_close(proc, "}");
+}
+
+static int
+vfs_utimens_out(struct trace_proc * proc, const message * m_out)
+{
+       int has_path, has_flags;
+
+       /* Here we do not care about the utimens/lutimens distinction. */
+       has_path = (m_out->m_vfs_utimens.name != NULL);
+       has_flags = !!(m_out->m_vfs_utimens.flags & ~AT_SYMLINK_NOFOLLOW);
+
+       if (has_path && has_flags)
+               put_field(proc, "fd", "AT_CWD"); /* utimensat */
+       else if (!has_path)
+               put_fd(proc, "fd", m_out->m_vfs_utimens.fd); /* futimes */
+       if (has_path || has_flags) /* lutimes, utimes, utimensat */
+               put_buf(proc, "path", PF_PATH,
+                   (vir_bytes)m_out->m_vfs_utimens.name,
+                   m_out->m_vfs_utimens.len);
+
+       put_open(proc, "times", 0, "[", ", ");
+       put_utimens_timespec(proc, "atime", m_out->m_vfs_utimens.atime,
+           m_out->m_vfs_utimens.ansec);
+       put_utimens_timespec(proc, "mtime", m_out->m_vfs_utimens.mtime,
+           m_out->m_vfs_utimens.mnsec);
+       put_close(proc, "]");
+
+       if (has_flags)
+               put_flags(proc, "flag", at_flags, COUNT(at_flags), "0x%x",
+                   m_out->m_vfs_utimens.flags);
+
+       return CT_DONE;
+}
+
+static const struct flags statvfs_flags[] = {
+       FLAG(ST_WAIT),
+       FLAG(ST_NOWAIT),
+};
+
+static const struct flags st_flags[] = {
+       FLAG(ST_RDONLY),
+       FLAG(ST_SYNCHRONOUS),
+       FLAG(ST_NOEXEC),
+       FLAG(ST_NOSUID),
+       FLAG(ST_NODEV),
+       FLAG(ST_UNION),
+       FLAG(ST_ASYNC),
+       FLAG(ST_NOCOREDUMP),
+       FLAG(ST_RELATIME),
+       FLAG(ST_IGNORE),
+       FLAG(ST_NOATIME),
+       FLAG(ST_SYMPERM),
+       FLAG(ST_NODEVMTIME),
+       FLAG(ST_SOFTDEP),
+       FLAG(ST_LOG),
+       FLAG(ST_EXTATTR),
+       FLAG(ST_EXRDONLY),
+       FLAG(ST_EXPORTED),
+       FLAG(ST_DEFEXPORTED),
+       FLAG(ST_EXPORTANON),
+       FLAG(ST_EXKERB),
+       FLAG(ST_EXNORESPORT),
+       FLAG(ST_EXPUBLIC),
+       FLAG(ST_LOCAL),
+       FLAG(ST_QUOTA),
+       FLAG(ST_ROOTFS),
+       FLAG(ST_NOTRUNC),
+};
+
+static void
+put_struct_statvfs(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr)
+{
+       struct statvfs buf;
+
+       if (!put_open_struct(proc, name, flags, addr, &buf, sizeof(buf)))
+               return;
+
+       put_flags(proc, "f_flag", st_flags, COUNT(st_flags), "0x%x",
+           buf.f_flag);
+       put_value(proc, "f_bsize", "%lu", buf.f_bsize);
+       if (verbose > 0 || buf.f_bsize != buf.f_frsize)
+               put_value(proc, "f_frsize", "%lu", buf.f_frsize);
+       if (verbose > 1)
+               put_value(proc, "f_iosize", "%lu", buf.f_iosize);
+
+       put_value(proc, "f_blocks", "%"PRIu64, buf.f_blocks);
+       put_value(proc, "f_bfree", "%"PRIu64, buf.f_bfree);
+       if (verbose > 1) {
+               put_value(proc, "f_bavail", "%"PRIu64, buf.f_bavail);
+               put_value(proc, "f_bresvd", "%"PRIu64, buf.f_bresvd);
+       }
+
+       if (verbose > 0) {
+               put_value(proc, "f_files", "%"PRIu64, buf.f_files);
+               put_value(proc, "f_ffree", "%"PRIu64, buf.f_ffree);
+       }
+       if (verbose > 1) {
+               put_value(proc, "f_favail", "%"PRIu64, buf.f_favail);
+               put_value(proc, "f_fresvd", "%"PRIu64, buf.f_fresvd);
+       }
+
+       if (verbose > 1) {
+               put_value(proc, "f_syncreads", "%"PRIu64, buf.f_syncreads);
+               put_value(proc, "f_syncwrites", "%"PRIu64, buf.f_syncwrites);
+               put_value(proc, "f_asyncreads", "%"PRIu64, buf.f_asyncreads);
+               put_value(proc, "f_asyncwrites", "%"PRIu64, buf.f_asyncwrites);
+
+               put_value(proc, "f_fsidx", "<%"PRId32",%"PRId32">",
+                   buf.f_fsidx.__fsid_val[0], buf.f_fsidx.__fsid_val[1]);
+       }
+       put_dev(proc, "f_fsid", buf.f_fsid); /* MINIX3 interpretation! */
+
+       if (verbose > 0)
+               put_value(proc, "f_namemax", "%lu", buf.f_namemax);
+       if (verbose > 1)
+               put_value(proc, "f_owner", "%u", buf.f_owner);
+
+       put_buf(proc, "f_fstypename", PF_STRING | PF_LOCADDR,
+           (vir_bytes)&buf.f_fstypename, sizeof(buf.f_fstypename));
+       if (verbose > 0)
+               put_buf(proc, "f_mntfromname", PF_STRING | PF_LOCADDR,
+                   (vir_bytes)&buf.f_mntfromname, sizeof(buf.f_mntfromname));
+       put_buf(proc, "f_mntonname", PF_STRING | PF_LOCADDR,
+           (vir_bytes)&buf.f_mntonname, sizeof(buf.f_mntonname));
+
+       put_close_struct(proc, verbose > 1);
+}
+
+static void
+put_statvfs_array(struct trace_proc * proc, const char * name, int flags,
+       vir_bytes addr, int count)
+{
+       struct statvfs buf;
+       int i, max;
+
+       if ((flags & PF_FAILED) || valuesonly || count < 0) {
+               put_ptr(proc, name, addr);
+
+               return;
+       }
+
+       if (count == 0) {
+               put_field(proc, name, "[]");
+
+               return;
+       }
+
+       if (verbose == 0)
+               max = 0;
+       else if (verbose == 1)
+               max = 1; /* TODO: is this reasonable? */
+       else
+               max = INT_MAX;
+
+       if (max > count)
+               max = count;
+
+       for (i = 0; i < max; i++) {
+               if (mem_get_data(proc->pid, addr + i * sizeof(buf), &buf,
+                   sizeof(buf)) < 0) {
+                       if (i == 0) {
+                               put_ptr(proc, name, addr);
+
+                               return;
+                       }
+
+                       break;
+               }
+
+               if (i == 0)
+                       put_open(proc, name, PF_NONAME, "[", ", ");
+
+               put_struct_statvfs(proc, NULL, PF_LOCADDR, (vir_bytes)&buf);
+       }
+
+       if (i == 0)
+               put_open(proc, name, PF_NONAME, "[", ", ");
+       if (i < max)
+               put_tail(proc, 0, 0);
+       else if (count > i)
+               put_tail(proc, count, i);
+       put_close(proc, "]");
+}
+
+static int
+vfs_getvfsstat_out(struct trace_proc * proc, const message * m_out)
+{
+
+       if (m_out->m_lc_vfs_getvfsstat.buf == 0) {
+               put_ptr(proc, "buf", m_out->m_lc_vfs_getvfsstat.buf);
+               put_value(proc, "bufsize", "%zu",
+                   m_out->m_lc_vfs_getvfsstat.len);
+               put_flags(proc, "flags", statvfs_flags, COUNT(statvfs_flags),
+                   "%d", m_out->m_lc_vfs_getvfsstat.flags);
+               return CT_DONE;
+       } else
+               return CT_NOTDONE;
+}
+
+static void
+vfs_getvfsstat_in(struct trace_proc * proc, const message * m_out,
+       const message * m_in, int failed)
+{
+
+       if (m_out->m_lc_vfs_getvfsstat.buf != 0) {
+               put_statvfs_array(proc, "buf", failed,
+                   m_out->m_lc_vfs_getvfsstat.buf, m_in->m_type);
+               put_value(proc, "bufsize", "%zu",
+                   m_out->m_lc_vfs_getvfsstat.len);
+               put_flags(proc, "flags", statvfs_flags, COUNT(statvfs_flags),
+                   "%d", m_out->m_lc_vfs_getvfsstat.flags);
+               put_equals(proc);
+       }
+       put_result(proc);
+}
+
+static int
+vfs_statvfs1_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_buf(proc, "path", PF_PATH, m_out->m_lc_vfs_statvfs1.name,
+           m_out->m_lc_vfs_statvfs1.len);
+
+       return CT_NOTDONE;
+}
+
+static void
+vfs_statvfs1_in(struct trace_proc * proc, const message * m_out,
+       const message * __unused m_in, int failed)
+{
+
+       put_struct_statvfs(proc, "buf", failed, m_out->m_lc_vfs_statvfs1.buf);
+       put_flags(proc, "flags", statvfs_flags, COUNT(statvfs_flags), "%d",
+           m_out->m_lc_vfs_statvfs1.flags);
+       put_equals(proc);
+       put_result(proc);
+}
+
+/* This function is shared between statvfs1 and fstatvfs1. */
+static int
+vfs_fstatvfs1_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_fd(proc, "fd", m_out->m_lc_vfs_statvfs1.fd);
+
+       return CT_NOTDONE;
+}
+
+static int
+vfs_getrusage_out(struct trace_proc * __unused proc,
+       const message * __unused m_out)
+{
+
+       return CT_NOTDONE;
+}
+
+static void
+vfs_getrusage_in(struct trace_proc * proc, const message * m_out,
+       const message * __unused m_in, int failed)
+{
+       struct rusage buf;
+
+       /* Inline; we will certainly not be reusing this anywhere else. */
+       if (put_open_struct(proc, "rusage", failed,
+           m_out->m_lc_vfs_rusage.addr, &buf, sizeof(buf))) {
+               /* Reason for hiding these two better: they're always zero. */
+               if (verbose > 1) {
+                       put_value(proc, "ru_inblock", "%ld", buf.ru_inblock);
+                       put_value(proc, "ru_oublock", "%ld", buf.ru_oublock);
+               }
+               if (verbose > 0) {
+                       put_value(proc, "ru_ixrss", "%ld", buf.ru_ixrss);
+                       put_value(proc, "ru_idrss", "%ld", buf.ru_idrss);
+                       put_value(proc, "ru_isrss", "%ld", buf.ru_isrss);
+               }
+
+               put_close_struct(proc, verbose > 1);
+       }
+       put_equals(proc);
+       put_result(proc);
+}
+
+static int
+vfs_svrctl_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_ioctl_req(proc, "request", m_out->m_lc_svrctl.request,
+           TRUE /*is_svrctl*/);
+       return put_ioctl_arg_out(proc, "arg", m_out->m_lc_svrctl.request,
+           m_out->m_lc_svrctl.arg, TRUE /*is_svrctl*/);
+}
+
+static void
+vfs_svrctl_in(struct trace_proc * proc, const message * m_out,
+       const message * __unused m_in, int failed)
+{
+
+       put_ioctl_arg_in(proc, "arg", failed, m_out->m_lc_svrctl.request,
+           m_out->m_lc_svrctl.arg, TRUE /*is_svrctl*/);
+}
+
+static int
+vfs_gcov_flush_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_ptr(proc, "buff", m_out->m_lc_vfs_gcov.buff_p);
+       put_value(proc, "buff_sz", "%zu", m_out->m_lc_vfs_gcov.buff_sz);
+       put_value(proc, "server_pid", "%d", m_out->m_lc_vfs_gcov.pid);
+
+       return CT_DONE;
+}
+
+#define VFS_CALL(c) [((VFS_ ## c) - VFS_BASE)]
+
+static const struct call_handler vfs_map[] = {
+       VFS_CALL(READ) = HANDLER("read", vfs_read_out, vfs_read_in),
+       VFS_CALL(WRITE) = HANDLER("write", vfs_write_out, default_in),
+       VFS_CALL(LSEEK) = HANDLER("lseek", vfs_lseek_out, vfs_lseek_in),
+       VFS_CALL(OPEN) = HANDLER("open", vfs_open_out, vfs_open_in),
+       VFS_CALL(CREAT) = HANDLER("open", vfs_creat_out, vfs_open_in),
+       VFS_CALL(CLOSE) = HANDLER("close", vfs_close_out, default_in),
+       VFS_CALL(LINK) = HANDLER("link", vfs_link_out, default_in),
+       VFS_CALL(UNLINK) = HANDLER("unlink", vfs_path_out, default_in),
+       VFS_CALL(CHDIR) = HANDLER("chdir", vfs_path_out, default_in),
+       VFS_CALL(MKDIR) = HANDLER("mkdir", vfs_path_mode_out, default_in),
+       VFS_CALL(MKNOD) = HANDLER("mknod", vfs_mknod_out, default_in),
+       VFS_CALL(CHMOD) = HANDLER("chmod", vfs_path_mode_out, default_in),
+       VFS_CALL(CHOWN) = HANDLER("chown", vfs_chown_out, default_in),
+       VFS_CALL(MOUNT) = HANDLER("mount", vfs_mount_out, default_in),
+       VFS_CALL(UMOUNT) = HANDLER("umount", vfs_umount_out, vfs_umount_in),
+       VFS_CALL(ACCESS) = HANDLER("access", vfs_access_out, default_in),
+       VFS_CALL(SYNC) = HANDLER("sync", default_out, default_in),
+       VFS_CALL(RENAME) = HANDLER("rename", vfs_link_out, default_in),
+       VFS_CALL(RMDIR) = HANDLER("rmdir", vfs_path_out, default_in),
+       VFS_CALL(SYMLINK) = HANDLER("symlink", vfs_link_out, default_in),
+       VFS_CALL(READLINK) = HANDLER("readlink", vfs_readlink_out,
+           vfs_readlink_in),
+       VFS_CALL(STAT) = HANDLER("stat", vfs_stat_out, vfs_stat_in),
+       VFS_CALL(FSTAT) = HANDLER("fstat", vfs_fstat_out, vfs_fstat_in),
+       VFS_CALL(LSTAT) = HANDLER("lstat", vfs_stat_out, vfs_stat_in),
+       VFS_CALL(IOCTL) = HANDLER("ioctl", vfs_ioctl_out, vfs_ioctl_in),
+       VFS_CALL(FCNTL) = HANDLER("fcntl", vfs_fcntl_out, vfs_fcntl_in),
+       VFS_CALL(PIPE2) = HANDLER("pipe2", vfs_pipe2_out, vfs_pipe2_in),
+       VFS_CALL(UMASK) = HANDLER("umask", vfs_umask_out, vfs_umask_in),
+       VFS_CALL(CHROOT) = HANDLER("chroot", vfs_path_out, default_in),
+       VFS_CALL(GETDENTS) = HANDLER("getdents", vfs_getdents_out,
+           vfs_getdents_in),
+       VFS_CALL(SELECT) = HANDLER("select", vfs_select_out, vfs_select_in),
+       VFS_CALL(FCHDIR) = HANDLER("fchdir", vfs_fchdir_out, default_in),
+       VFS_CALL(FSYNC) = HANDLER("fsync", vfs_fsync_out, default_in),
+       VFS_CALL(TRUNCATE) = HANDLER("truncate", vfs_truncate_out, default_in),
+       VFS_CALL(FTRUNCATE) = HANDLER("ftruncate", vfs_ftruncate_out,
+           default_in),
+       VFS_CALL(FCHMOD) = HANDLER("fchmod", vfs_fchmod_out, default_in),
+       VFS_CALL(FCHOWN) = HANDLER("fchown", vfs_fchown_out, default_in),
+       VFS_CALL(UTIMENS) = HANDLER_NAME(vfs_utimens_name, vfs_utimens_out,
+           default_in),
+       VFS_CALL(GETVFSSTAT) = HANDLER("getvfsstat", vfs_getvfsstat_out,
+           vfs_getvfsstat_in),
+       VFS_CALL(STATVFS1) = HANDLER("statvfs1", vfs_statvfs1_out,
+           vfs_statvfs1_in),
+       VFS_CALL(FSTATVFS1) = HANDLER("fstatvfs1", vfs_fstatvfs1_out,
+           vfs_statvfs1_in),
+       VFS_CALL(GETRUSAGE) = HANDLER("vfs_getrusage", vfs_getrusage_out,
+           vfs_getrusage_in),
+       VFS_CALL(SVRCTL) = HANDLER("vfs_svrctl", vfs_svrctl_out,
+           vfs_svrctl_in),
+       VFS_CALL(GCOV_FLUSH) = HANDLER("gcov_flush", vfs_gcov_flush_out,
+           default_in),
+};
+
+const struct calls vfs_calls = {
+       .endpt = VFS_PROC_NR,
+       .base = VFS_BASE,
+       .map = vfs_map,
+       .count = COUNT(vfs_map)
+};
diff --git a/minix/usr.bin/trace/service/vm.c b/minix/usr.bin/trace/service/vm.c
new file mode 100644 (file)
index 0000000..43d5423
--- /dev/null
@@ -0,0 +1,135 @@
+
+#include "inc.h"
+
+#include <sys/mman.h>
+#include <sys/resource.h>
+
+static int
+vm_brk_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_ptr(proc, "addr", (vir_bytes)m_out->m_lc_vm_brk.addr);
+
+       return CT_DONE;
+}
+
+static const struct flags mmap_prot[] = {
+       FLAG_ZERO(PROT_NONE),
+       FLAG(PROT_READ),
+       FLAG(PROT_WRITE),
+       FLAG(PROT_EXEC),
+};
+
+static const struct flags mmap_flags[] = {
+       FLAG(MAP_SHARED),
+       FLAG(MAP_PRIVATE),
+       FLAG(MAP_FIXED),
+       FLAG(MAP_RENAME),
+       FLAG(MAP_NORESERVE),
+       FLAG(MAP_INHERIT),
+       FLAG(MAP_HASSEMAPHORE),
+       FLAG(MAP_TRYFIXED),
+       FLAG(MAP_WIRED),
+       FLAG_MASK(MAP_ANON | MAP_STACK, MAP_FILE),
+       FLAG(MAP_ANON),
+       FLAG(MAP_STACK),
+       FLAG(MAP_UNINITIALIZED),
+       FLAG(MAP_PREALLOC),
+       FLAG(MAP_CONTIG),
+       FLAG(MAP_LOWER16M),
+       FLAG(MAP_LOWER1M),
+       FLAG(MAP_THIRDPARTY),
+       /* TODO: interpret alignments for which there is no constant */
+       FLAG_MASK(MAP_ALIGNMENT_MASK, MAP_ALIGNMENT_64KB),
+       FLAG_MASK(MAP_ALIGNMENT_MASK, MAP_ALIGNMENT_16MB),
+       FLAG_MASK(MAP_ALIGNMENT_MASK, MAP_ALIGNMENT_4GB),
+       FLAG_MASK(MAP_ALIGNMENT_MASK, MAP_ALIGNMENT_1TB),
+       FLAG_MASK(MAP_ALIGNMENT_MASK, MAP_ALIGNMENT_256TB),
+       FLAG_MASK(MAP_ALIGNMENT_MASK, MAP_ALIGNMENT_64PB),
+};
+
+static int
+vm_mmap_out(struct trace_proc * proc, const message * m_out)
+{
+
+       if (m_out->m_mmap.flags & MAP_THIRDPARTY)
+               put_endpoint(proc, "forwhom", m_out->m_mmap.forwhom);
+       put_ptr(proc, "addr", (vir_bytes)m_out->m_mmap.addr);
+       put_value(proc, "len", "%zu", m_out->m_mmap.len);
+       put_flags(proc, "prot", mmap_prot, COUNT(mmap_prot), "0x%x",
+           m_out->m_mmap.prot);
+       put_flags(proc, "flags", mmap_flags, COUNT(mmap_flags), "0x%x",
+           m_out->m_mmap.flags);
+       put_fd(proc, "fd", m_out->m_mmap.fd);
+       put_value(proc, "offset", "%"PRId64, m_out->m_mmap.offset);
+
+       return CT_DONE;
+}
+
+static void
+vm_mmap_in(struct trace_proc * proc, const message * __unused m_out,
+       const message * m_in, int failed)
+{
+
+       if (!failed)
+               put_ptr(proc, NULL, (vir_bytes)m_in->m_mmap.retaddr);
+       else
+               /* TODO: consider printing MAP_FAILED in the right cases */
+               put_result(proc);
+}
+
+static int
+vm_munmap_out(struct trace_proc * proc, const message * m_out)
+{
+
+       put_ptr(proc, "addr", (vir_bytes)m_out->m_mmap.addr);
+       put_value(proc, "len", "%zu", m_out->m_mmap.len);
+
+       return CT_DONE;
+}
+
+static int
+vm_getrusage_out(struct trace_proc * __unused proc,
+       const message * __unused m_out)
+{
+
+       return CT_NOTDONE;
+}
+
+static void
+vm_getrusage_in(struct trace_proc * proc, const message * m_out,
+       const message * __unused m_in, int failed)
+{
+       struct rusage buf;
+
+       /* Inline; we will certainly not be reusing this anywhere else. */
+       if (put_open_struct(proc, "rusage", failed,
+           m_out->m_lc_vm_rusage.addr, &buf, sizeof(buf))) {
+               if (verbose > 0) {
+                       put_value(proc, "ru_maxrss", "%ld", buf.ru_maxrss);
+                       put_value(proc, "ru_minflt", "%ld", buf.ru_minflt);
+                       put_value(proc, "ru_majflt", "%ld", buf.ru_majflt);
+               }
+
+               put_close_struct(proc, verbose > 0);
+       }
+       put_equals(proc);
+       put_result(proc);
+}
+
+#define VM_CALL(c) [((VM_ ## c) - VM_RQ_BASE)]
+
+static const struct call_handler vm_map[] = {
+       VM_CALL(BRK) = HANDLER("brk", vm_brk_out, default_in),
+       VM_CALL(MMAP) = HANDLER("mmap", vm_mmap_out, vm_mmap_in),
+       VM_CALL(MUNMAP) = HANDLER("munmap", vm_munmap_out, default_in),
+       VM_CALL(GETRUSAGE) = HANDLER("vm_getrusage", vm_getrusage_out,
+           vm_getrusage_in),
+};
+
+const struct calls vm_calls = {
+       .endpt = VM_PROC_NR,
+       .base = VM_RQ_BASE,
+       .map = vm_map,
+       .count = COUNT(vm_map)
+};
diff --git a/minix/usr.bin/trace/signal.awk b/minix/usr.bin/trace/signal.awk
new file mode 100644 (file)
index 0000000..84c54b8
--- /dev/null
@@ -0,0 +1,32 @@
+# This one is a bit trickier than error.awk, because sys/signal.h is not as
+# easy to parse.  We currently assume that all (userland) signals are listed
+# before the first reference to "_KERNEL", and anything else that looks like a
+# signal definition (but isn't) is after that first reference.
+
+BEGIN {
+       printf("/* This file is automatically generated by signal.awk */\n\n");
+       printf("#include \"inc.h\"\n\n");
+       printf("static const char *const signals[] = {\n");
+}
+/^#define/ {
+       name = $2;
+       if (!match(name, "SIG[^_]"))
+               next;
+       number = $3;
+       if (number < 0 || number == "SIGABRT")
+               next;
+       printf("\t[%s] = \"%s\",\n", name, name);
+}
+/_KERNEL/ {
+       exit;
+}
+END {
+       printf("};\n\n");
+       printf("const char *\nget_signal_name(int sig)\n{\n\n");
+       printf("\tif (sig >= 0 && sig < sizeof(signals) / sizeof(signals[0]) &&\n");
+       printf("\t    signals[sig] != NULL)\n");
+       printf("\t\treturn signals[sig];\n");
+       printf("\telse\n");
+       printf("\t\treturn NULL;\n");
+       printf("}\n");
+}
diff --git a/minix/usr.bin/trace/trace.1 b/minix/usr.bin/trace/trace.1
new file mode 100644 (file)
index 0000000..239fa97
--- /dev/null
@@ -0,0 +1,334 @@
+.Dd November 2, 2014
+.Dt TRACE 1
+.Os
+.Sh NAME
+.Nm trace
+.Nd print process system calls and signals
+.Sh SYNOPSIS
+.Nm
+.Op Fl fgNsVv
+.Op Fl o Ar file
+.Op Fl p Ar pid
+.Op Ar command
+.Sh DESCRIPTION
+The
+.Nm
+utility shows one or more processes to be traced.
+For each traced process,
+.Nm
+prints the system calls the process makes and the signals
+it receives.
+The user can let
+.Nm
+start a
+.Ar command
+to be traced, and/or attach to one or more existing processes.
+.Pp
+The utility will run until no processes are left to trace, or until the user
+presses the interrupt key (typically Ctrl-C).
+Pressing this key once will cause all attached processes to be detached, with
+the hope that the command that was started will also terminate cleanly from the
+interruption.
+Pressing the interrupt key once more kills the command that was started.
+.Pp
+The following options are available:
+.Bl -tag -width XoXfileXX
+.It Fl f
+Follow forks.
+Attach automatically to forked child processes.
+Child processes of the started command will be treated as attached processes,
+in that upon Ctrl-C presses they will be detached rather than killed.
+.It Fl g
+Enable call grouping.
+With this option, the tracing engine tries to reduce noise from call preemption
+by first polling the process that was active last.
+This should reduce in cleaner output, but may also cause a single process to be
+scheduled repeatedly and thus cause starvation.
+.It Fl N
+Print all names.
+By default, the most structure fields are printed with their name.
+This option enables printing of all available names, which also includes
+system call parameter names.
+This flag may be useful to figure out the meaning of a parameter, and for
+automatic processing of the output.
+.It Fl s
+Print stack traces.
+Each system call, and each signal arriving outside a system call, will be
+preceded by a line showing the process's current stack trace.
+For signals blocked by the target process, the stack trace may not be
+meaningful.
+Stack traces may not be supported on all platforms.
+.It Fl V
+Print values only.
+If this flag is given once, numerical values will be printed instead of
+string constants.
+In addition, if it is given twice, the addresses of structures will be printed
+instead of their contents.
+.It Fl v
+Increase verbosity.
+By default, the output will be terse, in that not all structure fields are
+shown, and strings and arrays are not always printed in full.
+If this flag is provided once, more and longer output will be printed.
+If it is provided twice, the tracer will print as much as possible.
+.It Fl o Ar file
+Redirect output.
+By default, the output is sent to standard error.
+With this option, the output is written to the given
+.Ar file
+instead.
+.It Fl p Ar pid
+Attach to a process.
+This option makes
+.Nm
+attach to an existing process with process ID
+.Ar pid .
+This option may be used multiple times.
+When attaching to one or more processes this way, starting a command becomes
+optional.
+.El
+.Pp
+If the user presses the information key (typically Ctrl-T), the list of traced
+process along with their current status will be printed.
+.Sh OUTPUT FORMAT
+System calls are printed with the following general output format:
+.Bd -literal -offset indent
+.Sy name Ns ( Ns Sy parameters Ns ) = Sy result
+.Ed
+.Pp
+Other informational lines may be printed about the status of the process.
+These lines typically start with an uppercase letter, while system calls
+always start with a lowercase letter or an underscore.
+The following example shows the tracer output for a program that prints its
+own user ID:
+.Bd -literal -offset indent
+Tracing printuid (pid 12685)
+minix_getinfo() = 0
+getuid() = 0 (euid=1)
+write(1, "My uid: 0\en", 10) = 10
+exit(0)
+Process exited normally with code 0
+.Ed
+.Pp
+The first and last lines of the output provide status information about the
+traced process.
+Some calls return multiple results; extended results are printed in parentheses
+after the primary call result, typically in
+.Va name Ns = Ns Va value
+format for clarity.
+System calls that do not return on success, such as
+.Fn exit ,
+are printed without the equals sign and result, unless they fail.
+System call failure is printed according to POSIX conventions; that is, the
+call is assumed to return -1 with the value of
+.Va errno
+printed in square brackets after it:
+.Bd -literal -offset indent
+setuid(0) = -1 [EPERM]
+.Ed
+.Pp
+If a system call ends up in an IPC-level failure, the -1 value will be preceded
+by an
+.Dq Li <ipc>
+string.
+However, this string will be omitted if the system call itself is printed at
+the IPC level (that is, as an
+.Fn ipc_sendrec
+call), generally because
+.Nm
+has no handler to print the actual system call.
+.Pp
+Signals are printed as they arrive at the traced process, using two asterisks
+on both side of the signal name.
+Signals may arrive both during and outside the execution of a system call:
+.Bd -literal -offset indent
+read(3, ** SIGUSR1 ** &0xeffff867, 4096) = -1 [EINTR]
+** SIGUSR2 **
+getpid() = 5278 (ppid=5277)
+kill(5278, SIGTERM) = ** SIGTERM ** <..>
+Process terminated from signal SIGTERM
+.Ed
+.Pp
+Multiple signals may be printed consecutively.
+The above example illustrates a few other important aspects of output
+formatting.
+Some call parameters may be printed only after the system call returns, in
+order to show their actual value.
+For the
+.Fn read
+call, this would be the bytes that were read.
+Upon failure, no bytes were read, so the buffer pointer is printed instead.
+Finally, if a call that is expected to return (here,
+.Fn kill )
+does not return before the process terminates, the line ends with a
+.Dq Li <..>
+marker.
+This is an instance of call preemption; more about that later.
+.Pp
+Pointers are printed with a
+.Sq Li &
+prefix, except for NULL, which is printed using its own name.
+In general, named constants are used instead of numerical constants wherever
+that makes sense.
+For pointers of which the address is not available, typically because its
+contents are passed by value,
+.Dq Li &..
+is shown instead.
+.Pp
+Data buffers are printed as double-quoted strings, using C-style character
+escaping for nontextual bytes.
+If either the verbosity level or a copy error prevents the whole data buffer
+from being printed, two dots will be printed after the closing quote.
+The same is done when printing a string buffer which does not have a null
+termination byte within its range.
+Path names are shown in full regardless of the verbosity level.
+.Pp
+Structures are printed as a set of structure fields enclosed in curly brackets.
+The
+.Va name Ns = Ns Va value
+format is used, unless printing names for that structure type would introduce
+too much noise and the
+.Dq print all names
+option is not given.
+For many structures, by default only a subset of their fields are printed.
+In this case, a
+.Dq Li ..
+entry is added at the end.
+In some cases, an attempt is made to print only the most useful fields:
+.Bd -literal -offset indent
+stat("/etc/motd", {st_mode=S_IFREG|0755, st_size=747, ..}) = 0
+stat("/dev/tty", {st_mode=S_IFCHR|0666, st_rdev=<5,0>, ..}) = 0
+.Ed
+.Pp
+As shown in the above example, flag fields are printed as a combination of
+named constants, separated by a
+.Sq Li |
+pipe symbol.
+Any leftover numerical bits are printed at the end.
+The example also shows the format in which major/minor pairs are printed for
+device numbers.
+This is a custom format; there are a few other custom formats throughout the
+.Nm
+output which are supposed to be sufficiently self-explanatory (and rare).
+.Pp
+Arrays are printed using square brackets.
+.Bd -literal -offset indent
+pipe2([3, 4], 0) = 0
+getdents(3, [..(45)], 4096) = 1824
+getdents(3, [{d_name="."}, ..(+44)], 4096) = 1824
+getdents(3, [], 4096) = 0
+.Ed
+.Pp
+If the array contents are not printed as per the settings for the verbosity
+level, a single pseudo-element shows how many actual elements were in the array
+(the second line in the example).
+If the number of printed elements is limited, a final pseudo-element shows how
+many additional elements were not printed (the third line in the example).
+If a copy error occurs while part of the array has been printed already, a
+last
+.Dq Li ..(?)
+pseudo-element is printed; for immediate failure, the array's pointer is shown.
+Empty arrays will be printed as
+.Dq Li [] .
+.Pp
+Bit sets are printed as arrays except with just a space and no comma as
+bit separator, closely following the output format of
+.Nm Ns 's
+original inspiration
+.Sy strace .
+For signal sets in particular, an inverted bit set may be shown, thus printing
+only the bits which are not set; such sets are prefixed with a
+.Sq Li ~
+to the opening bracket:
+.Bd -literal -offset indent
+sigprocmask(SIG_SETMASK, ~[USR1 USR2], []) = 0
+.Ed
+.Pp
+Note how the
+.Dq Li SIG
+prefixes are omitted for brevity in this case.
+.Pp
+When multiple processes are traced at once, each line will have a prefix that
+shows the PID of the corresponding process.
+When the number of processes drops to one again, one more line is prefixed with
+the PID of the remaining process, but using a
+.Sq Li '
+instead of a
+.Sq Li |
+symbol:
+.Bd -literal -offset indent
+fork() = 25813
+25813| Tracing test*F (pid 25813)
+25813| fork() = 0
+25812| waitpid(-1, &.., WNOHANG) = 0
+25813| exit(1)
+25813| Process exited normally with code 1
+25812' waitpid(-1, W_EXITED(1), WNOHANG) = 25813
+exit(0)
+Process exited normally with code 0
+.Ed
+.Pp
+If a process is preempted while making a system call, the system call will
+be shown as suspended with the
+.Dq Li <..>
+suffix.
+Later, when the system call is resumed, the output so far will be repeated,
+either in full or (due to memory limitations) with
+.Dq Li <..>
+in its body, before the remaining part of the system call is printed.
+This time, the line will have a
+.Sq Li *
+asterisk in its prefix, to indicate that this is not a new system call:
+.Bd -literal -offset indent
+25812| write(1, "test\en", 5) = <..>
+25813| setuid(0) = 0
+25812|*write(1, "test\en", 5) = 5
+.Ed
+.Pp
+Finally,
+.Nm
+prints three dashes on their own line whenever the process context (program
+counter and/or stack pointer) is changed during a system call.
+This feature intends to help identify blocks of code run from signal handlers.
+The following example shows a SIGALRM signal handler being invoked.
+.Bd -literal -offset indent
+sigsuspend([]) = ** SIGALRM ** -1 [EINTR]
+---
+sigprocmask(SIG_SETMASK, ~[], [ALRM]) = 0
+sigreturn({sc_mask=[], ..})
+---
+exit(0)
+.Ed
+.Pp
+However, the three dashes are not printed when a signal handler is invoked
+while the program is not in a system call, because the tracer does not see such
+invocations.
+It is however also printed for successful
+.Fn execve
+calls.
+.Sh DIAGNOSTICS
+.Ex
+.Sh SEE ALSO
+.Xr ptrace 2
+.Sh AUTHORS
+The
+.Nm
+utility was written by
+.An David van Moolenbroek
+.Aq david@minix3.org .
+.Sh BUGS
+While the utility aims to provide output for all system calls that can possibly
+be made by user programs, output printers for a small number of rarely-used
+structures and IOCTLs are still missing.  In such cases, plain pointers will be
+printed instead of actual contents.
+.Pp
+A signal arrives at the tracing process when sent to the target process, even
+when the target process is blocking the signal and will thus receive it later.
+This is a limitation of the ptrace infrastructure, although it does ensure that
+a target process is not able to block signals generated for tracing purposes.
+The result is that signals are not always shown at the time that they are
+taken in by the target process, and that stack traces for signals may be off.
+.Pp
+Attaching to system services is currently not supported, due to limitations of
+the ptrace infrastructure.  The
+.Nm
+utility will detect and safely detach from system services, though.
diff --git a/minix/usr.bin/trace/trace.c b/minix/usr.bin/trace/trace.c
new file mode 100644 (file)
index 0000000..32ae373
--- /dev/null
@@ -0,0 +1,817 @@
+/* trace(1) - the MINIX3 system call tracer - by D.C. van Moolenbroek */
+
+#include "inc.h"
+
+#include <signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <err.h>
+
+/* Global variables, used only for a subset of the command line options. */
+int allnames;           /* FALSE = structure field names, TRUE = all names */
+unsigned int valuesonly; /* 0 = normal, 1 = no symbols, 2 = no structures */
+unsigned int verbose;   /* 0 = essentials, 1 = elaborate, 2 = everything */
+
+/* Local variables, for signal handling. */
+static int got_signal, got_info;
+
+/*
+ * Signal handler for signals that are supposed to make us terminate.  Let the
+ * main loop do the actual work, since it might be in the middle of processing
+ * a process status change right now.
+ */
+static void
+sig_handler(int __unused sig)
+{
+
+       got_signal = TRUE;
+
+}
+
+/*
+ * Signal handler for the SIGINFO signal.  Let the main loop report on all
+ * processes currenty being traced.  Since SIGINFO is sent to the current
+ * process group, traced children may get the signal as well.  This is both
+ * intentional and impossible to prevent.
+ */
+static void
+info_handler(int __unused sig)
+{
+
+       got_info = TRUE;
+}
+
+/*
+ * Print a list of traced processes and their call status.  We must not
+ * interfere with actual process output, so perform out-of-band printing
+ * (with info lines rather than lines prefixed by each process's PID).
+ */
+static void
+list_info(void)
+{
+       struct trace_proc *proc;
+       int no_call, in_call;
+
+       put_newline();
+
+       for (proc = proc_next(NULL); proc != NULL; proc = proc_next(proc)) {
+               /*
+                * When attaching to an existing process, there is no way to
+                * find out whether the process is in a system call or not.
+                */
+               no_call = (proc->trace_flags & TF_NOCALL);
+               in_call = (proc->trace_flags & TF_INCALL);
+               assert(!in_call || !no_call);
+
+               put_fmt(NULL, "Tracing %s (pid %d), %s%s%s", proc->name,
+                   proc->pid, no_call ? "call status unknown" :
+                   (in_call ? "in a " : "not in a call"),
+                   in_call ? call_name(proc) : "",
+                   in_call ? " call" : "");
+               put_newline();
+       }
+}
+
+/*
+ * Either we have just started or attached to the given process, it the process
+ * has performed a successful execve() call.  Obtain the new process name, and
+ * print a banner for it.
+ */
+static void
+new_exec(struct trace_proc * proc)
+{
+
+       /* Failure to obtain the process name is worrisome, but not fatal.. */
+       if (kernel_get_name(proc->pid, proc->name, sizeof(proc->name)) < 0)
+               strlcpy(proc->name, "<unknown>", sizeof(proc->name));
+
+       put_newline();
+       put_fmt(proc, "Tracing %s (pid %d)", proc->name, proc->pid);
+       put_newline();
+}
+
+/*
+ * We have started or attached to a process.  Set the appropriate flags, and
+ * print a banner showing that we are now tracing it.
+ */
+static void
+new_proc(struct trace_proc * proc, int follow_fork)
+{
+       int fl;
+
+       /* Set the desired tracing options. */
+       fl = TO_ALTEXEC;
+       if (follow_fork) fl |= TO_TRACEFORK;
+
+       (void)ptrace(T_SETOPT, proc->pid, 0, fl);
+
+       /*
+        * When attaching to an arbitrary process, this process might be in the
+        * middle of an execve().  Now that we have enabled TO_ALTEXEC, we may
+        * now get a SIGSTOP signal next.  Guard against this by marking the
+        * first system call as a possible execve().
+        */
+       if ((proc->trace_flags & (TF_ATTACH | TF_STOPPING)) == TF_ATTACH)
+               proc->trace_flags |= TF_EXEC;
+
+       new_exec(proc);
+}
+
+/*
+ * A process has terminated or is being detached.  Print the resulting status.
+ */
+static void
+discard_proc(struct trace_proc * proc, int status)
+{
+       const char *signame;
+
+       /*
+        * The exit() calls are of type no-return, meaning they are expected
+        * not to return.  However, calls of this type may in fact return an
+        * error, in which case the error must be printed.  Thus, such calls
+        * are not actually finished until the end of the call-leave phase.
+        * For exit() calls, a successful call will never get to the call-leave
+        * phase.  The result is that such calls will end up being shown as
+        * suspended, which is unintuitive.  To counter this, we pretend that a
+        * clean process exit is in fact preceded by a call-leave event, thus
+        * allowing the call to be printed without suspension.  An example:
+        *
+        *        3| exit(0) <..>
+        *        2| setsid() = 2
+        * [A]    3| exit(0)
+        *        3| Process exited normally with code 0
+        *
+        * The [A] line is the result of the following code.
+        */
+       if (WIFEXITED(status) && (proc->trace_flags & TF_INCALL))
+               call_leave(proc, TRUE /*skip*/);
+
+       put_newline();
+       if (WIFEXITED(status)) {
+               put_fmt(proc, "Process exited normally with code %d",
+                   WEXITSTATUS(status));
+       } else if (WIFSIGNALED(status)) {
+               if ((signame = get_signal_name(WTERMSIG(status))) != NULL)
+                       put_fmt(proc, "Process terminated from signal %s",
+                           signame);
+               else
+                       put_fmt(proc, "Process terminated from signal %d",
+                           WTERMSIG(status));
+       } else if (WIFSTOPPED(status))
+               put_text(proc, "Process detached");
+       else
+               put_fmt(proc, "Bogus wait result (%04x)", status);
+       put_newline();
+
+       proc_del(proc);
+}
+
+/*
+ * The given process has been stopped on a system call, either entering or
+ * leaving that call.
+ */
+static void
+handle_call(struct trace_proc * proc, int show_stack)
+{
+       reg_t pc, sp;
+       int class, skip, new_ctx;
+
+       proc->trace_flags &= ~TF_NOCALL;
+
+       if (proc->trace_flags & TF_SKIP) {
+               /* Skip the call leave phase after a successful execve(). */
+               proc->trace_flags &= ~(TF_INCALL | TF_SKIP);
+       } else if (!(proc->trace_flags & TF_INCALL)) {
+               /*
+                * The call_enter call returns the class of the call:
+                * TC_NORMAL, TC_EXEC, or TC_SIGRET.  TC_EXEC means that an
+                * execve() call is being performed.  This means that if a
+                * SIGSTOP follows for the current process, the process has
+                * successfully started a different executable.  TC_SIGRET
+                * means that if successful, the call will have a bogus return
+                * value.  TC_NORMAL means that the call requires no exception.
+                */
+               class = call_enter(proc, show_stack);
+
+               switch (class) {
+               case TC_NORMAL:
+                       break;
+               case TC_EXEC:
+                       proc->trace_flags |= TF_EXEC;
+                       break;
+               case TC_SIGRET:
+                       proc->trace_flags |= TF_CTX_SKIP;
+                       break;
+               default:
+                       assert(0);
+               }
+
+               /* Save the current program counter and stack pointer. */
+               if (!kernel_get_context(proc->pid, &pc, &sp, NULL /*fp*/)) {
+                       proc->last_pc = pc;
+                       proc->last_sp = sp;
+               } else
+                       proc->last_pc = proc->last_sp = 0;
+
+               proc->trace_flags |= TF_INCALL;
+       } else {
+               /*
+                * Check if the program counter or stack pointer have changed
+                * during the system call.  If so, this is a strong indication
+                * that a sigreturn call has succeeded, and thus its result
+                * must be skipped, since the result register will not contain
+                * the result of the call.
+                */
+               new_ctx = (proc->last_pc != 0 &&
+                   !kernel_get_context(proc->pid, &pc, &sp, NULL /*fp*/) &&
+                   (pc != proc->last_pc || sp != proc->last_sp));
+
+               skip = ((proc->trace_flags & TF_CTX_SKIP) && new_ctx);
+
+               call_leave(proc, skip);
+
+               /*
+                * On such context changes, also print a short dashed line.
+                * This helps in identifying signal handler invocations,
+                * although it is not reliable for that purpose: no dashed line
+                * will be printed if a signal handler is invoked while the
+                * process is not making a system call.
+                */
+               if (new_ctx) {
+                       put_text(proc, "---");
+                       put_newline();
+               }
+
+               proc->trace_flags &= ~(TF_INCALL | TF_CTX_SKIP | TF_EXEC);
+       }
+}
+
+/*
+ * The given process has received the given signal.  Report the receipt.  Due
+ * to the way that signal handling with traced processes works, the signal may
+ * in fact be delivered to the process much later, or never--a problem inherent
+ * to the way signals are handled in PM right now (namely, deferring signal
+ * delivery would let the traced process block signals meant for the tracer).
+ */
+static void
+report_signal(struct trace_proc * proc, int sig, int show_stack)
+{
+       const char *signame;
+
+       /*
+        * Print a stack trace only if we are not in a call; otherwise, we
+        * would simply get the same stack trace twice and mess up the output
+        * in the process, because call suspension is not expected if we are
+        * tracing a single process only.
+        * FIXME: the check should be for whether we actually print the call..
+        */
+       if (show_stack && !(proc->trace_flags & TF_INCALL))
+               kernel_put_stacktrace(proc);
+
+       /*
+        * If this process is in the middle of a call, the signal will be
+        * printed within the call.  This will always happen on the call split,
+        * that is, between the call's entering (out) and leaving (in) phases.
+        * This also means that the recording of the call-enter phase may be
+        * replayed more than once, and the call may be suspended more than
+        * once--after all, a signal is not necessarily followed immediately
+        * by the call result.  If the process is not in the middle of a call,
+        * the signal will end up on a separate line.  In both cases, multiple
+        * consecutive signals may be printed right after one another.  The
+        * following scenario shows a number of possible combinations:
+        *
+        *       2| foo(<..>
+        *       3| ** SIGHUP ** ** SIGUSR1 **
+        *       3| bar() = <..>
+        *       2|*foo(** SIGUSR1 ** ** SIGUSR2 ** <..>
+        *       3|*bar() = ** SIGCHLD ** 0
+        *       2|*foo(** SIGINT ** &0xef852000) = -1 [EINTR]
+        *       3| kill(3, SIGTERM) = ** SIGTERM ** <..>
+        *       3| Process terminated from signal SIGTERM
+        */
+
+       call_replay(proc);
+
+       if (!valuesonly && (signame = get_signal_name(sig)) != NULL)
+               put_fmt(proc, "** %s **", signame);
+       else
+               put_fmt(proc, "** SIGNAL %d **", sig);
+
+       put_space(proc);
+
+       output_flush();
+}
+
+/*
+ * Wait for the given process ID to stop on the given signal.  Upon success,
+ * the function will return zero.  Upon failure, it will return -1, and errno
+ * will be either set to an error code, or to zero in order to indicate that
+ * the process exited instead.
+ */
+static int
+wait_sig(pid_t pid, int sig)
+{
+       int status;
+
+       for (;;) {
+               if (waitpid(pid, &status, 0) == -1) {
+                       if (errno == EINTR) continue;
+
+                       return -1;
+               }
+
+               if (!WIFSTOPPED(status)) {
+                       /* The process terminated just now. */
+                       errno = 0;
+
+                       return -1;
+               }
+
+               if (WSTOPSIG(status) == sig)
+                       break;
+
+               (void)ptrace(T_RESUME, pid, 0, WSTOPSIG(status));
+       }
+
+       return 0;
+}
+
+/*
+ * Attach to the given process, and wait for the resulting SIGSTOP signal.
+ * Other signals may arrive first; we pass these on to the process without
+ * reporting them, thus logically modelling them as having arrived before we
+ * attached to the process.  The process might also exit in the meantime,
+ * typically as a result of a lethal signal; following the same logical model,
+ * we pretend the process did not exist in the first place.  Since the SIGSTOP
+ * signal will be pending right after attaching to the process, this procedure
+ * will never block.
+ */
+static int
+attach(pid_t pid)
+{
+
+       if (ptrace(T_ATTACH, pid, 0, 0) != 0) {
+               warn("Unable to attach to pid %d", pid);
+
+               return -1;
+       }
+
+       if (wait_sig(pid, SIGSTOP) != 0) {
+               /* If the process terminated, report it as not found. */
+               if (errno == 0)
+                       errno = ESRCH;
+
+               warn("Unable to attach to pid %d", pid);
+
+               return -1;
+       }
+
+       /* Verify that we can read values from the kernel at all. */
+       if (kernel_check(pid) == FALSE) {
+               (void)ptrace(T_DETACH, pid, 0, 0);
+
+               warnx("Kernel magic check failed, recompile trace(1)");
+
+               return -1;
+       }
+
+       /*
+        * System services are managed by RS, which prevents them from
+        * being traced properly by PM.  Attaching to a service could
+        * therefore cause problems, so we should detach immediately.
+        */
+       if (kernel_is_service(pid) == TRUE) {
+               (void)ptrace(T_DETACH, pid, 0, 0);
+
+               warnx("Cannot attach to system services!");
+
+               return -1;
+       }
+
+       return 0;
+}
+
+/*
+ * Detach from all processes, knowning that they were all processes to which we
+ * attached explicitly (i.e., not started by us) and are all currently stopped.
+ */
+static void
+detach_stopped(void)
+{
+       struct trace_proc *proc;
+
+       for (proc = proc_next(NULL); proc != NULL; proc = proc_next(proc))
+               (void)ptrace(T_DETACH, proc->pid, 0, 0);
+}
+
+/*
+ * Start detaching from all processes to which we previously attached.  The
+ * function is expected to return before detaching is completed, and the caller
+ * must deal with the new situation appropriately.  Do not touch any processes
+ * started by us (to allow graceful termination), unless force is set, in which
+ * case those processes are killed.
+ */
+static void
+detach_running(int force)
+{
+       struct trace_proc *proc;
+
+       for (proc = proc_next(NULL); proc != NULL; proc = proc_next(proc)) {
+               if (proc->trace_flags & TF_ATTACH) {
+                       /* Already detaching?  Then do nothing. */
+                       if (proc->trace_flags & TF_DETACH)
+                               continue;
+
+                       if (!(proc->trace_flags & TF_STOPPING))
+                               (void)kill(proc->pid, SIGSTOP);
+
+                       proc->trace_flags |= TF_DETACH | TF_STOPPING;
+               } else {
+                       /*
+                        * The child processes may be ignoring SIGINTs, so upon
+                        * the second try, force them to terminate.
+                        */
+                       if (force)
+                               (void)kill(proc->pid, SIGKILL);
+               }
+       }
+}
+
+/*
+ * Print command usage.
+ */
+static void __dead
+usage(void)
+{
+
+       (void)fprintf(stderr, "usage: %s [-fgNsVv] [-o file] [-p pid] "
+           "[command]\n", getprogname());
+
+       exit(EXIT_FAILURE);
+}
+
+/*
+ * The main function of the system call tracer.
+ */
+int
+main(int argc, char * argv[])
+{
+       struct trace_proc *proc;
+       const char *output_file;
+       int status, sig, follow_fork, show_stack, grouping, first_signal;
+       pid_t pid, last_pid;
+       int c, error;
+
+       setprogname(argv[0]);
+
+       proc_init();
+
+       follow_fork = FALSE;
+       show_stack = FALSE;
+       grouping = FALSE;
+       output_file = NULL;
+
+       allnames = FALSE;
+       verbose = 0;
+       valuesonly = 0;
+
+       while ((c = getopt(argc, argv, "fgNsVvo:p:")) != -1) {
+               switch (c) {
+               case 'f':
+                       follow_fork = TRUE;
+                       break;
+               case 'g':
+                       grouping = TRUE;
+                       break;
+               case 'N':
+                       allnames = TRUE;
+                       break;
+               case 's':
+                       show_stack = TRUE;
+                       break;
+               case 'V':
+                       valuesonly++;
+                       break;
+               case 'v':
+                       verbose++;
+                       break;
+               case 'o':
+                       output_file = optarg;
+                       break;
+               case 'p':
+                       pid = atoi(optarg);
+                       if (pid <= 0)
+                               usage();
+
+                       if (proc_get(pid) == NULL && proc_add(pid) == NULL)
+                               err(EXIT_FAILURE, NULL);
+
+                       break;
+               default:
+                       usage();
+               }
+       }
+
+       argv += optind;
+       argc -= optind;
+
+       first_signal = TRUE;
+       got_signal = FALSE;
+       got_info = FALSE;
+
+       signal(SIGINT, sig_handler);
+       signal(SIGINFO, info_handler);
+
+       /* Attach to any processes for which PIDs were given. */
+       for (proc = proc_next(NULL); proc != NULL; proc = proc_next(proc)) {
+               if (attach(proc->pid) != 0) {
+                       /*
+                        * Detach from the processes that we have attached to
+                        * so far, i.e. the ones with the TF_ATTACH flag.
+                        */
+                       detach_stopped();
+
+                       return EXIT_FAILURE;
+               }
+
+               proc->trace_flags = TF_ATTACH | TF_NOCALL;
+       }
+
+       /* If a command is given, start a child that executes the command. */
+       if (argc >= 1) {
+               pid = fork();
+
+               switch (pid) {
+               case -1:
+                       warn("Unable to fork");
+
+                       detach_stopped();
+
+                       return EXIT_FAILURE;
+
+               case 0:
+                       (void)ptrace(T_OK, 0, 0, 0);
+
+                       (void)execvp(argv[0], argv);
+
+                       err(EXIT_FAILURE, "Unable to start %s", argv[0]);
+
+               default:
+                       break;
+               }
+
+               /*
+                * The first signal will now be SIGTRAP from the execvp(),
+                * unless that fails, in which case the child will terminate.
+                */
+               if (wait_sig(pid, SIGTRAP) != 0) {
+                       /*
+                        * If the child exited, the most likely cause is a
+                        * failure to execute the command.  Let the child
+                        * report the error, and do not say anything here.
+                        */
+                       if (errno != 0)
+                               warn("Unable to start process");
+
+                       detach_stopped();
+
+                       return EXIT_FAILURE;
+               }
+
+               /* If we haven't already, perform the kernel magic check. */
+               if (proc_count() == 0 && kernel_check(pid) == FALSE) {
+                       warnx("Kernel magic check failed, recompile trace(1)");
+
+                       (void)kill(pid, SIGKILL);
+
+                       detach_stopped();
+
+                       return EXIT_FAILURE;
+               }
+
+               if ((proc = proc_add(pid)) == NULL) {
+                       warn(NULL);
+
+                       (void)kill(pid, SIGKILL);
+
+                       detach_stopped();
+
+                       return EXIT_FAILURE;
+               }
+
+               proc->trace_flags = 0;
+       } else
+               pid = -1;
+
+       /* The user will have to give us at least one process to trace. */
+       if (proc_count() == 0)
+               usage();
+
+       /*
+        * Open an alternative output file if needed.  After that, standard
+        * error should no longer be used directly, and all output has to go
+        * through the output module.
+        */
+       if (output_init(output_file) < 0) {
+               warn("Unable to open output file");
+
+               if (pid > 0)
+                       (void)kill(pid, SIGKILL);
+
+               detach_stopped();
+
+               return EXIT_FAILURE;
+       }
+
+       /*
+        * All the traced processes are currently stopped.  Initialize, report,
+        * and resume them.
+        */
+       for (proc = proc_next(NULL); proc != NULL; proc = proc_next(proc)) {
+               new_proc(proc, follow_fork);
+
+               (void)ptrace(T_SYSCALL, proc->pid, 0, 0);
+       }
+
+       /*
+        * Handle events until there are no traced processes left.
+        */
+       last_pid = 0;
+       error = FALSE;
+
+       for (;;) {
+               /* If an output error occurred, exit as soon as possible. */
+               if (!error && output_error()) {
+                       detach_running(TRUE /*force*/);
+
+                       error = TRUE;
+               }
+
+               /*
+                * If the user pressed ^C once, start detaching the processes
+                * that we did not start, if any.  If the user pressed ^C
+                * twice, kill the process that we did start, if any.
+                */
+               if (got_signal) {
+                       detach_running(!first_signal);
+
+                       got_signal = FALSE;
+                       first_signal = FALSE;
+               }
+
+               /* Upon getting SIGINFO, print a list of traced processes. */
+               if (got_info) {
+                       list_info();
+
+                       got_info = FALSE;
+               }
+
+               /*
+                * Block until something happens to a traced process.  If
+                * enabled from the command line, first try waiting for the
+                * last process for which we got results, so as to reduce call
+                * suspensions a bit.
+                */
+               if (grouping && last_pid > 0 &&
+                   waitpid(last_pid, &status, WNOHANG) > 0)
+                       pid = last_pid;
+               else
+                   if ((pid = waitpid(-1, &status, 0)) <= 0) {
+                       if (pid == -1 && errno == EINTR) continue;
+                       if (pid == -1 && errno == ECHILD) break; /* all done */
+
+                       put_fmt(NULL, "Unexpected waitpid failure: %s",
+                           (pid == 0) ? "No result" : strerror(errno));
+                       put_newline();
+
+                       /*
+                        * We need waitpid to function correctly in order to
+                        * detach from any attached processes, so we can do
+                        * little more than just exit, effectively killing all
+                        * traced processes.
+                        */
+                       return EXIT_FAILURE;
+               }
+
+               last_pid = 0;
+
+               /* Get the trace data structure for the process. */
+               if ((proc = proc_get(pid)) == NULL) {
+                       /*
+                        * The waitpid() call returned the status of a process
+                        * that we have not yet seen.  This must be a newly
+                        * forked child.  If it is not stopped, it must have
+                        * died immediately, and we choose not to report it.
+                        */
+                       if (!WIFSTOPPED(status))
+                               continue;
+
+                       if ((proc = proc_add(pid)) == NULL) {
+                               put_fmt(NULL,
+                                   "Error attaching to new child %d: %s",
+                                   pid, strerror(errno));
+                               put_newline();
+
+                               /*
+                                * Out of memory allocating a new child object!
+                                * We can not trace this child, so just let it
+                                * run free by detaching from it.
+                                */
+                               if (WSTOPSIG(status) != SIGSTOP) {
+                                       (void)ptrace(T_RESUME, pid, 0,
+                                           WSTOPSIG(status));
+
+                                       if (wait_sig(pid, SIGSTOP) != 0)
+                                               continue; /* it died.. */
+                               }
+
+                               (void)ptrace(T_DETACH, pid, 0, 0);
+
+                               continue;
+                       }
+
+                       /*
+                        * We must specify TF_ATTACH here, even though it may
+                        * be a child of a process we started, in which case it
+                        * should be killed when we exit.  We do not keep track
+                        * of ancestry though, so better safe than sorry.
+                        */
+                       proc->trace_flags = TF_ATTACH | TF_STOPPING;
+
+                       new_proc(proc, follow_fork);
+
+                       /* Repeat entering the fork call for the child. */
+                       handle_call(proc, show_stack);
+               }
+
+               /* If the process died, report its status and clean it up. */
+               if (!WIFSTOPPED(status)) {
+                       discard_proc(proc, status);
+
+                       continue;
+               }
+
+               sig = WSTOPSIG(status);
+
+               if (sig == SIGSTOP && (proc->trace_flags & TF_STOPPING)) {
+                       /* We expected the process to be stopped; now it is. */
+                       proc->trace_flags &= ~TF_STOPPING;
+
+                       if (proc->trace_flags & TF_DETACH) {
+                               if (ptrace(T_DETACH, proc->pid, 0, 0) == 0)
+                                       discard_proc(proc, status);
+
+                               /*
+                                * If detaching failed, the process must have
+                                * died, and we'll get notified through wait().
+                                */
+                               continue;
+                       }
+
+                       sig = 0;
+               } else if (sig == SIGSTOP && (proc->trace_flags & TF_EXEC)) {
+                       /* The process has performed a successful execve(). */
+                       call_leave(proc, TRUE /*skip*/);
+
+                       put_text(proc, "---");
+
+                       new_exec(proc);
+
+                       /*
+                        * A successful execve() has no result, in the sense
+                        * that there is no reply message.  We should therefore
+                        * not even try to copy in the reply message from the
+                        * original location, because it will be invalid.
+                        * Thus, we skip the exec's call leave phase entirely.
+                        */
+                       proc->trace_flags &= ~TF_EXEC;
+                       proc->trace_flags |= TF_SKIP;
+
+                       sig = 0;
+               } else if (sig == SIGTRAP) {
+                       /* The process is entering or leaving a system call. */
+                       if (!(proc->trace_flags & TF_DETACH))
+                               handle_call(proc, show_stack);
+
+                       sig = 0;
+               } else {
+                       /* The process has received a signal. */
+                       report_signal(proc, sig, show_stack);
+
+                       /*
+                        * Only in this case do we pass the signal to the
+                        * traced process.
+                        */
+               }
+
+               /*
+                * Resume process execution.  If this call fails, the process
+                * has probably died.  We will find out soon enough.
+                */
+               (void)ptrace(T_SYSCALL, proc->pid, 0, sig);
+
+               last_pid = proc->pid;
+       }
+
+       return (error) ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/minix/usr.bin/trace/type.h b/minix/usr.bin/trace/type.h
new file mode 100644 (file)
index 0000000..e126a8d
--- /dev/null
@@ -0,0 +1,32 @@
+
+#define COUNT(s) (sizeof(s) / sizeof(s[0]))
+
+struct call_handler {
+       const char *name;
+       const char *(*namefunc)(const message *m_out);
+       int (*outfunc)(struct trace_proc *proc, const message *m_out);
+       void (*infunc)(struct trace_proc *proc, const message *m_out,
+           const message *m_in, int failed);
+};
+#define HANDLER(n,o,i) { .name = n, .outfunc = o, .infunc = i }
+#define HANDLER_NAME(n,o,i) { .namefunc = n, .outfunc = o, .infunc = i }
+
+struct calls {
+       endpoint_t endpt;
+       unsigned int base;
+       const struct call_handler *map;
+       unsigned int count;
+};
+
+struct flags {
+       unsigned int mask;
+       unsigned int value;
+       const char *name;
+};
+#define FLAG(f) { f, f, #f }
+#define FLAG_MASK(m,f) { m, f, #f }
+#define FLAG_ZERO(f) { ~0, f, #f }
+
+/* not great, but it prevents a massive potential for typos.. */
+#define NAME(r) case r: return #r
+#define TEXT(v) case v: text = #v; break