| #include <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <libgen.h> |
| #include <limits.h> |
| #include <spawn.h> |
| #include <stdarg.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| #if !defined __linux__ |
| #include <libproc.h> |
| #endif |
| #include "../proc.h" |
| #include "../fsatrace.h" |
| |
| void |
| procPath(char *fullpath) |
| { |
| #if defined __linux__ |
| char exepath[64]; |
| ssize_t ret; |
| snprintf(exepath, sizeof(exepath), "/proc/%d/exe", getpid()); |
| ret = readlink(exepath, fullpath, PATH_MAX); |
| assert(ret != -1); |
| fullpath[ret] = 0; |
| #else |
| proc_pidpath(getpid(), fullpath, PATH_MAX); |
| #endif |
| } |
| |
| void |
| procDumpArgs(unsigned nargs, char *const args[]) |
| { |
| unsigned i; |
| for (i = 0; i < nargs; i++) |
| error("argv[%d]=%s\n", i, args[i]); |
| } |
| |
| static enum procerr |
| waitchild(int child, int *rc) |
| { |
| enum procerr ret; |
| if (-1 != waitpid(child, rc, 0)) { |
| int st = *rc; |
| if (WIFEXITED(st)) { |
| ret = ERR_PROC_OK; |
| *rc = WEXITSTATUS(st); |
| } else if (WIFSIGNALED(st)) { |
| ret = ERR_PROC_SIGNALED; |
| *rc = WTERMSIG(st); |
| } else if (WIFSTOPPED(st)) { |
| ret = ERR_PROC_STOPPED; |
| *rc = WSTOPSIG(st); |
| } else { |
| ret = ERR_PROC_UNKNOWN; |
| } |
| } else |
| ret = ERR_PROC_WAIT; |
| return ret; |
| } |
| |
| static bool |
| dump(const char *path, char *p, size_t sz) |
| { |
| int fd; |
| ssize_t r = 0; |
| if (strcmp(path, "-")) { |
| fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0777); |
| if (fd < 0) { |
| error("unable to open output file '%s'", path); |
| return false; |
| } |
| } else { |
| fd = 1; |
| } |
| r = write(fd, p, sz); |
| if (r != sz) { |
| error("short write (%ld/%ld)", r, sz); |
| return false; |
| } |
| if (fd != 1) |
| return (close(fd) == 0 || errno != EINTR); |
| return true; |
| } |
| |
| void |
| errv(const char *fmt, const char *pref, va_list ap) |
| { |
| char fullpath[PATH_MAX]; |
| char *es = strerror(errno); |
| procPath(fullpath); |
| fprintf(stderr, "%s(%d): %s", basename(fullpath), getpid(), pref); |
| vfprintf(stderr, fmt, ap); |
| fprintf(stderr, ": %s\n", es); |
| fflush(stderr); |
| } |
| |
| void |
| fatal(const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| errv(fmt, "fatal error: ", ap); |
| va_end(ap); |
| exit(EXIT_FAILURE); |
| } |
| |
| void |
| error(const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| errv(fmt, "error: ", ap); |
| va_end(ap); |
| } |
| |
| void |
| aerror(unsigned n, char *const *l, const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| errv(fmt, "error: ", ap); |
| va_end(ap); |
| procDumpArgs(n, l); |
| } |
| |
| enum procerr |
| procRun(unsigned nargs, char *const args[], int *rc, char const *out) |
| { |
| extern char **environ; |
| int ret; |
| int mkfiforet; |
| int child; |
| char so[PATH_MAX + 3]; |
| char fullpath[PATH_MAX]; |
| char fifo[PATH_MAX]; |
| |
| procPath(fullpath); |
| snprintf(so, sizeof(so), "%s.so", fullpath); |
| #if defined __linux__ |
| setenv("LD_PRELOAD", so, 1); |
| #else |
| setenv("DYLD_INSERT_LIBRARIES", so, 1); |
| setenv("DYLD_FORCE_FLAT_NAMESPACE", "1", 1); |
| #endif |
| |
| snprintf(fifo, strlen(out) + 6, "%s.fifo", out); |
| unlink(fifo); // Clean up legacy fifo from previous runs. |
| mkfiforet = mkfifo(fifo, 0666); |
| // If the $out.fifo is not accessible (e.g. /dev/null.fifo), attempt to |
| // place fifo in temp dir instead. |
| if (-1 == mkfiforet && errno == EACCES) { |
| // Re-populate `fifo` so it gets cleaned up properly below. |
| snprintf(fifo, 26, "/tmp/fsatrace_fifo.XXXXXX"); |
| // NOTE: mkstep here is not strictly safe, it's possible for |
| // other processes to open the same path. Fuchsia uses this tool |
| // in a controlled environment, and this is an unused code path |
| // in production, so we don't consider this as a thread. |
| mkstemp(fifo); |
| // Remove the temp file immediately because we only want a |
| // unique name for fifo. |
| unlink(fifo); |
| mkfiforet = mkfifo(fifo, 0666); |
| } |
| if (-1 == mkfiforet) { |
| error("failed to make fifo %s"); |
| return ERR_PROC_UNKNOWN; |
| } |
| |
| if (-1 == setenv(ENV_FIFO_PATH, fifo, 1)) { |
| error("failed to setenv %s=%s", ENV_FIFO_PATH, fifo); |
| return ERR_PROC_UNKNOWN; |
| } |
| |
| child = fork(); |
| switch (child) { |
| case -1: |
| ret = ERR_PROC_FORK; |
| break; |
| case 0: |
| execvp(args[0], args); |
| _exit(EXIT_FAILURE); |
| break; |
| default: { |
| char *buf; |
| size_t sz; |
| char c; |
| |
| const int fd = open(fifo, O_CREAT | O_RDONLY); |
| if (fd < 0) { |
| error("failed to open fifo %s for read"); |
| return ERR_PROC_UNKNOWN; |
| } |
| |
| FILE *memstream = open_memstream(&buf, &sz); |
| while (read(fd, &c, 1) > 0) |
| fprintf(memstream, "%c", c); |
| fclose(memstream); |
| close(fd); |
| unlink(fifo); |
| if (!dump(out, buf, sz)) |
| return ERR_PROC_UNKNOWN; |
| ret = waitchild(child, rc); |
| } |
| } |
| return ret; |
| } |