blob: 666c219121ff2be5e3cfc97657dd1425dbab9e86 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <launchpad/launchpad.h>
#include <zircon/syscalls.h>
#include <fdio/io.h>
#include <pthread.h>
#include <pwd.h>
#include <assert.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include "openbsd-compat/bsd-misc.h"
#include "misc.h"
int chroot(const char* path) { return -1; }
typedef struct Authctxt Authctxt;
int sys_auth_passwd(Authctxt* authctxt, const char* password) {
// Password authentication always fails.
return 0;
}
struct passwd* getpwent(void) {
static struct passwd static_passwd = {
.pw_name = "fuchsia",
.pw_passwd = "",
.pw_uid = 23, // matches ZX_UID
.pw_gid = 23,
.pw_gecos = "Fuchsia",
.pw_dir = "/",
.pw_shell = "/boot/bin/sh",
};
return &static_passwd;
}
struct passwd* getpwnam(const char* name) {
return getpwent();
}
struct passwd* getpwuid(uid_t uid) {
return getpwent();
}
#define ARGV_MAX 256
typedef struct {
enum { UNUSED, RUNNING, STOPPED } state;
zx_handle_t handle;
int exit_code;
} Child;
#define BASE_PID 2
#define NUM_CHILDREN 256
static Child children[NUM_CHILDREN];
static Child* get_child(pid_t pid) {
assert(pid - BASE_PID < NUM_CHILDREN);
assert(pid >= BASE_PID);
return &children[pid - BASE_PID];
}
static pid_t get_unused_pid() {
for (int i = 0; i < NUM_CHILDREN; i++) {
if (children[i].state == UNUSED) {
return i + BASE_PID;
}
}
fprintf(stderr, "Can't allocate new pid.\n");
exit(1);
}
static volatile mysig_t sigchld_handler = SIG_IGN;
static void* wait_thread_func(void* voidp) {
Child* child = voidp;
zx_signals_t observed;
zx_object_wait_one(child->handle, ZX_PROCESS_TERMINATED, ZX_TIME_INFINITE, &observed);
zx_info_process_t info;
size_t actual;
zx_object_get_info(child->handle, ZX_INFO_PROCESS, &info, sizeof(info), &actual, NULL);
child->state = STOPPED;
child->exit_code = info.return_code;
mysig_t handler = sigchld_handler;
if (handler == SIG_IGN || handler == SIG_DFL) {
// Don't call a handler
} else {
handler(SIGCHLD);
}
return NULL;
}
// Write to a non-blocking fd, blocking until writing has completed or an error has occurred.
static bool blocking_write(int fd, const char* buffer, size_t length) {
uint32_t events;
size_t offset = 0;
while (offset < length) {
if (fdio_wait_fd(fd, FDIO_EVT_WRITABLE, &events, ZX_TIME_INFINITE) < 0) {
// Wait failed.
return false;
}
ssize_t length_written = write(fd, buffer + offset, length - offset);
if (length_written <= 0) {
// EOF or error.
return false;
}
offset += length_written;
}
return true;
}
// A thread that processes output.
// Currently just nothing but shuffle bytes.
static void* process_input_thread_func(void* voidp) {
int* fds = voidp;
char buf[128];
for (;;) {
uint32_t events;
if (fdio_wait_fd(fds[0], FDIO_EVT_READABLE, &events, ZX_TIME_INFINITE) < 0) {
// Wait failed.
break;
}
int length = read(fds[0], buf, sizeof(buf));
if (length <= 0) {
// EOF or error.
break;
}
if (!blocking_write(fds[1], buf, length)) {
break;
}
}
close(fds[0]);
close(fds[1]);
free(fds);
return NULL;
}
// Start an input processing thread that reads from fd.
// The returned fd will receive the processed input.
static int run_input_processing_thread(int fd) {
int pip[2];
if (pipe(pip) < 0) {
fprintf(stderr, "Error creating pipe: %s\n", strerror(errno));
return -1;
}
set_nonblock(pip[0]);
set_nonblock(pip[1]);
// Allocate the structure to send the fds to the thread.
int* fds = malloc(2 * sizeof(int));
if (!fds) {
fprintf(stderr, "Malloc failed.\n");
return -1;
}
fds[0] = fd;
fds[1] = pip[1];
// Start the thread.
pthread_t processing_thread;
if (pthread_create(&processing_thread, NULL, process_input_thread_func, fds) != 0) {
fprintf(stderr, "Failed to create input processing thread for %d: %s\n", fd, strerror(errno));
return -1;
}
return pip[0];
}
// A thread that processes output.
// Currently just does \n -> \r\n translation.
static void* process_output_thread_func(void* voidp) {
int* fds = voidp;
char buf[128];
for (;;) {
uint32_t events;
if (fdio_wait_fd(fds[0], FDIO_EVT_READABLE, &events, ZX_TIME_INFINITE) < 0) {
// Wait failed.
break;
}
int length = read(fds[0], buf, sizeof(buf));
if (length <= 0) {
// EOF or error.
break;
}
char* p = buf;
while (length > 0) {
char* lf = memchr(p, '\n', length);
if (lf == NULL) {
// No \n found.
if (!blocking_write(fds[1], p, length)) {
goto out_end;
}
break;
} else {
// \n found at lf.
if (lf != p) {
// There are some bytes to print there first.
if (!blocking_write(fds[1], p, lf - p)) {
goto out_end;
}
}
// Send \r\n.
const char* crlf = "\r\n";
if (!blocking_write(fds[1], crlf, 2)) {
goto out_end;
}
// Skip over the \n and what came before it.
length -= (lf - p) + 1;
p = lf + 1;
}
}
}
out_end:
close(fds[0]);
close(fds[1]);
free(fds);
return NULL;
}
// Start an output processing thread that reads from fd.
// The returned fd will receive the processed output.
static int run_output_processing_thread(int fd) {
// Create a pipe to return processed bytes.
int pip[2];
if (pipe(pip) < 0) {
fprintf(stderr, "Error creating pipe: %s\n", strerror(errno));
return -1;
}
set_nonblock(pip[0]);
set_nonblock(pip[1]);
// Allocate the structure to send the fds to the thread.
int* fds = malloc(2 * sizeof(int));
if (!fds) {
fprintf(stderr, "Malloc failed.\n");
return -1;
}
fds[0] = pip[0];
fds[1] = fd;
// Start the thread.
pthread_t processing_thread;
if (pthread_create(&processing_thread, NULL, process_output_thread_func, fds) != 0) {
fprintf(stderr, "Failed to create output processing thread for %d: %s\n", fd, strerror(errno));
return -1;
}
return pip[1];
}
pid_t fuchsia_launch_child(const char* command, int in, int out, int err, bool transform) {
const char* argv[ARGV_MAX];
int argc = 1;
argv[0] = "/boot/bin/sh";
if (command) {
argv[argc++] = "-c";
argv[argc++] = command;
} else {
command = argv[0];
}
argv[argc] = NULL;
if (transform) {
in = run_input_processing_thread(in);
bool same = (out == err);
out = run_output_processing_thread(out);
if (same) {
err = out;
} else {
err = run_output_processing_thread(err);
}
}
launchpad_t* lp;
launchpad_create(0, command, &lp);
launchpad_load_from_file(lp, argv[0]);
launchpad_set_args(lp, argc, argv);
launchpad_clone(lp, LP_CLONE_FDIO_NAMESPACE | LP_CLONE_FDIO_CWD);
// TODO: set up environment
if (in == out) {
launchpad_clone_fd(lp, in, STDIN_FILENO);
} else {
launchpad_transfer_fd(lp, in, STDIN_FILENO);
}
if (out == err) {
launchpad_clone_fd(lp, out, STDOUT_FILENO);
} else {
launchpad_transfer_fd(lp, out, STDOUT_FILENO);
}
launchpad_transfer_fd(lp, err, STDERR_FILENO);
zx_handle_t proc = 0;
const char* errmsg;
zx_status_t status = launchpad_go(lp, &proc, &errmsg);
if (status < 0) {
fprintf(stderr, "error from launchpad_go: %s\n", errmsg);
fprintf(stderr, " status=%d\n", launchpad_get_status(lp));
exit(1);
}
pid_t pid = get_unused_pid();
Child* child = get_child(pid);
child->state = RUNNING;
child->handle = proc;
pthread_t wait_thread;
if (pthread_create(&wait_thread, NULL, wait_thread_func, (void*)child) != 0) {
fprintf(stderr, "Failed to create process waiter thread: %s\n", strerror(errno));
exit(1);
}
return pid;
}
mysig_t mysignal(int signum, mysig_t handler) {
if (signum == SIGCHLD) {
sigchld_handler = handler;
}
// Ignore all non-SIGCHLD requests
return handler;
}
pid_t waitpid(pid_t pid, int* status, int options) {
if (pid == -1 || pid == 0) {
// Find an exited process.
for (pid = BASE_PID; pid < BASE_PID + NUM_CHILDREN; pid++) {
if (get_child(pid)->state == STOPPED) {
return waitpid(pid, status, options);
}
}
if (options & WNOHANG) {
return 0;
} else {
fprintf(stderr, "No child pids waiting for wait.\n");
exit(1);
}
}
Child* child = get_child(pid);
if (child->state != STOPPED) {
fprintf(stderr, "Child with pid %d isn't stopped.\n", pid);
exit(1);
}
if (status) {
*status = child->exit_code;
}
child->state = UNUSED;
return pid;
}