| // Copyright 2016 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 <ctype.h> |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <threads.h> |
| #include <unistd.h> |
| #include <utility> |
| |
| #include <fbl/string_printf.h> |
| #include <fbl/unique_fd.h> |
| #include <fbl/vector.h> |
| #include <fuchsia/boot/c/fidl.h> |
| #include <launchpad/launchpad.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/devmgr-launcher/processargs.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/io.h> |
| #include <lib/fdio/namespace.h> |
| #include <lib/fdio/spawn.h> |
| #include <lib/fdio/watcher.h> |
| #include <lib/zx/debuglog.h> |
| #include <lib/zx/event.h> |
| #include <lib/zx/port.h> |
| #include <lib/zx/resource.h> |
| #include <lib/zx/time.h> |
| #include <lib/zx/vmo.h> |
| #include <loader-service/loader-service.h> |
| #include <zircon/boot/image.h> |
| #include <zircon/device/vfs.h> |
| #include <zircon/dlfcn.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/log.h> |
| #include <zircon/syscalls/object.h> |
| #include <zircon/syscalls/policy.h> |
| |
| #include "boot-args.h" |
| #include "coordinator.h" |
| #include "devfs.h" |
| #include "devhost-loader-service.h" |
| |
| #include "../shared/env.h" |
| #include "../shared/fdio.h" |
| #include "../shared/log.h" |
| |
| namespace { |
| |
| constexpr char kArgumentsPath[] = "/bootsvc/" fuchsia_boot_Arguments_Name; |
| constexpr char kItemsPath[] = "/bootsvc/" fuchsia_boot_Items_Name; |
| constexpr char kRootResourcePath[] = "/bootsvc/" fuchsia_boot_RootResource_Name; |
| |
| struct { |
| // The handle used to transmit messages to appmgr. |
| zx::channel appmgr_client; |
| |
| // The handle used by appmgr to serve incoming requests. |
| // If appmgr cannot be launched within a timeout, this handle is closed. |
| zx::channel appmgr_server; |
| |
| zx::unowned_job root_job; |
| zx::job svc_job; |
| zx::job fuchsia_job; |
| zx::channel svchost_outgoing; |
| |
| zx::channel fs_root; |
| |
| // Used to bind the svchost to the virtual-console binary to provide fidl |
| // services. |
| zx::channel virtcon_fidl; |
| } g_handles; |
| |
| // Wait for the requested file. Its parent directory must exist. |
| zx_status_t wait_for_file(const char* path, zx::time deadline) { |
| char path_copy[PATH_MAX]; |
| if (strlen(path) >= PATH_MAX) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| strcpy(path_copy, path); |
| |
| char* last_slash = strrchr(path_copy, '/'); |
| // Waiting on the root of the fs or paths with no slashes is not supported by this function |
| if (last_slash == path_copy || last_slash == nullptr) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| last_slash[0] = 0; |
| char* dirname = path_copy; |
| char* basename = last_slash + 1; |
| |
| auto watch_func = [](int dirfd, int event, const char* fn, void* cookie) -> zx_status_t { |
| auto basename = static_cast<const char*>(cookie); |
| if (event != WATCH_EVENT_ADD_FILE) { |
| return ZX_OK; |
| } |
| if (!strcmp(fn, basename)) { |
| return ZX_ERR_STOP; |
| } |
| return ZX_OK; |
| }; |
| |
| fbl::unique_fd dirfd(open(dirname, O_RDONLY)); |
| if (!dirfd.is_valid()) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| zx_status_t status = fdio_watch_directory(dirfd.get(), watch_func, deadline.get(), |
| reinterpret_cast<void*>(basename)); |
| if (status == ZX_ERR_STOP) { |
| return ZX_OK; |
| } |
| return status; |
| } |
| |
| void do_autorun(const char* name, const char* cmd) { |
| if (cmd != nullptr) { |
| auto args = devmgr::ArgumentVector::FromCmdline(cmd); |
| args.Print("autorun"); |
| devmgr::devmgr_launch(g_handles.svc_job, name, args.argv(), nullptr, -1, |
| nullptr, nullptr, 0, nullptr, FS_ALL); |
| } |
| } |
| |
| // Get kernel arguments from the arguments service. |
| zx_status_t get_arguments(zx::vmo* args_vmo, size_t* args_size) { |
| zx::channel local, remote; |
| zx_status_t status = zx::channel::create(0, &local, &remote); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = fdio_service_connect(kArgumentsPath, remote.release()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return fuchsia_boot_ArgumentsGet(local.get(), args_vmo->reset_and_get_address(), args_size); |
| } |
| |
| // Get ramdisk from the boot items service. |
| zx_status_t get_ramdisk(zx::vmo* ramdisk_vmo) { |
| zx::channel local, remote; |
| zx_status_t status = zx::channel::create(0, &local, &remote); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = fdio_service_connect(kItemsPath, remote.release()); |
| if (status != ZX_OK) { |
| fprintf(stderr, "devcoordinator: get_arguments: fdio_service_connect returned %d\n", |
| status); |
| return status; |
| } |
| uint32_t length; |
| return fuchsia_boot_ItemsGet(local.get(), ZBI_TYPE_STORAGE_RAMDISK, 0, |
| ramdisk_vmo->reset_and_get_address(), &length); |
| } |
| |
| // Get the root resource from the root resource service. Not receiving the |
| // startup handle is logged, but not fatal. In test environments, it would not |
| // be present. |
| zx_status_t get_root_resource(zx::resource* root_resource) { |
| zx::channel local, remote; |
| zx_status_t status = zx::channel::create(0, &local, &remote); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = fdio_service_connect(kRootResourcePath, remote.release()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return fuchsia_boot_RootResourceGet(local.get(), root_resource->reset_and_get_address()); |
| } |
| |
| int fuchsia_starter(void* arg) { |
| auto coordinator = static_cast<devmgr::Coordinator*>(arg); |
| bool appmgr_started = false; |
| bool autorun_started = false; |
| bool drivers_loaded = false; |
| |
| size_t appmgr_timeout = 20; |
| zx::time deadline = zx::deadline_after(zx::sec(appmgr_timeout)); |
| |
| do { |
| zx_status_t status = coordinator->fshost_event().wait_one( |
| FSHOST_SIGNAL_READY, deadline, nullptr); |
| if (status == ZX_ERR_TIMED_OUT) { |
| if (g_handles.appmgr_server.is_valid()) { |
| if (coordinator->require_system()) { |
| fprintf(stderr, "devcoordinator: appmgr not launched in %zus, closing appmgr handle\n", |
| appmgr_timeout); |
| } |
| g_handles.appmgr_server.reset(); |
| } |
| deadline = zx::time::infinite(); |
| continue; |
| } |
| if (status != ZX_OK) { |
| fprintf(stderr, "devcoordinator: error waiting on fuchsia start event: %d\n", status); |
| break; |
| } |
| status = coordinator->fshost_event().signal(FSHOST_SIGNAL_READY, 0); |
| if (status != ZX_OK) { |
| fprintf(stderr, "devcoordinator: error signaling fshost: %d\n", status); |
| } |
| |
| if (!drivers_loaded) { |
| // we're starting the appmgr because /system is present |
| // so we also signal the device coordinator that those |
| // drivers are now loadable |
| coordinator->set_system_available(true); |
| coordinator->ScanSystemDrivers(); |
| drivers_loaded = true; |
| } |
| |
| const char* argv_appmgr[] = { |
| "/system/bin/appmgr", |
| nullptr, |
| }; |
| |
| struct stat s; |
| if (!appmgr_started && stat(argv_appmgr[0], &s) == 0) { |
| unsigned int appmgr_hnd_count = 0; |
| zx_handle_t appmgr_hnds[2] = {}; |
| uint32_t appmgr_ids[2] = {}; |
| if (g_handles.appmgr_server.is_valid()) { |
| assert(appmgr_hnd_count < fbl::count_of(appmgr_hnds)); |
| appmgr_hnds[appmgr_hnd_count] = g_handles.appmgr_server.release(); |
| appmgr_ids[appmgr_hnd_count] = PA_DIRECTORY_REQUEST; |
| appmgr_hnd_count++; |
| } |
| devmgr::devmgr_launch(g_handles.fuchsia_job, "appmgr", |
| argv_appmgr, nullptr, -1, appmgr_hnds, appmgr_ids, |
| appmgr_hnd_count, nullptr, FS_FOR_APPMGR); |
| appmgr_started = true; |
| } |
| if (!autorun_started) { |
| do_autorun("autorun:system", coordinator->boot_args().Get("zircon.autorun.system")); |
| autorun_started = true; |
| } |
| } while (!appmgr_started); |
| return 0; |
| } |
| |
| int console_starter(void* arg) { |
| auto& boot_args = *static_cast<const devmgr::BootArgs*>(arg); |
| |
| // If we got a TERM environment variable (aka a TERM=... argument on |
| // the kernel command line), pass this down; otherwise pass TERM=uart. |
| const char* term = boot_args.Get("TERM"); |
| if (term == nullptr) { |
| term = "TERM=uart"; |
| } else { |
| term -= sizeof("TERM=") - 1; |
| } |
| |
| const char* device = boot_args.Get("console.path"); |
| if (device == nullptr) { |
| device = "/dev/misc/console"; |
| } |
| |
| const char* envp[] = { |
| term, |
| nullptr, |
| }; |
| |
| zx_status_t status = wait_for_file(device, zx::time::infinite()); |
| if (status != ZX_OK) { |
| printf("devcoordinator: failed to wait for console '%s'\n", device); |
| return 1; |
| } |
| fbl::unique_fd fd(open(device, O_RDWR)); |
| if (!fd.is_valid()) { |
| printf("devcoordinator: failed to open console '%s'\n", device); |
| return 1; |
| } |
| |
| const char* argv_sh[] = {"/boot/bin/sh", nullptr}; |
| devmgr::devmgr_launch(g_handles.svc_job, "sh:console", argv_sh, |
| envp, fd.release(), nullptr, nullptr, 0, nullptr, FS_ALL); |
| return 0; |
| } |
| |
| int pwrbtn_monitor_starter(void* arg) { |
| const char* name = "pwrbtn-monitor"; |
| const char* argv[] = {"/boot/bin/pwrbtn-monitor", nullptr}; |
| |
| zx::job job_copy; |
| zx_status_t status = |
| g_handles.svc_job.duplicate(ZX_RIGHTS_BASIC | ZX_RIGHT_READ | ZX_RIGHT_WRITE, &job_copy); |
| if (status != ZX_OK) { |
| printf("devcoordinator: svc_job.duplicate failed %s\n", zx_status_get_string(status)); |
| return 1; |
| } |
| |
| zx::debuglog debuglog; |
| if ((status = zx::debuglog::create(zx::resource(), 0, &debuglog) != ZX_OK)) { |
| printf("devcoordinator: cannot create debuglog handle\n"); |
| return 1; |
| } |
| |
| zx::channel input_handle = devmgr::fs_clone("dev/class/input"); |
| if (!input_handle.is_valid()) { |
| printf("devcoordinator: failed to clone /dev/input\n"); |
| return 1; |
| } |
| |
| zx::channel svc_handle = devmgr::fs_clone("svc"); |
| if (!svc_handle.is_valid()) { |
| printf("devcoordinator: failed to clone /svc\n"); |
| return 1; |
| } |
| |
| fdio_spawn_action_t actions[] = { |
| {.action = FDIO_SPAWN_ACTION_SET_NAME, .name = {.data = name}}, |
| {.action = FDIO_SPAWN_ACTION_ADD_NS_ENTRY, |
| .ns = {.prefix = "/input", .handle = input_handle.release()}}, |
| // Ideally we'd only expose /svc/fuchsia.device.manager.Administrator, but we do not |
| // support exposing single services. |
| {.action = FDIO_SPAWN_ACTION_ADD_NS_ENTRY, |
| .ns = {.prefix = "/svc", .handle = svc_handle.release()}}, |
| {.action = FDIO_SPAWN_ACTION_ADD_HANDLE, |
| .h = {.id = PA_HND(PA_FD, FDIO_FLAG_USE_FOR_STDIO | 0), |
| .handle = debuglog.release()}}, |
| }; |
| |
| char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; |
| uint32_t spawn_flags = FDIO_SPAWN_CLONE_JOB | FDIO_SPAWN_DEFAULT_LDSVC; |
| status = fdio_spawn_etc(job_copy.get(), spawn_flags, argv[0], argv, |
| nullptr, fbl::count_of(actions), actions, |
| nullptr, err_msg); |
| if (status != ZX_OK) { |
| printf("devcoordinator: spawn %s (%s) failed: %s: %s\n", argv[0], name, err_msg, |
| zx_status_get_string(status)); |
| return 1; |
| } |
| |
| printf("devcoordinator: launch %s (%s) OK\n", argv[0], name); |
| return 0; |
| } |
| |
| void start_console_shell(const devmgr::BootArgs& boot_args) { |
| // Only start a shell on the kernel console if it isn't already running a shell. |
| if (boot_args.GetBool("kernel.shell", false)) { |
| return; |
| } |
| thrd_t t; |
| int ret = thrd_create_with_name(&t, console_starter, const_cast<devmgr::BootArgs*>(&boot_args), |
| "console-starter"); |
| if (ret == thrd_success) { |
| thrd_detach(t); |
| } |
| } |
| |
| zx_status_t fuchsia_create_job() { |
| zx_status_t status = zx::job::create(*g_handles.root_job, 0u, &g_handles.fuchsia_job); |
| if (status != ZX_OK) { |
| printf("devcoordinator: unable to create fuchsia job: %d (%s)\n", status, |
| zx_status_get_string(status)); |
| return status; |
| } |
| |
| g_handles.fuchsia_job.set_property(ZX_PROP_NAME, "fuchsia", 7); |
| |
| const zx_policy_basic_t basic_policy[] = { |
| // Lock down process creation. Child tasks must use fuchsia.process.Launcher. |
| {.condition = ZX_POL_NEW_PROCESS, .policy = ZX_POL_ACTION_DENY}}; |
| |
| status = g_handles.fuchsia_job.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_BASIC, basic_policy, |
| fbl::count_of(basic_policy)); |
| if (status != ZX_OK) { |
| printf("devcoordinator: unable to set basic policy for fuchsia job: %d (%s)\n", status, |
| zx_status_get_string(status)); |
| return status; |
| } |
| |
| // Set the minimum timer slack amount and default mode. The amount should be large enough to |
| // allow for some coalescing of timers, but small enough to ensure applications don't miss |
| // deadlines. |
| // |
| // Why LATE and not CENTER or EARLY? Timers firing a little later than requested is not uncommon |
| // in non-realtime systems. Programs are generally tolerant of some delays. However, timers |
| // firing before their dealine can be unexpected and lead to bugs. |
| const zx_policy_timer_slack_t timer_slack_policy{ZX_USEC(500), ZX_TIMER_SLACK_LATE}; |
| |
| status = g_handles.fuchsia_job.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_TIMER_SLACK, |
| &timer_slack_policy, 1); |
| if (status != ZX_OK) { |
| printf("devcoordinator: unable to set timer slack policy for fuchsia job: %d (%s)\n", status, |
| zx_status_get_string(status)); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t svchost_start(bool require_system, devmgr::Coordinator* coordinator, |
| zx::channel fshost_client) { |
| const auto& root_resource = coordinator->root_resource(); |
| zx::channel dir_request, svchost_local; |
| zx::debuglog logger; |
| zx::channel appmgr_svc_req; |
| zx::channel appmgr_svc; |
| |
| zx_status_t status = zx::channel::create(0, &dir_request, &svchost_local); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = zx::debuglog::create(zx::resource(), 0, &logger); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = zx::channel::create(0, &appmgr_svc_req, &appmgr_svc); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = |
| fdio_service_connect_at(g_handles.appmgr_client.get(), "svc", appmgr_svc_req.release()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| const char* name = "svchost"; |
| const char* argv[2] = { |
| "/boot/bin/svchost", |
| require_system ? "--require-system" : nullptr, |
| }; |
| int argc = require_system ? 2 : 1; |
| |
| zx::job svc_job_copy; |
| status = g_handles.svc_job.duplicate( |
| ZX_RIGHTS_BASIC | ZX_RIGHT_MANAGE_JOB | ZX_RIGHT_MANAGE_PROCESS, &svc_job_copy); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| zx::job root_job_copy; |
| status = g_handles.root_job->duplicate(ZX_RIGHTS_BASIC | ZX_RIGHTS_IO | ZX_RIGHTS_PROPERTY | |
| ZX_RIGHT_ENUMERATE | ZX_RIGHT_MANAGE_PROCESS, |
| &root_job_copy); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| zx::channel fidl_client; |
| { |
| zx::channel fidl_server; |
| status = zx::channel::create(0, &fidl_server, &fidl_client); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = coordinator->BindOutgoingServices(std::move(fidl_server)); |
| if (status != ZX_OK) { |
| printf("Unable to start fidl services.\n"); |
| return status; |
| } |
| } |
| |
| zx::channel virtcon_client; |
| status = zx::channel::create(0, &virtcon_client, &g_handles.virtcon_fidl); |
| if (status != ZX_OK) { |
| printf("Unable to create virtcon channel.\n"); |
| return status; |
| } |
| |
| // svchost needs to hold this to talk to zx_kerneldebug but doesn't need any rights. |
| // TODO(ZX-971): when zx_debug_send_command syscall is descoped, update this too. |
| zx::resource root_resource_copy; |
| if (root_resource.is_valid()) { |
| status = root_resource.duplicate(ZX_RIGHT_TRANSFER, &root_resource_copy); |
| if (status != ZX_OK) { |
| return status; |
| } |
| } |
| |
| launchpad_t* lp = nullptr; |
| launchpad_create(svc_job_copy.get(), name, &lp); |
| launchpad_load_from_file(lp, argv[0]); |
| launchpad_set_args(lp, argc, argv); |
| launchpad_add_handle(lp, dir_request.release(), PA_DIRECTORY_REQUEST); |
| launchpad_add_handle(lp, logger.release(), PA_HND(PA_FD, FDIO_FLAG_USE_FOR_STDIO)); |
| |
| // Remove once svchost hosts the tracelink service itself. |
| launchpad_add_handle(lp, appmgr_svc.release(), PA_HND(PA_USER0, 0)); |
| |
| // Give svchost a restricted root job handle. svchost is already a privileged system service |
| // as it controls system-wide process launching. With the root job it can consolidate a few |
| // services such as crashsvc and the profile service. |
| launchpad_add_handle(lp, root_job_copy.release(), PA_HND(PA_USER0, 1)); |
| |
| // Also give svchost a restricted root resource handle, this allows it to run the kernel-debug |
| // service. |
| if (root_resource_copy.is_valid()) { |
| launchpad_add_handle(lp, root_resource_copy.release(), PA_HND(PA_USER0, 2)); |
| } |
| |
| // TODO(smklein): Merge "fidl_client" (proxying requests to devmgr) and |
| // "fshost_client" (proxying requests to fshost) into one service provider |
| // PseudoDirectory. |
| |
| // Add handle to channel to allow svchost to proxy fidl services to us. |
| launchpad_add_handle(lp, fidl_client.release(), PA_HND(PA_USER0, 3)); |
| |
| // Add a handle to allow svchost to proxy services to fshost. |
| launchpad_add_handle(lp, fshost_client.release(), PA_HND(PA_USER0, 4)); |
| if (!coordinator->boot_args().GetBool("virtcon.disable", false)) { |
| // Add handle to channel to allow svchost to proxy fidl services to |
| // virtcon. |
| launchpad_add_handle(lp, virtcon_client.release(), PA_HND(PA_USER0, 5)); |
| } |
| |
| // Give svchost access to /dev/class/sysmem, to enable svchost to forward sysmem service |
| // requests to the sysmem driver. Create a namespace containing /dev/class/sysmem. |
| const char* nametable[1] = {}; |
| uint32_t count = 0; |
| zx::channel fs_handle = devmgr::fs_clone("dev/class/sysmem"); |
| if (fs_handle.is_valid()) { |
| nametable[count] = "/sysmem"; |
| launchpad_add_handle(lp, fs_handle.release(), PA_HND(PA_NS_DIR, count++)); |
| } else { |
| launchpad_abort(lp, ZX_ERR_BAD_STATE, "devcoordinator: failed to clone /dev/class/sysmem"); |
| // The launchpad_go() call below will fail, but will still free lp. |
| } |
| |
| launchpad_set_nametable(lp, count, nametable); |
| |
| const char* errmsg = nullptr; |
| if ((status = launchpad_go(lp, nullptr, &errmsg)) != ZX_OK) { |
| printf("devcoordinator: launchpad %s (%s) failed: %s: %d\n", argv[0], name, errmsg, status); |
| return status; |
| } else { |
| printf("devcoordinator: launch %s (%s) OK\n", argv[0], name); |
| } |
| |
| zx::channel svchost_public_remote; |
| status = zx::channel::create(0, &svchost_public_remote, &g_handles.svchost_outgoing); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| return fdio_service_connect_at(svchost_local.get(), "public", svchost_public_remote.release()); |
| } |
| |
| void fshost_start(devmgr::Coordinator* coordinator, const devmgr::DevmgrArgs& devmgr_args, |
| zx::channel fshost_server) { |
| // assemble handles to pass down to fshost |
| zx_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES]; |
| uint32_t types[fbl::count_of(handles)]; |
| size_t n = 0; |
| zx_handle_t ldsvc; |
| |
| // Pass "fs_root", and ldsvc handles to fshost. |
| if (zx_channel_create(0, g_handles.fs_root.reset_and_get_address(), &handles[n]) == ZX_OK) { |
| types[n++] = PA_HND(PA_USER0, 0); |
| } |
| if (zx_channel_create(0, &ldsvc, &handles[n]) == ZX_OK) { |
| types[n++] = PA_HND(PA_USER0, 2); |
| } else { |
| ldsvc = ZX_HANDLE_INVALID; |
| } |
| |
| // The "public directory" of the fshost service. |
| handles[n] = fshost_server.release(); |
| types[n++] = PA_HND(PA_USER0, 3); |
| |
| // pass fuchsia start event to fshost |
| zx::event fshost_event_duplicate; |
| if (coordinator->fshost_event().duplicate(ZX_RIGHT_SAME_RIGHTS, &fshost_event_duplicate) == |
| ZX_OK) { |
| handles[n] = fshost_event_duplicate.release(); |
| types[n++] = PA_HND(PA_USER1, 0); |
| } |
| |
| // pass VDSO VMOS to fshost |
| for (uint32_t m = 0; n < fbl::count_of(handles); m++) { |
| uint32_t type = PA_HND(PA_VMO_VDSO, m); |
| if (m == 0) { |
| // By this point, launchpad has already moved PA_HND(PA_VMO_VDSO, 0) into a static. |
| handles[n] = ZX_HANDLE_INVALID; |
| launchpad_get_vdso_vmo(&handles[n]); |
| } else { |
| handles[n] = zx_take_startup_handle(type); |
| } |
| |
| if (handles[n] != ZX_HANDLE_INVALID) { |
| types[n++] = type; |
| } else { |
| break; |
| } |
| } |
| |
| // pass ramdisk to fshost |
| zx::vmo ramdisk_vmo; |
| zx_status_t status = get_ramdisk(&ramdisk_vmo); |
| if (status == ZX_OK && ramdisk_vmo.is_valid()) { |
| handles[n] = ramdisk_vmo.release(); |
| types[n++] = PA_HND(PA_VMO_BOOTDATA, 0); |
| } |
| |
| // pass command line to the fshost |
| fbl::Vector<const char*> args{"/boot/bin/fshost"}; |
| if (coordinator->boot_args().GetBool("netsvc.netboot", false) || |
| coordinator->boot_args().GetBool("zircon.system.disable-automount", false)) { |
| args.push_back("--netboot"); |
| } |
| if (devmgr_args.disable_block_watcher) { |
| args.push_back("--disable-block-watcher"); |
| } |
| args.push_back(nullptr); |
| |
| // pass zircon.system.* options to the fshost as environment variables |
| fbl::Vector<const char*> env; |
| coordinator->boot_args().Collect("zircon.system", &env); |
| env.push_back(nullptr); |
| |
| devmgr::devmgr_launch(g_handles.svc_job, "fshost", args.get(), env.get(), -1, handles, types, n, |
| nullptr, FS_BOOT | FS_DEV | FS_SVC); |
| |
| // switch to system loader service provided by fshost |
| zx_handle_close(dl_set_loader_service(ldsvc)); |
| } |
| |
| void devmgr_vfs_init(devmgr::Coordinator* coordinator, const devmgr::DevmgrArgs& devmgr_args, |
| bool needs_svc_mount, zx::channel fshost_server) { |
| fdio_ns_t* ns; |
| zx_status_t r; |
| r = fdio_ns_get_installed(&ns); |
| ZX_ASSERT_MSG(r == ZX_OK, "devcoordinator: cannot get namespace: %s\n", zx_status_get_string(r)); |
| r = fdio_ns_bind(ns, "/dev", devmgr::fs_clone("dev").release()); |
| ZX_ASSERT_MSG(r == ZX_OK, "devcoordinator: cannot bind /dev to namespace: %s\n", |
| zx_status_get_string(r)); |
| |
| if (needs_svc_mount) { |
| r = fdio_ns_bind(ns, "/svc", devmgr::fs_clone("svc").release()); |
| ZX_ASSERT_MSG(r == ZX_OK, "devcoordinator: cannot bind /svc to namespace: %s\n", |
| zx_status_get_string(r)); |
| } |
| |
| // Start fshost before binding /system, since it publishes it. |
| fshost_start(coordinator, devmgr_args, std::move(fshost_server)); |
| |
| if ((r = fdio_ns_bind(ns, "/system", devmgr::fs_clone("system").release())) != ZX_OK) { |
| printf("devcoordinator: cannot bind /system to namespace: %d\n", r); |
| } |
| } |
| |
| int service_starter(void* arg) { |
| auto coordinator = static_cast<devmgr::Coordinator*>(arg); |
| |
| bool netboot = false; |
| bool vruncmd = false; |
| fbl::String vcmd; |
| if (!(coordinator->boot_args().GetBool("netsvc.disable", false) || |
| coordinator->disable_netsvc())) { |
| const char* args[] = { |
| "/boot/bin/netsvc", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; |
| int argc = 1; |
| |
| if (coordinator->boot_args().GetBool("netsvc.netboot", false)) { |
| args[argc++] = "--netboot"; |
| netboot = true; |
| vruncmd = true; |
| } |
| |
| if (coordinator->boot_args().GetBool("netsvc.advertise", true)) { |
| args[argc++] = "--advertise"; |
| } |
| |
| const char* interface = coordinator->boot_args().Get("netsvc.interface"); |
| if (interface != nullptr) { |
| args[argc++] = "--interface"; |
| args[argc++] = interface; |
| } |
| |
| const char* nodename = coordinator->boot_args().Get("zircon.nodename"); |
| if (nodename) { |
| args[argc++] = nodename; |
| } |
| |
| zx::process proc; |
| zx_status_t status = devmgr::devmgr_launch(g_handles.svc_job, "netsvc", args, nullptr, -1, |
| nullptr, nullptr, 0, &proc, FS_ALL); |
| if (status == ZX_OK) { |
| if (vruncmd) { |
| zx_info_handle_basic_t info = {}; |
| proc.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr); |
| proc.reset(); |
| vcmd = fbl::StringPrintf("dlog -f -t -p %zu", info.koid); |
| } |
| } else { |
| vruncmd = false; |
| } |
| __UNUSED auto leaked_handle = proc.release(); |
| } |
| |
| if (!coordinator->boot_args().GetBool("virtcon.disable", false)) { |
| // pass virtcon.* options along |
| fbl::Vector<const char*> env; |
| coordinator->boot_args().Collect("virtcon.", &env); |
| env.push_back(nullptr); |
| |
| const char* num_shells = |
| coordinator->require_system() && !netboot ? "0" : "3"; |
| size_t handle_count = 0; |
| zx_handle_t handles[2]; |
| uint32_t types[2]; |
| |
| handles[handle_count] = g_handles.virtcon_fidl.release(); |
| types[handle_count] = PA_HND(PA_USER0, 0); |
| ++handle_count; |
| |
| zx::debuglog debuglog; |
| zx_status_t status = zx::debuglog::create(coordinator->root_resource(), |
| ZX_LOG_FLAG_READABLE, &debuglog); |
| if (status == ZX_OK) { |
| handles[handle_count] = debuglog.release(); |
| types[handle_count] = PA_HND(PA_USER0, 1); |
| ++handle_count; |
| } |
| |
| const char* args[] = {"/boot/bin/virtual-console", "--shells", num_shells, nullptr, nullptr, |
| nullptr}; |
| if (vruncmd) { |
| args[3] = "--run"; |
| args[4] = vcmd.data(); |
| } |
| devmgr::devmgr_launch(g_handles.svc_job, "virtual-console", args, env.get(), -1, handles, |
| types, handle_count, nullptr, FS_ALL); |
| } |
| |
| const char* epoch = coordinator->boot_args().Get("devmgr.epoch"); |
| if (epoch) { |
| zx_time_t offset = ZX_SEC(atoi(epoch)); |
| zx_clock_adjust(coordinator->root_resource().get(), ZX_CLOCK_UTC, offset); |
| } |
| |
| do_autorun("autorun:boot", coordinator->boot_args().Get("zircon.autorun.boot")); |
| |
| thrd_t t; |
| int ret = thrd_create_with_name(&t, fuchsia_starter, coordinator, "fuchsia-starter"); |
| if (ret == thrd_success) { |
| thrd_detach(t); |
| } |
| |
| return 0; |
| } |
| |
| void ParseArgs(int argc, char** argv, devmgr::DevmgrArgs* out) { |
| enum { |
| kDriverSearchPath, |
| kLoadDriver, |
| kSysDeviceDriver, |
| kUseSystemSvchost, |
| kDisableBlockWatcher, |
| kDisableNetsvc, |
| }; |
| option options[] = { |
| {"driver-search-path", required_argument, nullptr, kDriverSearchPath}, |
| {"load-driver", required_argument, nullptr, kLoadDriver}, |
| {"sys-device-driver", required_argument, nullptr, kSysDeviceDriver}, |
| {"use-system-svchost", no_argument, nullptr, kUseSystemSvchost}, |
| {"disable-block-watcher", no_argument, nullptr, kDisableBlockWatcher}, |
| {"disable-netsvc", no_argument, nullptr, kDisableNetsvc}, |
| }; |
| |
| auto print_usage_and_exit = [options]() { |
| printf("devcoordinator: supported arguments:\n"); |
| for (const auto& option : options) { |
| printf(" --%s\n", option.name); |
| } |
| abort(); |
| }; |
| |
| auto check_not_duplicated = [print_usage_and_exit](const char* arg) { |
| if (arg != nullptr) { |
| printf("devcoordinator: duplicated argument\n"); |
| print_usage_and_exit(); |
| } |
| }; |
| |
| // Reset the args state |
| *out = devmgr::DevmgrArgs(); |
| |
| int opt; |
| while ((opt = getopt_long(argc, argv, "", options, nullptr)) != -1) { |
| switch (opt) { |
| case kDriverSearchPath: |
| out->driver_search_paths.push_back(optarg); |
| break; |
| case kLoadDriver: |
| out->load_drivers.push_back(optarg); |
| break; |
| case kSysDeviceDriver: |
| check_not_duplicated(out->sys_device_driver); |
| out->sys_device_driver = optarg; |
| break; |
| case kUseSystemSvchost: |
| out->use_system_svchost = true; |
| break; |
| case kDisableBlockWatcher: |
| out->disable_block_watcher = true; |
| break; |
| case kDisableNetsvc: |
| out->disable_netsvc = true; |
| break; |
| default: |
| print_usage_and_exit(); |
| } |
| } |
| } |
| |
| zx_status_t CreateDevhostJob(const zx::job& root_job, zx::job* devhost_job_out) { |
| zx::job devhost_job; |
| zx_status_t status = zx::job::create(root_job, 0u, &devhost_job); |
| if (status != ZX_OK) { |
| log(ERROR, "devcoordinator: unable to create devhost job\n"); |
| return status; |
| } |
| static const zx_policy_basic_t policy[] = { |
| {ZX_POL_BAD_HANDLE, ZX_POL_ACTION_EXCEPTION}, |
| }; |
| status = devhost_job.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_BASIC, &policy, |
| fbl::count_of(policy)); |
| if (status != ZX_OK) { |
| log(ERROR, "devcoordinator: zx_job_set_policy() failed\n"); |
| return status; |
| } |
| status = devhost_job.set_property(ZX_PROP_NAME, "zircon-drivers", 15); |
| if (status != ZX_OK) { |
| log(ERROR, "devcoordinator: zx_job_set_property() failed\n"); |
| return status; |
| } |
| |
| *devhost_job_out = std::move(devhost_job); |
| return ZX_OK; |
| } |
| |
| } // namespace |
| |
| namespace devmgr { |
| |
| zx::channel fs_clone(const char* path) { |
| if (!strcmp(path, "dev")) { |
| return devfs_root_clone(); |
| } |
| zx::channel h0, h1; |
| if (zx::channel::create(0, &h0, &h1) != ZX_OK) { |
| return zx::channel(); |
| } |
| if (!strcmp(path, "boot")) { |
| fdio_open("/boot", ZX_FS_RIGHT_READABLE, h1.release()); |
| return h0; |
| } |
| zx::unowned_channel fs(g_handles.fs_root); |
| int flags = FS_DIR_FLAGS; |
| if (!strcmp(path, "hub")) { |
| fs = zx::unowned_channel(g_handles.appmgr_client); |
| } else if (!strcmp(path, "svc")) { |
| flags = ZX_FS_RIGHT_READABLE | ZX_FS_RIGHT_WRITABLE; |
| fs = zx::unowned_channel(g_handles.svchost_outgoing); |
| path = "."; |
| } else if (!strncmp(path, "dev/", 4)) { |
| fs = devfs_root_borrow(); |
| path += 4; |
| } |
| fdio_open_at(fs->get(), path, flags, h1.release()); |
| return h0; |
| } |
| |
| } // namespace devmgr |
| |
| int main(int argc, char** argv) { |
| devmgr::BootArgs boot_args; |
| zx::vmo args_vmo; |
| size_t args_size; |
| zx_status_t status = get_arguments(&args_vmo, &args_size); |
| if (status == ZX_OK) { |
| status = devmgr::BootArgs::Create(std::move(args_vmo), args_size, &boot_args); |
| if (status != ZX_OK) { |
| fprintf(stderr, "devcoordinator: failed to create boot arguments (size %lu): %d\n", |
| args_size, status); |
| return 1; |
| } |
| } else { |
| fprintf(stderr, "devcoordinator: failed to get boot arguments (status: %d), assuming test " |
| "environment and continuing\n", status); |
| } |
| |
| if (boot_args.GetBool("devmgr.verbose", false)) { |
| devmgr::log_flags |= LOG_ALL; |
| } |
| |
| devmgr::DevmgrArgs devmgr_args; |
| ParseArgs(argc, argv, &devmgr_args); |
| // Set up the default values for our arguments if they weren't given. |
| if (devmgr_args.driver_search_paths.size() == 0) { |
| devmgr_args.driver_search_paths.push_back("/boot/driver"); |
| } |
| if (devmgr_args.sys_device_driver == nullptr) { |
| devmgr_args.sys_device_driver = "/boot/driver/platform-bus.so"; |
| } |
| |
| g_handles.root_job = zx::job::default_job(); |
| g_handles.root_job->set_property(ZX_PROP_NAME, "root", 4); |
| bool require_system = boot_args.GetBool("devmgr.require-system", false); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToThread); |
| devmgr::CoordinatorConfig config{}; |
| config.dispatcher = loop.dispatcher(); |
| config.boot_args = &boot_args; |
| config.require_system = require_system; |
| config.asan_drivers = boot_args.GetBool("devmgr.devhost.asan", false); |
| config.suspend_fallback = boot_args.GetBool("devmgr.suspend-timeout-fallback", false); |
| config.suspend_debug = boot_args.GetBool("devmgr.suspend-timeout-debug", false); |
| config.disable_netsvc = devmgr_args.disable_netsvc; |
| |
| status = get_root_resource(&config.root_resource); |
| if (status != ZX_OK) { |
| fprintf(stderr, "devcoordinator: failed to get root resource, assuming test " |
| "environment and continuing\n"); |
| } |
| // TODO: limit to enumerate rights |
| status = g_handles.root_job->duplicate(ZX_RIGHT_SAME_RIGHTS, &config.sysinfo_job); |
| if (status != ZX_OK) { |
| fprintf(stderr, "devcoordinator: failed to duplicate root job for sysinfo: %d\n", status); |
| } |
| status = CreateDevhostJob(*g_handles.root_job, &config.devhost_job); |
| if (status != ZX_OK) { |
| fprintf(stderr, "devcoordinator: failed to create devhost job: %d\n", status); |
| return 1; |
| } |
| status = zx::event::create(0, &config.fshost_event); |
| if (status != ZX_OK) { |
| fprintf(stderr, "devcoordinator: failed to create fshost event: %d\n", status); |
| return 1; |
| } |
| |
| devmgr::Coordinator coordinator(std::move(config)); |
| status = coordinator.InitializeCoreDevices(devmgr_args.sys_device_driver); |
| if (status != ZX_OK) { |
| log(ERROR, "devcoordinator: failed to initialize core devices\n"); |
| return 1; |
| } |
| |
| devmgr::devfs_init(coordinator.root_device(), loop.dispatcher()); |
| devfs_publish(coordinator.root_device(), coordinator.misc_device()); |
| devfs_publish(coordinator.root_device(), coordinator.sys_device()); |
| devfs_publish(coordinator.root_device(), coordinator.test_device()); |
| |
| // Check if whatever launched devmgr gave a channel to be connected to /dev. |
| // This is for use in tests to let the test environment see devfs. |
| zx::channel devfs_client(zx_take_startup_handle(DEVMGR_LAUNCHER_DEVFS_ROOT_HND)); |
| if (devfs_client.is_valid()) { |
| fdio_service_clone_to(devmgr::devfs_root_borrow()->get(), devfs_client.release()); |
| } |
| |
| status = zx::job::create(*g_handles.root_job, 0u, &g_handles.svc_job); |
| if (status != ZX_OK) { |
| fprintf(stderr, "devcoordinator: failed to create service job: %d\n", status); |
| return 1; |
| } |
| g_handles.svc_job.set_property(ZX_PROP_NAME, "zircon-services", 16); |
| |
| status = fuchsia_create_job(); |
| if (status != ZX_OK) { |
| return 1; |
| } |
| |
| zx::channel fshost_client, fshost_server; |
| zx::channel::create(0, &fshost_client, &fshost_server); |
| zx::channel::create(0, &g_handles.appmgr_client, &g_handles.appmgr_server); |
| |
| if (devmgr_args.use_system_svchost) { |
| zx::channel dir_request; |
| zx_status_t status = zx::channel::create(0, &dir_request, &g_handles.svchost_outgoing); |
| if (status != ZX_OK) { |
| fprintf(stderr, "devcoordinator: failed to create svchost_outgoing channel\n"); |
| return 1; |
| } |
| status = fdio_service_connect("/svc", dir_request.release()); |
| if (status != ZX_OK) { |
| fprintf(stderr, "devcoordinator: failed to connect to /svc\n"); |
| return 1; |
| } |
| } else { |
| status = svchost_start(require_system, &coordinator, std::move(fshost_client)); |
| if (status != ZX_OK) { |
| fprintf(stderr, "devcoordinator: failed to start svchost: %d", status); |
| return 1; |
| } |
| } |
| |
| const bool needs_svc_mount = !devmgr_args.use_system_svchost; |
| devmgr_vfs_init(&coordinator, devmgr_args, needs_svc_mount, std::move(fshost_server)); |
| |
| // If this is not a full Fuchsia build, do not setup appmgr services, as |
| // this will delay startup. |
| if (!require_system) { |
| devmgr::devmgr_disable_appmgr_services(); |
| } |
| |
| thrd_t t; |
| int ret = thrd_create_with_name(&t, pwrbtn_monitor_starter, nullptr, "pwrbtn-monitor-starter"); |
| if (ret != thrd_success) { |
| log(ERROR, "devcoordinator: failed to create pwrbtn monitor starter thread\n"); |
| return 1; |
| } |
| thrd_detach(t); |
| |
| start_console_shell(boot_args); |
| |
| ret = thrd_create_with_name(&t, service_starter, &coordinator, "service-starter"); |
| if (ret != thrd_success) { |
| log(ERROR, "devcoordinator: failed to create service starter thread\n"); |
| return 1; |
| } |
| thrd_detach(t); |
| |
| fbl::unique_ptr<devmgr::DevhostLoaderService> loader_service; |
| if (boot_args.GetBool("devmgr.devhost.strict-linking", false)) { |
| status = devmgr::DevhostLoaderService::Create(loop.dispatcher(), &loader_service); |
| if (status != ZX_OK) { |
| return 1; |
| } |
| coordinator.set_loader_service(loader_service.get()); |
| } |
| |
| for (const char* path : devmgr_args.driver_search_paths) { |
| devmgr::find_loadable_drivers( |
| path, fit::bind_member(&coordinator, &devmgr::Coordinator::DriverAddedInit)); |
| } |
| for (const char* driver : devmgr_args.load_drivers) { |
| devmgr::load_driver(driver, |
| fit::bind_member(&coordinator, &devmgr::Coordinator::DriverAddedInit)); |
| } |
| |
| // Special case early handling for the ramdisk boot |
| // path where /system is present before the coordinator |
| // starts. This avoids breaking the "priority hack" and |
| // can be removed once the real driver priority system |
| // exists. |
| if (coordinator.system_available()) { |
| status = coordinator.ScanSystemDrivers(); |
| if (status != ZX_OK) { |
| return 1; |
| } |
| } |
| |
| if (coordinator.require_system() && !coordinator.system_loaded()) { |
| printf( |
| "devcoordinator: full system required, ignoring fallback drivers until /system is loaded\n"); |
| } else { |
| coordinator.UseFallbackDrivers(); |
| } |
| |
| coordinator.PrepareProxy(coordinator.sys_device(), nullptr); |
| coordinator.PrepareProxy(coordinator.test_device(), nullptr); |
| // Initial bind attempt for drivers enumerated at startup. |
| coordinator.BindDrivers(); |
| |
| coordinator.set_running(true); |
| status = loop.Run(); |
| fprintf(stderr, "devcoordinator: coordinator exited unexpectedly: %d\n", status); |
| return status == ZX_OK ? 0 : 1; |
| } |