platform_system_core/init/init.cpp

1117 lines
32 KiB
C++
Raw Normal View History

2008-10-21 16:00:00 +02:00
/*
* 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>
2008-10-21 16:00:00 +02:00
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
2008-10-21 16:00:00 +02:00
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
2008-10-21 16:00:00 +02:00
#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>
2008-10-21 16:00:00 +02:00
#include <memory>
2008-10-21 16:00:00 +02:00
#include "devices.h"
#include "init.h"
#include "log.h"
2008-10-21 16:00:00 +02:00
#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"
2008-10-21 16:00:00 +02:00
struct selabel_handle *sehandle;
struct selabel_handle *sehandle_prop;
2008-10-21 16:00:00 +02:00
static int property_triggers_enabled = 0;
static char qemu[32];
static struct action *cur_action = NULL;
static struct command *cur_command = NULL;
2008-10-21 16:00:00 +02:00
static int have_console;
static char console_name[PROP_VALUE_MAX] = "/dev/console";
2008-10-21 16:00:00 +02:00
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);
}
2008-10-21 16:00:00 +02:00
/* 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);
2008-10-21 16:00:00 +02:00
ENV[n] = entry;
return 0;
}
}
ERROR("No env. room to store: '%s':'%s'\n", key, val);
return -1;
2008-10-21 16:00:00 +02:00
}
void zap_stdio(void)
2008-10-21 16:00:00 +02:00
{
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);
2008-10-21 16:00:00 +02:00
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)
2008-10-21 16:00:00 +02:00
{
// 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));
2008-10-21 16:00:00 +02:00
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.
2008-10-21 16:00:00 +02:00
if (svc->flags & SVC_RUNNING) {
return;
}
bool needs_console = (svc->flags & SVC_CONSOLE);
if (needs_console && !have_console) {
2008-10-21 16:00:00 +02:00
ERROR("service '%s' requires console\n", svc->name);
svc->flags |= SVC_DISABLED;
return;
}
struct stat s;
2008-10-21 16:00:00 +02:00
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);
2008-10-21 16:00:00 +02:00
pid_t pid = fork();
2008-10-21 16:00:00 +02:00
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);
}
2008-10-21 16:00:00 +02:00
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);
2008-10-21 16:00:00 +02:00
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));
}
}
2008-10-21 16:00:00 +02:00
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]);
}
2008-10-21 16:00:00 +02:00
}
setpgid(0, getpid());
// As requested, set our gid, supplemental gids, and uid.
2008-10-21 16:00:00 +02:00
if (svc->gid) {
if (setgid(svc->gid) != 0) {
ERROR("setgid failed: %s\n", strerror(errno));
_exit(127);
}
2008-10-21 16:00:00 +02:00
}
if (svc->nr_supp_gids) {
if (setgroups(svc->nr_supp_gids, svc->supp_gids) != 0) {
ERROR("setgroups failed: %s\n", strerror(errno));
_exit(127);
}
2008-10-21 16:00:00 +02:00
}
if (svc->uid) {
if (setuid(svc->uid) != 0) {
ERROR("setuid failed: %s\n", strerror(errno));
_exit(127);
}
2008-10-21 16:00:00 +02:00
}
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);
}
2008-10-21 16:00:00 +02:00
_exit(127);
}
freecon(scon);
2008-10-21 16:00:00 +02:00
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");
2008-10-21 16:00:00 +02:00
}
/* The how field should be either SVC_DISABLED, SVC_RESET, or SVC_RESTART */
static void service_stop_or_reset(struct service *svc, int how)
2008-10-21 16:00:00 +02:00
{
init: Safely restart services to avoid race conditions. Previously, service restarts (either an explicit "restart", or a "stop, start" pair) exhibited a race condition whereby the new (restarting) service process was often spawned before the old (stopping) process had terminated. This may have resulted in the new service process failing to acquire a limited resource (file lock, socket bind, etc.) that the old process had not yet released. Now, a stopping service remains in the SVC_RUNNING state until its exiting process has been reaped by waitpid. This prevents a "stop, start" sequence from spawning a second service process before resources held by the first are released. This enables safe service restarts by stopping the service, waiting for the old service process to terminate, and (only then) starting the new service process. In the event of "restarting" an already stopped service, the previous behavior is maintained whereby the service is simply started. This scenario could be special-cased by the restart command, however, we have observed instances where services are, unintentionally, stopped and started "too quickly," and so simultaneous processes for the same service should never be allowed. Note that this commit alters the behaviors for explicit restarts of critical and oneshot services. Previously these serivces would simply be restarted, whereas now, an explicit restart of a critical service counts as a crash (which may result in a recovery reboot) and oneshot services go into the disabled state.
2012-02-06 16:32:13 +01:00
/* 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);
2008-10-21 16:00:00 +02:00
if ((how != SVC_DISABLED) && (how != SVC_RESET) && (how != SVC_RESTART)) {
/* Hrm, an illegal flag. Default to SVC_DISABLED */
how = SVC_DISABLED;
}
2008-10-21 16:00:00 +02:00
/* 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;
}
2008-10-21 16:00:00 +02:00
if (svc->pid) {
NOTICE("Service '%s' is being killed...\n", svc->name);
kill(-svc->pid, SIGKILL);
svc->NotifyStateChange("stopping");
2008-10-21 16:00:00 +02:00
} else {
svc->NotifyStateChange("stopped");
2008-10-21 16:00:00 +02:00
}
}
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. */
}
2008-10-21 16:00:00 +02:00
void property_changed(const char *name, const char *value)
{
if (property_triggers_enabled)
2008-10-21 16:00:00 +02:00
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);
2008-10-21 16:00:00 +02:00
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);
}
}
2008-10-21 16:00:00 +02:00
if (svc) {
service_start(svc, args);
2008-10-21 16:00:00 +02:00
} else {
ERROR("no such service '%s'\n", name);
}
if (tmp)
free(tmp);
2008-10-21 16:00:00 +02:00
}
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);
2008-10-21 16:00:00 +02:00
}
}
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);
}
}
2008-10-21 16:00:00 +02:00
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);
2008-10-21 16:00:00 +02:00
} 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)
2008-10-21 16:00:00 +02:00
{
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] = "";
2008-10-21 16:00:00 +02:00
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);
2008-10-21 16:00:00 +02:00
}
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());
}
2008-10-21 16:00:00 +02:00
}
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)
2008-10-21 16:00:00 +02:00
{
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, int 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(0, import_kernel_nv);
if (qemu[0])
import_kernel_cmdline(1, 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();
}
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;
}
char tmp[PROP_VALUE_MAX];
if ((property_get("ro.boot.selinux", tmp) != 0) && (strcmp(tmp, "disabled") == 0)) {
// SELinux is compiled into the kernel, but we've been told to disable it.
return true;
}
}
return false;
}
static bool selinux_is_enforcing(void)
{
if (ALLOW_DISABLE_SELINUX) {
char tmp[PROP_VALUE_MAX];
if (property_get("ro.boot.selinux", tmp) == 0) {
// Property is not set. Assume enforcing.
return true;
}
if (strcmp(tmp, "permissive") == 0) {
// SELinux is in the kernel, but we've been told to go into permissive mode.
return false;
}
if (strcmp(tmp, "enforcing") != 0) {
ERROR("SELinux: Unknown value of ro.boot.selinux. Got: \"%s\". Assuming enforcing.\n", tmp);
}
}
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) {
if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) {
ERROR("couldn't write to /sys/fs/selinux/checkreqprot: %s\n",
strerror(errno));
security_failure();
}
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);
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.
2008-10-21 16:00:00 +02:00
umask(0);
add_environment("PATH", _PATH_DEFPATH);
// 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.
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
2008-10-21 16:00:00 +02:00
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);
// Indicate that booting is in progress to background fw loaders, etc.
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
// 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.
2008-10-21 16:00:00 +02:00
open_devnull_stdio();
klog_init();
klog_set_level(KLOG_NOTICE_LEVEL);
bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0);
NOTICE("init%s started!\n", is_first_stage ? "" : " second stage");
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");
2008-10-21 16:00:00 +02:00
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");
2008-10-21 16:00:00 +02:00
// Trigger all the boot actions to get us started.
2008-10-21 16:00:00 +02:00
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");
2008-10-21 16:00:00 +02:00
// 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);
}
2008-10-21 16:00:00 +02:00
// 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();
}
2008-10-21 16:00:00 +02:00
int timeout = -1;
2008-10-21 16:00:00 +02:00
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)();
2008-10-21 16:00:00 +02:00
}
}
return 0;
}