blob: 60498738094bf897fca1acfed16efedfe53ad046 [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 <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef _ALL_SOURCE
#define _ALL_SOURCE // Enables thrd_create_with_name in <threads.h>.
#endif
#include <lib/async-loop/default.h>
#include <lib/async-loop/loop.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/io.h>
#include <lib/fdio/spawn.h>
#include <lib/fdio/unsafe.h>
#include <lib/zircon-internal/paths.h>
#include <threads.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/port.h>
#include "includes.h"
#include "fuchsia/loader-wrapper.h"
#include "misc.h"
#include "openbsd-compat/bsd-misc.h"
#include "xmalloc.h"
static async_loop_t* loop;
static thrd_t loop_thread;
static loader_service_t* ldsvc;
async_dispatcher_t* get_async() { return async_loop_get_dispatcher(loop); }
void fuchsia_init_async(void) {
zx_status_t status;
status = async_loop_create(&kAsyncLoopConfigNoAttachToCurrentThread, &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);
}
int boot_lib_dir;
status = fdio_open_fd("/boot/lib", lib_dir_open_flags(), &boot_lib_dir);
if (status != ZX_OK) {
fprintf(stderr, "fatal: failed to open /boot/lib: %d\n", status);
exit(1);
}
if ((status = loader_service_create(get_async(), boot_lib_dir, "openssh", &ldsvc)) != ZX_OK) {
fprintf(stderr, "failed to create loader service: %s\n", zx_status_get_string(status));
exit(1);
}
}
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 = ZX_SHELL_DEFAULT,
};
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;
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 sshsig_t sigchld_handler = SIG_IGN;
typedef struct {
pid_t pid;
zx_handle_t in, out, err;
char* command;
char** env;
} LaunchRequest;
// make_launch_request copies command, but takes ownership over env.
LaunchRequest* make_launch_request(const char* command, char** env, int in, int out, int err) {
LaunchRequest* lr = xmalloc(sizeof(LaunchRequest));
lr->pid = get_unused_pid();
if (command == NULL) {
lr->command = NULL;
} else {
lr->command = xmalloc(strlen(command) + 1);
strcpy(lr->command, command);
}
lr->env = env;
/*
* ssh's session.c will close out our file descriptors, as it is expecting a fork() to occur, so
* we can't use these fd's "some time later" in the child thread. Clone handles from the fd's now
* and use those on the thread. fdio_fd_transfer cannot be used because that immediately consumes
* and closes the fd, which results in racy behavior since session.c will still also close these
* fd numbers, but they may have been reused by other threads by then.
*/
zx_status_t status;
if ((status = fdio_fd_clone(in, &lr->in)) != ZX_OK) {
fprintf(stderr, "failed to clone in fd %d\n", in);
}
if ((status = fdio_fd_clone(out, &lr->out)) != ZX_OK) {
fprintf(stderr, "failed to clone out fd %d\n", out);
}
if ((status = fdio_fd_clone(err, &lr->err)) != ZX_OK) {
fprintf(stderr, "failed to clone err fd %d\n", err);
}
return lr;
}
// free_env free's a **env as produced by session.c
void free_env(char** env) {
char** envf = env;
while (*envf != NULL) {
free(*envf++);
}
free(env);
}
// free_launch_request frees a LaunchRequest. It frees command and env. It does
// not modify child, and does not close any of the handles.
void free_launch_request(LaunchRequest* lr) {
free(lr->command);
free_env(lr->env);
free(lr);
}
static zx_status_t spawn_launch_request(LaunchRequest* lr, zx_handle_t* proc, char* err_msg) {
const char* command = lr->command;
char** env = lr->env;
// TODO(fxbug.dev/42121054): replace most of this with a "shell runner" instead
const char* argv[ARGV_MAX];
int argc = 1;
argv[0] = ZX_SHELL_DEFAULT;
if (command) {
argv[argc++] = "-c";
argv[argc++] = command;
} else {
command = argv[0];
}
argv[argc] = NULL;
zx_status_t status;
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));
exit(1);
}
fdio_spawn_action_t actions[4] = {
{
.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
.h = {.id = PA_LDSVC_LOADER, .handle = ldsvc_hnd},
},
{
.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
.h = {.id = PA_HND(PA_FD, STDIN_FILENO), .handle = lr->in},
},
{
.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
.h = {.id = PA_HND(PA_FD, STDOUT_FILENO), .handle = lr->out},
},
{
.action = FDIO_SPAWN_ACTION_ADD_HANDLE,
.h = {.id = PA_HND(PA_FD, STDERR_FILENO), .handle = lr->err},
},
};
uint32_t flags = FDIO_SPAWN_CLONE_JOB | FDIO_SPAWN_CLONE_NAMESPACE | FDIO_SPAWN_CLONE_UTC_CLOCK;
return fdio_spawn_etc(ZX_HANDLE_INVALID, flags, argv[0], argv, (const char* const*)env, 4,
actions, proc, err_msg);
}
static int child_thread_func(void* voidp) {
thrd_detach(thrd_current());
Child* child = get_child(((LaunchRequest*)voidp)->pid);
zx_handle_t proc;
char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
zx_status_t status = spawn_launch_request(voidp, &proc, err_msg);
free_launch_request(voidp);
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);
}
zx_signals_t observed;
zx_object_wait_one(proc, ZX_PROCESS_TERMINATED, ZX_TIME_INFINITE, &observed);
zx_info_process_t info;
size_t actual;
zx_object_get_info(proc, ZX_INFO_PROCESS, &info, sizeof(info), &actual, NULL);
child->state = STOPPED;
child->exit_code = info.return_code;
sshsig_t handler = sigchld_handler;
if (handler == SIG_IGN || handler == SIG_DFL) {
// Don't call a handler
} else {
handler(SIGCHLD);
}
zx_handle_close(proc);
return 0;
}
// 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) {
LaunchRequest* lr = make_launch_request(command, env, in, out, err);
pid_t pid = lr->pid;
get_child(pid)->state = RUNNING;
thrd_t child_thread;
if (thrd_create_with_name(&child_thread, child_thread_func, (void*)lr, "child-waiter") != 0) {
fprintf(stderr, "Failed to create process spawn thread: %s\n", strerror(errno));
exit(1);
}
return pid;
}
sshsig_t ssh_signal(int signum, sshsig_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;
}