blob: 4c11d0f9cca3f7ca5f844ef76d451f3eaaec780d [file] [log] [blame]
#include <assert.h>
#include <errno.h>
#include <fcntl.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;
}