#include "inc.h" #include #include /* * 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, " "); */ }