blob: 5b95690fecbdbc5f8e9375ea58aba9e29330899a [file]
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <poll.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++)
fprintf(stderr, "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 = 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;
enum procerr ret = OK;
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;
}
char abspath[PATH_MAX];
if (-1 == setenv(ENV_FIFO_PATH, realpath(fifo, abspath), 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;
int fd = open(fifo, O_RDONLY | O_NONBLOCK);
if (fd < 0) {
error("failed to open fifo %s for read");
return ERR_FIFO_UNKNOWN;
}
// Initial poll to wait for the write side to open. If this times out, it's likely the
// traced process never dynamically loads libc.
//
// fsatrace allows the forked process 2 seconds to load libc and run the installed hooks.
// Otherwise, it's assumed the forked process will not load libc. This 2 second timeout is
// designed to be long enough to allow even slow builders to avoid hitting the timeout, but
// short enough that a user would not give up and send SIGINT before the timeout expires.
// Sending SIGINT before the timeout expires would preempt printing a helpful error message.
{
struct pollfd pfd = {
.fd = fd,
.events = POLLIN,
.revents = 0,
};
int poll_result = poll(&pfd, 1, 2000);
if (poll_result == -1) {
error("poll()");
close(fd);
return ERR_FIFO_UNKNOWN;
}
if (poll_result == 0) {
close(fd);
return ERR_FIFO_TIMEOUT;
}
if (!(pfd.revents & POLLIN)) {
error("Initial poll of fifo did not receive POLLIN: %d", pfd.revents);
close(fd);
return ERR_FIFO_UNKNOWN;
}
}
// Remove the O_NONBLOCK flag to make subsequent read operations blocking.
if (fcntl(fd, F_SETFL, O_RDONLY) == -1) {
error("fcntl F_SETFL");
close(fd);
return ERR_FIFO_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;
}