| // 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 <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <threads.h> |
| #include <unistd.h> |
| |
| #include <launchpad/launchpad.h> |
| #include <launchpad/loader-service.h> |
| #include <zircon/dlfcn.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/syscalls/object.h> |
| #include <zircon/status.h> |
| |
| #include <fdio/util.h> |
| |
| #include "devmgr.h" |
| #include "memfs-private.h" |
| |
| // When adding VMOs to the boot filesystem, add them under the directory |
| // /boot/VMO_SUBDIR. This constant must end, but not start, with a slash. |
| #define VMO_SUBDIR "kernel/" |
| #define VMO_SUBDIR_LEN (sizeof(VMO_SUBDIR) - 1) |
| |
| // The handle used to transmit messages to appmgr. |
| static zx_handle_t svc_root_handle; |
| // The handle used by appmgr to serve incoming requests. |
| // If appmgr cannot be launched within a timeout, this handle is closed. |
| static zx_handle_t svc_request_handle; |
| |
| zx_handle_t get_service_root(void) { |
| return fdio_service_clone(svc_root_handle); |
| } |
| |
| bool getenv_bool(const char* key, bool _default) { |
| const char* value = getenv(key); |
| if (value == NULL) { |
| return _default; |
| } |
| if ((strcmp(value, "0") == 0) || |
| (strcmp(value, "false") == 0) || |
| (strcmp(value, "off") == 0)) { |
| return false; |
| } |
| return true; |
| } |
| |
| static zx_handle_t root_resource_handle; |
| static zx_handle_t root_job_handle; |
| static zx_handle_t svcs_job_handle; |
| static zx_handle_t fuchsia_job_handle; |
| |
| zx_handle_t virtcon_open; |
| |
| zx_handle_t get_root_resource(void) { |
| return root_resource_handle; |
| } |
| |
| zx_handle_t get_sysinfo_job_root(void) { |
| zx_handle_t h; |
| //TODO: limit to enumerate rights |
| if (zx_handle_duplicate(root_job_handle, ZX_RIGHT_SAME_RIGHTS, &h) < 0) { |
| return ZX_HANDLE_INVALID; |
| } else { |
| return h; |
| } |
| } |
| |
| static const char* argv_sh[] = { "/boot/bin/sh" }; |
| static const char* argv_autorun0[] = { "/boot/bin/sh", "/boot/autorun" }; |
| static const char* argv_appmgr[] = { "/system/bin/appmgr" }; |
| |
| void do_autorun(const char* name, const char* env) { |
| char* bin = getenv(env); |
| if (bin) { |
| printf("devmgr: %s: starting %s...\n", env, bin); |
| devmgr_launch(svcs_job_handle, name, |
| 1, (const char* const*) &bin, |
| NULL, -1, NULL, NULL, 0, NULL); |
| } |
| } |
| |
| static mtx_t appmgr_lock = MTX_INIT; |
| |
| int devmgr_start_appmgr(void* arg) { |
| static bool appmgr_started = false; |
| static bool autorun_started = false; |
| |
| // we're starting the appmgr because /system is present |
| // so we also signal the device coordinator that those |
| // drivers are now loadable |
| load_system_drivers(); |
| |
| mtx_lock(&appmgr_lock); |
| 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 (svc_request_handle) { |
| assert(appmgr_hnd_count < countof(appmgr_hnds)); |
| appmgr_hnds[appmgr_hnd_count] = svc_request_handle; |
| appmgr_ids[appmgr_hnd_count] = PA_SERVICE_REQUEST; |
| appmgr_hnd_count++; |
| svc_request_handle = ZX_HANDLE_INVALID; |
| } |
| devmgr_launch(fuchsia_job_handle, "appmgr", countof(argv_appmgr), |
| argv_appmgr, NULL, -1, appmgr_hnds, appmgr_ids, |
| appmgr_hnd_count, NULL); |
| appmgr_started = true; |
| } |
| if (!autorun_started) { |
| do_autorun("autorun:system", "zircon.autorun.system"); |
| autorun_started = true; |
| } |
| mtx_unlock(&appmgr_lock); |
| return 0; |
| } |
| |
| int service_timeout(void* arg) { |
| zx_nanosleep(zx_deadline_after(ZX_SEC(10))); |
| mtx_lock(&appmgr_lock); |
| if (svc_request_handle != ZX_HANDLE_INVALID) { |
| printf("devmgr: appmgr not found, closing service handle\n"); |
| zx_handle_close(svc_request_handle); |
| } |
| mtx_unlock(&appmgr_lock); |
| return 0; |
| } |
| |
| int service_starter(void* arg) { |
| // create a directory for sevice rendezvous |
| mkdir("/svc", 0755); |
| |
| char vcmd[64]; |
| bool netboot = false; |
| bool vruncmd = false; |
| |
| if (!getenv_bool("netsvc.disable", false)) { |
| const char* args[] = { "/boot/bin/netsvc", NULL, NULL }; |
| int argc = 1; |
| |
| if (getenv_bool("netsvc.netboot", false)) { |
| args[argc++] = "--netboot"; |
| netboot = true; |
| vruncmd = true; |
| } |
| |
| const char* nodename = getenv("zircon.nodename"); |
| if (nodename) { |
| args[argc++] = nodename; |
| } |
| |
| zx_handle_t proc; |
| if (devmgr_launch(svcs_job_handle, "netsvc", argc, args, |
| NULL, -1, NULL, NULL, 0, &proc) == ZX_OK) { |
| if (vruncmd) { |
| zx_info_handle_basic_t info = { |
| .koid = 0, |
| }; |
| zx_object_get_info(proc, ZX_INFO_HANDLE_BASIC, |
| &info, sizeof(info), NULL, NULL); |
| zx_handle_close(proc); |
| snprintf(vcmd, sizeof(vcmd), "dlog -f -t -p %zu", info.koid); |
| } |
| } else { |
| vruncmd = false; |
| } |
| } |
| |
| if (!getenv_bool("virtcon.disable", false)) { |
| // pass virtcon.* options along |
| const char* envp[16]; |
| unsigned envc = 0; |
| char** e = environ; |
| while (*e && (envc < countof(envp))) { |
| if (!strncmp(*e, "virtcon.", 8)) { |
| envp[envc++] = *e; |
| } |
| e++; |
| } |
| envp[envc] = NULL; |
| |
| uint32_t type = PA_HND(PA_USER0, 0); |
| zx_handle_t h = ZX_HANDLE_INVALID; |
| zx_channel_create(0, &h, &virtcon_open); |
| const char* args[] = { "/boot/bin/virtual-console", "--run", vcmd }; |
| devmgr_launch(svcs_job_handle, "virtual-console", |
| vruncmd ? 3 : 1, args, envp, -1, |
| &h, &type, (h == ZX_HANDLE_INVALID) ? 0 : 1, NULL); |
| } |
| |
| |
| do_autorun("autorun:boot", "zircon.autorun.boot"); |
| struct stat s; |
| if (stat(argv_autorun0[1], &s) == 0) { |
| printf("devmgr: starting /boot/autorun ...\n"); |
| devmgr_launch(svcs_job_handle, "sh:autorun0", |
| countof(argv_autorun0), argv_autorun0, |
| NULL, -1, NULL, NULL, 0, NULL); |
| } |
| |
| if (!netboot) { |
| block_device_watcher(svcs_job_handle); |
| } |
| return 0; |
| } |
| |
| #if !_ZX_KERNEL_HAS_SHELL |
| static int console_starter(void* arg) { |
| // if no kernel shell on serial uart, start a sh there |
| printf("devmgr: shell startup\n"); |
| |
| // If we got a TERM environment variable (aka a TERM=... argument on |
| // the kernel command line), pass this down. |
| const char* term = getenv("TERM"); |
| if (term != NULL) |
| term -= sizeof("TERM=") - 1; |
| |
| const char* envp[] = { term ? term : NULL, NULL, }; |
| for (unsigned n = 0; n < 30; n++) { |
| int fd; |
| if ((fd = open("/dev/misc/console", O_RDWR)) >= 0) { |
| devmgr_launch(svcs_job_handle, "sh:console", |
| countof(argv_sh), argv_sh, envp, fd, NULL, NULL, 0, NULL); |
| break; |
| } |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(100))); |
| } |
| return 0; |
| } |
| |
| static void start_console_shell(void) { |
| thrd_t t; |
| if ((thrd_create_with_name(&t, console_starter, NULL, "console-starter")) == thrd_success) { |
| thrd_detach(t); |
| } |
| } |
| #else |
| static void start_console_shell(void) {} |
| #endif |
| |
| // Look for VMOs passed as startup handles of PA_HND_TYPE type, and add them to |
| // the filesystem under the path /boot/VMO_SUBDIR_LEN/<vmo-name>. |
| static void fetch_vmos(uint_fast8_t type, const char* debug_type_name) { |
| for (uint_fast16_t i = 0; true; ++i) { |
| zx_handle_t vmo = zx_get_startup_handle(PA_HND(type, i)); |
| if (vmo == ZX_HANDLE_INVALID) |
| break; |
| |
| if (type == PA_VMO_VDSO && i == 0) { |
| // The first vDSO is the default vDSO. Since we've stolen |
| // the startup handle, launchpad won't find it on its own. |
| // So point launchpad at it. |
| launchpad_set_vdso_vmo(vmo); |
| } |
| |
| // The vDSO VMOs have names like "vdso/default", so those |
| // become VMO files at "/boot/kernel/vdso/default". |
| char name[VMO_SUBDIR_LEN + ZX_MAX_NAME_LEN] = VMO_SUBDIR; |
| size_t size; |
| zx_status_t status = zx_object_get_property(vmo, ZX_PROP_NAME, |
| name + VMO_SUBDIR_LEN, sizeof(name) - VMO_SUBDIR_LEN); |
| if (status != ZX_OK) { |
| printf("devmgr: zx_object_get_property on %s %u: %s\n", |
| debug_type_name, i, zx_status_get_string(status)); |
| continue; |
| } |
| status = zx_vmo_get_size(vmo, &size); |
| if (status != ZX_OK) { |
| printf("devmgr: zx_vmo_get_size on %s %u: %s\n", |
| debug_type_name, i, zx_status_get_string(status)); |
| continue; |
| } |
| if (size == 0) { |
| // empty vmos do not get installed |
| zx_handle_close(vmo); |
| continue; |
| } |
| if (!strcmp(name + VMO_SUBDIR_LEN, "crashlog")) { |
| // the crashlog has a special home |
| strcpy(name, "log/last-panic.txt"); |
| } |
| status = bootfs_add_file(name, vmo, 0, size); |
| if (status != ZX_OK) { |
| printf("devmgr: failed to add %s %u to filesystem: %s\n", |
| debug_type_name, i, zx_status_get_string(status)); |
| } |
| } |
| } |
| |
| static void load_cmdline_from_bootfs(void) { |
| int fd = open("/boot/config/devmgr", O_RDONLY); |
| if (fd < 0) { |
| return; |
| } |
| off_t sz = lseek(fd, 0, SEEK_END); |
| lseek(fd, 0, SEEK_SET); |
| char* cfg; |
| if ((sz < 0) || ((cfg = malloc(sz + 1)) == NULL)) { |
| close(fd); |
| return; |
| } |
| char* x = cfg; |
| while (sz > 0) { |
| int r = read(fd, x, sz); |
| if (r <= 0) { |
| close(fd); |
| free(cfg); |
| return; |
| } |
| x += r; |
| sz -= r; |
| } |
| *x = 0; |
| close(fd); |
| |
| x = cfg; |
| while (*x) { |
| // skip any leading whitespace |
| while (isspace(*x)) { |
| x++; |
| } |
| |
| // find the next line (seek for CR or NL) |
| char* next = x; |
| for (;;) { |
| // eof? we're all done then |
| if (*next == 0) { |
| return; |
| } |
| if ((*next == '\r') || (*next == '\n')) { |
| *next++ = 0; |
| break; |
| } |
| next++; |
| } |
| |
| // process line if not a comment and not a zero-length name |
| if ((*x != '#') && (*x != '=')) { |
| for (char *y = x; *y != 0; y++) { |
| // space in name is invalid, give up |
| if (isspace(*y)) { |
| break; |
| } |
| // valid looking env entry? store it |
| if (*y == '=') { |
| putenv(x); |
| break; |
| } |
| } |
| } |
| |
| x = next; |
| } |
| } |
| |
| int main(int argc, char** argv) { |
| // Close the loader-service channel so the service can go away. |
| // We won't use it any more (no dlopen calls in this process). |
| zx_handle_t loader_svc = dl_set_loader_service(ZX_HANDLE_INVALID); |
| zx_handle_close(loader_svc); |
| |
| // Ensure that devmgr doesn't try to connect to the global |
| // loader sevice (as this leads to deadlocks in devhost v2) |
| loader_service_force_local(); |
| |
| devmgr_io_init(); |
| |
| root_resource_handle = zx_get_startup_handle(PA_HND(PA_RESOURCE, 0)); |
| root_job_handle = zx_job_default(); |
| |
| printf("devmgr: main()\n"); |
| |
| devmgr_init(root_job_handle); |
| devmgr_vfs_init(); |
| |
| load_cmdline_from_bootfs(); |
| |
| char** e = environ; |
| while (*e) { |
| printf("cmdline: %s\n", *e++); |
| } |
| |
| zx_object_set_property(root_job_handle, ZX_PROP_NAME, "root", 4); |
| |
| fetch_vmos(PA_VMO_VDSO, "PA_VMO_VDSO"); |
| fetch_vmos(PA_VMO_KERNEL_FILE, "PA_VMO_KERNEL_FILE"); |
| |
| |
| zx_status_t status = zx_job_create(root_job_handle, 0u, &svcs_job_handle); |
| if (status < 0) { |
| printf("unable to create service job\n"); |
| } |
| zx_object_set_property(svcs_job_handle, ZX_PROP_NAME, "zircon-services", 16); |
| |
| status = zx_job_create(root_job_handle, 0u, &fuchsia_job_handle); |
| if (status < 0) { |
| printf("unable to create service job\n"); |
| } |
| zx_object_set_property(fuchsia_job_handle, ZX_PROP_NAME, "fuchsia", 7); |
| |
| // Features like Intel Processor Trace need a dump of ld.so activity. |
| // The output has a specific format, and will eventually be recorded |
| // via a specific mechanism (zircon tracing support), so we use a specific |
| // env var (and don't, for example, piggyback on LD_DEBUG). |
| // We enable this pretty early so that we get a trace of as many processes |
| // as possible. |
| if (getenv(LDSO_TRACE_CMDLINE)) { |
| // This takes care of places that clone our environment. |
| putenv(strdup(LDSO_TRACE_ENV)); |
| // There is still devmgr_launch() which does not clone our enviroment. |
| // It has its own check. |
| } |
| |
| // Start crashlogger. |
| if (!getenv_bool("crashlogger.disable", false)) { |
| static const char* argv_crashlogger[] = { |
| "/boot/bin/crashlogger", |
| NULL, // room for -pton |
| }; |
| const char* crashlogger_pt = getenv("crashlogger.pt"); |
| int argc_crashlogger = 1; |
| if (crashlogger_pt && strcmp(crashlogger_pt, "true") == 0) { |
| // /dev/misc/intel-pt may not be available yet, so we can't |
| // actually turn on PT here. Just tell crashlogger to dump the |
| // trace buffers if they're available. |
| argv_crashlogger[argc_crashlogger++] = "-pton"; |
| } |
| |
| // Bind the exception port now, to avoid missing any crashes that |
| // might occur early on before the crashlogger process has finished |
| // initializing. |
| zx_handle_t exception_port; |
| // This should match the value used by crashlogger. |
| const uint64_t kSysExceptionKey = 1166444u; |
| if (zx_port_create(0, &exception_port) == ZX_OK && |
| zx_task_bind_exception_port(ZX_HANDLE_INVALID, exception_port, |
| kSysExceptionKey, 0) == ZX_OK) { |
| zx_handle_t handles[] = { exception_port }; |
| uint32_t handle_types[] = { PA_HND(PA_USER0, 0) }; |
| |
| devmgr_launch(svcs_job_handle, "crashlogger", |
| argc_crashlogger, argv_crashlogger, |
| NULL, -1, handles, handle_types, |
| countof(handles), NULL); |
| } |
| } |
| |
| zx_channel_create(0, &svc_root_handle, &svc_request_handle); |
| |
| start_console_shell(); |
| |
| if (secondary_bootfs_ready()) { |
| devmgr_start_appmgr(NULL); |
| } |
| |
| thrd_t t; |
| if ((thrd_create_with_name(&t, service_starter, NULL, "service-starter")) == thrd_success) { |
| thrd_detach(t); |
| } |
| |
| if ((thrd_create_with_name(&t, service_timeout, NULL, "service-timout")) == thrd_success) { |
| thrd_detach(t); |
| } |
| |
| devmgr_handle_messages(); |
| printf("devmgr: message handler returned?!\n"); |
| return 0; |
| } |