2020-06-02 10:14:03 +00:00
|
|
|
// Copyright 2020 the V8 project authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
|
|
// found in the LICENSE file.
|
|
|
|
// Copyright 2019 Google LLC
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
#ifndef _GNU_SOURCE
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#endif
|
|
|
|
|
2020-06-02 10:14:03 +00:00
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <poll.h>
|
|
|
|
#include <signal.h>
|
2020-09-14 07:40:49 +00:00
|
|
|
#include <stdarg.h>
|
2020-06-02 10:14:03 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2020-09-14 07:40:49 +00:00
|
|
|
#include <sys/mman.h>
|
2020-06-02 10:14:03 +00:00
|
|
|
#include <sys/time.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "libreprl.h"
|
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
// Well-known file descriptor numbers for reprl <-> child communication, child process side
|
|
|
|
#define REPRL_CHILD_CTRL_IN 100
|
|
|
|
#define REPRL_CHILD_CTRL_OUT 101
|
|
|
|
#define REPRL_CHILD_DATA_IN 102
|
|
|
|
#define REPRL_CHILD_DATA_OUT 103
|
2020-06-02 10:14:03 +00:00
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
#define MIN(x, y) ((x) < (y) ? (x) : (y))
|
2020-06-02 10:14:03 +00:00
|
|
|
|
|
|
|
static uint64_t current_millis()
|
|
|
|
{
|
|
|
|
struct timespec ts;
|
|
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
|
|
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
|
|
}
|
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
static char** copy_string_array(const char** orig)
|
2020-06-02 10:14:03 +00:00
|
|
|
{
|
2020-09-14 07:40:49 +00:00
|
|
|
size_t num_entries = 0;
|
|
|
|
for (const char** current = orig; *current; current++) {
|
|
|
|
num_entries += 1;
|
|
|
|
}
|
|
|
|
char** copy = calloc(num_entries + 1, sizeof(char*));
|
|
|
|
for (size_t i = 0; i < num_entries; i++) {
|
|
|
|
copy[i] = strdup(orig[i]);
|
2020-06-02 10:14:03 +00:00
|
|
|
}
|
2020-09-14 07:40:49 +00:00
|
|
|
return copy;
|
|
|
|
}
|
2020-06-02 10:14:03 +00:00
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
static void free_string_array(char** arr)
|
|
|
|
{
|
|
|
|
if (!arr) return;
|
|
|
|
for (char** current = arr; *current; current++) {
|
|
|
|
free(*current);
|
|
|
|
}
|
|
|
|
free(arr);
|
|
|
|
}
|
|
|
|
|
|
|
|
// A unidirectional communication channel for larger amounts of data, up to a maximum size (REPRL_MAX_DATA_SIZE).
|
|
|
|
// Implemented as a (RAM-backed) file for which the file descriptor is shared with the child process and which is mapped into our address space.
|
|
|
|
struct data_channel {
|
|
|
|
// File descriptor of the underlying file. Directly shared with the child process.
|
|
|
|
int fd;
|
|
|
|
// Memory mapping of the file, always of size REPRL_MAX_DATA_SIZE.
|
|
|
|
char* mapping;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct reprl_context {
|
|
|
|
// Whether reprl_initialize has been successfully performed on this context.
|
|
|
|
int initialized;
|
|
|
|
|
|
|
|
// Read file descriptor of the control pipe. Only valid if a child process is running (i.e. pid is nonzero).
|
|
|
|
int ctrl_in;
|
|
|
|
// Write file descriptor of the control pipe. Only valid if a child process is running (i.e. pid is nonzero).
|
|
|
|
int ctrl_out;
|
|
|
|
|
|
|
|
// Data channel REPRL -> Child
|
|
|
|
struct data_channel* data_in;
|
|
|
|
// Data channel Child -> REPRL
|
|
|
|
struct data_channel* data_out;
|
|
|
|
|
|
|
|
// Optional data channel for the child's stdout and stderr.
|
|
|
|
struct data_channel* stdout;
|
|
|
|
struct data_channel* stderr;
|
|
|
|
|
|
|
|
// PID of the child process. Will be zero if no child process is currently running.
|
|
|
|
int pid;
|
|
|
|
|
|
|
|
// Arguments and environment for the child process.
|
|
|
|
char** argv;
|
|
|
|
char** envp;
|
|
|
|
|
|
|
|
// A malloc'd string containing a description of the last error that occurred.
|
|
|
|
char* last_error;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int reprl_error(struct reprl_context* ctx, const char *format, ...)
|
|
|
|
{
|
|
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
|
|
free(ctx->last_error);
|
|
|
|
vasprintf(&ctx->last_error, format, args);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct data_channel* reprl_create_data_channel(struct reprl_context* ctx)
|
|
|
|
{
|
|
|
|
#ifdef __linux__
|
|
|
|
int fd = memfd_create("REPRL_DATA_CHANNEL", MFD_CLOEXEC);
|
|
|
|
#else
|
|
|
|
char path[] = "/tmp/reprl_data_channel_XXXXXXXX";
|
|
|
|
if (mktemp(path) < 0) {
|
|
|
|
reprl_error(ctx, "Failed to create temporary filename for data channel: %s", strerror(errno));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
int fd = open(path, O_RDWR | O_CREAT| O_CLOEXEC);
|
|
|
|
unlink(path);
|
|
|
|
#endif
|
|
|
|
if (fd == -1 || ftruncate(fd, REPRL_MAX_DATA_SIZE) != 0) {
|
|
|
|
reprl_error(ctx, "Failed to create data channel file: %s", strerror(errno));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
char* mapping = mmap(0, REPRL_MAX_DATA_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
|
|
if (mapping == MAP_FAILED) {
|
|
|
|
reprl_error(ctx, "Failed to mmap data channel file: %s", strerror(errno));
|
|
|
|
return NULL;
|
2020-06-02 10:14:03 +00:00
|
|
|
}
|
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
struct data_channel* channel = malloc(sizeof(struct data_channel));
|
|
|
|
channel->fd = fd;
|
|
|
|
channel->mapping = mapping;
|
|
|
|
return channel;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void reprl_destroy_data_channel(struct reprl_context* ctx, struct data_channel* channel)
|
|
|
|
{
|
|
|
|
if (!channel) return;
|
|
|
|
close(channel->fd);
|
|
|
|
munmap(channel->mapping, REPRL_MAX_DATA_SIZE);
|
|
|
|
free(channel);
|
|
|
|
}
|
2020-06-02 10:14:03 +00:00
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
static void reprl_child_terminated(struct reprl_context* ctx)
|
|
|
|
{
|
|
|
|
if (!ctx->pid) return;
|
|
|
|
ctx->pid = 0;
|
|
|
|
close(ctx->ctrl_in);
|
|
|
|
close(ctx->ctrl_out);
|
|
|
|
}
|
2020-06-02 10:14:03 +00:00
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
static void reprl_terminate_child(struct reprl_context* ctx)
|
|
|
|
{
|
|
|
|
if (!ctx->pid) return;
|
|
|
|
int status;
|
|
|
|
kill(ctx->pid, SIGKILL);
|
|
|
|
waitpid(ctx->pid, &status, 0);
|
|
|
|
reprl_child_terminated(ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int reprl_spawn_child(struct reprl_context* ctx)
|
|
|
|
{
|
|
|
|
// This is also a good time to ensure the data channel backing files don't grow too large.
|
|
|
|
ftruncate(ctx->data_in->fd, REPRL_MAX_DATA_SIZE);
|
|
|
|
ftruncate(ctx->data_out->fd, REPRL_MAX_DATA_SIZE);
|
|
|
|
if (ctx->stdout) ftruncate(ctx->stdout->fd, REPRL_MAX_DATA_SIZE);
|
|
|
|
if (ctx->stderr) ftruncate(ctx->stderr->fd, REPRL_MAX_DATA_SIZE);
|
|
|
|
|
|
|
|
int crpipe[2] = { 0, 0 }; // control pipe child -> reprl
|
|
|
|
int cwpipe[2] = { 0, 0 }; // control pipe reprl -> child
|
|
|
|
|
|
|
|
if (pipe(crpipe) != 0) {
|
|
|
|
return reprl_error(ctx, "Could not create pipe for REPRL communication: %s", strerror(errno));
|
|
|
|
}
|
|
|
|
if (pipe(cwpipe) != 0) {
|
|
|
|
close(crpipe[0]);
|
|
|
|
close(crpipe[1]);
|
|
|
|
return reprl_error(ctx, "Could not create pipe for REPRL communication: %s", strerror(errno));
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx->ctrl_in = crpipe[0];
|
|
|
|
ctx->ctrl_out = cwpipe[1];
|
|
|
|
fcntl(ctx->ctrl_in, F_SETFD, FD_CLOEXEC);
|
|
|
|
fcntl(ctx->ctrl_out, F_SETFD, FD_CLOEXEC);
|
2020-06-02 10:14:03 +00:00
|
|
|
|
|
|
|
int pid = fork();
|
|
|
|
if (pid == 0) {
|
2020-09-14 07:40:49 +00:00
|
|
|
dup2(cwpipe[0], REPRL_CHILD_CTRL_IN);
|
|
|
|
dup2(crpipe[1], REPRL_CHILD_CTRL_OUT);
|
2020-06-02 10:14:03 +00:00
|
|
|
close(cwpipe[0]);
|
|
|
|
close(crpipe[1]);
|
2020-09-14 07:40:49 +00:00
|
|
|
|
|
|
|
dup2(ctx->data_out->fd, REPRL_CHILD_DATA_IN);
|
|
|
|
dup2(ctx->data_in->fd, REPRL_CHILD_DATA_OUT);
|
2020-06-02 10:14:03 +00:00
|
|
|
|
|
|
|
int devnull = open("/dev/null", O_RDWR);
|
|
|
|
dup2(devnull, 0);
|
2020-09-14 07:40:49 +00:00
|
|
|
if (ctx->stdout) dup2(ctx->stdout->fd, 1);
|
|
|
|
else dup2(devnull, 1);
|
|
|
|
if (ctx->stderr) dup2(ctx->stderr->fd, 2);
|
|
|
|
else dup2(devnull, 2);
|
2020-06-02 10:14:03 +00:00
|
|
|
close(devnull);
|
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
// close all other FDs. We try to use FD_CLOEXEC everywhere, but let's be extra sure we don't leak any fds to the child.
|
|
|
|
int tablesize = getdtablesize();
|
|
|
|
for (int i = 3; i < tablesize; i++) {
|
|
|
|
if (i == REPRL_CHILD_CTRL_IN || i == REPRL_CHILD_CTRL_OUT || i == REPRL_CHILD_DATA_IN || i == REPRL_CHILD_DATA_OUT) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
close(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
execve(ctx->argv[0], ctx->argv, ctx->envp);
|
|
|
|
|
|
|
|
fprintf(stderr, "Failed to execute child process %s: %s\n", ctx->argv[0], strerror(errno));
|
|
|
|
fflush(stderr);
|
2020-06-02 10:14:03 +00:00
|
|
|
_exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
close(crpipe[1]);
|
|
|
|
close(cwpipe[0]);
|
2020-09-14 07:40:49 +00:00
|
|
|
|
|
|
|
if (pid < 0) {
|
|
|
|
close(ctx->ctrl_in);
|
|
|
|
close(ctx->ctrl_out);
|
|
|
|
return reprl_error(ctx, "Failed to fork: %s", strerror(errno));
|
|
|
|
}
|
|
|
|
ctx->pid = pid;
|
|
|
|
|
|
|
|
char helo[4] = { 0 };
|
|
|
|
if (read(ctx->ctrl_in, helo, 4) != 4) {
|
|
|
|
reprl_terminate_child(ctx);
|
|
|
|
return reprl_error(ctx, "Did not receive HELO message from child");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strncmp(helo, "HELO", 4) != 0) {
|
|
|
|
reprl_terminate_child(ctx);
|
|
|
|
return reprl_error(ctx, "Received invalid HELO message from child");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (write(ctx->ctrl_out, helo, 4) != 4) {
|
|
|
|
reprl_terminate_child(ctx);
|
|
|
|
return reprl_error(ctx, "Failed to send HELO reply message to child");
|
2020-06-02 10:14:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
struct reprl_context* reprl_create_context()
|
2020-06-02 10:14:03 +00:00
|
|
|
{
|
2020-09-14 07:40:49 +00:00
|
|
|
struct reprl_context* ctx = malloc(sizeof(struct reprl_context));
|
|
|
|
memset(ctx, 0, sizeof(struct reprl_context));
|
|
|
|
return ctx;
|
|
|
|
}
|
2020-06-02 10:14:03 +00:00
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
int reprl_initialize_context(struct reprl_context* ctx, const char** argv, const char** envp, int capture_stdout, int capture_stderr)
|
|
|
|
{
|
|
|
|
if (ctx->initialized) {
|
|
|
|
return reprl_error(ctx, "Context is already initialized");
|
|
|
|
}
|
2020-06-02 10:14:03 +00:00
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
// We need to ignore SIGPIPE since we could end up writing to a pipe after our child process has exited.
|
|
|
|
signal(SIGPIPE, SIG_IGN);
|
2020-06-02 10:14:03 +00:00
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
ctx->argv = copy_string_array(argv);
|
|
|
|
ctx->envp = copy_string_array(envp);
|
2020-06-02 10:14:03 +00:00
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
ctx->data_in = reprl_create_data_channel(ctx);
|
|
|
|
ctx->data_out = reprl_create_data_channel(ctx);
|
|
|
|
if (capture_stdout) {
|
|
|
|
ctx->stdout = reprl_create_data_channel(ctx);
|
|
|
|
}
|
|
|
|
if (capture_stderr) {
|
|
|
|
ctx->stderr = reprl_create_data_channel(ctx);
|
|
|
|
}
|
|
|
|
if (!ctx->data_in || !ctx->data_out || (capture_stdout && !ctx->stdout) || (capture_stderr && !ctx->stderr)) {
|
|
|
|
// Proper error message will have been set by reprl_create_data_channel
|
|
|
|
return -1;
|
|
|
|
}
|
2020-06-02 10:14:03 +00:00
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
ctx->initialized = 1;
|
|
|
|
return 0;
|
2020-06-02 10:14:03 +00:00
|
|
|
}
|
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
void reprl_destroy_context(struct reprl_context* ctx)
|
2020-06-02 10:14:03 +00:00
|
|
|
{
|
2020-09-14 07:40:49 +00:00
|
|
|
reprl_terminate_child(ctx);
|
2020-06-02 10:14:03 +00:00
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
free_string_array(ctx->argv);
|
|
|
|
free_string_array(ctx->envp);
|
|
|
|
|
|
|
|
reprl_destroy_data_channel(ctx, ctx->data_in);
|
|
|
|
reprl_destroy_data_channel(ctx, ctx->data_out);
|
|
|
|
reprl_destroy_data_channel(ctx, ctx->stdout);
|
|
|
|
reprl_destroy_data_channel(ctx, ctx->stderr);
|
|
|
|
|
|
|
|
free(ctx->last_error);
|
|
|
|
free(ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
int reprl_execute(struct reprl_context* ctx, const char* script, uint64_t script_length, uint64_t timeout, uint64_t* execution_time, int fresh_instance)
|
|
|
|
{
|
|
|
|
if (!ctx->initialized) {
|
|
|
|
return reprl_error(ctx, "REPRL context is not initialized");
|
|
|
|
}
|
|
|
|
if (script_length > REPRL_MAX_DATA_SIZE) {
|
|
|
|
return reprl_error(ctx, "Script too large");
|
2020-06-02 10:14:03 +00:00
|
|
|
}
|
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
// Terminate any existing instance if requested.
|
|
|
|
if (fresh_instance && ctx->pid) {
|
|
|
|
reprl_terminate_child(ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset file position so the child can simply read(2) and write(2) to these fds.
|
|
|
|
lseek(ctx->data_out->fd, 0, SEEK_SET);
|
|
|
|
lseek(ctx->data_in->fd, 0, SEEK_SET);
|
|
|
|
if (ctx->stdout) {
|
|
|
|
lseek(ctx->stdout->fd, 0, SEEK_SET);
|
|
|
|
}
|
|
|
|
if (ctx->stderr) {
|
|
|
|
lseek(ctx->stderr->fd, 0, SEEK_SET);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Spawn a new instance if necessary.
|
|
|
|
if (!ctx->pid) {
|
|
|
|
int r = reprl_spawn_child(ctx);
|
|
|
|
if (r != 0) return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy the script to the data channel.
|
|
|
|
memcpy(ctx->data_out->mapping, script, script_length);
|
|
|
|
|
|
|
|
// Tell child to execute the script.
|
|
|
|
if (write(ctx->ctrl_out, "exec", 4) != 4 ||
|
|
|
|
write(ctx->ctrl_out, &script_length, 8) != 8) {
|
|
|
|
// These can fail if the child unexpectedly terminated between executions.
|
|
|
|
// Check for that here to be able to provide a better error message.
|
|
|
|
int status;
|
|
|
|
if (waitpid(ctx->pid, &status, WNOHANG) == ctx->pid) {
|
|
|
|
reprl_child_terminated(ctx);
|
|
|
|
if (WIFEXITED(status)) {
|
|
|
|
return reprl_error(ctx, "Child unexpectedly exited with status %i between executions", WEXITSTATUS(status));
|
|
|
|
} else {
|
|
|
|
return reprl_error(ctx, "Child unexpectedly terminated with signal %i between executions", WTERMSIG(status));
|
|
|
|
}
|
2020-06-02 10:14:03 +00:00
|
|
|
}
|
2020-09-14 07:40:49 +00:00
|
|
|
return reprl_error(ctx, "Failed to send command to child process: %s", strerror(errno));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for child to finish execution (or crash).
|
|
|
|
uint64_t start_time = current_millis();
|
|
|
|
struct pollfd fds = {.fd = ctx->ctrl_in, .events = POLLIN, .revents = 0};
|
|
|
|
int res = poll(&fds, 1, (int)timeout);
|
|
|
|
*execution_time = current_millis() - start_time;
|
|
|
|
if (res == 0) {
|
|
|
|
// Execution timed out. Kill child and return a timeout status.
|
|
|
|
reprl_terminate_child(ctx);
|
|
|
|
return 1 << 16;
|
|
|
|
} else if (res != 1) {
|
|
|
|
// An error occurred.
|
|
|
|
// We expect all signal handlers to be installed with SA_RESTART, so receiving EINTR here is unexpected and thus also an error.
|
|
|
|
return reprl_error(ctx, "Failed to poll: %s", strerror(errno));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Poll succeeded, so there must be something to read now (either the status or EOF).
|
|
|
|
int status;
|
|
|
|
ssize_t rv = read(ctx->ctrl_in, &status, 4);
|
|
|
|
if (rv < 0) {
|
|
|
|
return reprl_error(ctx, "Failed to read from control pipe: %s", strerror(errno));
|
|
|
|
} else if (rv != 4) {
|
|
|
|
// Most likely, the child process crashed and closed the write end of the control pipe.
|
|
|
|
// Unfortunately, there probably is nothing that guarantees that waitpid() will immediately succeed now,
|
|
|
|
// and we also don't want to block here. So just retry waitpid() a few times...
|
|
|
|
int success = 0;
|
|
|
|
do {
|
|
|
|
success = waitpid(ctx->pid, &status, WNOHANG) == ctx->pid;
|
|
|
|
if (!success) usleep(10);
|
|
|
|
} while (!success && current_millis() - start_time < timeout);
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
// Wait failed, so something weird must have happened. Maybe somehow the control pipe was closed without the child exiting?
|
|
|
|
// Probably the best we can do is kill the child and return an error.
|
|
|
|
reprl_terminate_child(ctx);
|
|
|
|
return reprl_error(ctx, "Child in weird state after execution");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cleanup any state related to this child process.
|
|
|
|
reprl_child_terminated(ctx);
|
|
|
|
|
|
|
|
if (WIFEXITED(status)) {
|
|
|
|
status = WEXITSTATUS(status) << 8;
|
|
|
|
} else if (WIFSIGNALED(status)) {
|
|
|
|
status = WTERMSIG(status);
|
|
|
|
} else {
|
|
|
|
// This shouldn't happen, since we don't specify WUNTRACED for waitpid...
|
|
|
|
return reprl_error(ctx, "Waitpid returned unexpected child state %i", status);
|
2020-06-02 10:14:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
// The status must be a positive number, see the status encoding format below.
|
|
|
|
// We also don't allow the child process to indicate a timeout. If we wanted,
|
|
|
|
// we could treat it as an error if the upper bits are set.
|
|
|
|
status &= 0xffff;
|
2020-06-02 10:14:03 +00:00
|
|
|
|
2020-09-14 07:40:49 +00:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The 32bit REPRL exit status as returned by reprl_execute has the following format:
|
|
|
|
/// [ 00000000 | did_timeout | exit_code | terminating_signal ]
|
|
|
|
/// Only one of did_timeout, exit_code, or terminating_signal may be set at one time.
|
|
|
|
int RIFSIGNALED(int status)
|
|
|
|
{
|
|
|
|
return (status & 0xff) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int RIFEXITED(int status)
|
|
|
|
{
|
|
|
|
return !RIFSIGNALED(status) && !RIFTIMEDOUT(status);
|
|
|
|
}
|
|
|
|
|
|
|
|
int RIFTIMEDOUT(int status)
|
|
|
|
{
|
|
|
|
return (status & 0xff0000) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int RTERMSIG(int status)
|
|
|
|
{
|
|
|
|
return status & 0xff;
|
|
|
|
}
|
|
|
|
|
|
|
|
int REXITSTATUS(int status)
|
|
|
|
{
|
|
|
|
return (status >> 8) & 0xff;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char* fetch_data_channel_content(struct data_channel* channel)
|
|
|
|
{
|
|
|
|
if (!channel) return "";
|
|
|
|
size_t pos = lseek(channel->fd, 0, SEEK_CUR);
|
|
|
|
pos = MIN(pos, REPRL_MAX_DATA_SIZE - 1);
|
|
|
|
channel->mapping[pos] = 0;
|
|
|
|
return channel->mapping;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* reprl_fetch_fuzzout(struct reprl_context* ctx)
|
|
|
|
{
|
|
|
|
return fetch_data_channel_content(ctx->data_in);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* reprl_fetch_stdout(struct reprl_context* ctx)
|
|
|
|
{
|
|
|
|
return fetch_data_channel_content(ctx->stdout);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* reprl_fetch_stderr(struct reprl_context* ctx)
|
|
|
|
{
|
|
|
|
return fetch_data_channel_content(ctx->stderr);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char* reprl_get_last_error(struct reprl_context* ctx)
|
|
|
|
{
|
|
|
|
return ctx->last_error;
|
2020-06-02 10:14:03 +00:00
|
|
|
}
|