f667a3247a
Don't double mount /dev and its subdirectories anymore. Instead, the first stage init is solely responsible for mounting it. Don't have init prepare the property space. This is the responsibility of the second stage init. Don't have SELinux use the property space to determine how we should be running. Instead, create a new function and extract the data we need directly from /proc/cmdline. SELinux needs this information in the first stage init process where the property service isn't available. Change-Id: I5b4f3bec79463a7381a68f30bdda78b5cc122a96
1124 lines
32 KiB
C++
1124 lines
32 KiB
C++
/*
|
|
* 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;
|
|
}
|