diff options
Diffstat (limited to 'init/init.cpp')
-rw-r--r-- | init/init.cpp | 1124 |
1 files changed, 1124 insertions, 0 deletions
diff --git a/init/init.cpp b/init/init.cpp new file mode 100644 index 0000000..dd74538 --- /dev/null +++ b/init/init.cpp @@ -0,0 +1,1124 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <paths.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/epoll.h> +#include <sys/mount.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <termios.h> +#include <unistd.h> + +#include <mtd/mtd-user.h> + +#include <selinux/selinux.h> +#include <selinux/label.h> +#include <selinux/android.h> + +#include <base/file.h> +#include <base/stringprintf.h> +#include <cutils/android_reboot.h> +#include <cutils/fs.h> +#include <cutils/iosched_policy.h> +#include <cutils/list.h> +#include <cutils/sockets.h> +#include <private/android_filesystem_config.h> + +#include <memory> + +#include "devices.h" +#include "init.h" +#include "log.h" +#include "property_service.h" +#include "bootchart.h" +#include "signal_handler.h" +#include "keychords.h" +#include "init_parser.h" +#include "util.h" +#include "ueventd.h" +#include "watchdogd.h" + +struct selabel_handle *sehandle; +struct selabel_handle *sehandle_prop; + +static int property_triggers_enabled = 0; + +static char qemu[32]; + +static struct action *cur_action = NULL; +static struct command *cur_command = NULL; + +static int have_console; +static char console_name[PROP_VALUE_MAX] = "/dev/console"; +static time_t process_needs_restart; + +static const char *ENV[32]; + +bool waiting_for_exec = false; + +static int epoll_fd = -1; + +void register_epoll_handler(int fd, void (*fn)()) { + epoll_event ev; + ev.events = EPOLLIN; + ev.data.ptr = reinterpret_cast<void*>(fn); + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { + ERROR("epoll_ctl failed: %s\n", strerror(errno)); + } +} + +void service::NotifyStateChange(const char* new_state) { + if (!properties_initialized()) { + // If properties aren't available yet, we can't set them. + return; + } + + if ((flags & SVC_EXEC) != 0) { + // 'exec' commands don't have properties tracking their state. + return; + } + + char prop_name[PROP_NAME_MAX]; + if (snprintf(prop_name, sizeof(prop_name), "init.svc.%s", name) >= PROP_NAME_MAX) { + // If the property name would be too long, we can't set it. + ERROR("Property name \"init.svc.%s\" too long; not setting to %s\n", name, new_state); + return; + } + + property_set(prop_name, new_state); +} + +/* add_environment - add "key=value" to the current environment */ +int add_environment(const char *key, const char *val) +{ + size_t n; + size_t key_len = strlen(key); + + /* The last environment entry is reserved to terminate the list */ + for (n = 0; n < (ARRAY_SIZE(ENV) - 1); n++) { + + /* Delete any existing entry for this key */ + if (ENV[n] != NULL) { + size_t entry_key_len = strcspn(ENV[n], "="); + if ((entry_key_len == key_len) && (strncmp(ENV[n], key, entry_key_len) == 0)) { + free((char*)ENV[n]); + ENV[n] = NULL; + } + } + + /* Add entry if a free slot is available */ + if (ENV[n] == NULL) { + char* entry; + asprintf(&entry, "%s=%s", key, val); + ENV[n] = entry; + return 0; + } + } + + ERROR("No env. room to store: '%s':'%s'\n", key, val); + + return -1; +} + +void zap_stdio(void) +{ + int fd; + fd = open("/dev/null", O_RDWR); + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + close(fd); +} + +static void open_console() +{ + int fd; + if ((fd = open(console_name, O_RDWR)) < 0) { + fd = open("/dev/null", O_RDWR); + } + ioctl(fd, TIOCSCTTY, 0); + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + close(fd); +} + +static void publish_socket(const char *name, int fd) +{ + char key[64] = ANDROID_SOCKET_ENV_PREFIX; + char val[64]; + + strlcpy(key + sizeof(ANDROID_SOCKET_ENV_PREFIX) - 1, + name, + sizeof(key) - sizeof(ANDROID_SOCKET_ENV_PREFIX)); + snprintf(val, sizeof(val), "%d", fd); + add_environment(key, val); + + /* make sure we don't close-on-exec */ + fcntl(fd, F_SETFD, 0); +} + +void service_start(struct service *svc, const char *dynamic_args) +{ + // Starting a service removes it from the disabled or reset state and + // immediately takes it out of the restarting state if it was in there. + svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START)); + svc->time_started = 0; + + // Running processes require no additional work --- if they're in the + // process of exiting, we've ensured that they will immediately restart + // on exit, unless they are ONESHOT. + if (svc->flags & SVC_RUNNING) { + return; + } + + bool needs_console = (svc->flags & SVC_CONSOLE); + if (needs_console && !have_console) { + ERROR("service '%s' requires console\n", svc->name); + svc->flags |= SVC_DISABLED; + return; + } + + struct stat s; + if (stat(svc->args[0], &s) != 0) { + ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name); + svc->flags |= SVC_DISABLED; + return; + } + + if ((!(svc->flags & SVC_ONESHOT)) && dynamic_args) { + ERROR("service '%s' must be one-shot to use dynamic args, disabling\n", + svc->args[0]); + svc->flags |= SVC_DISABLED; + return; + } + + char* scon = NULL; + if (is_selinux_enabled() > 0) { + if (svc->seclabel) { + scon = strdup(svc->seclabel); + if (!scon) { + ERROR("Out of memory while starting '%s'\n", svc->name); + return; + } + } else { + char *mycon = NULL, *fcon = NULL; + + INFO("computing context for service '%s'\n", svc->args[0]); + int rc = getcon(&mycon); + if (rc < 0) { + ERROR("could not get context while starting '%s'\n", svc->name); + return; + } + + rc = getfilecon(svc->args[0], &fcon); + if (rc < 0) { + ERROR("could not get context while starting '%s'\n", svc->name); + freecon(mycon); + return; + } + + rc = security_compute_create(mycon, fcon, string_to_security_class("process"), &scon); + if (rc == 0 && !strcmp(scon, mycon)) { + ERROR("Warning! Service %s needs a SELinux domain defined; please fix!\n", svc->name); + } + freecon(mycon); + freecon(fcon); + if (rc < 0) { + ERROR("could not get context while starting '%s'\n", svc->name); + return; + } + } + } + + NOTICE("Starting service '%s'...\n", svc->name); + + pid_t pid = fork(); + if (pid == 0) { + struct socketinfo *si; + struct svcenvinfo *ei; + char tmp[32]; + int fd, sz; + + umask(077); + if (properties_initialized()) { + get_property_workspace(&fd, &sz); + snprintf(tmp, sizeof(tmp), "%d,%d", dup(fd), sz); + add_environment("ANDROID_PROPERTY_WORKSPACE", tmp); + } + + for (ei = svc->envvars; ei; ei = ei->next) + add_environment(ei->name, ei->value); + + for (si = svc->sockets; si; si = si->next) { + int socket_type = ( + !strcmp(si->type, "stream") ? SOCK_STREAM : + (!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET)); + int s = create_socket(si->name, socket_type, + si->perm, si->uid, si->gid, si->socketcon ?: scon); + if (s >= 0) { + publish_socket(si->name, s); + } + } + + freecon(scon); + scon = NULL; + + if (svc->ioprio_class != IoSchedClass_NONE) { + if (android_set_ioprio(getpid(), svc->ioprio_class, svc->ioprio_pri)) { + ERROR("Failed to set pid %d ioprio = %d,%d: %s\n", + getpid(), svc->ioprio_class, svc->ioprio_pri, strerror(errno)); + } + } + + if (needs_console) { + setsid(); + open_console(); + } else { + zap_stdio(); + } + + if (false) { + for (size_t n = 0; svc->args[n]; n++) { + INFO("args[%zu] = '%s'\n", n, svc->args[n]); + } + for (size_t n = 0; ENV[n]; n++) { + INFO("env[%zu] = '%s'\n", n, ENV[n]); + } + } + + setpgid(0, getpid()); + + // As requested, set our gid, supplemental gids, and uid. + if (svc->gid) { + if (setgid(svc->gid) != 0) { + ERROR("setgid failed: %s\n", strerror(errno)); + _exit(127); + } + } + if (svc->nr_supp_gids) { + if (setgroups(svc->nr_supp_gids, svc->supp_gids) != 0) { + ERROR("setgroups failed: %s\n", strerror(errno)); + _exit(127); + } + } + if (svc->uid) { + if (setuid(svc->uid) != 0) { + ERROR("setuid failed: %s\n", strerror(errno)); + _exit(127); + } + } + if (svc->seclabel) { + if (is_selinux_enabled() > 0 && setexeccon(svc->seclabel) < 0) { + ERROR("cannot setexeccon('%s'): %s\n", svc->seclabel, strerror(errno)); + _exit(127); + } + } + + if (!dynamic_args) { + if (execve(svc->args[0], (char**) svc->args, (char**) ENV) < 0) { + ERROR("cannot execve('%s'): %s\n", svc->args[0], strerror(errno)); + } + } else { + char *arg_ptrs[INIT_PARSER_MAXARGS+1]; + int arg_idx = svc->nargs; + char *tmp = strdup(dynamic_args); + char *next = tmp; + char *bword; + + /* Copy the static arguments */ + memcpy(arg_ptrs, svc->args, (svc->nargs * sizeof(char *))); + + while((bword = strsep(&next, " "))) { + arg_ptrs[arg_idx++] = bword; + if (arg_idx == INIT_PARSER_MAXARGS) + break; + } + arg_ptrs[arg_idx] = NULL; + execve(svc->args[0], (char**) arg_ptrs, (char**) ENV); + } + _exit(127); + } + + freecon(scon); + + if (pid < 0) { + ERROR("failed to start '%s'\n", svc->name); + svc->pid = 0; + return; + } + + svc->time_started = gettime(); + svc->pid = pid; + svc->flags |= SVC_RUNNING; + + if ((svc->flags & SVC_EXEC) != 0) { + INFO("SVC_EXEC pid %d (uid %d gid %d+%zu context %s) started; waiting...\n", + svc->pid, svc->uid, svc->gid, svc->nr_supp_gids, svc->seclabel); + waiting_for_exec = true; + } + + svc->NotifyStateChange("running"); +} + +/* The how field should be either SVC_DISABLED, SVC_RESET, or SVC_RESTART */ +static void service_stop_or_reset(struct service *svc, int how) +{ + /* The service is still SVC_RUNNING until its process exits, but if it has + * already exited it shoudn't attempt a restart yet. */ + svc->flags &= ~(SVC_RESTARTING | SVC_DISABLED_START); + + if ((how != SVC_DISABLED) && (how != SVC_RESET) && (how != SVC_RESTART)) { + /* Hrm, an illegal flag. Default to SVC_DISABLED */ + how = SVC_DISABLED; + } + /* if the service has not yet started, prevent + * it from auto-starting with its class + */ + if (how == SVC_RESET) { + svc->flags |= (svc->flags & SVC_RC_DISABLED) ? SVC_DISABLED : SVC_RESET; + } else { + svc->flags |= how; + } + + if (svc->pid) { + NOTICE("Service '%s' is being killed...\n", svc->name); + kill(-svc->pid, SIGKILL); + svc->NotifyStateChange("stopping"); + } else { + svc->NotifyStateChange("stopped"); + } +} + +void service_reset(struct service *svc) +{ + service_stop_or_reset(svc, SVC_RESET); +} + +void service_stop(struct service *svc) +{ + service_stop_or_reset(svc, SVC_DISABLED); +} + +void service_restart(struct service *svc) +{ + if (svc->flags & SVC_RUNNING) { + /* Stop, wait, then start the service. */ + service_stop_or_reset(svc, SVC_RESTART); + } else if (!(svc->flags & SVC_RESTARTING)) { + /* Just start the service since it's not running. */ + service_start(svc, NULL); + } /* else: Service is restarting anyways. */ +} + +void property_changed(const char *name, const char *value) +{ + if (property_triggers_enabled) + queue_property_triggers(name, value); +} + +static void restart_service_if_needed(struct service *svc) +{ + time_t next_start_time = svc->time_started + 5; + + if (next_start_time <= gettime()) { + svc->flags &= (~SVC_RESTARTING); + service_start(svc, NULL); + return; + } + + if ((next_start_time < process_needs_restart) || + (process_needs_restart == 0)) { + process_needs_restart = next_start_time; + } +} + +static void restart_processes() +{ + process_needs_restart = 0; + service_for_each_flags(SVC_RESTARTING, + restart_service_if_needed); +} + +static void msg_start(const char *name) +{ + struct service *svc = NULL; + char *tmp = NULL; + char *args = NULL; + + if (!strchr(name, ':')) + svc = service_find_by_name(name); + else { + tmp = strdup(name); + if (tmp) { + args = strchr(tmp, ':'); + *args = '\0'; + args++; + + svc = service_find_by_name(tmp); + } + } + + if (svc) { + service_start(svc, args); + } else { + ERROR("no such service '%s'\n", name); + } + if (tmp) + free(tmp); +} + +static void msg_stop(const char *name) +{ + struct service *svc = service_find_by_name(name); + + if (svc) { + service_stop(svc); + } else { + ERROR("no such service '%s'\n", name); + } +} + +static void msg_restart(const char *name) +{ + struct service *svc = service_find_by_name(name); + + if (svc) { + service_restart(svc); + } else { + ERROR("no such service '%s'\n", name); + } +} + +void handle_control_message(const char *msg, const char *arg) +{ + if (!strcmp(msg,"start")) { + msg_start(arg); + } else if (!strcmp(msg,"stop")) { + msg_stop(arg); + } else if (!strcmp(msg,"restart")) { + msg_restart(arg); + } else { + ERROR("unknown control msg '%s'\n", msg); + } +} + +static struct command *get_first_command(struct action *act) +{ + struct listnode *node; + node = list_head(&act->commands); + if (!node || list_empty(&act->commands)) + return NULL; + + return node_to_item(node, struct command, clist); +} + +static struct command *get_next_command(struct action *act, struct command *cmd) +{ + struct listnode *node; + node = cmd->clist.next; + if (!node) + return NULL; + if (node == &act->commands) + return NULL; + + return node_to_item(node, struct command, clist); +} + +static int is_last_command(struct action *act, struct command *cmd) +{ + return (list_tail(&act->commands) == &cmd->clist); +} + + +void build_triggers_string(char *name_str, int length, struct action *cur_action) { + struct listnode *node; + struct trigger *cur_trigger; + + list_for_each(node, &cur_action->triggers) { + cur_trigger = node_to_item(node, struct trigger, nlist); + if (node != cur_action->triggers.next) { + strlcat(name_str, " " , length); + } + strlcat(name_str, cur_trigger->name , length); + } +} + +void execute_one_command() { + Timer t; + + char cmd_str[256] = ""; + char name_str[256] = ""; + + if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) { + cur_action = action_remove_queue_head(); + cur_command = NULL; + if (!cur_action) { + return; + } + + build_triggers_string(name_str, sizeof(name_str), cur_action); + + INFO("processing action %p (%s)\n", cur_action, name_str); + cur_command = get_first_command(cur_action); + } else { + cur_command = get_next_command(cur_action, cur_command); + } + + if (!cur_command) { + return; + } + + int result = cur_command->func(cur_command->nargs, cur_command->args); + if (klog_get_level() >= KLOG_INFO_LEVEL) { + for (int i = 0; i < cur_command->nargs; i++) { + strlcat(cmd_str, cur_command->args[i], sizeof(cmd_str)); + if (i < cur_command->nargs - 1) { + strlcat(cmd_str, " ", sizeof(cmd_str)); + } + } + char source[256]; + if (cur_command->filename) { + snprintf(source, sizeof(source), " (%s:%d)", cur_command->filename, cur_command->line); + } else { + *source = '\0'; + } + INFO("Command '%s' action=%s%s returned %d took %.2fs\n", + cmd_str, cur_action ? name_str : "", source, result, t.duration()); + } +} + +static int wait_for_coldboot_done_action(int nargs, char **args) { + Timer t; + + NOTICE("Waiting for %s...\n", COLDBOOT_DONE); + if (wait_for_file(COLDBOOT_DONE, COMMAND_RETRY_TIMEOUT)) { + ERROR("Timed out waiting for %s\n", COLDBOOT_DONE); + } + + NOTICE("Waiting for %s took %.2fs.\n", COLDBOOT_DONE, t.duration()); + return 0; +} + +/* + * Writes 512 bytes of output from Hardware RNG (/dev/hw_random, backed + * by Linux kernel's hw_random framework) into Linux RNG's via /dev/urandom. + * Does nothing if Hardware RNG is not present. + * + * Since we don't yet trust the quality of Hardware RNG, these bytes are not + * mixed into the primary pool of Linux RNG and the entropy estimate is left + * unmodified. + * + * If the HW RNG device /dev/hw_random is present, we require that at least + * 512 bytes read from it are written into Linux RNG. QA is expected to catch + * devices/configurations where these I/O operations are blocking for a long + * time. We do not reboot or halt on failures, as this is a best-effort + * attempt. + */ +static int mix_hwrng_into_linux_rng_action(int nargs, char **args) +{ + int result = -1; + int hwrandom_fd = -1; + int urandom_fd = -1; + char buf[512]; + ssize_t chunk_size; + size_t total_bytes_written = 0; + + hwrandom_fd = TEMP_FAILURE_RETRY( + open("/dev/hw_random", O_RDONLY | O_NOFOLLOW | O_CLOEXEC)); + if (hwrandom_fd == -1) { + if (errno == ENOENT) { + ERROR("/dev/hw_random not found\n"); + /* It's not an error to not have a Hardware RNG. */ + result = 0; + } else { + ERROR("Failed to open /dev/hw_random: %s\n", strerror(errno)); + } + goto ret; + } + + urandom_fd = TEMP_FAILURE_RETRY( + open("/dev/urandom", O_WRONLY | O_NOFOLLOW | O_CLOEXEC)); + if (urandom_fd == -1) { + ERROR("Failed to open /dev/urandom: %s\n", strerror(errno)); + goto ret; + } + + while (total_bytes_written < sizeof(buf)) { + chunk_size = TEMP_FAILURE_RETRY( + read(hwrandom_fd, buf, sizeof(buf) - total_bytes_written)); + if (chunk_size == -1) { + ERROR("Failed to read from /dev/hw_random: %s\n", strerror(errno)); + goto ret; + } else if (chunk_size == 0) { + ERROR("Failed to read from /dev/hw_random: EOF\n"); + goto ret; + } + + chunk_size = TEMP_FAILURE_RETRY(write(urandom_fd, buf, chunk_size)); + if (chunk_size == -1) { + ERROR("Failed to write to /dev/urandom: %s\n", strerror(errno)); + goto ret; + } + total_bytes_written += chunk_size; + } + + INFO("Mixed %zu bytes from /dev/hw_random into /dev/urandom", + total_bytes_written); + result = 0; + +ret: + if (hwrandom_fd != -1) { + close(hwrandom_fd); + } + if (urandom_fd != -1) { + close(urandom_fd); + } + return result; +} + +static int keychord_init_action(int nargs, char **args) +{ + keychord_init(); + return 0; +} + +static int console_init_action(int nargs, char **args) +{ + char console[PROP_VALUE_MAX]; + if (property_get("ro.boot.console", console) > 0) { + snprintf(console_name, sizeof(console_name), "/dev/%s", console); + } + + int fd = open(console_name, O_RDWR | O_CLOEXEC); + if (fd >= 0) + have_console = 1; + close(fd); + + fd = open("/dev/tty0", O_WRONLY | O_CLOEXEC); + if (fd >= 0) { + const char *msg; + msg = "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" // console is 40 cols x 30 lines + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + " A N D R O I D "; + write(fd, msg, strlen(msg)); + close(fd); + } + + return 0; +} + +static void import_kernel_nv(char *name, bool for_emulator) +{ + char *value = strchr(name, '='); + int name_len = strlen(name); + + if (value == 0) return; + *value++ = 0; + if (name_len == 0) return; + + if (for_emulator) { + /* in the emulator, export any kernel option with the + * ro.kernel. prefix */ + char buff[PROP_NAME_MAX]; + int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name ); + + if (len < (int)sizeof(buff)) + property_set( buff, value ); + return; + } + + if (!strcmp(name,"qemu")) { + strlcpy(qemu, value, sizeof(qemu)); + } else if (!strncmp(name, "androidboot.", 12) && name_len > 12) { + const char *boot_prop_name = name + 12; + char prop[PROP_NAME_MAX]; + int cnt; + + cnt = snprintf(prop, sizeof(prop), "ro.boot.%s", boot_prop_name); + if (cnt < PROP_NAME_MAX) + property_set(prop, value); + } +} + +static void export_kernel_boot_props() { + struct { + const char *src_prop; + const char *dst_prop; + const char *default_value; + } prop_map[] = { + { "ro.boot.serialno", "ro.serialno", "", }, + { "ro.boot.mode", "ro.bootmode", "unknown", }, + { "ro.boot.baseband", "ro.baseband", "unknown", }, + { "ro.boot.bootloader", "ro.bootloader", "unknown", }, + { "ro.boot.hardware", "ro.hardware", "unknown", }, + { "ro.boot.revision", "ro.revision", "0", }, + }; + for (size_t i = 0; i < ARRAY_SIZE(prop_map); i++) { + char value[PROP_VALUE_MAX]; + int rc = property_get(prop_map[i].src_prop, value); + property_set(prop_map[i].dst_prop, (rc > 0) ? value : prop_map[i].default_value); + } +} + +static void process_kernel_dt(void) +{ + static const char android_dir[] = "/proc/device-tree/firmware/android"; + + std::string file_name = android::base::StringPrintf("%s/compatible", android_dir); + + std::string dt_file; + android::base::ReadFileToString(file_name, &dt_file); + if (!dt_file.compare("android,firmware")) { + ERROR("firmware/android is not compatible with 'android,firmware'\n"); + return; + } + + std::unique_ptr<DIR, int(*)(DIR*)>dir(opendir(android_dir), closedir); + if (!dir) + return; + + struct dirent *dp; + while ((dp = readdir(dir.get())) != NULL) { + if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible")) + continue; + + file_name = android::base::StringPrintf("%s/%s", android_dir, dp->d_name); + + android::base::ReadFileToString(file_name, &dt_file); + std::replace(dt_file.begin(), dt_file.end(), ',', '.'); + + std::string property_name = android::base::StringPrintf("ro.boot.%s", dp->d_name); + property_set(property_name.c_str(), dt_file.c_str()); + } +} + +static void process_kernel_cmdline(void) +{ + /* don't expose the raw commandline to nonpriv processes */ + chmod("/proc/cmdline", 0440); + + /* first pass does the common stuff, and finds if we are in qemu. + * second pass is only necessary for qemu to export all kernel params + * as props. + */ + import_kernel_cmdline(false, import_kernel_nv); + if (qemu[0]) + import_kernel_cmdline(true, import_kernel_nv); +} + +static int queue_property_triggers_action(int nargs, char **args) +{ + queue_all_property_triggers(); + /* enable property triggers */ + property_triggers_enabled = 1; + return 0; +} + +static void selinux_init_all_handles(void) +{ + sehandle = selinux_android_file_context_handle(); + selinux_android_set_sehandle(sehandle); + sehandle_prop = selinux_android_prop_context_handle(); +} + +enum selinux_enforcing_status { SELINUX_DISABLED, SELINUX_PERMISSIVE, SELINUX_ENFORCING }; + +static selinux_enforcing_status selinux_status_from_cmdline() { + selinux_enforcing_status status = SELINUX_ENFORCING; + + std::function<void(char*,bool)> fn = [&](char* name, bool in_qemu) { + char *value = strchr(name, '='); + if (value == nullptr) { return; } + *value++ = '\0'; + if (strcmp(name, "androidboot.selinux") == 0) { + if (strcmp(value, "disabled") == 0) { + status = SELINUX_DISABLED; + } else if (strcmp(value, "permissive") == 0) { + status = SELINUX_PERMISSIVE; + } + } + }; + import_kernel_cmdline(false, fn); + + return status; +} + + +static bool selinux_is_disabled(void) +{ + if (ALLOW_DISABLE_SELINUX) { + if (access("/sys/fs/selinux", F_OK) != 0) { + // SELinux is not compiled into the kernel, or has been disabled + // via the kernel command line "selinux=0". + return true; + } + return selinux_status_from_cmdline() == SELINUX_DISABLED; + } + + return false; +} + +static bool selinux_is_enforcing(void) +{ + if (ALLOW_DISABLE_SELINUX) { + return selinux_status_from_cmdline() == SELINUX_ENFORCING; + } + return true; +} + +int selinux_reload_policy(void) +{ + if (selinux_is_disabled()) { + return -1; + } + + INFO("SELinux: Attempting to reload policy files\n"); + + if (selinux_android_reload_policy() == -1) { + return -1; + } + + if (sehandle) + selabel_close(sehandle); + + if (sehandle_prop) + selabel_close(sehandle_prop); + + selinux_init_all_handles(); + return 0; +} + +static int audit_callback(void *data, security_class_t /*cls*/, char *buf, size_t len) { + snprintf(buf, len, "property=%s", !data ? "NULL" : (char *)data); + return 0; +} + +static void security_failure() { + ERROR("Security failure; rebooting into recovery mode...\n"); + android_reboot(ANDROID_RB_RESTART2, 0, "recovery"); + while (true) { pause(); } // never reached +} + +static void selinux_initialize(bool in_kernel_domain) { + Timer t; + + selinux_callback cb; + cb.func_log = selinux_klog_callback; + selinux_set_callback(SELINUX_CB_LOG, cb); + cb.func_audit = audit_callback; + selinux_set_callback(SELINUX_CB_AUDIT, cb); + + if (selinux_is_disabled()) { + return; + } + + if (in_kernel_domain) { + INFO("Loading SELinux policy...\n"); + if (selinux_android_load_policy() < 0) { + ERROR("failed to load policy: %s\n", strerror(errno)); + security_failure(); + } + + bool is_enforcing = selinux_is_enforcing(); + security_setenforce(is_enforcing); + + if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) { + security_failure(); + } + + NOTICE("(Initializing SELinux %s took %.2fs.)\n", + is_enforcing ? "enforcing" : "non-enforcing", t.duration()); + } else { + selinux_init_all_handles(); + } +} + +int main(int argc, char** argv) { + if (!strcmp(basename(argv[0]), "ueventd")) { + return ueventd_main(argc, argv); + } + + if (!strcmp(basename(argv[0]), "watchdogd")) { + return watchdogd_main(argc, argv); + } + + // Clear the umask. + umask(0); + + add_environment("PATH", _PATH_DEFPATH); + + bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0); + + // Get the basic filesystem setup we need put together in the initramdisk + // on / and then we'll let the rc file figure out the rest. + if (is_first_stage) { + mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); + mkdir("/dev/pts", 0755); + mkdir("/dev/socket", 0755); + mount("devpts", "/dev/pts", "devpts", 0, NULL); + mount("proc", "/proc", "proc", 0, NULL); + mount("sysfs", "/sys", "sysfs", 0, NULL); + } + + // We must have some place other than / to create the device nodes for + // kmsg and null, otherwise we won't be able to remount / read-only + // later on. Now that tmpfs is mounted on /dev, we can actually talk + // to the outside world. + open_devnull_stdio(); + klog_init(); + klog_set_level(KLOG_NOTICE_LEVEL); + + NOTICE("init%s started!\n", is_first_stage ? "" : " second stage"); + + if (!is_first_stage) { + // Indicate that booting is in progress to background fw loaders, etc. + close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); + + property_init(); + + // If arguments are passed both on the command line and in DT, + // properties set in DT always have priority over the command-line ones. + process_kernel_dt(); + process_kernel_cmdline(); + + // Propogate the kernel variables to internal variables + // used by init as well as the current required properties. + export_kernel_boot_props(); + } + + // Set up SELinux, including loading the SELinux policy if we're in the kernel domain. + selinux_initialize(is_first_stage); + + // If we're in the kernel domain, re-exec init to transition to the init domain now + // that the SELinux policy has been loaded. + if (is_first_stage) { + if (restorecon("/init") == -1) { + ERROR("restorecon failed: %s\n", strerror(errno)); + security_failure(); + } + char* path = argv[0]; + char* args[] = { path, const_cast<char*>("--second-stage"), nullptr }; + if (execv(path, args) == -1) { + ERROR("execv(\"%s\") failed: %s\n", path, strerror(errno)); + security_failure(); + } + } + + // These directories were necessarily created before initial policy load + // and therefore need their security context restored to the proper value. + // This must happen before /dev is populated by ueventd. + INFO("Running restorecon...\n"); + restorecon("/dev"); + restorecon("/dev/socket"); + restorecon("/dev/__properties__"); + restorecon_recursive("/sys"); + + epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (epoll_fd == -1) { + ERROR("epoll_create1 failed: %s\n", strerror(errno)); + exit(1); + } + + signal_handler_init(); + + property_load_boot_defaults(); + start_property_service(); + + init_parse_config_file("/init.rc"); + + action_for_each_trigger("early-init", action_add_queue_tail); + + // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev... + queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); + // ... so that we can start queuing up actions that require stuff from /dev. + queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); + queue_builtin_action(keychord_init_action, "keychord_init"); + queue_builtin_action(console_init_action, "console_init"); + + // Trigger all the boot actions to get us started. + action_for_each_trigger("init", action_add_queue_tail); + + // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random + // wasn't ready immediately after wait_for_coldboot_done + queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); + + // Don't mount filesystems or start core system services in charger mode. + char bootmode[PROP_VALUE_MAX]; + if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) { + action_for_each_trigger("charger", action_add_queue_tail); + } else { + action_for_each_trigger("late-init", action_add_queue_tail); + } + + // Run all property triggers based on current state of the properties. + queue_builtin_action(queue_property_triggers_action, "queue_property_triggers"); + + while (true) { + if (!waiting_for_exec) { + execute_one_command(); + restart_processes(); + } + + int timeout = -1; + if (process_needs_restart) { + timeout = (process_needs_restart - gettime()) * 1000; + if (timeout < 0) + timeout = 0; + } + + if (!action_queue_empty() || cur_action) { + timeout = 0; + } + + bootchart_sample(&timeout); + + epoll_event ev; + int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, timeout)); + if (nr == -1) { + ERROR("epoll_wait failed: %s\n", strerror(errno)); + } else if (nr == 1) { + ((void (*)()) ev.data.ptr)(); + } + } + + return 0; +} |