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.
|
|
|
|
*/
|
|
|
|
|
2017-04-07 01:30:22 +02:00
|
|
|
#include "init.h"
|
|
|
|
|
2015-02-07 05:15:18 +01:00
|
|
|
#include <ctype.h>
|
2015-02-28 15:39:11 +01:00
|
|
|
#include <dirent.h>
|
2015-02-07 05:15:18 +01:00
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
2016-11-11 02:43:47 +01:00
|
|
|
#include <inttypes.h>
|
2015-02-07 05:15:18 +01:00
|
|
|
#include <libgen.h>
|
2015-03-18 04:01:13 +01:00
|
|
|
#include <paths.h>
|
2017-07-18 00:14:02 +02:00
|
|
|
#include <seccomp_policy.h>
|
2015-02-07 05:15:18 +01:00
|
|
|
#include <signal.h>
|
|
|
|
#include <stdarg.h>
|
2008-10-21 16:00:00 +02:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2015-04-25 06:13:44 +02:00
|
|
|
#include <sys/epoll.h>
|
2008-10-21 16:00:00 +02:00
|
|
|
#include <sys/mount.h>
|
|
|
|
#include <sys/socket.h>
|
2015-02-07 05:15:18 +01:00
|
|
|
#include <sys/stat.h>
|
2016-07-26 18:32:33 +02:00
|
|
|
#include <sys/sysmacros.h>
|
2015-02-07 05:15:18 +01:00
|
|
|
#include <sys/types.h>
|
2008-10-21 16:00:00 +02:00
|
|
|
#include <sys/un.h>
|
2015-02-07 05:15:18 +01:00
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
2017-03-24 19:43:02 +01:00
|
|
|
#include <android-base/chrono_utils.h>
|
2015-12-05 07:00:26 +01:00
|
|
|
#include <android-base/file.h>
|
2017-04-07 01:30:22 +02:00
|
|
|
#include <android-base/logging.h>
|
2017-03-29 01:40:41 +02:00
|
|
|
#include <android-base/properties.h>
|
2015-12-05 07:00:26 +01:00
|
|
|
#include <android-base/strings.h>
|
2017-02-28 21:32:20 +01:00
|
|
|
#include <android-base/unique_fd.h>
|
2017-05-10 02:09:06 +02:00
|
|
|
#include <keyutils.h>
|
2017-02-14 15:06:20 +01:00
|
|
|
#include <libavb/libavb.h>
|
2010-04-21 21:04:20 +02:00
|
|
|
#include <private/android_filesystem_config.h>
|
2017-05-10 02:09:06 +02:00
|
|
|
#include <selinux/android.h>
|
|
|
|
#include <selinux/selinux.h>
|
2008-10-21 16:00:00 +02:00
|
|
|
|
2016-11-11 04:22:18 +01:00
|
|
|
#include <fstream>
|
2015-02-28 15:39:11 +01:00
|
|
|
#include <memory>
|
2017-07-31 22:23:18 +02:00
|
|
|
#include <optional>
|
2017-02-18 11:33:13 +01:00
|
|
|
#include <vector>
|
2015-02-28 15:39:11 +01:00
|
|
|
|
2015-07-31 21:45:25 +02:00
|
|
|
#include "bootchart.h"
|
2015-08-26 20:43:36 +02:00
|
|
|
#include "import_parser.h"
|
2017-04-17 16:17:09 +02:00
|
|
|
#include "init_first_stage.h"
|
2015-07-31 21:45:25 +02:00
|
|
|
#include "keychords.h"
|
2010-04-20 02:05:34 +02:00
|
|
|
#include "log.h"
|
2008-10-21 16:00:00 +02:00
|
|
|
#include "property_service.h"
|
2017-04-18 01:34:20 +02:00
|
|
|
#include "reboot.h"
|
2010-04-14 04:48:59 +02:00
|
|
|
#include "signal_handler.h"
|
2010-04-21 21:04:20 +02:00
|
|
|
#include "ueventd.h"
|
2015-07-31 21:45:25 +02:00
|
|
|
#include "util.h"
|
2012-06-14 06:51:56 +02:00
|
|
|
#include "watchdogd.h"
|
2008-10-21 16:00:00 +02:00
|
|
|
|
2017-06-23 01:50:31 +02:00
|
|
|
using namespace std::string_literals;
|
|
|
|
|
2017-03-24 19:43:02 +01:00
|
|
|
using android::base::boot_clock;
|
2017-03-29 01:40:41 +02:00
|
|
|
using android::base::GetProperty;
|
2017-07-06 23:20:11 +02:00
|
|
|
using android::base::Timer;
|
2017-08-04 00:45:51 +02:00
|
|
|
using android::base::unique_fd;
|
2016-11-11 02:43:47 +01:00
|
|
|
|
2017-06-22 21:53:17 +02:00
|
|
|
namespace android {
|
|
|
|
namespace init {
|
|
|
|
|
2012-01-13 14:48:47 +01:00
|
|
|
struct selabel_handle *sehandle;
|
2012-08-09 16:05:49 +02:00
|
|
|
struct selabel_handle *sehandle_prop;
|
2012-01-13 14:48:47 +01:00
|
|
|
|
2008-10-21 16:00:00 +02:00
|
|
|
static int property_triggers_enabled = 0;
|
|
|
|
|
|
|
|
static char qemu[32];
|
|
|
|
|
2016-03-21 09:08:07 +01:00
|
|
|
std::string default_console = "/dev/console";
|
2008-10-21 16:00:00 +02:00
|
|
|
|
2015-07-31 21:45:25 +02:00
|
|
|
const char *ENV[32];
|
2008-10-21 16:00:00 +02:00
|
|
|
|
2015-04-25 06:13:44 +02:00
|
|
|
static int epoll_fd = -1;
|
|
|
|
|
2017-01-26 01:27:03 +01:00
|
|
|
static std::unique_ptr<Timer> waiting_for_prop(nullptr);
|
|
|
|
static std::string wait_prop_name;
|
|
|
|
static std::string wait_prop_value;
|
2017-06-28 07:08:45 +02:00
|
|
|
static bool shutting_down;
|
2017-01-26 01:27:03 +01:00
|
|
|
|
2017-07-27 21:54:48 +02:00
|
|
|
std::vector<std::string> late_import_paths;
|
|
|
|
|
2017-04-20 00:31:58 +02:00
|
|
|
void DumpState() {
|
2017-07-28 01:20:58 +02:00
|
|
|
ServiceList::GetInstance().DumpState();
|
2017-04-20 00:31:58 +02:00
|
|
|
ActionManager::GetInstance().DumpState();
|
|
|
|
}
|
|
|
|
|
2017-07-28 01:20:58 +02:00
|
|
|
Parser CreateParser(ActionManager& action_manager, ServiceList& service_list) {
|
2017-07-27 21:54:48 +02:00
|
|
|
Parser parser;
|
|
|
|
|
2017-07-28 01:20:58 +02:00
|
|
|
parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list));
|
2017-07-27 21:54:48 +02:00
|
|
|
parser.AddSectionParser("on", std::make_unique<ActionParser>(&action_manager));
|
|
|
|
parser.AddSectionParser("import", std::make_unique<ImportParser>(&parser));
|
|
|
|
|
|
|
|
return parser;
|
|
|
|
}
|
|
|
|
|
2017-07-28 01:20:58 +02:00
|
|
|
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
|
|
|
|
Parser parser = CreateParser(action_manager, service_list);
|
2017-07-27 21:54:48 +02:00
|
|
|
|
|
|
|
std::string bootscript = GetProperty("ro.boot.init_rc", "");
|
|
|
|
if (bootscript.empty()) {
|
|
|
|
parser.ParseConfig("/init.rc");
|
|
|
|
if (!parser.ParseConfig("/system/etc/init")) {
|
|
|
|
late_import_paths.emplace_back("/system/etc/init");
|
|
|
|
}
|
|
|
|
if (!parser.ParseConfig("/vendor/etc/init")) {
|
|
|
|
late_import_paths.emplace_back("/vendor/etc/init");
|
|
|
|
}
|
|
|
|
if (!parser.ParseConfig("/odm/etc/init")) {
|
|
|
|
late_import_paths.emplace_back("/odm/etc/init");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
parser.ParseConfig(bootscript);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-25 06:13:44 +02:00
|
|
|
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) {
|
2016-06-25 00:12:21 +02:00
|
|
|
PLOG(ERROR) << "epoll_ctl failed";
|
2015-04-25 06:13:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
{
|
2014-05-16 12:36:36 +02:00
|
|
|
size_t n;
|
|
|
|
size_t key_len = strlen(key);
|
|
|
|
|
|
|
|
/* The last environment entry is reserved to terminate the list */
|
2016-06-25 00:12:21 +02:00
|
|
|
for (n = 0; n < (arraysize(ENV) - 1); n++) {
|
2014-05-16 12:36:36 +02:00
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
}
|
2011-09-28 18:55:31 +02:00
|
|
|
|
2014-05-16 12:36:36 +02:00
|
|
|
/* Add entry if a free slot is available */
|
|
|
|
if (ENV[n] == NULL) {
|
2015-02-04 02:12:07 +01:00
|
|
|
char* entry;
|
|
|
|
asprintf(&entry, "%s=%s", key, val);
|
2008-10-21 16:00:00 +02:00
|
|
|
ENV[n] = entry;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-25 00:12:21 +02:00
|
|
|
LOG(ERROR) << "No env. room to store: '" << key << "':'" << val << "'";
|
2014-05-16 12:36:36 +02:00
|
|
|
|
|
|
|
return -1;
|
2008-10-21 16:00:00 +02:00
|
|
|
}
|
|
|
|
|
2017-02-02 19:52:39 +01:00
|
|
|
bool start_waiting_for_property(const char *name, const char *value)
|
2017-01-26 01:27:03 +01:00
|
|
|
{
|
|
|
|
if (waiting_for_prop) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-03-29 01:40:41 +02:00
|
|
|
if (GetProperty(name, "") != value) {
|
2017-01-26 01:27:03 +01:00
|
|
|
// Current property value is not equal to expected value
|
|
|
|
wait_prop_name = name;
|
|
|
|
wait_prop_value = value;
|
|
|
|
waiting_for_prop.reset(new Timer());
|
|
|
|
} else {
|
2017-02-02 19:52:39 +01:00
|
|
|
LOG(INFO) << "start_waiting_for_property(\""
|
|
|
|
<< name << "\", \"" << value << "\"): already set";
|
2017-01-26 01:27:03 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-06-28 07:08:45 +02:00
|
|
|
void ResetWaitForProp() {
|
|
|
|
wait_prop_name.clear();
|
|
|
|
wait_prop_value.clear();
|
|
|
|
waiting_for_prop.reset();
|
|
|
|
}
|
|
|
|
|
2017-04-18 01:34:20 +02:00
|
|
|
void property_changed(const std::string& name, const std::string& value) {
|
|
|
|
// If the property is sys.powerctl, we bypass the event queue and immediately handle it.
|
|
|
|
// This is to ensure that init will always and immediately shutdown/reboot, regardless of
|
|
|
|
// if there are other pending events to process or if init is waiting on an exec service or
|
|
|
|
// waiting on a property.
|
2017-06-28 07:08:45 +02:00
|
|
|
// In non-thermal-shutdown case, 'shutdown' trigger will be fired to let device specific
|
|
|
|
// commands to be executed.
|
|
|
|
if (name == "sys.powerctl") {
|
|
|
|
if (HandlePowerctlMessage(value)) {
|
|
|
|
shutting_down = true;
|
|
|
|
}
|
|
|
|
}
|
2017-04-18 01:34:20 +02:00
|
|
|
|
2017-04-17 22:25:29 +02:00
|
|
|
if (property_triggers_enabled) ActionManager::GetInstance().QueuePropertyChange(name, value);
|
|
|
|
|
2017-01-26 01:27:03 +01:00
|
|
|
if (waiting_for_prop) {
|
|
|
|
if (wait_prop_name == name && wait_prop_value == value) {
|
|
|
|
LOG(INFO) << "Wait for property took " << *waiting_for_prop;
|
2017-06-28 07:08:45 +02:00
|
|
|
ResetWaitForProp();
|
2017-01-26 01:27:03 +01:00
|
|
|
}
|
|
|
|
}
|
2008-10-21 16:00:00 +02:00
|
|
|
}
|
|
|
|
|
2017-07-31 22:23:18 +02:00
|
|
|
static std::optional<boot_clock::time_point> RestartProcesses() {
|
|
|
|
std::optional<boot_clock::time_point> next_process_restart_time;
|
2017-07-28 01:20:58 +02:00
|
|
|
for (const auto& s : ServiceList::GetInstance()) {
|
|
|
|
if (!(s->flags() & SVC_RESTARTING)) continue;
|
2017-07-31 22:23:18 +02:00
|
|
|
|
|
|
|
auto restart_time = s->time_started() + 5s;
|
|
|
|
if (boot_clock::now() > restart_time) {
|
|
|
|
s->Start();
|
|
|
|
} else {
|
|
|
|
if (!next_process_restart_time || restart_time < *next_process_restart_time) {
|
|
|
|
next_process_restart_time = restart_time;
|
|
|
|
}
|
|
|
|
}
|
2017-07-28 01:20:58 +02:00
|
|
|
}
|
2017-07-31 22:23:18 +02:00
|
|
|
return next_process_restart_time;
|
2008-10-21 16:00:00 +02:00
|
|
|
}
|
|
|
|
|
2016-04-13 00:38:27 +02:00
|
|
|
void handle_control_message(const std::string& msg, const std::string& name) {
|
2017-07-28 01:20:58 +02:00
|
|
|
Service* svc = ServiceList::GetInstance().FindService(name);
|
2016-04-13 00:38:27 +02:00
|
|
|
if (svc == nullptr) {
|
2016-06-25 00:12:21 +02:00
|
|
|
LOG(ERROR) << "no such service '" << name << "'";
|
2016-04-13 00:38:27 +02:00
|
|
|
return;
|
2012-01-26 05:48:46 +01:00
|
|
|
}
|
|
|
|
|
2015-07-31 21:45:25 +02:00
|
|
|
if (msg == "start") {
|
2016-04-13 00:38:27 +02:00
|
|
|
svc->Start();
|
2015-07-31 21:45:25 +02:00
|
|
|
} else if (msg == "stop") {
|
2016-04-13 00:38:27 +02:00
|
|
|
svc->Stop();
|
2015-07-31 21:45:25 +02:00
|
|
|
} else if (msg == "restart") {
|
2016-04-13 00:38:27 +02:00
|
|
|
svc->Restart();
|
2008-10-21 16:00:00 +02:00
|
|
|
} else {
|
2016-06-25 00:12:21 +02:00
|
|
|
LOG(ERROR) << "unknown control msg '" << msg << "'";
|
2008-10-21 16:00:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-30 22:52:55 +02:00
|
|
|
static int wait_for_coldboot_done_action(const std::vector<std::string>& args) {
|
2015-04-25 03:50:30 +02:00
|
|
|
Timer t;
|
|
|
|
|
2016-06-25 00:12:21 +02:00
|
|
|
LOG(VERBOSE) << "Waiting for " COLDBOOT_DONE "...";
|
2016-11-29 20:20:58 +01:00
|
|
|
|
|
|
|
// Historically we had a 1s timeout here because we weren't otherwise
|
|
|
|
// tracking boot time, and many OEMs made their sepolicy regular
|
|
|
|
// expressions too expensive (http://b/19899875).
|
|
|
|
|
|
|
|
// Now we're tracking boot time, just log the time taken to a system
|
|
|
|
// property. We still panic if it takes more than a minute though,
|
|
|
|
// because any build that slow isn't likely to boot at all, and we'd
|
|
|
|
// rather any test lab devices fail back to the bootloader.
|
|
|
|
if (wait_for_file(COLDBOOT_DONE, 60s) < 0) {
|
2016-06-25 00:12:21 +02:00
|
|
|
LOG(ERROR) << "Timed out waiting for " COLDBOOT_DONE;
|
2016-11-29 20:20:58 +01:00
|
|
|
panic();
|
2015-04-25 03:50:30 +02:00
|
|
|
}
|
|
|
|
|
2017-07-06 23:20:11 +02:00
|
|
|
property_set("ro.boottime.init.cold_boot_wait", std::to_string(t.duration().count()));
|
2015-04-25 03:50:30 +02:00
|
|
|
return 0;
|
2010-04-14 04:52:01 +02:00
|
|
|
}
|
|
|
|
|
2013-08-16 22:19:24 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2017-08-04 00:45:51 +02:00
|
|
|
static int mix_hwrng_into_linux_rng_action(const std::vector<std::string>& args) {
|
|
|
|
unique_fd hwrandom_fd(
|
|
|
|
TEMP_FAILURE_RETRY(open("/dev/hw_random", O_RDONLY | O_NOFOLLOW | O_CLOEXEC)));
|
2013-08-16 22:19:24 +02:00
|
|
|
if (hwrandom_fd == -1) {
|
|
|
|
if (errno == ENOENT) {
|
2017-08-04 00:45:51 +02:00
|
|
|
LOG(INFO) << "/dev/hw_random not found";
|
2016-06-25 00:12:21 +02:00
|
|
|
// It's not an error to not have a Hardware RNG.
|
2017-08-04 00:45:51 +02:00
|
|
|
return 0;
|
2013-08-16 22:19:24 +02:00
|
|
|
}
|
2017-08-04 00:45:51 +02:00
|
|
|
PLOG(ERROR) << "Failed to open /dev/hw_random";
|
|
|
|
return -1;
|
2013-08-16 22:19:24 +02:00
|
|
|
}
|
|
|
|
|
2017-08-04 00:45:51 +02:00
|
|
|
unique_fd urandom_fd(
|
|
|
|
TEMP_FAILURE_RETRY(open("/dev/urandom", O_WRONLY | O_NOFOLLOW | O_CLOEXEC)));
|
2013-08-16 22:19:24 +02:00
|
|
|
if (urandom_fd == -1) {
|
2016-06-25 00:12:21 +02:00
|
|
|
PLOG(ERROR) << "Failed to open /dev/urandom";
|
2017-08-04 00:45:51 +02:00
|
|
|
return -1;
|
2013-08-16 22:19:24 +02:00
|
|
|
}
|
|
|
|
|
2017-08-04 00:45:51 +02:00
|
|
|
char buf[512];
|
|
|
|
size_t total_bytes_written = 0;
|
2013-08-16 22:19:24 +02:00
|
|
|
while (total_bytes_written < sizeof(buf)) {
|
2017-08-04 00:45:51 +02:00
|
|
|
ssize_t chunk_size =
|
|
|
|
TEMP_FAILURE_RETRY(read(hwrandom_fd, buf, sizeof(buf) - total_bytes_written));
|
2013-08-16 22:19:24 +02:00
|
|
|
if (chunk_size == -1) {
|
2016-06-25 00:12:21 +02:00
|
|
|
PLOG(ERROR) << "Failed to read from /dev/hw_random";
|
2017-08-04 00:45:51 +02:00
|
|
|
return -1;
|
2013-08-16 22:19:24 +02:00
|
|
|
} else if (chunk_size == 0) {
|
2016-06-25 00:12:21 +02:00
|
|
|
LOG(ERROR) << "Failed to read from /dev/hw_random: EOF";
|
2017-08-04 00:45:51 +02:00
|
|
|
return -1;
|
2013-08-16 22:19:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
chunk_size = TEMP_FAILURE_RETRY(write(urandom_fd, buf, chunk_size));
|
|
|
|
if (chunk_size == -1) {
|
2016-06-25 00:12:21 +02:00
|
|
|
PLOG(ERROR) << "Failed to write to /dev/urandom";
|
2017-08-04 00:45:51 +02:00
|
|
|
return -1;
|
2013-08-16 22:19:24 +02:00
|
|
|
}
|
|
|
|
total_bytes_written += chunk_size;
|
|
|
|
}
|
|
|
|
|
2016-06-25 00:12:21 +02:00
|
|
|
LOG(INFO) << "Mixed " << total_bytes_written << " bytes from /dev/hw_random into /dev/urandom";
|
2017-08-04 00:45:51 +02:00
|
|
|
return 0;
|
2013-08-16 22:19:24 +02:00
|
|
|
}
|
|
|
|
|
2016-03-26 00:49:05 +01:00
|
|
|
static void security_failure() {
|
|
|
|
LOG(ERROR) << "Security failure...";
|
|
|
|
panic();
|
|
|
|
}
|
|
|
|
|
2017-01-20 03:03:34 +01:00
|
|
|
static bool set_highest_available_option_value(std::string path, int min, int max)
|
|
|
|
{
|
2016-03-26 00:49:05 +01:00
|
|
|
std::ifstream inf(path, std::fstream::in);
|
|
|
|
if (!inf) {
|
|
|
|
LOG(ERROR) << "Cannot open for reading: " << path;
|
|
|
|
return false;
|
|
|
|
}
|
2017-01-20 03:03:34 +01:00
|
|
|
|
|
|
|
int current = max;
|
|
|
|
while (current >= min) {
|
2016-03-26 00:49:05 +01:00
|
|
|
// try to write out new value
|
2017-01-20 03:03:34 +01:00
|
|
|
std::string str_val = std::to_string(current);
|
2016-03-26 00:49:05 +01:00
|
|
|
std::ofstream of(path, std::fstream::out);
|
|
|
|
if (!of) {
|
|
|
|
LOG(ERROR) << "Cannot open for writing: " << path;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
of << str_val << std::endl;
|
|
|
|
of.close();
|
|
|
|
|
|
|
|
// check to make sure it was recorded
|
|
|
|
inf.seekg(0);
|
|
|
|
std::string str_rec;
|
|
|
|
inf >> str_rec;
|
|
|
|
if (str_val.compare(str_rec) == 0) {
|
|
|
|
break;
|
|
|
|
}
|
2017-01-20 03:03:34 +01:00
|
|
|
current--;
|
2016-03-26 00:49:05 +01:00
|
|
|
}
|
|
|
|
inf.close();
|
2017-01-20 03:03:34 +01:00
|
|
|
|
|
|
|
if (current < min) {
|
|
|
|
LOG(ERROR) << "Unable to set minimum option value " << min << " in " << path;
|
2016-03-26 00:49:05 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-01-20 03:03:34 +01:00
|
|
|
#define MMAP_RND_PATH "/proc/sys/vm/mmap_rnd_bits"
|
|
|
|
#define MMAP_RND_COMPAT_PATH "/proc/sys/vm/mmap_rnd_compat_bits"
|
|
|
|
|
|
|
|
/* __attribute__((unused)) due to lack of mips support: see mips block
|
|
|
|
* in set_mmap_rnd_bits_action */
|
|
|
|
static bool __attribute__((unused)) set_mmap_rnd_bits_min(int start, int min, bool compat) {
|
|
|
|
std::string path;
|
|
|
|
if (compat) {
|
|
|
|
path = MMAP_RND_COMPAT_PATH;
|
|
|
|
} else {
|
|
|
|
path = MMAP_RND_PATH;
|
|
|
|
}
|
|
|
|
|
|
|
|
return set_highest_available_option_value(path, min, start);
|
|
|
|
}
|
|
|
|
|
2016-03-26 00:49:05 +01:00
|
|
|
/*
|
|
|
|
* Set /proc/sys/vm/mmap_rnd_bits and potentially
|
|
|
|
* /proc/sys/vm/mmap_rnd_compat_bits to the maximum supported values.
|
|
|
|
* Returns -1 if unable to set these to an acceptable value.
|
|
|
|
*
|
|
|
|
* To support this sysctl, the following upstream commits are needed:
|
|
|
|
*
|
|
|
|
* d07e22597d1d mm: mmap: add new /proc tunable for mmap_base ASLR
|
|
|
|
* e0c25d958f78 arm: mm: support ARCH_MMAP_RND_BITS
|
|
|
|
* 8f0d3aa9de57 arm64: mm: support ARCH_MMAP_RND_BITS
|
|
|
|
* 9e08f57d684a x86: mm: support ARCH_MMAP_RND_BITS
|
|
|
|
* ec9ee4acd97c drivers: char: random: add get_random_long()
|
|
|
|
* 5ef11c35ce86 mm: ASLR: use get_random_long()
|
|
|
|
*/
|
2017-08-04 00:45:51 +02:00
|
|
|
static int set_mmap_rnd_bits_action(const std::vector<std::string>& args) {
|
|
|
|
/* values are arch-dependent */
|
2017-07-18 03:03:13 +02:00
|
|
|
#if defined(USER_MODE_LINUX)
|
|
|
|
/* uml does not support mmap_rnd_bits */
|
2017-08-04 00:45:51 +02:00
|
|
|
return 0;
|
2017-07-18 03:03:13 +02:00
|
|
|
#elif defined(__aarch64__)
|
2016-03-26 00:49:05 +01:00
|
|
|
/* arm64 supports 18 - 33 bits depending on pagesize and VA_SIZE */
|
|
|
|
if (set_mmap_rnd_bits_min(33, 24, false)
|
|
|
|
&& set_mmap_rnd_bits_min(16, 16, true)) {
|
2017-08-04 00:45:51 +02:00
|
|
|
return 0;
|
2016-03-26 00:49:05 +01:00
|
|
|
}
|
|
|
|
#elif defined(__x86_64__)
|
|
|
|
/* x86_64 supports 28 - 32 bits */
|
|
|
|
if (set_mmap_rnd_bits_min(32, 32, false)
|
|
|
|
&& set_mmap_rnd_bits_min(16, 16, true)) {
|
2017-08-04 00:45:51 +02:00
|
|
|
return 0;
|
2016-03-26 00:49:05 +01:00
|
|
|
}
|
|
|
|
#elif defined(__arm__) || defined(__i386__)
|
|
|
|
/* check to see if we're running on 64-bit kernel */
|
|
|
|
bool h64 = !access(MMAP_RND_COMPAT_PATH, F_OK);
|
|
|
|
/* supported 32-bit architecture must have 16 bits set */
|
|
|
|
if (set_mmap_rnd_bits_min(16, 16, h64)) {
|
2017-08-04 00:45:51 +02:00
|
|
|
return 0;
|
2016-03-26 00:49:05 +01:00
|
|
|
}
|
|
|
|
#elif defined(__mips__) || defined(__mips64__)
|
|
|
|
// TODO: add mips support b/27788820
|
2017-08-04 00:45:51 +02:00
|
|
|
return 0;
|
2016-03-26 00:49:05 +01:00
|
|
|
#else
|
2016-12-14 17:37:11 +01:00
|
|
|
LOG(ERROR) << "Unknown architecture";
|
2016-03-26 00:49:05 +01:00
|
|
|
#endif
|
|
|
|
|
2017-08-04 00:45:51 +02:00
|
|
|
LOG(ERROR) << "Unable to set adequate mmap entropy value!";
|
|
|
|
security_failure();
|
|
|
|
return -1;
|
2016-03-26 00:49:05 +01:00
|
|
|
}
|
|
|
|
|
2017-01-20 03:03:34 +01:00
|
|
|
#define KPTR_RESTRICT_PATH "/proc/sys/kernel/kptr_restrict"
|
|
|
|
#define KPTR_RESTRICT_MINVALUE 2
|
|
|
|
#define KPTR_RESTRICT_MAXVALUE 4
|
|
|
|
|
|
|
|
/* Set kptr_restrict to the highest available level.
|
|
|
|
*
|
|
|
|
* Aborts if unable to set this to an acceptable value.
|
|
|
|
*/
|
|
|
|
static int set_kptr_restrict_action(const std::vector<std::string>& args)
|
|
|
|
{
|
|
|
|
std::string path = KPTR_RESTRICT_PATH;
|
|
|
|
|
|
|
|
if (!set_highest_available_option_value(path, KPTR_RESTRICT_MINVALUE, KPTR_RESTRICT_MAXVALUE)) {
|
|
|
|
LOG(ERROR) << "Unable to set adequate kptr_restrict value!";
|
|
|
|
security_failure();
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-30 22:52:55 +02:00
|
|
|
static int keychord_init_action(const std::vector<std::string>& args)
|
2010-04-14 04:52:01 +02:00
|
|
|
{
|
|
|
|
keychord_init();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-30 22:52:55 +02:00
|
|
|
static int console_init_action(const std::vector<std::string>& args)
|
2008-10-21 16:00:00 +02:00
|
|
|
{
|
2017-03-29 01:40:41 +02:00
|
|
|
std::string console = GetProperty("ro.boot.console", "");
|
2016-03-21 09:23:43 +01:00
|
|
|
if (!console.empty()) {
|
2016-03-21 09:08:07 +01:00
|
|
|
default_console = "/dev/" + console;
|
2010-04-14 04:52:01 +02:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-05-07 04:19:24 +02:00
|
|
|
static void import_kernel_nv(const std::string& key, const std::string& value, bool for_emulator) {
|
|
|
|
if (key.empty()) return;
|
2011-12-19 20:21:32 +01:00
|
|
|
|
|
|
|
if (for_emulator) {
|
2015-05-07 04:19:24 +02:00
|
|
|
// In the emulator, export any kernel option with the "ro.kernel." prefix.
|
2017-06-23 01:50:31 +02:00
|
|
|
property_set("ro.kernel." + key, value);
|
2011-12-19 20:21:32 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-05-07 04:19:24 +02:00
|
|
|
if (key == "qemu") {
|
|
|
|
strlcpy(qemu, value.c_str(), sizeof(qemu));
|
|
|
|
} else if (android::base::StartsWith(key, "androidboot.")) {
|
2017-06-23 01:50:31 +02:00
|
|
|
property_set("ro.boot." + key.substr(12), value);
|
2011-12-19 20:21:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-07 13:33:58 +01:00
|
|
|
static void export_oem_lock_status() {
|
2017-03-29 01:40:41 +02:00
|
|
|
if (!android::base::GetBoolProperty("ro.oem_unlock_supported", false)) {
|
2015-12-07 13:33:58 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-29 01:40:41 +02:00
|
|
|
std::string value = GetProperty("ro.boot.verifiedbootstate", "");
|
2015-12-07 13:33:58 +01:00
|
|
|
|
|
|
|
if (!value.empty()) {
|
|
|
|
property_set("ro.boot.flash.locked", value == "orange" ? "0" : "1");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-20 16:50:46 +01:00
|
|
|
static void export_kernel_boot_props() {
|
2011-12-19 20:21:32 +01:00
|
|
|
struct {
|
|
|
|
const char *src_prop;
|
2015-03-20 16:50:46 +01:00
|
|
|
const char *dst_prop;
|
|
|
|
const char *default_value;
|
2011-12-19 20:21:32 +01:00
|
|
|
} prop_map[] = {
|
2015-03-20 16:50:46 +01:00
|
|
|
{ "ro.boot.serialno", "ro.serialno", "", },
|
|
|
|
{ "ro.boot.mode", "ro.bootmode", "unknown", },
|
|
|
|
{ "ro.boot.baseband", "ro.baseband", "unknown", },
|
2011-12-19 20:21:32 +01:00
|
|
|
{ "ro.boot.bootloader", "ro.bootloader", "unknown", },
|
2015-03-20 16:50:46 +01:00
|
|
|
{ "ro.boot.hardware", "ro.hardware", "unknown", },
|
|
|
|
{ "ro.boot.revision", "ro.revision", "0", },
|
2011-12-19 20:21:32 +01:00
|
|
|
};
|
2016-06-25 00:12:21 +02:00
|
|
|
for (size_t i = 0; i < arraysize(prop_map); i++) {
|
2017-03-29 01:40:41 +02:00
|
|
|
std::string value = GetProperty(prop_map[i].src_prop, "");
|
2017-06-23 01:50:31 +02:00
|
|
|
property_set(prop_map[i].dst_prop, (!value.empty()) ? value : prop_map[i].default_value);
|
2011-12-19 20:21:32 +01:00
|
|
|
}
|
|
|
|
}
|
2010-04-14 04:52:01 +02:00
|
|
|
|
2017-02-10 04:19:31 +01:00
|
|
|
static void process_kernel_dt() {
|
2017-04-17 16:17:09 +02:00
|
|
|
if (!is_android_dt_value_expected("compatible", "android,firmware")) {
|
|
|
|
return;
|
|
|
|
}
|
2017-02-10 04:19:31 +01:00
|
|
|
|
Allow the use of a custom Android DT directory
On platforms that use ACPI instead of Device Tree (DT), such as
Ranchu x86/x86_64, /proc/device-tree/firmware/android/ does not
exist. As a result, Android O is unable to mount /system, etc.
at the first stage of init:
init: First stage mount skipped (missing/incompatible fstab in
device tree)
Those platforms may create another directory that mimics the layout
of the standard DT directory in procfs, and store early mount
configuration there. E.g., Ranchu x86/x86_64 creates one in sysfs
using information encoded in the ACPI tables:
https://android-review.googlesource.com/442472
https://android-review.googlesource.com/443432
https://android-review.googlesource.com/442393
https://android-review.googlesource.com/442395
Therefore, instead of hardcoding the Android DT path, load it from
the kernel command line using a new Android-specific property key
("androidboot.android_dt_dir"). If no such property exists, fall
back to the standard procfs path (so no change is needed for DT-
aware platforms).
Note that init/ and fs_mgr/ each have their own copy of the Android
DT path, because they do not share any global state. A future CL
should remove the duplication by refactoring.
With this CL as well as the above ones, the said warning is gone,
but early mount fails. That is a separate bug, though, and will be
addressed by another CL.
Test: Boot patched sdk_phone_x86-userdebug system image with patched
Goldfish 3.18 x86 kernel in patched Android Emulator, verify
the "init: First stage mount skipped" warning no longer shows
in dmesg.
Change-Id: Ib6df577319503ec1ca778de2b5458cc72ce07415
Signed-off-by: Yu Ning <yu.ning@intel.com>
2017-07-26 11:54:08 +02:00
|
|
|
std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(get_android_dt_dir().c_str()), closedir);
|
2015-05-07 04:19:24 +02:00
|
|
|
if (!dir) return;
|
2015-02-28 15:39:11 +01:00
|
|
|
|
2017-02-10 04:19:31 +01:00
|
|
|
std::string dt_file;
|
2015-02-28 15:39:11 +01:00
|
|
|
struct dirent *dp;
|
|
|
|
while ((dp = readdir(dir.get())) != NULL) {
|
2015-11-09 02:51:50 +01:00
|
|
|
if (dp->d_type != DT_REG || !strcmp(dp->d_name, "compatible") || !strcmp(dp->d_name, "name")) {
|
2015-02-28 15:39:11 +01:00
|
|
|
continue;
|
2015-05-07 04:19:24 +02:00
|
|
|
}
|
2015-02-28 15:39:11 +01:00
|
|
|
|
Allow the use of a custom Android DT directory
On platforms that use ACPI instead of Device Tree (DT), such as
Ranchu x86/x86_64, /proc/device-tree/firmware/android/ does not
exist. As a result, Android O is unable to mount /system, etc.
at the first stage of init:
init: First stage mount skipped (missing/incompatible fstab in
device tree)
Those platforms may create another directory that mimics the layout
of the standard DT directory in procfs, and store early mount
configuration there. E.g., Ranchu x86/x86_64 creates one in sysfs
using information encoded in the ACPI tables:
https://android-review.googlesource.com/442472
https://android-review.googlesource.com/443432
https://android-review.googlesource.com/442393
https://android-review.googlesource.com/442395
Therefore, instead of hardcoding the Android DT path, load it from
the kernel command line using a new Android-specific property key
("androidboot.android_dt_dir"). If no such property exists, fall
back to the standard procfs path (so no change is needed for DT-
aware platforms).
Note that init/ and fs_mgr/ each have their own copy of the Android
DT path, because they do not share any global state. A future CL
should remove the duplication by refactoring.
With this CL as well as the above ones, the said warning is gone,
but early mount fails. That is a separate bug, though, and will be
addressed by another CL.
Test: Boot patched sdk_phone_x86-userdebug system image with patched
Goldfish 3.18 x86 kernel in patched Android Emulator, verify
the "init: First stage mount skipped" warning no longer shows
in dmesg.
Change-Id: Ib6df577319503ec1ca778de2b5458cc72ce07415
Signed-off-by: Yu Ning <yu.ning@intel.com>
2017-07-26 11:54:08 +02:00
|
|
|
std::string file_name = get_android_dt_dir() + dp->d_name;
|
2015-02-28 15:39:11 +01:00
|
|
|
|
|
|
|
android::base::ReadFileToString(file_name, &dt_file);
|
|
|
|
std::replace(dt_file.begin(), dt_file.end(), ',', '.');
|
|
|
|
|
2017-06-23 01:50:31 +02:00
|
|
|
property_set("ro.boot."s + dp->d_name, dt_file);
|
2015-02-28 15:39:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-07 04:19:24 +02:00
|
|
|
static void process_kernel_cmdline() {
|
|
|
|
// The first pass does the common stuff, and finds if we are in qemu.
|
|
|
|
// The second pass is only necessary for qemu to export all kernel params
|
|
|
|
// as properties.
|
2015-04-26 02:42:52 +02:00
|
|
|
import_kernel_cmdline(false, import_kernel_nv);
|
2015-05-07 04:19:24 +02:00
|
|
|
if (qemu[0]) import_kernel_cmdline(true, import_kernel_nv);
|
2010-04-14 04:52:01 +02:00
|
|
|
}
|
|
|
|
|
2016-11-26 14:14:07 +01:00
|
|
|
static int property_enable_triggers_action(const std::vector<std::string>& args)
|
|
|
|
{
|
|
|
|
/* Enable property triggers. */
|
|
|
|
property_triggers_enabled = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-30 22:52:55 +02:00
|
|
|
static int queue_property_triggers_action(const std::vector<std::string>& args)
|
2010-04-14 04:52:01 +02:00
|
|
|
{
|
2016-11-26 14:14:07 +01:00
|
|
|
ActionManager::GetInstance().QueueBuiltinAction(property_enable_triggers_action, "enable_property_trigger");
|
2017-04-17 22:25:29 +02:00
|
|
|
ActionManager::GetInstance().QueueAllPropertyActions();
|
2010-04-14 04:52:01 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-07-18 00:14:02 +02:00
|
|
|
static void global_seccomp() {
|
|
|
|
import_kernel_cmdline(false, [](const std::string& key, const std::string& value, bool in_qemu) {
|
|
|
|
if (key == "androidboot.seccomp" && value == "global" && !set_global_seccomp_filter()) {
|
|
|
|
LOG(ERROR) << "Failed to globally enable seccomp!";
|
|
|
|
panic();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-04-25 03:50:30 +02:00
|
|
|
static void selinux_init_all_handles(void)
|
2012-01-13 14:48:47 +01:00
|
|
|
{
|
2012-05-01 21:02:53 +02:00
|
|
|
sehandle = selinux_android_file_context_handle();
|
2014-01-28 16:34:09 +01:00
|
|
|
selinux_android_set_sehandle(sehandle);
|
2012-08-09 16:05:49 +02:00
|
|
|
sehandle_prop = selinux_android_prop_context_handle();
|
2012-05-01 21:02:53 +02:00
|
|
|
}
|
2012-01-13 14:48:47 +01:00
|
|
|
|
2015-04-28 21:39:41 +02:00
|
|
|
enum selinux_enforcing_status { SELINUX_PERMISSIVE, SELINUX_ENFORCING };
|
2015-04-26 02:42:52 +02:00
|
|
|
|
|
|
|
static selinux_enforcing_status selinux_status_from_cmdline() {
|
|
|
|
selinux_enforcing_status status = SELINUX_ENFORCING;
|
|
|
|
|
2015-05-07 04:19:24 +02:00
|
|
|
import_kernel_cmdline(false, [&](const std::string& key, const std::string& value, bool in_qemu) {
|
|
|
|
if (key == "androidboot.selinux" && value == "permissive") {
|
|
|
|
status = SELINUX_PERMISSIVE;
|
2015-04-26 02:42:52 +02:00
|
|
|
}
|
2015-05-07 04:19:24 +02:00
|
|
|
});
|
2015-04-26 02:42:52 +02:00
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2013-06-27 00:37:26 +02:00
|
|
|
static bool selinux_is_enforcing(void)
|
|
|
|
{
|
2015-04-28 21:39:41 +02:00
|
|
|
if (ALLOW_PERMISSIVE_SELINUX) {
|
2015-04-26 02:42:52 +02:00
|
|
|
return selinux_status_from_cmdline() == SELINUX_ENFORCING;
|
2013-06-27 00:37:26 +02:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-03-28 07:20:44 +01:00
|
|
|
static int audit_callback(void *data, security_class_t /*cls*/, char *buf, size_t len) {
|
2015-10-02 01:03:47 +02:00
|
|
|
|
|
|
|
property_audit_data *d = reinterpret_cast<property_audit_data*>(data);
|
|
|
|
|
|
|
|
if (!d || !d->name || !d->cr) {
|
2016-06-25 00:12:21 +02:00
|
|
|
LOG(ERROR) << "audit_callback invoked with null data arguments!";
|
2015-10-02 01:03:47 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(buf, len, "property=%s pid=%d uid=%d gid=%d", d->name,
|
|
|
|
d->cr->pid, d->cr->uid, d->cr->gid);
|
2012-08-09 16:05:49 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-02-28 21:32:20 +01:00
|
|
|
/*
|
|
|
|
* Forks, executes the provided program in the child, and waits for the completion in the parent.
|
2017-03-05 23:28:27 +01:00
|
|
|
* Child's stderr is captured and logged using LOG(ERROR).
|
2017-02-28 21:32:20 +01:00
|
|
|
*
|
|
|
|
* Returns true if the child exited with status code 0, returns false otherwise.
|
|
|
|
*/
|
|
|
|
static bool fork_execve_and_wait_for_completion(const char* filename, char* const argv[],
|
|
|
|
char* const envp[]) {
|
2017-03-05 23:28:27 +01:00
|
|
|
// Create a pipe used for redirecting child process's output.
|
|
|
|
// * pipe_fds[0] is the FD the parent will use for reading.
|
|
|
|
// * pipe_fds[1] is the FD the child will use for writing.
|
|
|
|
int pipe_fds[2];
|
|
|
|
if (pipe(pipe_fds) == -1) {
|
|
|
|
PLOG(ERROR) << "Failed to create pipe";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-02-28 21:32:20 +01:00
|
|
|
pid_t child_pid = fork();
|
|
|
|
if (child_pid == -1) {
|
|
|
|
PLOG(ERROR) << "Failed to fork for " << filename;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (child_pid == 0) {
|
|
|
|
// fork succeeded -- this is executing in the child process
|
2017-03-05 23:28:27 +01:00
|
|
|
|
|
|
|
// Close the pipe FD not used by this process
|
|
|
|
TEMP_FAILURE_RETRY(close(pipe_fds[0]));
|
|
|
|
|
|
|
|
// Redirect stderr to the pipe FD provided by the parent
|
|
|
|
if (TEMP_FAILURE_RETRY(dup2(pipe_fds[1], STDERR_FILENO)) == -1) {
|
|
|
|
PLOG(ERROR) << "Failed to redirect stderr of " << filename;
|
|
|
|
_exit(127);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
TEMP_FAILURE_RETRY(close(pipe_fds[1]));
|
|
|
|
|
2017-02-28 21:32:20 +01:00
|
|
|
if (execve(filename, argv, envp) == -1) {
|
|
|
|
PLOG(ERROR) << "Failed to execve " << filename;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Unreachable because execve will have succeeded and replaced this code
|
|
|
|
// with child process's code.
|
|
|
|
_exit(127);
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
// fork succeeded -- this is executing in the original/parent process
|
2017-03-05 23:28:27 +01:00
|
|
|
|
|
|
|
// Close the pipe FD not used by this process
|
|
|
|
TEMP_FAILURE_RETRY(close(pipe_fds[1]));
|
|
|
|
|
|
|
|
// Log the redirected output of the child process.
|
|
|
|
// It's unfortunate that there's no standard way to obtain an istream for a file descriptor.
|
|
|
|
// As a result, we're buffering all output and logging it in one go at the end of the
|
|
|
|
// invocation, instead of logging it as it comes in.
|
|
|
|
const int child_out_fd = pipe_fds[0];
|
|
|
|
std::string child_output;
|
|
|
|
if (!android::base::ReadFdToString(child_out_fd, &child_output)) {
|
|
|
|
PLOG(ERROR) << "Failed to capture full output of " << filename;
|
|
|
|
}
|
|
|
|
TEMP_FAILURE_RETRY(close(child_out_fd));
|
|
|
|
if (!child_output.empty()) {
|
|
|
|
// Log captured output, line by line, because LOG expects to be invoked for each line
|
|
|
|
std::istringstream in(child_output);
|
|
|
|
std::string line;
|
|
|
|
while (std::getline(in, line)) {
|
|
|
|
LOG(ERROR) << filename << ": " << line;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for child to terminate
|
2017-02-28 21:32:20 +01:00
|
|
|
int status;
|
|
|
|
if (TEMP_FAILURE_RETRY(waitpid(child_pid, &status, 0)) != child_pid) {
|
|
|
|
PLOG(ERROR) << "Failed to wait for " << filename;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (WIFEXITED(status)) {
|
|
|
|
int status_code = WEXITSTATUS(status);
|
|
|
|
if (status_code == 0) {
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
LOG(ERROR) << filename << " exited with status " << status_code;
|
|
|
|
}
|
|
|
|
} else if (WIFSIGNALED(status)) {
|
|
|
|
LOG(ERROR) << filename << " killed by signal " << WTERMSIG(status);
|
|
|
|
} else if (WIFSTOPPED(status)) {
|
|
|
|
LOG(ERROR) << filename << " stopped by signal " << WSTOPSIG(status);
|
|
|
|
} else {
|
|
|
|
LOG(ERROR) << "waitpid for " << filename << " returned unexpected status: " << status;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-07 23:12:01 +01:00
|
|
|
static bool read_first_line(const char* file, std::string* line) {
|
|
|
|
line->clear();
|
|
|
|
|
|
|
|
std::string contents;
|
|
|
|
if (!android::base::ReadFileToString(file, &contents, true /* follow symlinks */)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
std::istringstream in(contents);
|
|
|
|
std::getline(in, *line);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool selinux_find_precompiled_split_policy(std::string* file) {
|
|
|
|
file->clear();
|
|
|
|
|
|
|
|
static constexpr const char precompiled_sepolicy[] = "/vendor/etc/selinux/precompiled_sepolicy";
|
|
|
|
if (access(precompiled_sepolicy, R_OK) == -1) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
std::string actual_plat_id;
|
2017-04-06 18:51:23 +02:00
|
|
|
if (!read_first_line("/system/etc/selinux/plat_and_mapping_sepolicy.cil.sha256",
|
|
|
|
&actual_plat_id)) {
|
|
|
|
PLOG(INFO) << "Failed to read "
|
|
|
|
"/system/etc/selinux/plat_and_mapping_sepolicy.cil.sha256";
|
2017-03-07 23:12:01 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
std::string precompiled_plat_id;
|
2017-04-06 18:51:23 +02:00
|
|
|
if (!read_first_line("/vendor/etc/selinux/precompiled_sepolicy.plat_and_mapping.sha256",
|
2017-03-07 23:12:01 +01:00
|
|
|
&precompiled_plat_id)) {
|
2017-04-06 18:51:23 +02:00
|
|
|
PLOG(INFO) << "Failed to read "
|
|
|
|
"/vendor/etc/selinux/"
|
|
|
|
"precompiled_sepolicy.plat_and_mapping.sha256";
|
2017-03-07 23:12:01 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if ((actual_plat_id.empty()) || (actual_plat_id != precompiled_plat_id)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
*file = precompiled_sepolicy;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-03-02 21:43:10 +01:00
|
|
|
static constexpr const char plat_policy_cil_file[] = "/system/etc/selinux/plat_sepolicy.cil";
|
2017-02-28 21:32:20 +01:00
|
|
|
|
|
|
|
static bool selinux_is_split_policy_device() { return access(plat_policy_cil_file, R_OK) != -1; }
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Loads SELinux policy split across platform/system and non-platform/vendor files.
|
|
|
|
*
|
|
|
|
* Returns true upon success, false otherwise (failure cause is logged).
|
|
|
|
*/
|
|
|
|
static bool selinux_load_split_policy() {
|
|
|
|
// IMPLEMENTATION NOTE: Split policy consists of three CIL files:
|
|
|
|
// * platform -- policy needed due to logic contained in the system image,
|
|
|
|
// * non-platform -- policy needed due to logic contained in the vendor image,
|
|
|
|
// * mapping -- mapping policy which helps preserve forward-compatibility of non-platform policy
|
|
|
|
// with newer versions of platform policy.
|
|
|
|
//
|
|
|
|
// secilc is invoked to compile the above three policy files into a single monolithic policy
|
|
|
|
// file. This file is then loaded into the kernel.
|
|
|
|
|
2017-03-07 23:12:01 +01:00
|
|
|
// Load precompiled policy from vendor image, if a matching policy is found there. The policy
|
|
|
|
// must match the platform policy on the system image.
|
|
|
|
std::string precompiled_sepolicy_file;
|
|
|
|
if (selinux_find_precompiled_split_policy(&precompiled_sepolicy_file)) {
|
|
|
|
android::base::unique_fd fd(
|
|
|
|
open(precompiled_sepolicy_file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
|
|
|
|
if (fd != -1) {
|
|
|
|
if (selinux_android_load_policy_from_fd(fd, precompiled_sepolicy_file.c_str()) < 0) {
|
|
|
|
LOG(ERROR) << "Failed to load SELinux policy from " << precompiled_sepolicy_file;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// No suitable precompiled policy could be loaded
|
|
|
|
|
2017-02-28 21:32:20 +01:00
|
|
|
LOG(INFO) << "Compiling SELinux policy";
|
|
|
|
|
2017-03-09 19:56:58 +01:00
|
|
|
// Determine the highest policy language version supported by the kernel
|
|
|
|
set_selinuxmnt("/sys/fs/selinux");
|
|
|
|
int max_policy_version = security_policyvers();
|
|
|
|
if (max_policy_version == -1) {
|
|
|
|
PLOG(ERROR) << "Failed to determine highest policy version supported by kernel";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-02-28 21:32:20 +01:00
|
|
|
// We store the output of the compilation on /dev because this is the most convenient tmpfs
|
|
|
|
// storage mount available this early in the boot sequence.
|
|
|
|
char compiled_sepolicy[] = "/dev/sepolicy.XXXXXX";
|
|
|
|
android::base::unique_fd compiled_sepolicy_fd(mkostemp(compiled_sepolicy, O_CLOEXEC));
|
|
|
|
if (compiled_sepolicy_fd < 0) {
|
|
|
|
PLOG(ERROR) << "Failed to create temporary file " << compiled_sepolicy;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-03-09 19:56:58 +01:00
|
|
|
// clang-format off
|
|
|
|
const char* compile_args[] = {
|
|
|
|
"/system/bin/secilc",
|
|
|
|
plat_policy_cil_file,
|
|
|
|
"-M", "true",
|
|
|
|
// Target the highest policy language version supported by the kernel
|
|
|
|
"-c", std::to_string(max_policy_version).c_str(),
|
2017-04-06 18:51:23 +02:00
|
|
|
"/system/etc/selinux/mapping_sepolicy.cil",
|
2017-03-09 19:56:58 +01:00
|
|
|
"/vendor/etc/selinux/nonplat_sepolicy.cil",
|
|
|
|
"-o", compiled_sepolicy,
|
|
|
|
// We don't care about file_contexts output by the compiler
|
|
|
|
"-f", "/sys/fs/selinux/null", // /dev/null is not yet available
|
|
|
|
nullptr};
|
|
|
|
// clang-format on
|
2017-02-28 21:32:20 +01:00
|
|
|
|
|
|
|
if (!fork_execve_and_wait_for_completion(compile_args[0], (char**)compile_args, (char**)ENV)) {
|
|
|
|
unlink(compiled_sepolicy);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
unlink(compiled_sepolicy);
|
|
|
|
|
|
|
|
LOG(INFO) << "Loading compiled SELinux policy";
|
|
|
|
if (selinux_android_load_policy_from_fd(compiled_sepolicy_fd, compiled_sepolicy) < 0) {
|
|
|
|
LOG(ERROR) << "Failed to load SELinux policy from " << compiled_sepolicy;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Loads SELinux policy from a monolithic file.
|
|
|
|
*
|
|
|
|
* Returns true upon success, false otherwise (failure cause is logged).
|
|
|
|
*/
|
|
|
|
static bool selinux_load_monolithic_policy() {
|
|
|
|
LOG(VERBOSE) << "Loading SELinux policy from monolithic file";
|
|
|
|
if (selinux_android_load_policy() < 0) {
|
|
|
|
PLOG(ERROR) << "Failed to load monolithic SELinux policy";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Loads SELinux policy into the kernel.
|
|
|
|
*
|
|
|
|
* Returns true upon success, false otherwise (failure cause is logged).
|
|
|
|
*/
|
|
|
|
static bool selinux_load_policy() {
|
|
|
|
return selinux_is_split_policy_device() ? selinux_load_split_policy()
|
|
|
|
: selinux_load_monolithic_policy();
|
|
|
|
}
|
|
|
|
|
2015-04-24 20:38:17 +02:00
|
|
|
static void selinux_initialize(bool in_kernel_domain) {
|
2015-03-28 07:20:44 +01:00
|
|
|
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);
|
2014-02-12 22:17:00 +01:00
|
|
|
|
2015-04-24 20:38:17 +02:00
|
|
|
if (in_kernel_domain) {
|
2017-02-28 21:32:20 +01:00
|
|
|
LOG(INFO) << "Loading SELinux policy";
|
|
|
|
if (!selinux_load_policy()) {
|
|
|
|
panic();
|
2015-04-24 20:38:17 +02:00
|
|
|
}
|
|
|
|
|
2015-04-28 21:39:41 +02:00
|
|
|
bool kernel_enforcing = (security_getenforce() == 1);
|
2015-04-24 20:38:17 +02:00
|
|
|
bool is_enforcing = selinux_is_enforcing();
|
2015-04-28 21:39:41 +02:00
|
|
|
if (kernel_enforcing != is_enforcing) {
|
|
|
|
if (security_setenforce(is_enforcing)) {
|
2016-06-25 00:12:21 +02:00
|
|
|
PLOG(ERROR) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
|
2015-04-28 21:39:41 +02:00
|
|
|
security_failure();
|
|
|
|
}
|
|
|
|
}
|
2015-03-28 07:20:44 +01:00
|
|
|
|
2017-05-05 03:17:33 +02:00
|
|
|
std::string err;
|
|
|
|
if (!WriteFile("/sys/fs/selinux/checkreqprot", "0", &err)) {
|
|
|
|
LOG(ERROR) << err;
|
2015-04-25 23:10:03 +02:00
|
|
|
security_failure();
|
|
|
|
}
|
|
|
|
|
2016-11-29 20:20:58 +01:00
|
|
|
// init's first stage can't set properties, so pass the time to the second stage.
|
2017-07-06 23:20:11 +02:00
|
|
|
setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
|
2015-04-24 20:38:17 +02:00
|
|
|
} else {
|
|
|
|
selinux_init_all_handles();
|
|
|
|
}
|
2013-06-25 02:41:40 +02:00
|
|
|
}
|
|
|
|
|
2017-05-03 20:52:20 +02:00
|
|
|
// The files and directories that were created before initial sepolicy load or
|
|
|
|
// files on ramdisk need to have their security context restored to the proper
|
|
|
|
// value. This must happen before /dev is populated by ueventd.
|
2017-03-29 19:31:26 +02:00
|
|
|
static void selinux_restore_context() {
|
|
|
|
LOG(INFO) << "Running restorecon...";
|
2017-05-08 22:38:15 +02:00
|
|
|
selinux_android_restorecon("/dev", 0);
|
|
|
|
selinux_android_restorecon("/dev/kmsg", 0);
|
2017-03-28 22:07:15 +02:00
|
|
|
if constexpr (WORLD_WRITABLE_KMSG) {
|
2017-05-08 22:38:15 +02:00
|
|
|
selinux_android_restorecon("/dev/kmsg_debug", 0);
|
2017-03-28 22:07:15 +02:00
|
|
|
}
|
2017-05-08 22:38:15 +02:00
|
|
|
selinux_android_restorecon("/dev/socket", 0);
|
|
|
|
selinux_android_restorecon("/dev/random", 0);
|
|
|
|
selinux_android_restorecon("/dev/urandom", 0);
|
|
|
|
selinux_android_restorecon("/dev/__properties__", 0);
|
|
|
|
selinux_android_restorecon("/plat_property_contexts", 0);
|
|
|
|
selinux_android_restorecon("/nonplat_property_contexts", 0);
|
|
|
|
selinux_android_restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE);
|
|
|
|
selinux_android_restorecon("/dev/device-mapper", 0);
|
2017-05-03 20:52:20 +02:00
|
|
|
|
2017-06-09 22:25:46 +02:00
|
|
|
selinux_android_restorecon("/sbin/mke2fs_static", 0);
|
|
|
|
selinux_android_restorecon("/sbin/e2fsdroid_static", 0);
|
2017-03-29 19:31:26 +02:00
|
|
|
}
|
|
|
|
|
2016-06-29 15:30:00 +02:00
|
|
|
// Set the UDC controller for the ConfigFS USB Gadgets.
|
|
|
|
// Read the UDC controller in use from "/sys/class/udc".
|
|
|
|
// In case of multiple UDC controllers select the first one.
|
|
|
|
static void set_usb_controller() {
|
|
|
|
std::unique_ptr<DIR, decltype(&closedir)>dir(opendir("/sys/class/udc"), closedir);
|
|
|
|
if (!dir) return;
|
|
|
|
|
|
|
|
dirent* dp;
|
|
|
|
while ((dp = readdir(dir.get())) != nullptr) {
|
|
|
|
if (dp->d_name[0] == '.') continue;
|
|
|
|
|
|
|
|
property_set("sys.usb.controller", dp->d_name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-10 23:46:38 +01:00
|
|
|
static void install_reboot_signal_handlers() {
|
|
|
|
// Instead of panic'ing the kernel as is the default behavior when init crashes,
|
|
|
|
// we prefer to reboot to bootloader on development builds, as this will prevent
|
|
|
|
// boot looping bad configurations and allow both developers and test farms to easily
|
|
|
|
// recover.
|
|
|
|
struct sigaction action;
|
|
|
|
memset(&action, 0, sizeof(action));
|
|
|
|
sigfillset(&action.sa_mask);
|
|
|
|
action.sa_handler = [](int) {
|
|
|
|
// panic() reboots to bootloader
|
|
|
|
panic();
|
|
|
|
};
|
|
|
|
action.sa_flags = SA_RESTART;
|
|
|
|
sigaction(SIGABRT, &action, nullptr);
|
|
|
|
sigaction(SIGBUS, &action, nullptr);
|
|
|
|
sigaction(SIGFPE, &action, nullptr);
|
|
|
|
sigaction(SIGILL, &action, nullptr);
|
|
|
|
sigaction(SIGSEGV, &action, nullptr);
|
|
|
|
#if defined(SIGSTKFLT)
|
|
|
|
sigaction(SIGSTKFLT, &action, nullptr);
|
|
|
|
#endif
|
|
|
|
sigaction(SIGSYS, &action, nullptr);
|
|
|
|
sigaction(SIGTRAP, &action, nullptr);
|
|
|
|
}
|
|
|
|
|
2015-02-07 05:15:18 +01:00
|
|
|
int main(int argc, char** argv) {
|
2015-03-28 07:20:44 +01:00
|
|
|
if (!strcmp(basename(argv[0]), "ueventd")) {
|
2010-04-21 21:04:20 +02:00
|
|
|
return ueventd_main(argc, argv);
|
2015-03-28 07:20:44 +01:00
|
|
|
}
|
2010-04-21 21:04:20 +02:00
|
|
|
|
2015-03-28 07:20:44 +01:00
|
|
|
if (!strcmp(basename(argv[0]), "watchdogd")) {
|
2012-06-14 06:51:56 +02:00
|
|
|
return watchdogd_main(argc, argv);
|
2015-03-28 07:20:44 +01:00
|
|
|
}
|
2012-06-14 06:51:56 +02:00
|
|
|
|
2017-03-10 23:46:38 +01:00
|
|
|
if (REBOOT_BOOTLOADER_ON_PANIC) {
|
|
|
|
install_reboot_signal_handlers();
|
|
|
|
}
|
|
|
|
|
2015-03-18 04:01:13 +01:00
|
|
|
add_environment("PATH", _PATH_DEFPATH);
|
|
|
|
|
2016-11-11 02:43:47 +01:00
|
|
|
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
|
2015-04-26 00:50:03 +02:00
|
|
|
|
|
|
|
if (is_first_stage) {
|
init: cleanup is_first_stage conditionals
A recent change to the is_first_stage conditionals created a unneeded
else { } block as both the code in the else { } block and any code
that runs after it are both in the second stage of init. A first step
to clean this up is to remove this else block.
Secondly, given the above confusion, it makes sense to simplify the two
if (is_first_stage) conditions into one, which only now requires
duplicating one line to initialize logging and the actual "init
first/second stage started!" logs.
Lastly, there are a few commands ran at the beginning of both init
stages that do not need to be,
* boot_clock::time_point start_time = boot_clock::now();
This is only used in the first stage so keep it there
* umask(0);
umasks are preserved across execve() so it only needs to be set in the
first stage
* chmod("/proc/cmdline", 0440);
This needs to be moved until after /proc is mounted in the first
stage, but otherwise only needs to be done once
Test: Boot bullhead, check umask, check cmdline permissions, check
boot time property
Change-Id: Idb7df1d4330960ce282d9609f5c62281ee2638b9
2017-03-17 02:08:56 +01:00
|
|
|
boot_clock::time_point start_time = boot_clock::now();
|
|
|
|
|
|
|
|
// Clear the umask.
|
|
|
|
umask(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.
|
2015-04-26 02:42:52 +02:00
|
|
|
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
|
|
|
|
mkdir("/dev/pts", 0755);
|
|
|
|
mkdir("/dev/socket", 0755);
|
|
|
|
mount("devpts", "/dev/pts", "devpts", 0, NULL);
|
2015-11-08 01:52:17 +01:00
|
|
|
#define MAKE_STR(x) __STRING(x)
|
|
|
|
mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
|
init: cleanup is_first_stage conditionals
A recent change to the is_first_stage conditionals created a unneeded
else { } block as both the code in the else { } block and any code
that runs after it are both in the second stage of init. A first step
to clean this up is to remove this else block.
Secondly, given the above confusion, it makes sense to simplify the two
if (is_first_stage) conditions into one, which only now requires
duplicating one line to initialize logging and the actual "init
first/second stage started!" logs.
Lastly, there are a few commands ran at the beginning of both init
stages that do not need to be,
* boot_clock::time_point start_time = boot_clock::now();
This is only used in the first stage so keep it there
* umask(0);
umasks are preserved across execve() so it only needs to be set in the
first stage
* chmod("/proc/cmdline", 0440);
This needs to be moved until after /proc is mounted in the first
stage, but otherwise only needs to be done once
Test: Boot bullhead, check umask, check cmdline permissions, check
boot time property
Change-Id: Idb7df1d4330960ce282d9609f5c62281ee2638b9
2017-03-17 02:08:56 +01:00
|
|
|
// Don't expose the raw commandline to unprivileged processes.
|
|
|
|
chmod("/proc/cmdline", 0440);
|
2016-10-29 21:20:00 +02:00
|
|
|
gid_t groups[] = { AID_READPROC };
|
|
|
|
setgroups(arraysize(groups), groups);
|
2015-04-26 00:50:03 +02:00
|
|
|
mount("sysfs", "/sys", "sysfs", 0, NULL);
|
2016-03-03 19:40:12 +01:00
|
|
|
mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
|
2017-03-28 22:07:15 +02:00
|
|
|
|
2016-07-26 18:32:33 +02:00
|
|
|
mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
|
2017-03-28 22:07:15 +02:00
|
|
|
|
|
|
|
if constexpr (WORLD_WRITABLE_KMSG) {
|
2017-08-11 22:22:37 +02:00
|
|
|
mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11));
|
2017-03-28 22:07:15 +02:00
|
|
|
}
|
|
|
|
|
2016-11-18 23:58:40 +01:00
|
|
|
mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
|
|
|
|
mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
|
2008-10-21 16:00:00 +02:00
|
|
|
|
init: cleanup is_first_stage conditionals
A recent change to the is_first_stage conditionals created a unneeded
else { } block as both the code in the else { } block and any code
that runs after it are both in the second stage of init. A first step
to clean this up is to remove this else block.
Secondly, given the above confusion, it makes sense to simplify the two
if (is_first_stage) conditions into one, which only now requires
duplicating one line to initialize logging and the actual "init
first/second stage started!" logs.
Lastly, there are a few commands ran at the beginning of both init
stages that do not need to be,
* boot_clock::time_point start_time = boot_clock::now();
This is only used in the first stage so keep it there
* umask(0);
umasks are preserved across execve() so it only needs to be set in the
first stage
* chmod("/proc/cmdline", 0440);
This needs to be moved until after /proc is mounted in the first
stage, but otherwise only needs to be done once
Test: Boot bullhead, check umask, check cmdline permissions, check
boot time property
Change-Id: Idb7df1d4330960ce282d9609f5c62281ee2638b9
2017-03-17 02:08:56 +01:00
|
|
|
// Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
|
|
|
|
// talk to the outside world...
|
|
|
|
InitKernelLogging(argv);
|
2015-03-28 07:20:44 +01:00
|
|
|
|
init: cleanup is_first_stage conditionals
A recent change to the is_first_stage conditionals created a unneeded
else { } block as both the code in the else { } block and any code
that runs after it are both in the second stage of init. A first step
to clean this up is to remove this else block.
Secondly, given the above confusion, it makes sense to simplify the two
if (is_first_stage) conditions into one, which only now requires
duplicating one line to initialize logging and the actual "init
first/second stage started!" logs.
Lastly, there are a few commands ran at the beginning of both init
stages that do not need to be,
* boot_clock::time_point start_time = boot_clock::now();
This is only used in the first stage so keep it there
* umask(0);
umasks are preserved across execve() so it only needs to be set in the
first stage
* chmod("/proc/cmdline", 0440);
This needs to be moved until after /proc is mounted in the first
stage, but otherwise only needs to be done once
Test: Boot bullhead, check umask, check cmdline permissions, check
boot time property
Change-Id: Idb7df1d4330960ce282d9609f5c62281ee2638b9
2017-03-17 02:08:56 +01:00
|
|
|
LOG(INFO) << "init first stage started!";
|
2016-10-28 21:22:32 +02:00
|
|
|
|
2017-04-17 16:17:09 +02:00
|
|
|
if (!DoFirstStageMount()) {
|
2017-02-09 05:27:12 +01:00
|
|
|
LOG(ERROR) << "Failed to mount required partitions early ...";
|
|
|
|
panic();
|
|
|
|
}
|
2016-10-28 21:22:32 +02:00
|
|
|
|
2017-04-24 12:36:25 +02:00
|
|
|
SetInitAvbVersionInRecovery();
|
|
|
|
|
2017-07-18 00:14:02 +02:00
|
|
|
// Enable seccomp if global boot option was passed (otherwise it is enabled in zygote).
|
|
|
|
global_seccomp();
|
|
|
|
|
2016-11-11 02:43:47 +01:00
|
|
|
// Set up SELinux, loading the SELinux policy.
|
2016-10-28 21:22:32 +02:00
|
|
|
selinux_initialize(true);
|
|
|
|
|
2016-11-11 02:43:47 +01:00
|
|
|
// We're in the kernel domain, so re-exec init to transition to the init domain now
|
2016-10-28 21:22:32 +02:00
|
|
|
// that the SELinux policy has been loaded.
|
2017-05-08 22:38:15 +02:00
|
|
|
if (selinux_android_restorecon("/init", 0) == -1) {
|
2016-10-28 21:22:32 +02:00
|
|
|
PLOG(ERROR) << "restorecon failed";
|
|
|
|
security_failure();
|
|
|
|
}
|
2016-11-11 02:43:47 +01:00
|
|
|
|
|
|
|
setenv("INIT_SECOND_STAGE", "true", 1);
|
|
|
|
|
2017-01-26 20:55:44 +01:00
|
|
|
static constexpr uint32_t kNanosecondsPerMillisecond = 1e6;
|
|
|
|
uint64_t start_ms = start_time.time_since_epoch().count() / kNanosecondsPerMillisecond;
|
2017-06-23 01:50:31 +02:00
|
|
|
setenv("INIT_STARTED_AT", std::to_string(start_ms).c_str(), 1);
|
2016-11-11 02:43:47 +01:00
|
|
|
|
2016-10-28 21:22:32 +02:00
|
|
|
char* path = argv[0];
|
2016-11-11 02:43:47 +01:00
|
|
|
char* args[] = { path, nullptr };
|
init: cleanup is_first_stage conditionals
A recent change to the is_first_stage conditionals created a unneeded
else { } block as both the code in the else { } block and any code
that runs after it are both in the second stage of init. A first step
to clean this up is to remove this else block.
Secondly, given the above confusion, it makes sense to simplify the two
if (is_first_stage) conditions into one, which only now requires
duplicating one line to initialize logging and the actual "init
first/second stage started!" logs.
Lastly, there are a few commands ran at the beginning of both init
stages that do not need to be,
* boot_clock::time_point start_time = boot_clock::now();
This is only used in the first stage so keep it there
* umask(0);
umasks are preserved across execve() so it only needs to be set in the
first stage
* chmod("/proc/cmdline", 0440);
This needs to be moved until after /proc is mounted in the first
stage, but otherwise only needs to be done once
Test: Boot bullhead, check umask, check cmdline permissions, check
boot time property
Change-Id: Idb7df1d4330960ce282d9609f5c62281ee2638b9
2017-03-17 02:08:56 +01:00
|
|
|
execv(path, args);
|
2011-09-28 18:55:31 +02:00
|
|
|
|
init: cleanup is_first_stage conditionals
A recent change to the is_first_stage conditionals created a unneeded
else { } block as both the code in the else { } block and any code
that runs after it are both in the second stage of init. A first step
to clean this up is to remove this else block.
Secondly, given the above confusion, it makes sense to simplify the two
if (is_first_stage) conditions into one, which only now requires
duplicating one line to initialize logging and the actual "init
first/second stage started!" logs.
Lastly, there are a few commands ran at the beginning of both init
stages that do not need to be,
* boot_clock::time_point start_time = boot_clock::now();
This is only used in the first stage so keep it there
* umask(0);
umasks are preserved across execve() so it only needs to be set in the
first stage
* chmod("/proc/cmdline", 0440);
This needs to be moved until after /proc is mounted in the first
stage, but otherwise only needs to be done once
Test: Boot bullhead, check umask, check cmdline permissions, check
boot time property
Change-Id: Idb7df1d4330960ce282d9609f5c62281ee2638b9
2017-03-17 02:08:56 +01:00
|
|
|
// execv() only returns if an error happened, in which case we
|
|
|
|
// panic and never fall through this conditional.
|
|
|
|
PLOG(ERROR) << "execv(\"" << path << "\") failed";
|
|
|
|
security_failure();
|
|
|
|
}
|
2011-12-19 20:21:32 +01:00
|
|
|
|
init: cleanup is_first_stage conditionals
A recent change to the is_first_stage conditionals created a unneeded
else { } block as both the code in the else { } block and any code
that runs after it are both in the second stage of init. A first step
to clean this up is to remove this else block.
Secondly, given the above confusion, it makes sense to simplify the two
if (is_first_stage) conditions into one, which only now requires
duplicating one line to initialize logging and the actual "init
first/second stage started!" logs.
Lastly, there are a few commands ran at the beginning of both init
stages that do not need to be,
* boot_clock::time_point start_time = boot_clock::now();
This is only used in the first stage so keep it there
* umask(0);
umasks are preserved across execve() so it only needs to be set in the
first stage
* chmod("/proc/cmdline", 0440);
This needs to be moved until after /proc is mounted in the first
stage, but otherwise only needs to be done once
Test: Boot bullhead, check umask, check cmdline permissions, check
boot time property
Change-Id: Idb7df1d4330960ce282d9609f5c62281ee2638b9
2017-03-17 02:08:56 +01:00
|
|
|
// At this point we're in the second stage of init.
|
|
|
|
InitKernelLogging(argv);
|
|
|
|
LOG(INFO) << "init second stage started!";
|
2015-04-26 02:42:52 +02:00
|
|
|
|
2017-05-02 23:44:39 +02:00
|
|
|
// Set up a session keyring that all processes will have access to. It
|
|
|
|
// will hold things like FBE encryption keys. No process should override
|
|
|
|
// its session keyring.
|
2017-05-10 02:09:06 +02:00
|
|
|
keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 1);
|
2017-05-02 23:44:39 +02:00
|
|
|
|
init: cleanup is_first_stage conditionals
A recent change to the is_first_stage conditionals created a unneeded
else { } block as both the code in the else { } block and any code
that runs after it are both in the second stage of init. A first step
to clean this up is to remove this else block.
Secondly, given the above confusion, it makes sense to simplify the two
if (is_first_stage) conditions into one, which only now requires
duplicating one line to initialize logging and the actual "init
first/second stage started!" logs.
Lastly, there are a few commands ran at the beginning of both init
stages that do not need to be,
* boot_clock::time_point start_time = boot_clock::now();
This is only used in the first stage so keep it there
* umask(0);
umasks are preserved across execve() so it only needs to be set in the
first stage
* chmod("/proc/cmdline", 0440);
This needs to be moved until after /proc is mounted in the first
stage, but otherwise only needs to be done once
Test: Boot bullhead, check umask, check cmdline permissions, check
boot time property
Change-Id: Idb7df1d4330960ce282d9609f5c62281ee2638b9
2017-03-17 02:08:56 +01:00
|
|
|
// Indicate that booting is in progress to background fw loaders, etc.
|
|
|
|
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
|
2015-02-28 15:39:11 +01:00
|
|
|
|
init: cleanup is_first_stage conditionals
A recent change to the is_first_stage conditionals created a unneeded
else { } block as both the code in the else { } block and any code
that runs after it are both in the second stage of init. A first step
to clean this up is to remove this else block.
Secondly, given the above confusion, it makes sense to simplify the two
if (is_first_stage) conditions into one, which only now requires
duplicating one line to initialize logging and the actual "init
first/second stage started!" logs.
Lastly, there are a few commands ran at the beginning of both init
stages that do not need to be,
* boot_clock::time_point start_time = boot_clock::now();
This is only used in the first stage so keep it there
* umask(0);
umasks are preserved across execve() so it only needs to be set in the
first stage
* chmod("/proc/cmdline", 0440);
This needs to be moved until after /proc is mounted in the first
stage, but otherwise only needs to be done once
Test: Boot bullhead, check umask, check cmdline permissions, check
boot time property
Change-Id: Idb7df1d4330960ce282d9609f5c62281ee2638b9
2017-03-17 02:08:56 +01:00
|
|
|
property_init();
|
2016-11-29 20:20:58 +01:00
|
|
|
|
init: cleanup is_first_stage conditionals
A recent change to the is_first_stage conditionals created a unneeded
else { } block as both the code in the else { } block and any code
that runs after it are both in the second stage of init. A first step
to clean this up is to remove this else block.
Secondly, given the above confusion, it makes sense to simplify the two
if (is_first_stage) conditions into one, which only now requires
duplicating one line to initialize logging and the actual "init
first/second stage started!" logs.
Lastly, there are a few commands ran at the beginning of both init
stages that do not need to be,
* boot_clock::time_point start_time = boot_clock::now();
This is only used in the first stage so keep it there
* umask(0);
umasks are preserved across execve() so it only needs to be set in the
first stage
* chmod("/proc/cmdline", 0440);
This needs to be moved until after /proc is mounted in the first
stage, but otherwise only needs to be done once
Test: Boot bullhead, check umask, check cmdline permissions, check
boot time property
Change-Id: Idb7df1d4330960ce282d9609f5c62281ee2638b9
2017-03-17 02:08:56 +01:00
|
|
|
// 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();
|
2017-02-14 15:06:20 +01:00
|
|
|
|
init: cleanup is_first_stage conditionals
A recent change to the is_first_stage conditionals created a unneeded
else { } block as both the code in the else { } block and any code
that runs after it are both in the second stage of init. A first step
to clean this up is to remove this else block.
Secondly, given the above confusion, it makes sense to simplify the two
if (is_first_stage) conditions into one, which only now requires
duplicating one line to initialize logging and the actual "init
first/second stage started!" logs.
Lastly, there are a few commands ran at the beginning of both init
stages that do not need to be,
* boot_clock::time_point start_time = boot_clock::now();
This is only used in the first stage so keep it there
* umask(0);
umasks are preserved across execve() so it only needs to be set in the
first stage
* chmod("/proc/cmdline", 0440);
This needs to be moved until after /proc is mounted in the first
stage, but otherwise only needs to be done once
Test: Boot bullhead, check umask, check cmdline permissions, check
boot time property
Change-Id: Idb7df1d4330960ce282d9609f5c62281ee2638b9
2017-03-17 02:08:56 +01:00
|
|
|
// Propagate the kernel variables to internal variables
|
|
|
|
// used by init as well as the current required properties.
|
|
|
|
export_kernel_boot_props();
|
2016-11-11 02:43:47 +01:00
|
|
|
|
init: cleanup is_first_stage conditionals
A recent change to the is_first_stage conditionals created a unneeded
else { } block as both the code in the else { } block and any code
that runs after it are both in the second stage of init. A first step
to clean this up is to remove this else block.
Secondly, given the above confusion, it makes sense to simplify the two
if (is_first_stage) conditions into one, which only now requires
duplicating one line to initialize logging and the actual "init
first/second stage started!" logs.
Lastly, there are a few commands ran at the beginning of both init
stages that do not need to be,
* boot_clock::time_point start_time = boot_clock::now();
This is only used in the first stage so keep it there
* umask(0);
umasks are preserved across execve() so it only needs to be set in the
first stage
* chmod("/proc/cmdline", 0440);
This needs to be moved until after /proc is mounted in the first
stage, but otherwise only needs to be done once
Test: Boot bullhead, check umask, check cmdline permissions, check
boot time property
Change-Id: Idb7df1d4330960ce282d9609f5c62281ee2638b9
2017-03-17 02:08:56 +01:00
|
|
|
// Make the time that init started available for bootstat to log.
|
|
|
|
property_set("ro.boottime.init", getenv("INIT_STARTED_AT"));
|
|
|
|
property_set("ro.boottime.init.selinux", getenv("INIT_SELINUX_TOOK"));
|
|
|
|
|
|
|
|
// Set libavb version for Framework-only OTA match in Treble build.
|
2017-04-13 15:17:48 +02:00
|
|
|
const char* avb_version = getenv("INIT_AVB_VERSION");
|
|
|
|
if (avb_version) property_set("ro.boot.avb_version", avb_version);
|
init: cleanup is_first_stage conditionals
A recent change to the is_first_stage conditionals created a unneeded
else { } block as both the code in the else { } block and any code
that runs after it are both in the second stage of init. A first step
to clean this up is to remove this else block.
Secondly, given the above confusion, it makes sense to simplify the two
if (is_first_stage) conditions into one, which only now requires
duplicating one line to initialize logging and the actual "init
first/second stage started!" logs.
Lastly, there are a few commands ran at the beginning of both init
stages that do not need to be,
* boot_clock::time_point start_time = boot_clock::now();
This is only used in the first stage so keep it there
* umask(0);
umasks are preserved across execve() so it only needs to be set in the
first stage
* chmod("/proc/cmdline", 0440);
This needs to be moved until after /proc is mounted in the first
stage, but otherwise only needs to be done once
Test: Boot bullhead, check umask, check cmdline permissions, check
boot time property
Change-Id: Idb7df1d4330960ce282d9609f5c62281ee2638b9
2017-03-17 02:08:56 +01:00
|
|
|
|
|
|
|
// Clean up our environment.
|
|
|
|
unsetenv("INIT_SECOND_STAGE");
|
|
|
|
unsetenv("INIT_STARTED_AT");
|
|
|
|
unsetenv("INIT_SELINUX_TOOK");
|
2017-04-13 15:17:48 +02:00
|
|
|
unsetenv("INIT_AVB_VERSION");
|
init: cleanup is_first_stage conditionals
A recent change to the is_first_stage conditionals created a unneeded
else { } block as both the code in the else { } block and any code
that runs after it are both in the second stage of init. A first step
to clean this up is to remove this else block.
Secondly, given the above confusion, it makes sense to simplify the two
if (is_first_stage) conditions into one, which only now requires
duplicating one line to initialize logging and the actual "init
first/second stage started!" logs.
Lastly, there are a few commands ran at the beginning of both init
stages that do not need to be,
* boot_clock::time_point start_time = boot_clock::now();
This is only used in the first stage so keep it there
* umask(0);
umasks are preserved across execve() so it only needs to be set in the
first stage
* chmod("/proc/cmdline", 0440);
This needs to be moved until after /proc is mounted in the first
stage, but otherwise only needs to be done once
Test: Boot bullhead, check umask, check cmdline permissions, check
boot time property
Change-Id: Idb7df1d4330960ce282d9609f5c62281ee2638b9
2017-03-17 02:08:56 +01:00
|
|
|
|
|
|
|
// Now set up SELinux for second stage.
|
|
|
|
selinux_initialize(false);
|
2017-03-29 19:31:26 +02:00
|
|
|
selinux_restore_context();
|
2012-01-13 14:48:47 +01:00
|
|
|
|
2015-04-25 06:13:44 +02:00
|
|
|
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
|
|
|
if (epoll_fd == -1) {
|
2016-06-25 00:12:21 +02:00
|
|
|
PLOG(ERROR) << "epoll_create1 failed";
|
2015-04-25 06:13:44 +02:00
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
signal_handler_init();
|
2015-04-25 02:43:21 +02:00
|
|
|
|
2014-06-17 00:06:21 +02:00
|
|
|
property_load_boot_defaults();
|
2015-12-07 13:33:58 +01:00
|
|
|
export_oem_lock_status();
|
2015-04-25 03:50:30 +02:00
|
|
|
start_property_service();
|
2016-06-29 15:30:00 +02:00
|
|
|
set_usb_controller();
|
2011-12-16 23:18:06 +01:00
|
|
|
|
2015-08-26 20:43:36 +02:00
|
|
|
const BuiltinFunctionMap function_map;
|
|
|
|
Action::set_function_map(&function_map);
|
|
|
|
|
2017-04-20 00:31:58 +02:00
|
|
|
ActionManager& am = ActionManager::GetInstance();
|
2017-07-28 01:20:58 +02:00
|
|
|
ServiceList& sm = ServiceList::GetInstance();
|
2017-04-20 00:31:58 +02:00
|
|
|
|
2017-07-27 21:54:48 +02:00
|
|
|
LoadBootScripts(am, sm);
|
2008-10-21 16:00:00 +02:00
|
|
|
|
2017-03-13 20:24:49 +01:00
|
|
|
// Turning this on and letting the INFO logging be discarded adds 0.2s to
|
|
|
|
// Nexus 9 boot time, so it's disabled by default.
|
2017-04-20 00:31:58 +02:00
|
|
|
if (false) DumpState();
|
2015-07-24 02:53:11 +02:00
|
|
|
|
|
|
|
am.QueueEventTrigger("early-init");
|
2008-10-21 16:00:00 +02:00
|
|
|
|
2015-04-25 03:50:30 +02:00
|
|
|
// Queue an action that waits for coldboot done so we know ueventd has set up all of /dev...
|
2015-07-24 02:53:11 +02:00
|
|
|
am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done");
|
2015-04-25 03:50:30 +02:00
|
|
|
// ... so that we can start queuing up actions that require stuff from /dev.
|
2015-07-24 02:53:11 +02:00
|
|
|
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
|
2016-03-26 00:49:05 +01:00
|
|
|
am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits");
|
2017-01-20 03:03:34 +01:00
|
|
|
am.QueueBuiltinAction(set_kptr_restrict_action, "set_kptr_restrict");
|
2015-07-24 02:53:11 +02:00
|
|
|
am.QueueBuiltinAction(keychord_init_action, "keychord_init");
|
|
|
|
am.QueueBuiltinAction(console_init_action, "console_init");
|
2008-10-21 16:00:00 +02:00
|
|
|
|
2015-03-28 07:20:44 +01:00
|
|
|
// Trigger all the boot actions to get us started.
|
2015-07-24 02:53:11 +02:00
|
|
|
am.QueueEventTrigger("init");
|
2011-08-25 00:28:23 +02:00
|
|
|
|
2015-02-07 05:15:18 +01:00
|
|
|
// Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
|
|
|
|
// wasn't ready immediately after wait_for_coldboot_done
|
2015-07-24 02:53:11 +02:00
|
|
|
am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
|
2008-10-21 16:00:00 +02:00
|
|
|
|
2015-02-07 05:15:18 +01:00
|
|
|
// Don't mount filesystems or start core system services in charger mode.
|
2017-03-29 01:40:41 +02:00
|
|
|
std::string bootmode = GetProperty("ro.bootmode", "");
|
2015-07-24 19:11:05 +02:00
|
|
|
if (bootmode == "charger") {
|
2015-07-24 02:53:11 +02:00
|
|
|
am.QueueEventTrigger("charger");
|
2011-08-25 00:28:23 +02:00
|
|
|
} else {
|
2015-07-24 02:53:11 +02:00
|
|
|
am.QueueEventTrigger("late-init");
|
2011-08-25 00:28:23 +02:00
|
|
|
}
|
2008-10-21 16:00:00 +02:00
|
|
|
|
2015-02-07 05:15:18 +01:00
|
|
|
// Run all property triggers based on current state of the properties.
|
2015-07-24 02:53:11 +02:00
|
|
|
am.QueueBuiltinAction(queue_property_triggers_action, "queue_property_triggers");
|
2010-04-14 04:52:01 +02:00
|
|
|
|
2015-04-25 03:50:30 +02:00
|
|
|
while (true) {
|
2017-03-24 00:54:38 +01:00
|
|
|
// By default, sleep until something happens.
|
|
|
|
int epoll_timeout_ms = -1;
|
|
|
|
|
2017-07-28 23:48:41 +02:00
|
|
|
if (!(waiting_for_prop || Service::is_exec_service_running())) {
|
2015-07-24 02:53:11 +02:00
|
|
|
am.ExecuteOneCommand();
|
2015-02-07 05:15:18 +01:00
|
|
|
}
|
2017-07-28 23:48:41 +02:00
|
|
|
if (!(waiting_for_prop || Service::is_exec_service_running())) {
|
2017-07-31 22:23:18 +02:00
|
|
|
if (!shutting_down) {
|
|
|
|
auto next_process_restart_time = RestartProcesses();
|
|
|
|
|
|
|
|
// If there's a process that needs restarting, wake up in time for that.
|
|
|
|
if (next_process_restart_time) {
|
|
|
|
epoll_timeout_ms = std::chrono::ceil<std::chrono::milliseconds>(
|
|
|
|
*next_process_restart_time - boot_clock::now())
|
|
|
|
.count();
|
|
|
|
if (epoll_timeout_ms < 0) epoll_timeout_ms = 0;
|
|
|
|
}
|
2017-03-24 00:54:38 +01:00
|
|
|
}
|
2016-11-11 02:43:47 +01:00
|
|
|
|
2017-03-24 00:54:38 +01:00
|
|
|
// If there's more work to do, wake up again immediately.
|
|
|
|
if (am.HasMoreCommands()) epoll_timeout_ms = 0;
|
2015-02-04 23:46:36 +01:00
|
|
|
}
|
2010-04-14 04:52:01 +02:00
|
|
|
|
2015-04-25 06:13:44 +02:00
|
|
|
epoll_event ev;
|
2016-11-11 02:43:47 +01:00
|
|
|
int nr = TEMP_FAILURE_RETRY(epoll_wait(epoll_fd, &ev, 1, epoll_timeout_ms));
|
2015-04-25 06:13:44 +02:00
|
|
|
if (nr == -1) {
|
2016-06-25 00:12:21 +02:00
|
|
|
PLOG(ERROR) << "epoll_wait failed";
|
2015-04-25 06:13:44 +02:00
|
|
|
} else if (nr == 1) {
|
|
|
|
((void (*)()) ev.data.ptr)();
|
2008-10-21 16:00:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2017-06-22 21:53:17 +02:00
|
|
|
|
|
|
|
} // namespace init
|
|
|
|
} // namespace android
|
|
|
|
|
|
|
|
int main(int argc, char** argv) {
|
|
|
|
android::init::main(argc, argv);
|
|
|
|
}
|