blob: 9d284025755d01c699ec495f0c69d0a5c22c1bae [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 <assert.h>
#include <fcntl.h>
#include <pthread.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <fuchsia/hardware/pty/c/fidl.h>
#include <lib/async-loop/loop.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/io.h>
#include <lib/fdio/spawn.h>
#include <lib/fdio/unsafe.h>
#include <loader-service/loader-service.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/port.h>
#include "misc.h"
#include "openbsd-compat/bsd-misc.h"
static async_loop_t* loop;
static thrd_t loop_thread;
void fuchsia_init_async(void) {
zx_status_t status;
status = async_loop_create(&kAsyncLoopConfigNoAttachToThread, &loop);
if (status != ZX_OK) {
fprintf(stderr, "fatal: failed to create async loop\n");
exit(1);
}
status = async_loop_start_thread(loop, "sshd-async-loop", &loop_thread);
if (status != ZX_OK) {
fprintf(stderr, "fatal: failed to create async loop\n");
exit(1);
}
}
async_dispatcher_t* get_async() {
return async_loop_get_dispatcher(loop);
}
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;
}
#define USERNAME_MAX 32
static char username[USERNAME_MAX + 1] = { 'f', 'u', 'c', 'h', 's', 'i', 'a', 0 };
static struct passwd static_passwd = {
.pw_name = username,
.pw_passwd = "",
.pw_uid = 23, // matches ZX_UID
.pw_gid = 23,
.pw_gecos = "Fuchsia",
.pw_dir = "/",
.pw_shell = "/boot/bin/sh",
};
struct passwd* getpwnam(const char* name) {
size_t len = strlen(name);
if (len > USERNAME_MAX) {
errno = EINVAL;
return NULL;
}
strncpy(username, name, len);
username[len] = 0;
return &static_passwd;
}
struct passwd* getpwuid(uid_t uid) {
return &static_passwd;
}
#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) {
pthread_detach(pthread_self());
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);
}
zx_handle_close(child->handle);
child->handle = ZX_HANDLE_INVALID;
return NULL;
}
// Note: **env is consumed by this function and will be freed.
pid_t fuchsia_launch_child(const char* command, char** env, int in, int out, int err) {
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;
// TODO(CF-578): replace most of this with a "shell runner" instead
zx_status_t status;
loader_service_t *ldsvc;
if ((status = loader_service_create_fs(get_async(), &ldsvc)) != ZX_OK) {
fprintf(stderr, "failed to create loader service: %s\n", zx_status_get_string(status));
exit(1);
}
zx_handle_t ldsvc_hnd;
if ((status = loader_service_connect(ldsvc, &ldsvc_hnd)) != ZX_OK) {
fprintf(stderr, "failed to connect to loader service: %s\n", zx_status_get_string(status));
loader_service_release(ldsvc);
exit(1);
}
loader_service_release(ldsvc);
fdio_spawn_action_t actions[4] = {
{
.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
.h = {.id = PA_LDSVC_LOADER, .handle = ldsvc_hnd},
},
{
.action = (in == out) ? FDIO_SPAWN_ACTION_CLONE_FD
: FDIO_SPAWN_ACTION_TRANSFER_FD,
.fd = {.local_fd = in, .target_fd = STDIN_FILENO},
},
{
.action = (out == err) ? FDIO_SPAWN_ACTION_CLONE_FD
: FDIO_SPAWN_ACTION_TRANSFER_FD,
.fd = {.local_fd = out, .target_fd = STDOUT_FILENO},
},
{
.action = FDIO_SPAWN_ACTION_TRANSFER_FD,
.fd = {.local_fd = err, .target_fd = STDERR_FILENO},
},
};
zx_handle_t proc = 0;
char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
uint32_t flags = FDIO_SPAWN_CLONE_JOB | FDIO_SPAWN_CLONE_NAMESPACE;
status = fdio_spawn_etc(ZX_HANDLE_INVALID, flags, argv[0], argv, (const char* const *)env, 4, actions, &proc, err_msg);
// env is constructed in session.c by child_set_env that always printf's new values and callocs the list pointer.
// walk each member, freeing them, then free the list.
char **envf = env;
while(*envf != NULL) {
free(*envf++);
}
free(env);
if (status < 0) {
fprintf(stderr, "error from fdio_spawn_etc: %s\n", err_msg);
fprintf(stderr, " status=%d (%s)\n", status, zx_status_get_string(status));
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) {
// Make a status that can be parsed by WIFEXITED/WEXITSTATUS/etc.
*status = (0xFF & child->exit_code) << 8;
}
child->state = UNUSED;
return pid;
}
// This is a very inefficient way to emulate select() by creating a port, adding all of the fds
// of interest as async waits, and blocking until we get a port packet back.
// The callers of this (like serverloop) generally have a static set of fds they care about, so
// it'd be much more efficient for them to register async waits on a port object that persists
// across blocking calls.
int fuchsia_select(int nfds, void* readfds, void* writefds, struct timeval *timeout) {
fd_set* readfds_fd_set = (fd_set*) readfds;
fd_set* writefds_fd_set = (fd_set*) writefds;
int ret = 0;
zx_handle_t port = ZX_HANDLE_INVALID;
fdio_t* ios[FD_SETSIZE] = { 0 };
// Create a fresh port for this wait.
zx_status_t st = zx_port_create(0, &port);
if (st != ZX_OK) {
fprintf(stderr, "Can't allocate new port.\n");
errno = EINVAL;
ret = -1;
goto cleanup;
}
// Register port waits for file descriptors in the read and write sets.
for (int fd = 0; fd < nfds; ++fd) {
uint32_t events = 0;
if (readfds_fd_set && FD_ISSET(fd, readfds_fd_set)) {
events |= POLLIN;
}
if (writefds_fd_set && FD_ISSET(fd, writefds_fd_set)) {
events |= POLLOUT;
}
if (!events)
continue;
fdio_t* io;
// This acquires a reference to the fdio which is released in the cleanup path below.
if ((io = fdio_unsafe_fd_to_io(fd)) == NULL) {
errno = EBADF;
ret = -1;
goto cleanup;
}
ios[fd] = io;
zx_handle_t h;
zx_signals_t sigs;
// Translate the poll-style events to fdio-specific signal bits to wait on.
fdio_unsafe_wait_begin(io, events, &h, &sigs);
if (h == ZX_HANDLE_INVALID) {
errno = EBADF;
ret = -1;
goto cleanup;
}
uint64_t key = fd;
st = zx_object_wait_async(h, port, key, sigs, ZX_WAIT_ASYNC_ONCE);
if (st != ZX_OK) {
fprintf(stderr, "Can't wait on object %d.\n", st);
errno = EINVAL;
ret = -1;
goto cleanup;
}
}
zx_time_t deadline = (timeout == NULL) ? ZX_TIME_INFINITE :
zx_deadline_after(ZX_SEC(timeout->tv_sec) + ZX_USEC(timeout->tv_usec));
for (;;) {
zx_port_packet_t packet;
st = zx_port_wait(port, deadline, &packet);
// We expect zx_port_wait to return either ZX_ERR_TIMED_OUT if nothing happened, or ZX_OK
// if at least one thing happened.
if (st == ZX_OK) {
if (packet.type != ZX_PKT_TYPE_SIGNAL_ONE) {
fprintf(stderr, "Unexpected port packet type %u\n", packet.type);
errno = EINVAL;
ret = -1;
goto cleanup;
}
// We've heard about an fd in the set we care about. Update the read/write
// sets to reflect this information, then remove them from the set we are
// listening to.
int fd = (int)packet.key;
uint32_t events = 0;
fdio_t* io = ios[fd];
if (!io) {
fprintf(stderr, "Can't find fd for packet key %d.\n", fd);
errno = EINVAL;
ret = -1;
goto cleanup;
}
// fdio_unsafe_wait_end translates the signals back to poll-style flags.
fdio_unsafe_wait_end(io, packet.signal.observed, &events);
if (readfds_fd_set && FD_ISSET(fd, readfds_fd_set)) {
if (events & POLLIN)
ret++;
else
FD_CLR(fd, readfds_fd_set);
}
if (writefds_fd_set && FD_ISSET(fd, writefds_fd_set)) {
if (events & POLLOUT)
ret++;
else
FD_CLR(fd, writefds_fd_set);
}
// The read and write sets for this fd are now updated, and our wait has expired, so
// remove this fd from the set of things we care about.
ios[fd] = NULL;
fdio_unsafe_release(io);
} else if (st == ZX_ERR_TIMED_OUT) {
break;
} else {
fprintf(stderr, "Port wait return unexpected error %d.\n", st);
errno = EINVAL;
ret = -1;
goto cleanup;
}
// After pulling the first packet out, poll without blocking by doing another wait with a
// deadline in the past. This will populate any other members of the read/write set that
// are ready to go now.
deadline = 0;
}
// If there are any entries left in ios at this point, we have not received a port packet
// indicating that those fds are readable or writable and so we should clear those from the
// read/write sets.
for (int fd = 0; fd < nfds; ++fd) {
if (ios[fd]) {
if (readfds_fd_set && FD_ISSET(fd, readfds_fd_set)) {
FD_CLR(fd, readfds_fd_set);
}
if (writefds_fd_set && FD_ISSET(fd, writefds_fd_set)) {
FD_CLR(fd, writefds_fd_set);
}
}
}
cleanup:
// Release reference to any fdio objects we acquired with fdio_unsafe_fd_to_io().
for (int fd = 0; fd < nfds; ++fd) {
if (ios[fd]) {
fdio_unsafe_release(ios[fd]);
}
}
if (port != ZX_HANDLE_INVALID) {
zx_handle_close(port);
}
return ret;
}
int fuchsia_open_pty_client(int fd, uint32_t id) {
fdio_t* io = fdio_unsafe_fd_to_io(fd);
if (io == NULL) {
fprintf(stderr, "Failed to open PTY client: couldn't create fdio\n");
return -1;
}
zx_handle_t device_channel, client_channel;
zx_status_t status =
zx_channel_create(0 /* flags */, &device_channel, &client_channel);
if (status != ZX_OK) {
fprintf(stderr, "Failed to open PTY client: %s\n",
zx_status_get_string(status));
return -1;
}
zx_status_t fidl_status = fuchsia_hardware_pty_DeviceOpenClient(
fdio_unsafe_borrow_channel(io), id, device_channel, &status);
fdio_unsafe_release(io);
if (fidl_status != ZX_OK) {
fprintf(stderr, "Failed to open PTY client: %s\n",
zx_status_get_string(fidl_status));
zx_handle_close(device_channel);
zx_handle_close(client_channel);
return -1;
}
if (status != ZX_OK) {
fprintf(stderr, "Failed to open PTY client: %s\n",
zx_status_get_string(status));
zx_handle_close(device_channel);
zx_handle_close(client_channel);
return -1;
}
int client_fd;
status = fdio_fd_create(client_channel, &client_fd);
if (status != ZX_OK) {
fprintf(stderr, "Failed to open PTY client: %s\n",
zx_status_get_string(status));
zx_handle_close(device_channel);
zx_handle_close(client_channel);
return -1;
}
return client_fd;
}