diff options
Diffstat (limited to 'init')
-rw-r--r-- | init/Android.mk | 130 | ||||
-rw-r--r-- | init/README.BOOTCHART | 52 | ||||
-rw-r--r-- | init/bootchart.c | 378 | ||||
-rw-r--r-- | init/bootchart.cpp | 273 | ||||
-rw-r--r-- | init/bootchart.h | 16 | ||||
-rw-r--r-- | init/builtins.cpp (renamed from init/builtins.c) | 339 | ||||
-rw-r--r-- | init/devices.cpp (renamed from init/devices.c) | 187 | ||||
-rw-r--r-- | init/devices.h | 1 | ||||
-rwxr-xr-x | init/grab-bootchart.sh | 16 | ||||
-rw-r--r-- | init/init.cpp (renamed from init/init.c) | 742 | ||||
-rw-r--r-- | init/init.h | 65 | ||||
-rw-r--r-- | init/init_parser.cpp (renamed from init/init_parser.c) | 344 | ||||
-rw-r--r-- | init/init_parser.h | 5 | ||||
-rw-r--r-- | init/init_parser_test.cpp | 134 | ||||
-rw-r--r-- | init/keychords.cpp (renamed from init/keychords.c) | 64 | ||||
-rw-r--r-- | init/keychords.h | 6 | ||||
-rw-r--r-- | init/keywords.h | 23 | ||||
-rw-r--r-- | init/log.cpp | 62 | ||||
-rw-r--r-- | init/log.h | 9 | ||||
-rw-r--r-- | init/parser.cpp (renamed from init/parser.c) | 50 | ||||
-rw-r--r-- | init/parser.h | 2 | ||||
-rw-r--r-- | init/property_service.cpp (renamed from init/property_service.c) | 264 | ||||
-rw-r--r-- | init/property_service.h | 12 | ||||
-rw-r--r-- | init/readme.txt | 256 | ||||
-rw-r--r-- | init/signal_handler.c | 165 | ||||
-rw-r--r-- | init/signal_handler.cpp | 185 | ||||
-rw-r--r-- | init/signal_handler.h | 4 | ||||
-rw-r--r-- | init/ueventd.cpp (renamed from init/ueventd.c) | 77 | ||||
-rw-r--r-- | init/ueventd.h | 12 | ||||
-rw-r--r-- | init/ueventd_parser.cpp (renamed from init/ueventd_parser.c) | 33 | ||||
-rw-r--r-- | init/util.cpp (renamed from init/util.c) | 198 | ||||
-rw-r--r-- | init/util.h | 30 | ||||
-rw-r--r-- | init/util_test.cpp | 43 | ||||
-rw-r--r-- | init/watchdogd.cpp (renamed from init/watchdogd.c) | 38 |
34 files changed, 2057 insertions, 2158 deletions
diff --git a/init/Android.mk b/init/Android.mk index 228e645..31d2fcd 100644 --- a/init/Android.mk +++ b/init/Android.mk @@ -1,73 +1,99 @@ # Copyright 2005 The Android Open Source Project LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) -LOCAL_SRC_FILES:= \ - builtins.c \ - init.c \ - devices.c \ - property_service.c \ - util.c \ - parser.c \ - keychords.c \ - signal_handler.c \ - init_parser.c \ - ueventd.c \ - ueventd_parser.c \ - watchdogd.c - -LOCAL_CFLAGS += -Wno-unused-parameter - -ifeq ($(strip $(INIT_BOOTCHART)),true) -LOCAL_SRC_FILES += bootchart.c -LOCAL_CFLAGS += -DBOOTCHART=1 -endif +# -- ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT))) -LOCAL_CFLAGS += -DALLOW_LOCAL_PROP_OVERRIDE=1 -DALLOW_DISABLE_SELINUX=1 +init_options += -DALLOW_LOCAL_PROP_OVERRIDE=1 -DALLOW_DISABLE_SELINUX=1 +else +init_options += -DALLOW_LOCAL_PROP_OVERRIDE=0 -DALLOW_DISABLE_SELINUX=0 endif -# Enable ueventd logging -#LOCAL_CFLAGS += -DLOG_UEVENTS=1 +init_options += -DLOG_UEVENTS=0 -LOCAL_MODULE:= init +init_cflags += \ + $(init_options) \ + -Wall -Wextra \ + -Wno-unused-parameter \ + -Werror \ + +init_clang := true + +# -- + +include $(CLEAR_VARS) +LOCAL_CPPFLAGS := $(init_cflags) +LOCAL_SRC_FILES:= \ + init_parser.cpp \ + log.cpp \ + parser.cpp \ + util.cpp \ +LOCAL_STATIC_LIBRARIES := libbase +LOCAL_MODULE := libinit +LOCAL_CLANG := $(init_clang) +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_CPPFLAGS := $(init_cflags) +LOCAL_SRC_FILES:= \ + bootchart.cpp \ + builtins.cpp \ + devices.cpp \ + init.cpp \ + keychords.cpp \ + property_service.cpp \ + signal_handler.cpp \ + ueventd.cpp \ + ueventd_parser.cpp \ + watchdogd.cpp \ + +LOCAL_MODULE:= init +LOCAL_C_INCLUDES += system/extras/ext4_utils LOCAL_FORCE_STATIC_EXECUTABLE := true LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT) LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_UNSTRIPPED) LOCAL_STATIC_LIBRARIES := \ - libfs_mgr \ - liblogwrap \ - libcutils \ - liblog \ - libc \ - libselinux \ - libmincrypt \ - libext4_utils_static \ - libsparse_static \ - libz - -LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk + libinit \ + libfs_mgr \ + libsquashfs_utils \ + liblogwrap \ + libcutils \ + libbase \ + libext4_utils_static \ + libutils \ + liblog \ + libc \ + libselinux \ + libmincrypt \ + libc++_static \ + libdl \ + libsparse_static \ + libz +# Create symlinks +LOCAL_POST_INSTALL_CMD := $(hide) mkdir -p $(TARGET_ROOT_OUT)/sbin; \ + ln -sf ../init $(TARGET_ROOT_OUT)/sbin/ueventd; \ + ln -sf ../init $(TARGET_ROOT_OUT)/sbin/watchdogd + +LOCAL_CLANG := $(init_clang) include $(BUILD_EXECUTABLE) -# Make a symlink from /sbin/ueventd and /sbin/watchdogd to /init -SYMLINKS := \ - $(TARGET_ROOT_OUT)/sbin/ueventd \ - $(TARGET_ROOT_OUT)/sbin/watchdogd -$(SYMLINKS): INIT_BINARY := $(LOCAL_MODULE) -$(SYMLINKS): $(LOCAL_INSTALLED_MODULE) $(LOCAL_PATH)/Android.mk - @echo "Symlink: $@ -> ../$(INIT_BINARY)" - @mkdir -p $(dir $@) - @rm -rf $@ - $(hide) ln -sf ../$(INIT_BINARY) $@ -ALL_DEFAULT_INSTALLED_MODULES += $(SYMLINKS) -# We need this so that the installed files could be picked up based on the -# local module name -ALL_MODULES.$(LOCAL_MODULE).INSTALLED := \ - $(ALL_MODULES.$(LOCAL_MODULE).INSTALLED) $(SYMLINKS) +include $(CLEAR_VARS) +LOCAL_MODULE := init_tests +LOCAL_SRC_FILES := \ + init_parser_test.cpp \ + util_test.cpp \ + +LOCAL_SHARED_LIBRARIES += \ + libcutils \ + libbase \ + +LOCAL_STATIC_LIBRARIES := libinit +LOCAL_CLANG := $(init_clang) +include $(BUILD_NATIVE_TEST) diff --git a/init/README.BOOTCHART b/init/README.BOOTCHART deleted file mode 100644 index 70cf2c3..0000000 --- a/init/README.BOOTCHART +++ /dev/null @@ -1,52 +0,0 @@ -This version of init contains code to perform "bootcharting", i.e. generating log -files that can be later processed by the tools provided by www.bootchart.org. - -To activate it, you need to define build 'init' with the INIT_BOOTCHART environment -variable defined to 'true', for example: - - touch system/init/init.c - m INIT_BOOTCHART=true - -On the emulator, use the new -bootchart <timeout> option to boot with bootcharting -activated for <timeout> seconds. - -Otherwise, flash your device, and start it. Then create a file on the /data partition -with a command like the following: - - adb shell 'echo $TIMEOUT > /data/bootchart-start' - -Where the value of $TIMEOUT corresponds to the wanted bootcharted period in seconds; -for example, to bootchart for 2 minutes, do: - - adb shell 'echo 120 > /data/bootchart-start' - -Reboot your device, bootcharting will begin and stop after the period you gave. -You can also stop the bootcharting at any moment by doing the following: - - adb shell 'echo 1 > /data/bootchart-stop' - -Note that /data/bootchart-stop is deleted automatically by init at the end of the -bootcharting. This is not the case of /data/bootchart-start, so don't forget to delete it -when you're done collecting data: - - adb shell rm /data/bootchart-start - -The log files are placed in /data/bootchart/. you must run the script tools/grab-bootchart.sh -which will use ADB to retrieve them and create a bootchart.tgz file that can be used with -the bootchart parser/renderer, or even uploaded directly to the form located at: - - http://www.bootchart.org/download.html - -NOTE: the bootchart.org webform doesn't seem to work at the moment, you can generate an - image on your machine by doing the following: - - 1/ download the sources from www.bootchart.org - 2/ unpack them - 3/ in the source directory, type 'ant' to build the bootchart program - 4/ type 'java -jar bootchart.jar /path/to/bootchart.tgz - -technical note: - -this implementation of bootcharting does use the 'bootchartd' script provided by -www.bootchart.org, but a C re-implementation that is directly compiled into our init -program. diff --git a/init/bootchart.c b/init/bootchart.c deleted file mode 100644 index f72fcaa..0000000 --- a/init/bootchart.c +++ /dev/null @@ -1,378 +0,0 @@ -/* - * 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. - */ - -/* this code is used to generate a boot sequence profile that can be used - * with the 'bootchart' graphics generation tool. see www.bootchart.org - * note that unlike the original bootchartd, this is not a Bash script but - * some C code that is run right from the init script. - */ - -#include <stdio.h> -#include <time.h> -#include <dirent.h> -#include <unistd.h> -#include <fcntl.h> -#include <unistd.h> -#include <fcntl.h> -#include <unistd.h> -#include <fcntl.h> -#include <errno.h> -#include <stdlib.h> -#include <sys/stat.h> -#include "bootchart.h" - -#define VERSION "0.8" -#define SAMPLE_PERIOD 0.2 -#define LOG_ROOT "/data/bootchart" -#define LOG_STAT LOG_ROOT"/proc_stat.log" -#define LOG_PROCS LOG_ROOT"/proc_ps.log" -#define LOG_DISK LOG_ROOT"/proc_diskstats.log" -#define LOG_HEADER LOG_ROOT"/header" -#define LOG_ACCT LOG_ROOT"/kernel_pacct" - -#define LOG_STARTFILE "/data/bootchart-start" -#define LOG_STOPFILE "/data/bootchart-stop" - -static int -unix_read(int fd, void* buff, int len) -{ - int ret; - do { ret = read(fd, buff, len); } while (ret < 0 && errno == EINTR); - return ret; -} - -static int -unix_write(int fd, const void* buff, int len) -{ - int ret; - do { ret = write(fd, buff, len); } while (ret < 0 && errno == EINTR); - return ret; -} - -static int -proc_read(const char* filename, char* buff, size_t buffsize) -{ - int len = 0; - int fd = open(filename, O_RDONLY); - if (fd >= 0) { - len = unix_read(fd, buff, buffsize-1); - close(fd); - } - buff[len > 0 ? len : 0] = 0; - return len; -} - -#define FILE_BUFF_SIZE 65536 - -typedef struct { - int count; - int fd; - char data[FILE_BUFF_SIZE]; -} FileBuffRec, *FileBuff; - -static void -file_buff_open( FileBuff buff, const char* path ) -{ - buff->count = 0; - buff->fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0755); -} - -static void -file_buff_write( FileBuff buff, const void* src, int len ) -{ - while (len > 0) { - int avail = sizeof(buff->data) - buff->count; - if (avail > len) - avail = len; - - memcpy( buff->data + buff->count, src, avail ); - len -= avail; - src = (char*)src + avail; - - buff->count += avail; - if (buff->count == FILE_BUFF_SIZE) { - unix_write( buff->fd, buff->data, buff->count ); - buff->count = 0; - } - } -} - -static void -file_buff_done( FileBuff buff ) -{ - if (buff->count > 0) { - unix_write( buff->fd, buff->data, buff->count ); - buff->count = 0; - } -} - -static void -log_header(void) -{ - FILE* out; - char cmdline[1024]; - char uname[128]; - char cpuinfo[128]; - char* cpu; - char date[32]; - time_t now_t = time(NULL); - struct tm now = *localtime(&now_t); - strftime(date, sizeof(date), "%x %X", &now); - - out = fopen( LOG_HEADER, "w" ); - if (out == NULL) - return; - - proc_read("/proc/cmdline", cmdline, sizeof(cmdline)); - proc_read("/proc/version", uname, sizeof(uname)); - proc_read("/proc/cpuinfo", cpuinfo, sizeof(cpuinfo)); - - cpu = strchr( cpuinfo, ':' ); - if (cpu) { - char* p = strchr(cpu, '\n'); - cpu += 2; - if (p) - *p = 0; - } - - fprintf(out, "version = %s\n", VERSION); - fprintf(out, "title = Boot chart for Android ( %s )\n", date); - fprintf(out, "system.uname = %s\n", uname); - fprintf(out, "system.release = 0.0\n"); - fprintf(out, "system.cpu = %s\n", cpu); - fprintf(out, "system.kernel.options = %s\n", cmdline); - fclose(out); -} - -static void -close_on_exec(int fd) -{ - fcntl(fd, F_SETFD, FD_CLOEXEC); -} - -static void -open_log_file(int* plogfd, const char* logfile) -{ - int logfd = *plogfd; - - /* create log file if needed */ - if (logfd < 0) - { - logfd = open(logfile,O_WRONLY|O_CREAT|O_TRUNC,0755); - if (logfd < 0) { - *plogfd = -2; - return; - } - close_on_exec(logfd); - *plogfd = logfd; - } -} - -static void -do_log_uptime(FileBuff log) -{ - char buff[65]; - int fd, ret, len; - - fd = open("/proc/uptime",O_RDONLY); - if (fd >= 0) { - int ret; - ret = unix_read(fd, buff, 64); - close(fd); - buff[64] = 0; - if (ret >= 0) { - long long jiffies = 100LL*strtod(buff,NULL); - int len; - snprintf(buff,sizeof(buff),"%lld\n",jiffies); - len = strlen(buff); - file_buff_write(log, buff, len); - } - } -} - -static void -do_log_ln(FileBuff log) -{ - file_buff_write(log, "\n", 1); -} - - -static void -do_log_file(FileBuff log, const char* procfile) -{ - char buff[1024]; - int fd; - - do_log_uptime(log); - - /* append file content */ - fd = open(procfile,O_RDONLY); - if (fd >= 0) { - close_on_exec(fd); - for (;;) { - int ret; - ret = unix_read(fd, buff, sizeof(buff)); - if (ret <= 0) - break; - - file_buff_write(log, buff, ret); - if (ret < (int)sizeof(buff)) - break; - } - close(fd); - } - - do_log_ln(log); -} - -static void -do_log_procs(FileBuff log) -{ - DIR* dir = opendir("/proc"); - struct dirent* entry; - - do_log_uptime(log); - - while ((entry = readdir(dir)) != NULL) { - /* only match numeric values */ - char* end; - int pid = strtol( entry->d_name, &end, 10); - if (end != NULL && end > entry->d_name && *end == 0) { - char filename[32]; - char buff[1024]; - char cmdline[1024]; - int len; - int fd; - - /* read command line and extract program name */ - snprintf(filename,sizeof(filename),"/proc/%d/cmdline",pid); - proc_read(filename, cmdline, sizeof(cmdline)); - - /* read process stat line */ - snprintf(filename,sizeof(filename),"/proc/%d/stat",pid); - fd = open(filename,O_RDONLY); - if (fd >= 0) { - len = unix_read(fd, buff, sizeof(buff)-1); - close(fd); - if (len > 0) { - int len2 = strlen(cmdline); - if (len2 > 0) { - /* we want to substitute the process name with its real name */ - const char* p1; - const char* p2; - buff[len] = 0; - p1 = strchr(buff, '('); - p2 = strchr(p1, ')'); - file_buff_write(log, buff, p1+1-buff); - file_buff_write(log, cmdline, strlen(cmdline)); - file_buff_write(log, p2, strlen(p2)); - } else { - /* no substitution */ - file_buff_write(log,buff,len); - } - } - } - } - } - closedir(dir); - do_log_ln(log); -} - -static FileBuffRec log_stat[1]; -static FileBuffRec log_procs[1]; -static FileBuffRec log_disks[1]; - -/* called to setup bootcharting */ -int bootchart_init( void ) -{ - int ret; - char buff[4]; - int timeout = 0, count = 0; - - buff[0] = 0; - proc_read( LOG_STARTFILE, buff, sizeof(buff) ); - if (buff[0] != 0) { - timeout = atoi(buff); - } - else { - /* when running with emulator, androidboot.bootchart=<timeout> - * might be passed by as kernel parameters to specify the bootchart - * timeout. this is useful when using -wipe-data since the /data - * partition is fresh - */ - char cmdline[1024]; - char* s; -#define KERNEL_OPTION "androidboot.bootchart=" - proc_read( "/proc/cmdline", cmdline, sizeof(cmdline) ); - s = strstr(cmdline, KERNEL_OPTION); - if (s) { - s += sizeof(KERNEL_OPTION)-1; - timeout = atoi(s); - } - } - if (timeout == 0) - return 0; - - if (timeout > BOOTCHART_MAX_TIME_SEC) - timeout = BOOTCHART_MAX_TIME_SEC; - - count = (timeout*1000 + BOOTCHART_POLLING_MS-1)/BOOTCHART_POLLING_MS; - - do {ret=mkdir(LOG_ROOT,0755);}while (ret < 0 && errno == EINTR); - - file_buff_open(log_stat, LOG_STAT); - file_buff_open(log_procs, LOG_PROCS); - file_buff_open(log_disks, LOG_DISK); - - /* create kernel process accounting file */ - { - int fd = open( LOG_ACCT, O_WRONLY|O_CREAT|O_TRUNC,0644); - if (fd >= 0) { - close(fd); - acct( LOG_ACCT ); - } - } - - log_header(); - return count; -} - -/* called each time you want to perform a bootchart sampling op */ -int bootchart_step( void ) -{ - do_log_file(log_stat, "/proc/stat"); - do_log_file(log_disks, "/proc/diskstats"); - do_log_procs(log_procs); - - /* we stop when /data/bootchart-stop contains 1 */ - { - char buff[2]; - if (proc_read(LOG_STOPFILE,buff,sizeof(buff)) > 0 && buff[0] == '1') { - return -1; - } - } - - return 0; -} - -void bootchart_finish( void ) -{ - unlink( LOG_STOPFILE ); - file_buff_done(log_stat); - file_buff_done(log_disks); - file_buff_done(log_procs); - acct(NULL); -} diff --git a/init/bootchart.cpp b/init/bootchart.cpp new file mode 100644 index 0000000..95687cb --- /dev/null +++ b/init/bootchart.cpp @@ -0,0 +1,273 @@ +/* + * 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 "bootchart.h" +#include "keywords.h" +#include "log.h" +#include "property_service.h" + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/utsname.h> +#include <time.h> +#include <unistd.h> + +#include <memory> +#include <string> + +#include <base/file.h> + +#define LOG_ROOT "/data/bootchart" +#define LOG_STAT LOG_ROOT"/proc_stat.log" +#define LOG_PROCS LOG_ROOT"/proc_ps.log" +#define LOG_DISK LOG_ROOT"/proc_diskstats.log" +#define LOG_HEADER LOG_ROOT"/header" +#define LOG_ACCT LOG_ROOT"/kernel_pacct" + +#define LOG_STARTFILE LOG_ROOT"/start" +#define LOG_STOPFILE LOG_ROOT"/stop" + +// Polling period in ms. +static const int BOOTCHART_POLLING_MS = 200; + +// Max polling time in seconds. +static const int BOOTCHART_MAX_TIME_SEC = 10*60; + +static long long g_last_bootchart_time; +static int g_remaining_samples; + +static FILE* log_stat; +static FILE* log_procs; +static FILE* log_disks; + +static long long get_uptime_jiffies() { + std::string uptime; + if (!android::base::ReadFileToString("/proc/uptime", &uptime)) { + return 0; + } + return 100LL * strtod(uptime.c_str(), NULL); +} + +static void log_header() { + char date[32]; + time_t now_t = time(NULL); + struct tm now = *localtime(&now_t); + strftime(date, sizeof(date), "%F %T", &now); + + utsname uts; + if (uname(&uts) == -1) { + return; + } + + char fingerprint[PROP_VALUE_MAX]; + if (property_get("ro.build.fingerprint", fingerprint) == -1) { + return; + } + + std::string kernel_cmdline; + android::base::ReadFileToString("/proc/cmdline", &kernel_cmdline); + + FILE* out = fopen(LOG_HEADER, "we"); + if (out == NULL) { + return; + } + fprintf(out, "version = Android init 0.8 " __TIME__ "\n"); + fprintf(out, "title = Boot chart for Android (%s)\n", date); + fprintf(out, "system.uname = %s %s %s %s\n", uts.sysname, uts.release, uts.version, uts.machine); + fprintf(out, "system.release = %s\n", fingerprint); + // TODO: use /proc/cpuinfo "model name" line for x86, "Processor" line for arm. + fprintf(out, "system.cpu = %s\n", uts.machine); + fprintf(out, "system.kernel.options = %s\n", kernel_cmdline.c_str()); + fclose(out); +} + +static void do_log_uptime(FILE* log) { + fprintf(log, "%lld\n", get_uptime_jiffies()); +} + +static void do_log_file(FILE* log, const char* procfile) { + do_log_uptime(log); + + std::string content; + if (android::base::ReadFileToString(procfile, &content)) { + fprintf(log, "%s\n", content.c_str()); + } +} + +static void do_log_procs(FILE* log) { + do_log_uptime(log); + + std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir("/proc"), closedir); + struct dirent* entry; + while ((entry = readdir(dir.get())) != NULL) { + // Only match numeric values. + char* end; + int pid = strtol(entry->d_name, &end, 10); + if (end != NULL && end > entry->d_name && *end == 0) { + char filename[32]; + + // /proc/<pid>/stat only has truncated task names, so get the full + // name from /proc/<pid>/cmdline. + snprintf(filename, sizeof(filename), "/proc/%d/cmdline", pid); + std::string cmdline; + android::base::ReadFileToString(filename, &cmdline); + const char* full_name = cmdline.c_str(); // So we stop at the first NUL. + + // Read process stat line. + snprintf(filename, sizeof(filename), "/proc/%d/stat", pid); + std::string stat; + if (android::base::ReadFileToString(filename, &stat)) { + if (!cmdline.empty()) { + // Substitute the process name with its real name. + size_t open = stat.find('('); + size_t close = stat.find_last_of(')'); + if (open != std::string::npos && close != std::string::npos) { + stat.replace(open + 1, close - open - 1, full_name); + } + } + fputs(stat.c_str(), log); + } + } + } + + fputc('\n', log); +} + +static int bootchart_init() { + int timeout = 0; + + std::string start; + android::base::ReadFileToString(LOG_STARTFILE, &start); + if (!start.empty()) { + timeout = atoi(start.c_str()); + } else { + // When running with emulator, androidboot.bootchart=<timeout> + // might be passed by as kernel parameters to specify the bootchart + // timeout. this is useful when using -wipe-data since the /data + // partition is fresh. + std::string cmdline; + android::base::ReadFileToString("/proc/cmdline", &cmdline); +#define KERNEL_OPTION "androidboot.bootchart=" + if (strstr(cmdline.c_str(), KERNEL_OPTION) != NULL) { + timeout = atoi(cmdline.c_str() + sizeof(KERNEL_OPTION) - 1); + } + } + if (timeout == 0) + return 0; + + if (timeout > BOOTCHART_MAX_TIME_SEC) + timeout = BOOTCHART_MAX_TIME_SEC; + + int count = (timeout*1000 + BOOTCHART_POLLING_MS-1)/BOOTCHART_POLLING_MS; + + log_stat = fopen(LOG_STAT, "we"); + if (log_stat == NULL) { + return -1; + } + log_procs = fopen(LOG_PROCS, "we"); + if (log_procs == NULL) { + fclose(log_stat); + return -1; + } + log_disks = fopen(LOG_DISK, "we"); + if (log_disks == NULL) { + fclose(log_stat); + fclose(log_procs); + return -1; + } + + // Create kernel process accounting file. + close(open(LOG_ACCT, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644)); + acct(LOG_ACCT); + + log_header(); + return count; +} + +int do_bootchart_init(int nargs, char** args) { + g_remaining_samples = bootchart_init(); + if (g_remaining_samples < 0) { + ERROR("Bootcharting init failure: %s\n", strerror(errno)); + } else if (g_remaining_samples > 0) { + NOTICE("Bootcharting started (will run for %d s).\n", + (g_remaining_samples * BOOTCHART_POLLING_MS) / 1000); + } else { + NOTICE("Not bootcharting.\n"); + } + return 0; +} + +static int bootchart_step() { + do_log_file(log_stat, "/proc/stat"); + do_log_file(log_disks, "/proc/diskstats"); + do_log_procs(log_procs); + + // Stop if /data/bootchart/stop contains 1. + std::string stop; + if (android::base::ReadFileToString(LOG_STOPFILE, &stop) && stop == "1") { + return -1; + } + + return 0; +} + +/* called to get time (in ms) used by bootchart */ +static long long bootchart_gettime() { + return 10LL*get_uptime_jiffies(); +} + +static void bootchart_finish() { + unlink(LOG_STOPFILE); + fclose(log_stat); + fclose(log_disks); + fclose(log_procs); + acct(NULL); +} + +void bootchart_sample(int* timeout) { + // Do we have any more bootcharting to do? + if (g_remaining_samples <= 0) { + return; + } + + long long current_time = bootchart_gettime(); + int elapsed_time = current_time - g_last_bootchart_time; + + if (elapsed_time >= BOOTCHART_POLLING_MS) { + /* count missed samples */ + while (elapsed_time >= BOOTCHART_POLLING_MS) { + elapsed_time -= BOOTCHART_POLLING_MS; + g_remaining_samples--; + } + /* count may be negative, take a sample anyway */ + g_last_bootchart_time = current_time; + if (bootchart_step() < 0 || g_remaining_samples <= 0) { + bootchart_finish(); + g_remaining_samples = 0; + } + } + if (g_remaining_samples > 0) { + int remaining_time = BOOTCHART_POLLING_MS - elapsed_time; + if (*timeout < 0 || *timeout > remaining_time) { + *timeout = remaining_time; + } + } +} diff --git a/init/bootchart.h b/init/bootchart.h index 39d2d4f..cf61d83 100644 --- a/init/bootchart.h +++ b/init/bootchart.h @@ -17,20 +17,6 @@ #ifndef _BOOTCHART_H #define _BOOTCHART_H -#ifndef BOOTCHART -# define BOOTCHART 0 -#endif - -#if BOOTCHART - -extern int bootchart_init(void); -extern int bootchart_step(void); -extern void bootchart_finish(void); - -# define BOOTCHART_POLLING_MS 200 /* polling period in ms */ -# define BOOTCHART_DEFAULT_TIME_SEC (2*60) /* default polling time in seconds */ -# define BOOTCHART_MAX_TIME_SEC (10*60) /* max polling time in seconds */ - -#endif /* BOOTCHART */ +void bootchart_sample(int* timeout); #endif /* _BOOTCHART_H */ diff --git a/init/builtins.c b/init/builtins.cpp index c192551..4567b04 100644 --- a/init/builtins.c +++ b/init/builtins.cpp @@ -14,30 +14,32 @@ * limitations under the License. */ -#include <sys/types.h> -#include <sys/stat.h> +#include <errno.h> #include <fcntl.h> -#include <unistd.h> -#include <string.h> +#include <net/if.h> #include <stdio.h> -#include <linux/kd.h> -#include <errno.h> -#include <sys/socket.h> -#include <netinet/in.h> -#include <linux/if.h> -#include <arpa/inet.h> #include <stdlib.h> +#include <string.h> +#include <sys/socket.h> #include <sys/mount.h> #include <sys/resource.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> #include <sys/wait.h> +#include <unistd.h> #include <linux/loop.h> -#include <cutils/partition_utils.h> -#include <cutils/android_reboot.h> -#include <fs_mgr.h> +#include <ext4_crypt.h> #include <selinux/selinux.h> #include <selinux/label.h> +#include <fs_mgr.h> +#include <base/stringprintf.h> +#include <cutils/partition_utils.h> +#include <cutils/android_reboot.h> +#include <private/android_filesystem_config.h> + #include "init.h" #include "keywords.h" #include "property_service.h" @@ -46,121 +48,22 @@ #include "util.h" #include "log.h" -#include <private/android_filesystem_config.h> +#define chmod DO_NOT_USE_CHMOD_USE_FCHMODAT_SYMLINK_NOFOLLOW int add_environment(const char *name, const char *value); -extern int init_module(void *, unsigned long, const char *); - -static int write_file(const char *path, const char *value) -{ - int fd, ret, len; - - fd = open(path, O_WRONLY|O_CREAT|O_NOFOLLOW, 0600); - - if (fd < 0) - return -errno; - - len = strlen(value); - - do { - ret = write(fd, value, len); - } while (ret < 0 && errno == EINTR); - - close(fd); - if (ret < 0) { - return -errno; - } else { - return 0; - } -} - -static int _open(const char *path) -{ - int fd; - - fd = open(path, O_RDONLY | O_NOFOLLOW); - if (fd < 0) - fd = open(path, O_WRONLY | O_NOFOLLOW); - - return fd; -} - -static int _chown(const char *path, unsigned int uid, unsigned int gid) -{ - int fd; - int ret; - - fd = _open(path); - if (fd < 0) { - return -1; - } - - ret = fchown(fd, uid, gid); - if (ret < 0) { - int errno_copy = errno; - close(fd); - errno = errno_copy; - return -1; - } - - close(fd); - - return 0; -} - -static int _chmod(const char *path, mode_t mode) -{ - int fd; - int ret; - - fd = _open(path); - if (fd < 0) { - return -1; - } - - ret = fchmod(fd, mode); - if (ret < 0) { - int errno_copy = errno; - close(fd); - errno = errno_copy; - return -1; - } - - close(fd); - - return 0; -} +// System call provided by bionic but not in any header file. +extern "C" int init_module(void *, unsigned long, const char *); static int insmod(const char *filename, char *options) { - void *module; - unsigned size; - int ret; - - module = read_file(filename, &size); - if (!module) + std::string module; + if (!read_file(filename, &module)) { return -1; + } - ret = init_module(module, size, options); - - free(module); - - return ret; -} - -static int setkey(struct kbentry *kbe) -{ - int fd, ret; - - fd = open("/dev/tty0", O_RDWR | O_SYNC); - if (fd < 0) - return -1; - - ret = ioctl(fd, KDSKBENT, kbe); - - close(fd); - return ret; + // TODO: use finit_module for >= 3.8 kernels. + return init_module(&module[0], module.size(), options); } static int __ifupdown(const char *interface, int up) @@ -185,7 +88,7 @@ static int __ifupdown(const char *interface, int up) ifr.ifr_flags &= ~IFF_UP; ret = ioctl(s, SIOCSIFFLAGS, &ifr); - + done: close(s); return ret; @@ -200,18 +103,6 @@ static void service_start_if_not_disabled(struct service *svc) } } -int do_chdir(int nargs, char **args) -{ - chdir(args[1]); - return 0; -} - -int do_chroot(int nargs, char **args) -{ - chroot(args[1]); - return 0; -} - int do_class_start(int nargs, char **args) { /* Starting a class does not start services @@ -254,9 +145,13 @@ int do_enable(int nargs, char **args) return 0; } -int do_exec(int nargs, char **args) -{ - return -1; +int do_exec(int nargs, char** args) { + service* svc = make_exec_oneshot_service(nargs, args); + if (svc == NULL) { + return -1; + } + service_start(svc, NULL); + return 0; } int do_export(int nargs, char **args) @@ -319,7 +214,7 @@ int do_mkdir(int nargs, char **args) ret = make_dir(args[1], mode); /* chmod in case the directory already exists */ if (ret == -1 && errno == EEXIST) { - ret = _chmod(args[1], mode); + ret = fchmodat(AT_FDCWD, args[1], mode, AT_SYMLINK_NOFOLLOW); } if (ret == -1) { return -errno; @@ -333,20 +228,20 @@ int do_mkdir(int nargs, char **args) gid = decode_uid(args[4]); } - if (_chown(args[1], uid, gid) < 0) { + if (lchown(args[1], uid, gid) == -1) { return -errno; } /* chown may have cleared S_ISUID and S_ISGID, chmod again */ if (mode & (S_ISUID | S_ISGID)) { - ret = _chmod(args[1], mode); + ret = fchmodat(AT_FDCWD, args[1], mode, AT_SYMLINK_NOFOLLOW); if (ret == -1) { return -errno; } } } - return 0; + return e4crypt_set_directory_policy(args[1]); } static struct { @@ -410,7 +305,7 @@ int do_mount(int nargs, char **args) return -1; } - sprintf(tmp, "/dev/block/mtdblock%d", n); + snprintf(tmp, sizeof(tmp), "/dev/block/mtdblock%d", n); if (wait) wait_for_file(tmp, COMMAND_RETRY_TIMEOUT); @@ -424,15 +319,16 @@ int do_mount(int nargs, char **args) struct loop_info info; mode = (flags & MS_RDONLY) ? O_RDONLY : O_RDWR; - fd = open(source + 5, mode); + fd = open(source + 5, mode | O_CLOEXEC); if (fd < 0) { return -1; } for (n = 0; ; n++) { - sprintf(tmp, "/dev/block/loop%d", n); - loop = open(tmp, mode); + snprintf(tmp, sizeof(tmp), "/dev/block/loop%d", n); + loop = open(tmp, mode | O_CLOEXEC); if (loop < 0) { + close(fd); return -1; } @@ -476,7 +372,7 @@ exit_success: static int wipe_data_via_recovery() { mkdir("/cache/recovery", 0700); - int fd = open("/cache/recovery/command", O_RDWR|O_CREAT|O_TRUNC, 0600); + int fd = open("/cache/recovery/command", O_RDWR|O_CREAT|O_TRUNC|O_CLOEXEC, 0600); if (fd >= 0) { write(fd, "--wipe_data\n", strlen("--wipe_data\n") + 1); write(fd, "--reason=wipe_data_via_recovery\n", strlen("--reason=wipe_data_via_recovery\n") + 1); @@ -489,6 +385,17 @@ static int wipe_data_via_recovery() while (1) { pause(); } // never reached } +/* + * Callback to make a directory from the ext4 code + */ +static int do_mount_alls_make_dir(const char* dir) +{ + if (make_dir(dir, 0700) && errno != EEXIST) { + return -1; + } + + return 0; +} /* * This function might request a reboot, in which case it will @@ -500,7 +407,6 @@ int do_mount_all(int nargs, char **args) int ret = -1; int child_ret = -1; int status; - const char *prop; struct fstab *fstab; if (nargs != 2) { @@ -519,7 +425,7 @@ int do_mount_all(int nargs, char **args) int wp_ret = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0)); if (wp_ret < 0) { /* Unexpected error code. We will continue anyway. */ - NOTICE("waitpid failed rc=%d, errno=%d\n", wp_ret, errno); + NOTICE("waitpid failed rc=%d: %s\n", wp_ret, strerror(errno)); } if (WIFEXITED(status)) { @@ -558,6 +464,37 @@ int do_mount_all(int nargs, char **args) ERROR("fs_mgr_mount_all suggested recovery, so wiping data via recovery.\n"); ret = wipe_data_via_recovery(); /* If reboot worked, there is no return. */ + } else if (ret == FS_MGR_MNTALL_DEV_DEFAULT_FILE_ENCRYPTED) { + // We have to create the key files here. Only init can call make_dir, + // and we can't do it from fs_mgr as then fs_mgr would depend on + // make_dir creating a circular dependency. + fstab = fs_mgr_read_fstab(args[1]); + for (int i = 0; i < fstab->num_entries; ++i) { + if (fs_mgr_is_file_encrypted(&fstab->recs[i])) { + if (e4crypt_create_device_key(fstab->recs[i].mount_point, + do_mount_alls_make_dir)) { + ERROR("Could not create device key on %s" + " - continue unencrypted\n", + fstab->recs[i].mount_point); + } + } + } + fs_mgr_free_fstab(fstab); + + if (e4crypt_install_keyring()) { + return -1; + } + property_set("ro.crypto.state", "encrypted"); + + // Although encrypted, we have device key, so we do not need to + // do anything different from the nonencrypted case. + action_for_each_trigger("nonencrypted", action_add_queue_tail); + } else if (ret == FS_MGR_MNTALL_DEV_NON_DEFAULT_FILE_ENCRYPTED) { + if (e4crypt_install_keyring()) { + return -1; + } + property_set("ro.crypto.state", "encrypted"); + property_set("vold.decrypt", "trigger_restart_min_framework"); } else if (ret > 0) { ERROR("fs_mgr_mount_all returned unexpected error %d\n", ret); } @@ -578,33 +515,6 @@ int do_swapon_all(int nargs, char **args) return ret; } -int do_setcon(int nargs, char **args) { - if (is_selinux_enabled() <= 0) - return 0; - if (setcon(args[1]) < 0) { - return -errno; - } - return 0; -} - -int do_setenforce(int nargs, char **args) { - if (is_selinux_enabled() <= 0) - return 0; - if (security_setenforce(atoi(args[1])) < 0) { - return -errno; - } - return 0; -} - -int do_setkey(int nargs, char **args) -{ - struct kbentry kbe; - kbe.kb_table = strtoul(args[1], 0, 0); - kbe.kb_index = strtoul(args[2], 0, 0); - kbe.kb_value = strtoul(args[3], 0, 0); - return setkey(&kbe); -} - int do_setprop(int nargs, char **args) { const char *name = args[1]; @@ -667,7 +577,7 @@ int do_powerctl(int nargs, char **args) int res; int len = 0; int cmd = 0; - char *reboot_target; + const char *reboot_target; res = expand_props(command, args[1], sizeof(command)); if (res) { @@ -727,25 +637,41 @@ int do_sysclktz(int nargs, char **args) return -1; memset(&tz, 0, sizeof(tz)); - tz.tz_minuteswest = atoi(args[1]); + tz.tz_minuteswest = atoi(args[1]); if (settimeofday(NULL, &tz)) return -1; return 0; } +int do_verity_load_state(int nargs, char **args) { + int mode = -1; + int rc = fs_mgr_load_verity_state(&mode); + if (rc == 0 && mode == VERITY_MODE_LOGGING) { + action_for_each_trigger("verity-logging", action_add_queue_tail); + } + return rc; +} + +static void verity_update_property(fstab_rec *fstab, const char *mount_point, int mode, int status) { + property_set(android::base::StringPrintf("partition.%s.verified", mount_point).c_str(), + android::base::StringPrintf("%d", mode).c_str()); +} + +int do_verity_update_state(int nargs, char** args) { + return fs_mgr_update_verity_state(verity_update_property); +} + int do_write(int nargs, char **args) { const char *path = args[1]; const char *value = args[2]; - char prop_val[PROP_VALUE_MAX]; - int ret; - ret = expand_props(prop_val, value, sizeof(prop_val)); - if (ret) { + char expanded_value[256]; + if (expand_props(expanded_value, value, sizeof(expanded_value))) { ERROR("cannot expand '%s' while writing to '%s'\n", value, path); return -EINVAL; } - return write_file(path, prop_val); + return write_file(path, expanded_value); } int do_copy(int nargs, char **args) @@ -760,16 +686,16 @@ int do_copy(int nargs, char **args) if (nargs != 3) return -1; - if (stat(args[1], &info) < 0) + if (stat(args[1], &info) < 0) return -1; - if ((fd1 = open(args[1], O_RDONLY)) < 0) + if ((fd1 = open(args[1], O_RDONLY|O_CLOEXEC)) < 0) goto out_err; - if ((fd2 = open(args[2], O_WRONLY|O_CREAT|O_TRUNC, 0660)) < 0) + if ((fd2 = open(args[2], O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0660)) < 0) goto out_err; - if (!(buffer = malloc(info.st_size))) + if (!(buffer = (char*) malloc(info.st_size))) goto out_err; p = buffer; @@ -813,10 +739,10 @@ out: int do_chown(int nargs, char **args) { /* GID is optional. */ if (nargs == 3) { - if (_chown(args[2], decode_uid(args[1]), -1) < 0) + if (lchown(args[2], decode_uid(args[1]), -1) == -1) return -errno; } else if (nargs == 4) { - if (_chown(args[3], decode_uid(args[1]), decode_uid(args[2])) < 0) + if (lchown(args[3], decode_uid(args[1]), decode_uid(args[2])) == -1) return -errno; } else { return -1; @@ -839,7 +765,7 @@ static mode_t get_mode(const char *s) { int do_chmod(int nargs, char **args) { mode_t mode = get_mode(args[1]); - if (_chmod(args[2], mode) < 0) { + if (fchmodat(AT_FDCWD, args[2], mode, AT_SYMLINK_NOFOLLOW) < 0) { return -errno; } return 0; @@ -867,34 +793,6 @@ int do_restorecon_recursive(int nargs, char **args) { return ret; } -int do_setsebool(int nargs, char **args) { - const char *name = args[1]; - const char *value = args[2]; - SELboolean b; - int ret; - - if (is_selinux_enabled() <= 0) - return 0; - - b.name = name; - if (!strcmp(value, "1") || !strcasecmp(value, "true") || !strcasecmp(value, "on")) - b.value = 1; - else if (!strcmp(value, "0") || !strcasecmp(value, "false") || !strcasecmp(value, "off")) - b.value = 0; - else { - ERROR("setsebool: invalid value %s\n", value); - return -EINVAL; - } - - if (security_set_boolean_list(1, &b, 0) < 0) { - ret = -errno; - ERROR("setsebool: could not set %s to %s\n", name, value); - return ret; - } - - return 0; -} - int do_loglevel(int nargs, char **args) { int log_level; char log_level_str[PROP_VALUE_MAX] = ""; @@ -941,3 +839,12 @@ int do_wait(int nargs, char **args) } else return -1; } + +int do_installkey(int nargs, char **args) +{ + if (nargs == 2) { + return e4crypt_install_key(args[1]); + } + + return -1; +} diff --git a/init/devices.c b/init/devices.cpp index 73fe223..4944cec 100644 --- a/init/devices.c +++ b/init/devices.cpp @@ -48,12 +48,10 @@ #include "util.h" #include "log.h" -#define UNUSED __attribute__((__unused__)) - #define SYSFS_PREFIX "/sys" -#define FIRMWARE_DIR1 "/etc/firmware" -#define FIRMWARE_DIR2 "/vendor/firmware" -#define FIRMWARE_DIR3 "/firmware/image" +static const char *firmware_dirs[] = { "/etc/firmware", + "/vendor/firmware", + "/firmware/image" }; extern struct selabel_handle *sehandle; @@ -101,7 +99,7 @@ int add_dev_perms(const char *name, const char *attr, mode_t perm, unsigned int uid, unsigned int gid, unsigned short prefix, unsigned short wildcard) { - struct perm_node *node = calloc(1, sizeof(*node)); + struct perm_node *node = (perm_node*) calloc(1, sizeof(*node)); if (!node) return -ENOMEM; @@ -154,7 +152,7 @@ void fixup_sys_perms(const char *upath) if ((strlen(upath) + strlen(dp->attr) + 6) > sizeof(buf)) break; - sprintf(buf,"/sys%s/%s", upath, dp->attr); + snprintf(buf, sizeof(buf), "/sys%s/%s", upath, dp->attr); INFO("fixup %s %d %d 0%o\n", buf, dp->uid, dp->gid, dp->perm); chown(buf, dp->uid, dp->gid); chmod(buf, dp->perm); @@ -191,7 +189,6 @@ static bool perm_path_matches(const char *path, struct perms_ *dp) static mode_t get_device_perm(const char *path, const char **links, unsigned *uid, unsigned *gid) { - mode_t perm; struct listnode *node; struct perm_node *perm_node; struct perms_ *dp; @@ -232,7 +229,7 @@ static mode_t get_device_perm(const char *path, const char **links, } static void make_device(const char *path, - const char *upath UNUSED, + const char */*upath*/, int block, int major, int minor, const char **links) { @@ -269,7 +266,6 @@ static void make_device(const char *path, static void add_platform_device(const char *path) { int path_len = strlen(path); - struct listnode *node; struct platform_node *bus; const char *name = path; @@ -279,18 +275,9 @@ static void add_platform_device(const char *path) name += 9; } - list_for_each_reverse(node, &platform_names) { - bus = node_to_item(node, struct platform_node, list); - if ((bus->path_len < path_len) && - (path[bus->path_len] == '/') && - !strncmp(path, bus->path, bus->path_len)) - /* subdevice of an existing platform, ignore it */ - return; - } - INFO("adding platform device %s (%s)\n", name, path); - bus = calloc(1, sizeof(struct platform_node)); + bus = (platform_node*) calloc(1, sizeof(struct platform_node)); bus->path = strdup(path); bus->path_len = path_len; bus->name = bus->path + (name - path); @@ -367,24 +354,6 @@ static int find_pci_device_prefix(const char *path, char *buf, ssize_t buf_sz) return 0; } -#if LOG_UEVENTS - -static inline suseconds_t get_usecs(void) -{ - struct timeval tv; - gettimeofday(&tv, 0); - return tv.tv_sec * (suseconds_t) 1000000 + tv.tv_usec; -} - -#define log_event_print(x...) INFO(x) - -#else - -#define log_event_print(fmt, args...) do { } while (0) -#define get_usecs() 0 - -#endif - static void parse_event(const char *msg, struct uevent *uevent) { uevent->action = ""; @@ -433,9 +402,11 @@ static void parse_event(const char *msg, struct uevent *uevent) ; } - log_event_print("event { '%s', '%s', '%s', '%s', %d, %d }\n", - uevent->action, uevent->path, uevent->subsystem, - uevent->firmware, uevent->major, uevent->minor); + if (LOG_UEVENTS) { + INFO("event { '%s', '%s', '%s', '%s', %d, %d }\n", + uevent->action, uevent->path, uevent->subsystem, + uevent->firmware, uevent->major, uevent->minor); + } } static char **get_character_device_symlinks(struct uevent *uevent) @@ -451,14 +422,14 @@ static char **get_character_device_symlinks(struct uevent *uevent) if (!pdev) return NULL; - links = malloc(sizeof(char *) * 2); + links = (char**) malloc(sizeof(char *) * 2); if (!links) return NULL; memset(links, 0, sizeof(char *) * 2); /* skip "/devices/platform/<driver>" */ parent = strchr(uevent->path + pdev->path_len, '/'); - if (!*parent) + if (!parent) goto err; if (!strncmp(parent, "/usb", 4)) { @@ -497,15 +468,10 @@ static char **get_block_device_symlinks(struct uevent *uevent) struct platform_node *pdev; char *slash; const char *type; - int width; char buf[256]; char link_path[256]; - int fd; int link_num = 0; - int ret; char *p; - unsigned int size; - struct stat info; pdev = find_platform_device(uevent->path); if (pdev) { @@ -518,7 +484,7 @@ static char **get_block_device_symlinks(struct uevent *uevent) return NULL; } - char **links = malloc(sizeof(char *) * 4); + char **links = (char**) malloc(sizeof(char *) * 4); if (!links) return NULL; memset(links, 0, sizeof(char *) * 4); @@ -667,14 +633,9 @@ static void mkdir_recursive_for_devpath(const char *devpath) mkdir_recursive(dir, 0755); } -static inline void __attribute__((__deprecated__)) kernel_logger() -{ - INFO("kernel logger is deprecated\n"); -} - static void handle_generic_device_event(struct uevent *uevent) { - char *base; + const char *base; const char *name; char devpath[DEVPATH_LEN] = {0}; char **links = NULL; @@ -758,7 +719,7 @@ static void handle_generic_device_event(struct uevent *uevent) make_dir(base, 0755); } else if(!strncmp(uevent->subsystem, "misc", 4) && !strncmp(name, "log_", 4)) { - kernel_logger(); + INFO("kernel logger is deprecated\n"); base = "/dev/log/"; make_dir(base, 0755); name += 4; @@ -840,8 +801,9 @@ static int is_booting(void) static void process_firmware_event(struct uevent *uevent) { - char *root, *loading, *data, *file1 = NULL, *file2 = NULL, *file3 = NULL; + char *root, *loading, *data; int l, loading_fd, data_fd, fw_fd; + size_t i; int booting = is_booting(); INFO("firmware: loading '%s' for '%s'\n", @@ -859,62 +821,49 @@ static void process_firmware_event(struct uevent *uevent) if (l == -1) goto loading_free_out; - l = asprintf(&file1, FIRMWARE_DIR1"/%s", uevent->firmware); - if (l == -1) - goto data_free_out; - - l = asprintf(&file2, FIRMWARE_DIR2"/%s", uevent->firmware); - if (l == -1) - goto data_free_out; - - l = asprintf(&file3, FIRMWARE_DIR3"/%s", uevent->firmware); - if (l == -1) - goto data_free_out; - - loading_fd = open(loading, O_WRONLY); + loading_fd = open(loading, O_WRONLY|O_CLOEXEC); if(loading_fd < 0) - goto file_free_out; + goto data_free_out; - data_fd = open(data, O_WRONLY); + data_fd = open(data, O_WRONLY|O_CLOEXEC); if(data_fd < 0) goto loading_close_out; try_loading_again: - fw_fd = open(file1, O_RDONLY); - if(fw_fd < 0) { - fw_fd = open(file2, O_RDONLY); - if (fw_fd < 0) { - fw_fd = open(file3, O_RDONLY); - if (fw_fd < 0) { - if (booting) { - /* If we're not fully booted, we may be missing - * filesystems needed for firmware, wait and retry. - */ - usleep(100000); - booting = is_booting(); - goto try_loading_again; - } - INFO("firmware: could not open '%s' %d\n", uevent->firmware, errno); - write(loading_fd, "-1", 2); - goto data_close_out; - } + for (i = 0; i < ARRAY_SIZE(firmware_dirs); i++) { + char *file = NULL; + l = asprintf(&file, "%s/%s", firmware_dirs[i], uevent->firmware); + if (l == -1) + goto data_free_out; + fw_fd = open(file, O_RDONLY|O_CLOEXEC); + free(file); + if (fw_fd >= 0) { + if(!load_firmware(fw_fd, loading_fd, data_fd)) + INFO("firmware: copy success { '%s', '%s' }\n", root, uevent->firmware); + else + INFO("firmware: copy failure { '%s', '%s' }\n", root, uevent->firmware); + break; } } - - if(!load_firmware(fw_fd, loading_fd, data_fd)) - INFO("firmware: copy success { '%s', '%s' }\n", root, uevent->firmware); - else - INFO("firmware: copy failure { '%s', '%s' }\n", root, uevent->firmware); + if (fw_fd < 0) { + if (booting) { + /* If we're not fully booted, we may be missing + * filesystems needed for firmware, wait and retry. + */ + usleep(100000); + booting = is_booting(); + goto try_loading_again; + } + INFO("firmware: could not open '%s': %s\n", uevent->firmware, strerror(errno)); + write(loading_fd, "-1", 2); + goto data_close_out; + } close(fw_fd); data_close_out: close(data_fd); loading_close_out: close(loading_fd); -file_free_out: - free(file1); - free(file2); - free(file3); data_free_out: free(data); loading_free_out: @@ -926,7 +875,6 @@ root_free_out: static void handle_firmware_event(struct uevent *uevent) { pid_t pid; - int ret; if(strcmp(uevent->subsystem, "firmware")) return; @@ -938,7 +886,9 @@ static void handle_firmware_event(struct uevent *uevent) pid = fork(); if (!pid) { process_firmware_event(uevent); - exit(EXIT_SUCCESS); + _exit(EXIT_SUCCESS); + } else if (pid < 0) { + ERROR("could not fork to process firmware event: %s\n", strerror(errno)); } } @@ -977,7 +927,7 @@ void handle_device_fd() ** ** We drain any pending events from the netlink socket every time ** we poke another uevent file to make sure we don't overrun the -** socket's buffer. +** socket's buffer. */ static void do_coldboot(DIR *d) @@ -1023,12 +973,7 @@ static void coldboot(const char *path) } } -void device_init(void) -{ - suseconds_t t0, t1; - struct stat info; - int fd; - +void device_init() { sehandle = NULL; if (is_selinux_enabled() > 0) { sehandle = selinux_android_file_context_handle(); @@ -1037,24 +982,22 @@ void device_init(void) /* is 256K enough? udev uses 16MB! */ device_fd = uevent_open_socket(256*1024, true); - if(device_fd < 0) + if (device_fd == -1) { return; - - fcntl(device_fd, F_SETFD, FD_CLOEXEC); + } fcntl(device_fd, F_SETFL, O_NONBLOCK); - if (stat(coldboot_done, &info) < 0) { - t0 = get_usecs(); - coldboot("/sys/class"); - coldboot("/sys/block"); - coldboot("/sys/devices"); - t1 = get_usecs(); - fd = open(coldboot_done, O_WRONLY|O_CREAT, 0000); - close(fd); - log_event_print("coldboot %ld uS\n", ((long) (t1 - t0))); - } else { - log_event_print("skipping coldboot, already done\n"); + if (access(COLDBOOT_DONE, F_OK) == 0) { + NOTICE("Skipping coldboot, already done!\n"); + return; } + + Timer t; + coldboot("/sys/class"); + coldboot("/sys/block"); + coldboot("/sys/devices"); + close(open(COLDBOOT_DONE, O_WRONLY|O_CREAT|O_CLOEXEC, 0000)); + NOTICE("Coldboot took %.2fs.\n", t.duration()); } int get_device_fd() diff --git a/init/devices.h b/init/devices.h index 5d0fe88..6cb0a77 100644 --- a/init/devices.h +++ b/init/devices.h @@ -26,4 +26,5 @@ extern int add_dev_perms(const char *name, const char *attr, unsigned int gid, unsigned short prefix, unsigned short wildcard); int get_device_fd(); + #endif /* _INIT_DEVICES_H */ diff --git a/init/grab-bootchart.sh b/init/grab-bootchart.sh index 7fe8904..d6082aa 100755 --- a/init/grab-bootchart.sh +++ b/init/grab-bootchart.sh @@ -1,10 +1,9 @@ #!/bin/sh # -# this script is used to retrieve the bootchart log generated -# by init when compiled with INIT_BOOTCHART=true. -# -# for all details, see //device/system/init/README.BOOTCHART -# +# This script is used to retrieve a bootchart log generated by init. +# All options are passed to adb, for better or for worse. +# See the readme in this directory for more on bootcharting. + TMPDIR=/tmp/android-bootchart rm -rf $TMPDIR mkdir -p $TMPDIR @@ -15,8 +14,9 @@ TARBALL=bootchart.tgz FILES="header proc_stat.log proc_ps.log proc_diskstats.log kernel_pacct" for f in $FILES; do - adb pull $LOGROOT/$f $TMPDIR/$f 2>&1 > /dev/null + adb "${@}" pull $LOGROOT/$f $TMPDIR/$f 2>&1 > /dev/null done (cd $TMPDIR && tar -czf $TARBALL $FILES) -cp -f $TMPDIR/$TARBALL ./$TARBALL -echo "look at $TARBALL" +bootchart ${TMPDIR}/${TARBALL} +gnome-open ${TARBALL%.tgz}.png +echo "Clean up ${TMPDIR}/ and ./${TARBALL%.tgz}.png when done" diff --git a/init/init.c b/init/init.cpp index bd1db7a..dd74538 100644 --- a/init/init.c +++ b/init/init.cpp @@ -14,37 +14,43 @@ * limitations under the License. */ +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <paths.h> +#include <signal.h> +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <unistd.h> -#include <fcntl.h> -#include <ctype.h> -#include <signal.h> -#include <sys/wait.h> +#include <sys/epoll.h> #include <sys/mount.h> +#include <sys/socket.h> #include <sys/stat.h> -#include <sys/poll.h> -#include <errno.h> -#include <stdarg.h> -#include <mtd/mtd-user.h> #include <sys/types.h> -#include <sys/socket.h> #include <sys/un.h> +#include <sys/wait.h> +#include <termios.h> +#include <unistd.h> + +#include <mtd/mtd-user.h> #include <selinux/selinux.h> #include <selinux/label.h> #include <selinux/android.h> -#include <libgen.h> - -#include <cutils/list.h> +#include <base/file.h> +#include <base/stringprintf.h> #include <cutils/android_reboot.h> -#include <cutils/sockets.h> -#include <cutils/iosched_policy.h> #include <cutils/fs.h> +#include <cutils/iosched_policy.h> +#include <cutils/list.h> +#include <cutils/sockets.h> #include <private/android_filesystem_config.h> -#include <termios.h> + +#include <memory> #include "devices.h" #include "init.h" @@ -63,29 +69,10 @@ struct selabel_handle *sehandle_prop; static int property_triggers_enabled = 0; -#if BOOTCHART -static int bootchart_count; -#endif - -static char console[32]; -static char bootmode[32]; -static char hardware[32]; -static unsigned revision = 0; static char qemu[32]; static struct action *cur_action = NULL; static struct command *cur_command = NULL; -static struct listnode *command_queue = NULL; - -void notify_service_state(const char *name, const char *state) -{ - char pname[PROP_NAME_MAX]; - int len = strlen(name); - if ((len + 10) > PROP_NAME_MAX) - return; - snprintf(pname, sizeof(pname), "init.svc.%s", name); - property_set(pname, state); -} static int have_console; static char console_name[PROP_VALUE_MAX] = "/dev/console"; @@ -93,6 +80,40 @@ static time_t process_needs_restart; static const char *ENV[32]; +bool waiting_for_exec = false; + +static int epoll_fd = -1; + +void register_epoll_handler(int fd, void (*fn)()) { + epoll_event ev; + ev.events = EPOLLIN; + ev.data.ptr = reinterpret_cast<void*>(fn); + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { + ERROR("epoll_ctl failed: %s\n", strerror(errno)); + } +} + +void service::NotifyStateChange(const char* new_state) { + if (!properties_initialized()) { + // If properties aren't available yet, we can't set them. + return; + } + + if ((flags & SVC_EXEC) != 0) { + // 'exec' commands don't have properties tracking their state. + return; + } + + char prop_name[PROP_NAME_MAX]; + if (snprintf(prop_name, sizeof(prop_name), "init.svc.%s", name) >= PROP_NAME_MAX) { + // If the property name would be too long, we can't set it. + ERROR("Property name \"init.svc.%s\" too long; not setting to %s\n", name, new_state); + return; + } + + property_set(prop_name, new_state); +} + /* add_environment - add "key=value" to the current environment */ int add_environment(const char *key, const char *val) { @@ -113,9 +134,8 @@ int add_environment(const char *key, const char *val) /* Add entry if a free slot is available */ if (ENV[n] == NULL) { - size_t len = key_len + strlen(val) + 2; - char *entry = malloc(len); - snprintf(entry, len, "%s=%s", key, val); + char* entry; + asprintf(&entry, "%s=%s", key, val); ENV[n] = entry; return 0; } @@ -126,7 +146,7 @@ int add_environment(const char *key, const char *val) return -1; } -static void zap_stdio(void) +void zap_stdio(void) { int fd; fd = open("/dev/null", O_RDWR); @@ -166,36 +186,26 @@ static void publish_socket(const char *name, int fd) void service_start(struct service *svc, const char *dynamic_args) { - struct stat s; - pid_t pid; - int needs_console; - int n; - char *scon = NULL; - int rc; - - /* 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 - */ + // Starting a service removes it from the disabled or reset state and + // immediately takes it out of the restarting state if it was in there. svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START)); svc->time_started = 0; - /* running processes require no additional work -- if - * they're in the process of exiting, we've ensured - * that they will immediately restart on exit, unless - * they are ONESHOT - */ + // Running processes require no additional work --- if they're in the + // process of exiting, we've ensured that they will immediately restart + // on exit, unless they are ONESHOT. if (svc->flags & SVC_RUNNING) { return; } - needs_console = (svc->flags & SVC_CONSOLE) ? 1 : 0; - if (needs_console && (!have_console)) { + bool needs_console = (svc->flags & SVC_CONSOLE); + if (needs_console && !have_console) { ERROR("service '%s' requires console\n", svc->name); svc->flags |= SVC_DISABLED; return; } + struct stat s; if (stat(svc->args[0], &s) != 0) { ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name); svc->flags |= SVC_DISABLED; @@ -209,6 +219,7 @@ void service_start(struct service *svc, const char *dynamic_args) return; } + char* scon = NULL; if (is_selinux_enabled() > 0) { if (svc->seclabel) { scon = strdup(svc->seclabel); @@ -220,7 +231,7 @@ void service_start(struct service *svc, const char *dynamic_args) char *mycon = NULL, *fcon = NULL; INFO("computing context for service '%s'\n", svc->args[0]); - rc = getcon(&mycon); + int rc = getcon(&mycon); if (rc < 0) { ERROR("could not get context while starting '%s'\n", svc->name); return; @@ -246,10 +257,9 @@ void service_start(struct service *svc, const char *dynamic_args) } } - NOTICE("starting '%s'\n", svc->name); - - pid = fork(); + NOTICE("Starting service '%s'...\n", svc->name); + pid_t pid = fork(); if (pid == 0) { struct socketinfo *si; struct svcenvinfo *ei; @@ -257,9 +267,9 @@ void service_start(struct service *svc, const char *dynamic_args) int fd, sz; umask(077); - if (properties_inited()) { + if (properties_initialized()) { get_property_workspace(&fd, &sz); - sprintf(tmp, "%d,%d", dup(fd), sz); + snprintf(tmp, sizeof(tmp), "%d,%d", dup(fd), sz); add_environment("ANDROID_PROPERTY_WORKSPACE", tmp); } @@ -294,18 +304,18 @@ void service_start(struct service *svc, const char *dynamic_args) zap_stdio(); } -#if 0 - for (n = 0; svc->args[n]; n++) { - INFO("args[%d] = '%s'\n", n, svc->args[n]); - } - for (n = 0; ENV[n]; n++) { - INFO("env[%d] = '%s'\n", n, ENV[n]); + 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]); + } } -#endif setpgid(0, getpid()); - /* as requested, set our gid, supplemental gids, and uid */ + // As requested, set our gid, supplemental gids, and uid. if (svc->gid) { if (setgid(svc->gid) != 0) { ERROR("setgid failed: %s\n", strerror(errno)); @@ -350,7 +360,7 @@ void service_start(struct service *svc, const char *dynamic_args) if (arg_idx == INIT_PARSER_MAXARGS) break; } - arg_ptrs[arg_idx] = '\0'; + arg_ptrs[arg_idx] = NULL; execve(svc->args[0], (char**) arg_ptrs, (char**) ENV); } _exit(127); @@ -368,8 +378,13 @@ void service_start(struct service *svc, const char *dynamic_args) svc->pid = pid; svc->flags |= SVC_RUNNING; - if (properties_inited()) - notify_service_state(svc->name, "running"); + if ((svc->flags & SVC_EXEC) != 0) { + INFO("SVC_EXEC pid %d (uid %d gid %d+%zu context %s) started; waiting...\n", + svc->pid, svc->uid, svc->gid, svc->nr_supp_gids, svc->seclabel); + waiting_for_exec = true; + } + + svc->NotifyStateChange("running"); } /* The how field should be either SVC_DISABLED, SVC_RESET, or SVC_RESTART */ @@ -393,11 +408,11 @@ static void service_stop_or_reset(struct service *svc, int how) } if (svc->pid) { - NOTICE("service '%s' is being killed\n", svc->name); + NOTICE("Service '%s' is being killed...\n", svc->name); kill(-svc->pid, SIGKILL); - notify_service_state(svc->name, "stopping"); + svc->NotifyStateChange("stopping"); } else { - notify_service_state(svc->name, "stopped"); + svc->NotifyStateChange("stopped"); } } @@ -541,47 +556,74 @@ static int is_last_command(struct action *act, struct command *cmd) return (list_tail(&act->commands) == &cmd->clist); } -void execute_one_command(void) -{ - int ret, i; + +void build_triggers_string(char *name_str, int length, struct action *cur_action) { + struct listnode *node; + struct trigger *cur_trigger; + + list_for_each(node, &cur_action->triggers) { + cur_trigger = node_to_item(node, struct trigger, nlist); + if (node != cur_action->triggers.next) { + strlcat(name_str, " " , length); + } + strlcat(name_str, cur_trigger->name , length); + } +} + +void execute_one_command() { + Timer t; + char cmd_str[256] = ""; + char name_str[256] = ""; if (!cur_action || !cur_command || is_last_command(cur_action, cur_command)) { cur_action = action_remove_queue_head(); cur_command = NULL; - if (!cur_action) + if (!cur_action) { return; - INFO("processing action %p (%s)\n", cur_action, cur_action->name); + } + + build_triggers_string(name_str, sizeof(name_str), cur_action); + + INFO("processing action %p (%s)\n", cur_action, name_str); cur_command = get_first_command(cur_action); } else { cur_command = get_next_command(cur_action, cur_command); } - if (!cur_command) + if (!cur_command) { return; + } - ret = cur_command->func(cur_command->nargs, cur_command->args); + int result = cur_command->func(cur_command->nargs, cur_command->args); if (klog_get_level() >= KLOG_INFO_LEVEL) { - for (i = 0; i < cur_command->nargs; i++) { + 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)); } } - INFO("command '%s' action=%s status=%d (%s:%d)\n", - cmd_str, cur_action ? cur_action->name : "", ret, cur_command->filename, - cur_command->line); + char source[256]; + if (cur_command->filename) { + snprintf(source, sizeof(source), " (%s:%d)", cur_command->filename, cur_command->line); + } else { + *source = '\0'; + } + INFO("Command '%s' action=%s%s returned %d took %.2fs\n", + cmd_str, cur_action ? name_str : "", source, result, t.duration()); } } -static int wait_for_coldboot_done_action(int nargs, char **args) -{ - int ret; - INFO("wait for %s\n", coldboot_done); - ret = wait_for_file(coldboot_done, COMMAND_RETRY_TIMEOUT); - if (ret) - ERROR("Timed out waiting for %s\n", coldboot_done); - return ret; +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; } /* @@ -609,7 +651,7 @@ static int mix_hwrng_into_linux_rng_action(int nargs, char **args) size_t total_bytes_written = 0; hwrandom_fd = TEMP_FAILURE_RETRY( - open("/dev/hw_random", O_RDONLY | O_NOFOLLOW)); + open("/dev/hw_random", O_RDONLY | O_NOFOLLOW | O_CLOEXEC)); if (hwrandom_fd == -1) { if (errno == ENOENT) { ERROR("/dev/hw_random not found\n"); @@ -622,7 +664,7 @@ static int mix_hwrng_into_linux_rng_action(int nargs, char **args) } urandom_fd = TEMP_FAILURE_RETRY( - open("/dev/urandom", O_WRONLY | O_NOFOLLOW)); + 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; @@ -658,7 +700,6 @@ ret: if (urandom_fd != -1) { close(urandom_fd); } - memset(buf, 0, sizeof(buf)); return result; } @@ -670,18 +711,17 @@ static int keychord_init_action(int nargs, char **args) static int console_init_action(int nargs, char **args) { - int fd; - - if (console[0]) { + char console[PROP_VALUE_MAX]; + if (property_get("ro.boot.console", console) > 0) { snprintf(console_name, sizeof(console_name), "/dev/%s", console); } - fd = open(console_name, O_RDWR); + int fd = open(console_name, O_RDWR | O_CLOEXEC); if (fd >= 0) have_console = 1; close(fd); - fd = open("/dev/tty0", O_WRONLY); + fd = open("/dev/tty0", O_WRONLY | O_CLOEXEC); if (fd >= 0) { const char *msg; msg = "\n" @@ -706,7 +746,7 @@ static int console_init_action(int nargs, char **args) return 0; } -static void import_kernel_nv(char *name, int for_emulator) +static void import_kernel_nv(char *name, bool for_emulator) { char *value = strchr(name, '='); int name_len = strlen(name); @@ -739,55 +779,56 @@ static void import_kernel_nv(char *name, int for_emulator) } } -static void export_kernel_boot_props(void) -{ - char tmp[PROP_VALUE_MAX]; - int ret; - unsigned i; +static void export_kernel_boot_props() { struct { const char *src_prop; - const char *dest_prop; - const char *def_val; + 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.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); + } +} - for (i = 0; i < ARRAY_SIZE(prop_map); i++) { - ret = property_get(prop_map[i].src_prop, tmp); - if (ret > 0) - property_set(prop_map[i].dest_prop, tmp); - else - property_set(prop_map[i].dest_prop, prop_map[i].def_val); +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; } - ret = property_get("ro.boot.console", tmp); - if (ret) - strlcpy(console, tmp, sizeof(console)); - - /* save a copy for init's usage during boot */ - property_get("ro.bootmode", tmp); - strlcpy(bootmode, tmp, sizeof(bootmode)); - - /* if this was given on kernel command line, override what we read - * before (e.g. from /proc/cpuinfo), if anything */ - ret = property_get("ro.boot.hardware", tmp); - if (ret) - strlcpy(hardware, tmp, sizeof(hardware)); - property_set("ro.hardware", hardware); - - snprintf(tmp, PROP_VALUE_MAX, "%d", revision); - property_set("ro.revision", tmp); - - /* TODO: these are obsolete. We should delete them */ - if (!strcmp(bootmode,"factory")) - property_set("ro.factorytest", "1"); - else if (!strcmp(bootmode,"factory2")) - property_set("ro.factorytest", "2"); - else - property_set("ro.factorytest", "0"); + 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) @@ -799,40 +840,9 @@ static void process_kernel_cmdline(void) * second pass is only necessary for qemu to export all kernel params * as props. */ - import_kernel_cmdline(0, import_kernel_nv); + import_kernel_cmdline(false, import_kernel_nv); if (qemu[0]) - import_kernel_cmdline(1, import_kernel_nv); - - /* now propogate the info given on command line to internal variables - * used by init as well as the current required properties - */ - export_kernel_boot_props(); -} - -static int property_service_init_action(int nargs, char **args) -{ - /* read any property files on system or data and - * fire up the property service. This must happen - * after the ro.foo properties are set above so - * that /data/local.prop cannot interfere with them. - */ - start_property_service(); - if (get_property_set_fd() < 0) { - ERROR("start_property_service() failed\n"); - exit(1); - } - - return 0; -} - -static int signal_init_action(int nargs, char **args) -{ - signal_init(); - if (get_signal_fd() < 0) { - ERROR("signal_init() failed\n"); - exit(1); - } - return 0; + import_kernel_cmdline(true, import_kernel_nv); } static int queue_property_triggers_action(int nargs, char **args) @@ -843,90 +853,55 @@ static int queue_property_triggers_action(int nargs, char **args) return 0; } -#if BOOTCHART -static int bootchart_init_action(int nargs, char **args) +static void selinux_init_all_handles(void) { - bootchart_count = bootchart_init(); - if (bootchart_count < 0) { - ERROR("bootcharting init failure\n"); - } else if (bootchart_count > 0) { - NOTICE("bootcharting started (period=%d ms)\n", bootchart_count*BOOTCHART_POLLING_MS); - } else { - NOTICE("bootcharting ignored\n"); - } - - return 0; + sehandle = selinux_android_file_context_handle(); + selinux_android_set_sehandle(sehandle); + sehandle_prop = selinux_android_prop_context_handle(); } -#endif -static const struct selinux_opt seopts_prop[] = { - { SELABEL_OPT_PATH, "/property_contexts" }, - { SELABEL_OPT_PATH, "/data/security/current/property_contexts" }, - { 0, NULL } -}; +enum selinux_enforcing_status { SELINUX_DISABLED, SELINUX_PERMISSIVE, SELINUX_ENFORCING }; -struct selabel_handle* selinux_android_prop_context_handle(void) -{ - int policy_index = selinux_android_use_data_policy() ? 1 : 0; - struct selabel_handle* sehandle = selabel_open(SELABEL_CTX_ANDROID_PROP, - &seopts_prop[policy_index], 1); - if (!sehandle) { - ERROR("SELinux: Could not load property_contexts: %s\n", - strerror(errno)); - return NULL; - } - INFO("SELinux: Loaded property contexts from %s\n", seopts_prop[policy_index].value); - return sehandle; -} +static selinux_enforcing_status selinux_status_from_cmdline() { + selinux_enforcing_status status = SELINUX_ENFORCING; -void selinux_init_all_handles(void) -{ - sehandle = selinux_android_file_context_handle(); - selinux_android_set_sehandle(sehandle); - sehandle_prop = selinux_android_prop_context_handle(); + std::function<void(char*,bool)> fn = [&](char* name, bool in_qemu) { + char *value = strchr(name, '='); + if (value == nullptr) { return; } + *value++ = '\0'; + if (strcmp(name, "androidboot.selinux") == 0) { + if (strcmp(value, "disabled") == 0) { + status = SELINUX_DISABLED; + } else if (strcmp(value, "permissive") == 0) { + status = SELINUX_PERMISSIVE; + } + } + }; + import_kernel_cmdline(false, fn); + + return status; } + static bool selinux_is_disabled(void) { -#ifdef ALLOW_DISABLE_SELINUX - char tmp[PROP_VALUE_MAX]; - - 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; - } - - 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; + if (ALLOW_DISABLE_SELINUX) { + if (access("/sys/fs/selinux", F_OK) != 0) { + // SELinux is not compiled into the kernel, or has been disabled + // via the kernel command line "selinux=0". + return true; + } + return selinux_status_from_cmdline() == SELINUX_DISABLED; } -#endif return false; } static bool selinux_is_enforcing(void) { -#ifdef 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); + if (ALLOW_DISABLE_SELINUX) { + return selinux_status_from_cmdline() == SELINUX_ENFORCING; } - -#endif return true; } @@ -952,223 +927,196 @@ int selinux_reload_policy(void) return 0; } -static int audit_callback(void *data, security_class_t cls __attribute__((unused)), char *buf, size_t len) -{ +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; } -int log_callback(int type, const char *fmt, ...) -{ - int level; - va_list ap; - switch (type) { - case SELINUX_WARNING: - level = KLOG_WARNING_LEVEL; - break; - case SELINUX_INFO: - level = KLOG_INFO_LEVEL; - break; - default: - level = KLOG_ERROR_LEVEL; - break; - } - va_start(ap, fmt); - klog_vwrite(level, fmt, ap); - va_end(ap); - 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(void) -{ +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; } - INFO("loading selinux policy\n"); - if (selinux_android_load_policy() < 0) { - ERROR("SELinux: Failed to load policy; rebooting into recovery mode\n"); - android_reboot(ANDROID_RB_RESTART2, 0, "recovery"); - while (1) { pause(); } // never reached - } + if (in_kernel_domain) { + INFO("Loading SELinux policy...\n"); + if (selinux_android_load_policy() < 0) { + ERROR("failed to load policy: %s\n", strerror(errno)); + security_failure(); + } - selinux_init_all_handles(); - bool is_enforcing = selinux_is_enforcing(); - INFO("SELinux: security_setenforce(%d)\n", is_enforcing); - security_setenforce(is_enforcing); + bool is_enforcing = selinux_is_enforcing(); + security_setenforce(is_enforcing); + + if (write_file("/sys/fs/selinux/checkreqprot", "0") == -1) { + security_failure(); + } + + NOTICE("(Initializing SELinux %s took %.2fs.)\n", + is_enforcing ? "enforcing" : "non-enforcing", t.duration()); + } else { + selinux_init_all_handles(); + } } -int main(int argc, char **argv) -{ - int fd_count = 0; - struct pollfd ufds[4]; - char *tmpdev; - char* debuggable; - char tmp[32]; - int property_set_fd_init = 0; - int signal_fd_init = 0; - int keychord_fd_init = 0; - bool is_charger = false; - - if (!strcmp(basename(argv[0]), "ueventd")) +int main(int argc, char** argv) { + if (!strcmp(basename(argv[0]), "ueventd")) { return ueventd_main(argc, argv); + } - if (!strcmp(basename(argv[0]), "watchdogd")) + if (!strcmp(basename(argv[0]), "watchdogd")) { return watchdogd_main(argc, argv); + } - /* clear the umask */ + // 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. - */ - mkdir("/dev", 0755); - mkdir("/proc", 0755); - mkdir("/sys", 0755); - - mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); - mkdir("/dev/pts", 0755); - mkdir("/dev/socket", 0755); - mount("devpts", "/dev/pts", "devpts", 0, NULL); - mount("proc", "/proc", "proc", 0, NULL); - mount("sysfs", "/sys", "sysfs", 0, NULL); - - /* indicate that booting is in progress to background fw loaders, etc */ - close(open("/dev/.booting", O_WRONLY | O_CREAT, 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. - */ + add_environment("PATH", _PATH_DEFPATH); + + bool is_first_stage = (argc == 1) || (strcmp(argv[1], "--second-stage") != 0); + + // Get the basic filesystem setup we need put together in the initramdisk + // on / and then we'll let the rc file figure out the rest. + if (is_first_stage) { + mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755"); + mkdir("/dev/pts", 0755); + mkdir("/dev/socket", 0755); + mount("devpts", "/dev/pts", "devpts", 0, NULL); + mount("proc", "/proc", "proc", 0, NULL); + mount("sysfs", "/sys", "sysfs", 0, NULL); + } + + // We must have some place other than / to create the device nodes for + // kmsg and null, otherwise we won't be able to remount / read-only + // later on. Now that tmpfs is mounted on /dev, we can actually talk + // to the outside world. open_devnull_stdio(); klog_init(); - property_init(); + klog_set_level(KLOG_NOTICE_LEVEL); - get_hardware_name(hardware, &revision); + NOTICE("init%s started!\n", is_first_stage ? "" : " second stage"); - process_kernel_cmdline(); + if (!is_first_stage) { + // Indicate that booting is in progress to background fw loaders, etc. + close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); - union selinux_callback cb; - cb.func_log = log_callback; - selinux_set_callback(SELINUX_CB_LOG, cb); + property_init(); - cb.func_audit = audit_callback; - selinux_set_callback(SELINUX_CB_AUDIT, cb); + // 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(); - selinux_initialize(); - /* 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. - */ + // 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"); - is_charger = !strcmp(bootmode, "charger"); + epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (epoll_fd == -1) { + ERROR("epoll_create1 failed: %s\n", strerror(errno)); + exit(1); + } + + signal_handler_init(); - INFO("property init\n"); property_load_boot_defaults(); + start_property_service(); - INFO("reading config file\n"); init_parse_config_file("/init.rc"); action_for_each_trigger("early-init", action_add_queue_tail); + // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev... queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done"); + // ... so that we can start queuing up actions that require stuff from /dev. queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); queue_builtin_action(keychord_init_action, "keychord_init"); queue_builtin_action(console_init_action, "console_init"); - /* execute all the boot actions to get us started */ + // Trigger all the boot actions to get us started. action_for_each_trigger("init", action_add_queue_tail); - /* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random - * wasn't ready immediately after wait_for_coldboot_done - */ + // 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"); - queue_builtin_action(property_service_init_action, "property_service_init"); - queue_builtin_action(signal_init_action, "signal_init"); - /* Don't mount filesystems or start core system services if in charger mode. */ - if (is_charger) { + // Don't mount filesystems or start core system services in charger mode. + char bootmode[PROP_VALUE_MAX]; + if (property_get("ro.bootmode", bootmode) > 0 && strcmp(bootmode, "charger") == 0) { action_for_each_trigger("charger", action_add_queue_tail); } else { action_for_each_trigger("late-init", action_add_queue_tail); } - /* run all property triggers based on current state of the properties */ + // Run all property triggers based on current state of the properties. queue_builtin_action(queue_property_triggers_action, "queue_property_triggers"); - -#if BOOTCHART - queue_builtin_action(bootchart_init_action, "bootchart_init"); -#endif - - for(;;) { - int nr, i, timeout = -1; - - execute_one_command(); - restart_processes(); - - if (!property_set_fd_init && get_property_set_fd() > 0) { - ufds[fd_count].fd = get_property_set_fd(); - ufds[fd_count].events = POLLIN; - ufds[fd_count].revents = 0; - fd_count++; - property_set_fd_init = 1; - } - if (!signal_fd_init && get_signal_fd() > 0) { - ufds[fd_count].fd = get_signal_fd(); - ufds[fd_count].events = POLLIN; - ufds[fd_count].revents = 0; - fd_count++; - signal_fd_init = 1; - } - if (!keychord_fd_init && get_keychord_fd() > 0) { - ufds[fd_count].fd = get_keychord_fd(); - ufds[fd_count].events = POLLIN; - ufds[fd_count].revents = 0; - fd_count++; - keychord_fd_init = 1; + while (true) { + if (!waiting_for_exec) { + execute_one_command(); + restart_processes(); } + int timeout = -1; if (process_needs_restart) { timeout = (process_needs_restart - gettime()) * 1000; if (timeout < 0) timeout = 0; } - if (!action_queue_empty() || cur_action) + if (!action_queue_empty() || cur_action) { timeout = 0; - -#if BOOTCHART - if (bootchart_count > 0) { - if (timeout < 0 || timeout > BOOTCHART_POLLING_MS) - timeout = BOOTCHART_POLLING_MS; - if (bootchart_step() < 0 || --bootchart_count == 0) { - bootchart_finish(); - bootchart_count = 0; - } } -#endif - nr = poll(ufds, fd_count, timeout); - if (nr <= 0) - continue; + bootchart_sample(&timeout); - for (i = 0; i < fd_count; i++) { - if (ufds[i].revents & POLLIN) { - if (ufds[i].fd == get_property_set_fd()) - handle_property_set_fd(); - else if (ufds[i].fd == get_keychord_fd()) - handle_keychord(); - else if (ufds[i].fd == get_signal_fd()) - handle_signal(); - } + 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)(); } } diff --git a/init/init.h b/init/init.h index a7615a3..1cabb14 100644 --- a/init/init.h +++ b/init/init.h @@ -17,11 +17,10 @@ #ifndef _INIT_INIT_H #define _INIT_INIT_H -#include <cutils/list.h> - -#include <sys/stat.h> +#include <sys/types.h> -void handle_control_message(const char *msg, const char *arg); +#include <cutils/list.h> +#include <cutils/iosched_policy.h> struct command { @@ -37,6 +36,11 @@ struct command char *args[1]; }; +struct trigger { + struct listnode nlist; + const char *name; +}; + struct action { /* node in list of all actions */ struct listnode alist; @@ -46,8 +50,9 @@ struct action { struct listnode tlist; unsigned hash; - const char *name; + /* list of actions which triggers the commands*/ + struct listnode triggers; struct listnode commands; struct command *current; }; @@ -68,27 +73,29 @@ struct svcenvinfo { const char *value; }; -#define SVC_DISABLED 0x01 /* do not autostart with class */ -#define SVC_ONESHOT 0x02 /* do not restart on exit */ -#define SVC_RUNNING 0x04 /* currently active */ -#define SVC_RESTARTING 0x08 /* waiting to restart */ -#define SVC_CONSOLE 0x10 /* requires console */ -#define SVC_CRITICAL 0x20 /* will reboot into recovery if keeps crashing */ -#define SVC_RESET 0x40 /* Use when stopping a process, but not disabling - so it can be restarted with its class */ -#define SVC_RC_DISABLED 0x80 /* Remember if the disabled flag was set in the rc script */ -#define SVC_RESTART 0x100 /* Use to safely restart (stop, wait, start) a service */ -#define SVC_DISABLED_START 0x200 /* a start was requested but it was disabled at the time */ +#define SVC_DISABLED 0x001 // do not autostart with class +#define SVC_ONESHOT 0x002 // do not restart on exit +#define SVC_RUNNING 0x004 // currently active +#define SVC_RESTARTING 0x008 // waiting to restart +#define SVC_CONSOLE 0x010 // requires console +#define SVC_CRITICAL 0x020 // will reboot into recovery if keeps crashing +#define SVC_RESET 0x040 // Use when stopping a process, but not disabling so it can be restarted with its class. +#define SVC_RC_DISABLED 0x080 // Remember if the disabled flag was set in the rc script. +#define SVC_RESTART 0x100 // Use to safely restart (stop, wait, start) a service. +#define SVC_DISABLED_START 0x200 // A start was requested but it was disabled at the time. +#define SVC_EXEC 0x400 // This synthetic service corresponds to an 'exec'. #define NR_SVC_SUPP_GIDS 12 /* twelve supplementary groups */ #define COMMAND_RETRY_TIMEOUT 5 struct service { + void NotifyStateChange(const char* new_state); + /* list of all services */ struct listnode slist; - const char *name; + char *name; const char *classname; unsigned flags; @@ -96,25 +103,25 @@ struct service { time_t time_started; /* time of last start */ time_t time_crashed; /* first crash within inspection window */ int nr_crashed; /* number of times crashed within window */ - + uid_t uid; gid_t gid; gid_t supp_gids[NR_SVC_SUPP_GIDS]; size_t nr_supp_gids; - char *seclabel; + const char* seclabel; struct socketinfo *sockets; struct svcenvinfo *envvars; struct action onrestart; /* Actions to execute on restart. */ - + /* keycodes for triggering this service via /dev/keychord */ int *keycodes; int nkeycodes; int keychord_id; - int ioprio_class; + IoSchedClass ioprio_class; int ioprio_pri; int nargs; @@ -122,7 +129,13 @@ struct service { char *args[1]; }; /* ^-------'args' MUST be at the end of this struct! */ -void notify_service_state(const char *name, const char *state); +extern bool waiting_for_exec; +extern struct selabel_handle *sehandle; +extern struct selabel_handle *sehandle_prop; + +void build_triggers_string(char *name_str, int length, struct action *cur_action); + +void handle_control_message(const char *msg, const char *arg); struct service *service_find_by_name(const char *name); struct service *service_find_by_pid(pid_t pid); @@ -138,8 +151,10 @@ void service_restart(struct service *svc); void service_start(struct service *svc, const char *dynamic_args); void property_changed(const char *name, const char *value); -extern struct selabel_handle *sehandle; -extern struct selabel_handle *sehandle_prop; -extern int selinux_reload_policy(void); +int selinux_reload_policy(void); + +void zap_stdio(void); + +void register_epoll_handler(int fd, void (*fn)()); #endif /* _INIT_INIT_H */ diff --git a/init/init_parser.c b/init/init_parser.cpp index 6466db2..b76b04e 100644 --- a/init/init_parser.c +++ b/init/init_parser.cpp @@ -14,14 +14,16 @@ * limitations under the License. */ -#include <stdio.h> -#include <stdlib.h> -#include <unistd.h> +#include <ctype.h> +#include <errno.h> #include <fcntl.h> +#include <inttypes.h> #include <stdarg.h> -#include <string.h> #include <stddef.h> -#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> #include "init.h" #include "parser.h" @@ -73,14 +75,53 @@ static struct { #define kw_func(kw) (keyword_info[kw].func) #define kw_nargs(kw) (keyword_info[kw].nargs) +void dump_parser_state() { + if (false) { + struct listnode* node; + list_for_each(node, &service_list) { + service* svc = node_to_item(node, struct service, slist); + INFO("service %s\n", svc->name); + INFO(" class '%s'\n", svc->classname); + INFO(" exec"); + for (int n = 0; n < svc->nargs; n++) { + INFO(" '%s'", svc->args[n]); + } + INFO("\n"); + for (socketinfo* si = svc->sockets; si; si = si->next) { + INFO(" socket %s %s 0%o\n", si->name, si->type, si->perm); + } + } + + list_for_each(node, &action_list) { + action* act = node_to_item(node, struct action, alist); + INFO("on "); + char name_str[256] = ""; + build_triggers_string(name_str, sizeof(name_str), act); + INFO("%s", name_str); + INFO("\n"); + + struct listnode* node2; + list_for_each(node2, &act->commands) { + command* cmd = node_to_item(node2, struct command, clist); + INFO(" %p", cmd->func); + for (int n = 0; n < cmd->nargs; n++) { + INFO(" %s", cmd->args[n]); + } + INFO("\n"); + } + INFO("\n"); + } + } +} + static int lookup_keyword(const char *s) { switch (*s++) { + case 'b': + if (!strcmp(s, "ootchart_init")) return K_bootchart_init; + break; case 'c': - if (!strcmp(s, "opy")) return K_copy; - if (!strcmp(s, "apability")) return K_capability; - if (!strcmp(s, "hdir")) return K_chdir; - if (!strcmp(s, "hroot")) return K_chroot; + if (!strcmp(s, "opy")) return K_copy; if (!strcmp(s, "lass")) return K_class; if (!strcmp(s, "lass_start")) return K_class_start; if (!strcmp(s, "lass_stop")) return K_class_stop; @@ -110,6 +151,7 @@ static int lookup_keyword(const char *s) if (!strcmp(s, "fup")) return K_ifup; if (!strcmp(s, "nsmod")) return K_insmod; if (!strcmp(s, "mport")) return K_import; + if (!strcmp(s, "nstallkey")) return K_installkey; break; case 'k': if (!strcmp(s, "eycodes")) return K_keycodes; @@ -131,6 +173,7 @@ static int lookup_keyword(const char *s) break; case 'p': if (!strcmp(s, "owerctl")) return K_powerctl; + break; case 'r': if (!strcmp(s, "estart")) return K_restart; if (!strcmp(s, "estorecon")) return K_restorecon; @@ -141,13 +184,9 @@ static int lookup_keyword(const char *s) case 's': if (!strcmp(s, "eclabel")) return K_seclabel; if (!strcmp(s, "ervice")) return K_service; - if (!strcmp(s, "etcon")) return K_setcon; - if (!strcmp(s, "etenforce")) return K_setenforce; if (!strcmp(s, "etenv")) return K_setenv; - if (!strcmp(s, "etkey")) return K_setkey; if (!strcmp(s, "etprop")) return K_setprop; if (!strcmp(s, "etrlimit")) return K_setrlimit; - if (!strcmp(s, "etsebool")) return K_setsebool; if (!strcmp(s, "ocket")) return K_socket; if (!strcmp(s, "tart")) return K_start; if (!strcmp(s, "top")) return K_stop; @@ -161,6 +200,10 @@ static int lookup_keyword(const char *s) case 'u': if (!strcmp(s, "ser")) return K_user; break; + case 'v': + if (!strcmp(s, "erity_load_state")) return K_verity_load_state; + if (!strcmp(s, "erity_update_state")) return K_verity_update_state; + break; case 'w': if (!strcmp(s, "rite")) return K_write; if (!strcmp(s, "ait")) return K_wait; @@ -169,8 +212,7 @@ static int lookup_keyword(const char *s) return K_UNKNOWN; } -static void parse_line_no_op(struct parse_state *state, int nargs, char **args) -{ +static void parse_line_no_op(struct parse_state*, int, char**) { } static int push_chars(char **dst, int *len, const char *chars, int cnt) @@ -187,19 +229,14 @@ static int push_chars(char **dst, int *len, const char *chars, int cnt) int expand_props(char *dst, const char *src, int dst_size) { - int cnt = 0; char *dst_ptr = dst; const char *src_ptr = src; - int src_len; - int idx = 0; int ret = 0; int left = dst_size - 1; if (!src || !dst || dst_size == 0) return -1; - src_len = strlen(src); - /* - variables can either be $x.y or ${x.y}, in case they are only part * of the string. * - will accept $$ as a literal $. @@ -294,8 +331,7 @@ err: static void parse_import(struct parse_state *state, int nargs, char **args) { - struct listnode *import_list = state->priv; - struct import *import; + struct listnode *import_list = (listnode*) state->priv; char conf_file[PATH_MAX]; int ret; @@ -311,10 +347,10 @@ static void parse_import(struct parse_state *state, int nargs, char **args) return; } - import = calloc(1, sizeof(struct import)); + struct import* import = (struct import*) calloc(1, sizeof(struct import)); import->filename = strdup(conf_file); list_add_tail(import_list, &import->list); - INFO("found import '%s', adding to import list", import->filename); + INFO("Added '%s' to import list\n", import->filename); } static void parse_new_section(struct parse_state *state, int kw, @@ -344,7 +380,7 @@ static void parse_new_section(struct parse_state *state, int kw, state->parse_line = parse_line_no_op; } -static void parse_config(const char *fn, char *s) +static void parse_config(const char *fn, const std::string& data) { struct parse_state state; struct listnode import_list; @@ -355,7 +391,7 @@ static void parse_config(const char *fn, char *s) nargs = 0; state.filename = fn; state.line = 0; - state.ptr = s; + state.ptr = strdup(data.c_str()); // TODO: fix this code! state.nexttoken = 0; state.parse_line = parse_line_no_op; @@ -393,7 +429,6 @@ parser_done: struct import *import = node_to_item(node, struct import, list); int ret; - INFO("importing '%s'", import->filename); ret = init_parse_config_file(import->filename); if (ret) ERROR("could not import file '%s' from '%s'\n", @@ -401,14 +436,18 @@ parser_done: } } -int init_parse_config_file(const char *fn) -{ - char *data; - data = read_file(fn, 0); - if (!data) return -1; +int init_parse_config_file(const char* path) { + INFO("Parsing %s...\n", path); + Timer t; + std::string data; + if (!read_file(path, &data)) { + return -1; + } + + parse_config(path, data); + dump_parser_state(); - parse_config(fn, data); - DUMP(); + NOTICE("(Parsing %s took %.2fs.)\n", path, t.duration()); return 0; } @@ -504,83 +543,94 @@ void service_for_each_flags(unsigned matchflags, void action_for_each_trigger(const char *trigger, void (*func)(struct action *act)) { - struct listnode *node; + struct listnode *node, *node2; struct action *act; + struct trigger *cur_trigger; + list_for_each(node, &action_list) { act = node_to_item(node, struct action, alist); - if (!strcmp(act->name, trigger)) { - func(act); + list_for_each(node2, &act->triggers) { + cur_trigger = node_to_item(node2, struct trigger, nlist); + if (!strcmp(cur_trigger->name, trigger)) { + func(act); + } } } } + void queue_property_triggers(const char *name, const char *value) { - struct listnode *node; + struct listnode *node, *node2; struct action *act; + struct trigger *cur_trigger; + bool match; + int name_length; + list_for_each(node, &action_list) { act = node_to_item(node, struct action, alist); - if (!strncmp(act->name, "property:", strlen("property:"))) { - const char *test = act->name + strlen("property:"); - int name_length = strlen(name); - - if (!strncmp(name, test, name_length) && - test[name_length] == '=' && - (!strcmp(test + name_length + 1, value) || - !strcmp(test + name_length + 1, "*"))) { - action_add_queue_tail(act); - } + match = !name; + list_for_each(node2, &act->triggers) { + cur_trigger = node_to_item(node2, struct trigger, nlist); + if (!strncmp(cur_trigger->name, "property:", strlen("property:"))) { + const char *test = cur_trigger->name + strlen("property:"); + if (!match) { + name_length = strlen(name); + if (!strncmp(name, test, name_length) && + test[name_length] == '=' && + (!strcmp(test + name_length + 1, value) || + !strcmp(test + name_length + 1, "*"))) { + match = true; + continue; + } + } else { + const char* equals = strchr(test, '='); + if (equals) { + char prop_name[PROP_NAME_MAX + 1]; + char value[PROP_VALUE_MAX]; + int length = equals - test; + if (length <= PROP_NAME_MAX) { + int ret; + memcpy(prop_name, test, length); + prop_name[length] = 0; + + /* does the property exist, and match the trigger value? */ + ret = property_get(prop_name, value); + if (ret > 0 && (!strcmp(equals + 1, value) || + !strcmp(equals + 1, "*"))) { + continue; + } + } + } + } + } + match = false; + break; + } + if (match) { + action_add_queue_tail(act); } } } void queue_all_property_triggers() { - struct listnode *node; - struct action *act; - list_for_each(node, &action_list) { - act = node_to_item(node, struct action, alist); - if (!strncmp(act->name, "property:", strlen("property:"))) { - /* parse property name and value - syntax is property:<name>=<value> */ - const char* name = act->name + strlen("property:"); - const char* equals = strchr(name, '='); - if (equals) { - char prop_name[PROP_NAME_MAX + 1]; - char value[PROP_VALUE_MAX]; - int length = equals - name; - if (length > PROP_NAME_MAX) { - ERROR("property name too long in trigger %s", act->name); - } else { - int ret; - memcpy(prop_name, name, length); - prop_name[length] = 0; - - /* does the property exist, and match the trigger value? */ - ret = property_get(prop_name, value); - if (ret > 0 && (!strcmp(equals + 1, value) || - !strcmp(equals + 1, "*"))) { - action_add_queue_tail(act); - } - } - } - } - } + queue_property_triggers(NULL, NULL); } -void queue_builtin_action(int (*func)(int nargs, char **args), char *name) +void queue_builtin_action(int (*func)(int nargs, char **args), const char *name) { - struct action *act; - struct command *cmd; - - act = calloc(1, sizeof(*act)); - act->name = name; + action* act = (action*) calloc(1, sizeof(*act)); + trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger)); + cur_trigger->name = name; + list_init(&act->triggers); + list_add_tail(&act->triggers, &cur_trigger->nlist); list_init(&act->commands); list_init(&act->qlist); - cmd = calloc(1, sizeof(*cmd)); + command* cmd = (command*) calloc(1, sizeof(*cmd)); cmd->func = func; - cmd->args[0] = name; + cmd->args[0] = const_cast<char*>(name); cmd->nargs = 1; list_add_tail(&act->commands, &cmd->clist); @@ -613,9 +663,67 @@ int action_queue_empty() return list_empty(&action_queue); } +service* make_exec_oneshot_service(int nargs, char** args) { + // Parse the arguments: exec [SECLABEL [UID [GID]*] --] COMMAND ARGS... + int command_arg = 1; + for (int i = 1; i < nargs; ++i) { + if (strcmp(args[i], "--") == 0) { + command_arg = i + 1; + break; + } + } + if (command_arg > 4 + NR_SVC_SUPP_GIDS) { + ERROR("exec called with too many supplementary group ids\n"); + return NULL; + } + + int argc = nargs - command_arg; + char** argv = (args + command_arg); + if (argc < 1) { + ERROR("exec called without command\n"); + return NULL; + } + + service* svc = (service*) calloc(1, sizeof(*svc) + sizeof(char*) * argc); + if (svc == NULL) { + ERROR("Couldn't allocate service for exec of '%s': %s", argv[0], strerror(errno)); + return NULL; + } + + if (command_arg > 2) { + svc->seclabel = args[1]; + } + if (command_arg > 3) { + svc->uid = decode_uid(args[2]); + } + if (command_arg > 4) { + svc->gid = decode_uid(args[3]); + svc->nr_supp_gids = command_arg - 1 /* -- */ - 4 /* exec SECLABEL UID GID */; + for (size_t i = 0; i < svc->nr_supp_gids; ++i) { + svc->supp_gids[i] = decode_uid(args[4 + i]); + } + } + + static int exec_count; // Every service needs a unique name. + char* name = NULL; + asprintf(&name, "exec %d (%s)", exec_count++, argv[0]); + if (name == NULL) { + ERROR("Couldn't allocate name for exec service '%s'\n", argv[0]); + free(svc); + return NULL; + } + svc->name = name; + svc->classname = "default"; + svc->flags = SVC_EXEC | SVC_ONESHOT; + svc->nargs = argc; + memcpy(svc->args, argv, sizeof(char*) * svc->nargs); + svc->args[argc] = NULL; + list_add_tail(&service_list, &svc->slist); + return svc; +} + static void *parse_service(struct parse_state *state, int nargs, char **args) { - struct service *svc; if (nargs < 3) { parse_error(state, "services must have a name and a program\n"); return 0; @@ -625,24 +733,27 @@ static void *parse_service(struct parse_state *state, int nargs, char **args) return 0; } - svc = service_find_by_name(args[1]); + service* svc = (service*) service_find_by_name(args[1]); if (svc) { parse_error(state, "ignored duplicate definition of service '%s'\n", args[1]); return 0; } nargs -= 2; - svc = calloc(1, sizeof(*svc) + sizeof(char*) * nargs); + svc = (service*) calloc(1, sizeof(*svc) + sizeof(char*) * nargs); if (!svc) { parse_error(state, "out of memory\n"); return 0; } - svc->name = args[1]; + svc->name = strdup(args[1]); svc->classname = "default"; memcpy(svc->args, args + 2, sizeof(char*) * nargs); + trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger)); svc->args[nargs] = 0; svc->nargs = nargs; - svc->onrestart.name = "onrestart"; + list_init(&svc->onrestart.triggers); + cur_trigger->name = "onrestart"; + list_add_tail(&svc->onrestart.triggers, &cur_trigger->nlist); list_init(&svc->onrestart.commands); list_add_tail(&service_list, &svc->slist); return svc; @@ -650,7 +761,7 @@ static void *parse_service(struct parse_state *state, int nargs, char **args) static void parse_line_service(struct parse_state *state, int nargs, char **args) { - struct service *svc = state->context; + struct service *svc = (service*) state->context; struct command *cmd; int i, kw, kw_nargs; @@ -662,8 +773,6 @@ static void parse_line_service(struct parse_state *state, int nargs, char **args kw = lookup_keyword(args[0]); switch (kw) { - case K_capability: - break; case K_class: if (nargs != 2) { parse_error(state, "class option requires a classname\n"); @@ -719,7 +828,7 @@ static void parse_line_service(struct parse_state *state, int nargs, char **args if (nargs < 2) { parse_error(state, "keycodes option requires atleast one keycode\n"); } else { - svc->keycodes = malloc((nargs - 1) * sizeof(svc->keycodes[0])); + svc->keycodes = (int*) malloc((nargs - 1) * sizeof(svc->keycodes[0])); if (!svc->keycodes) { parse_error(state, "could not allocate keycodes\n"); } else { @@ -748,7 +857,7 @@ static void parse_line_service(struct parse_state *state, int nargs, char **args break; } - cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs); + cmd = (command*) malloc(sizeof(*cmd) + sizeof(char*) * nargs); cmd->func = kw_func(kw); cmd->nargs = nargs; memcpy(cmd->args, args, sizeof(char*) * nargs); @@ -758,12 +867,11 @@ static void parse_line_service(struct parse_state *state, int nargs, char **args svc->flags |= SVC_CRITICAL; break; case K_setenv: { /* name value */ - struct svcenvinfo *ei; if (nargs < 3) { parse_error(state, "setenv option requires name and value arguments\n"); break; } - ei = calloc(1, sizeof(*ei)); + svcenvinfo* ei = (svcenvinfo*) calloc(1, sizeof(*ei)); if (!ei) { parse_error(state, "out of memory\n"); break; @@ -775,7 +883,6 @@ static void parse_line_service(struct parse_state *state, int nargs, char **args break; } case K_socket: {/* name type perm [ uid gid context ] */ - struct socketinfo *si; if (nargs < 4) { parse_error(state, "socket option requires name, type, perm arguments\n"); break; @@ -785,7 +892,7 @@ static void parse_line_service(struct parse_state *state, int nargs, char **args parse_error(state, "socket type must be 'dgram', 'stream' or 'seqpacket'\n"); break; } - si = calloc(1, sizeof(*si)); + socketinfo* si = (socketinfo*) calloc(1, sizeof(*si)); if (!si) { parse_error(state, "out of memory\n"); break; @@ -825,17 +932,36 @@ static void parse_line_service(struct parse_state *state, int nargs, char **args static void *parse_action(struct parse_state *state, int nargs, char **args) { - struct action *act; + struct trigger *cur_trigger; + int i; if (nargs < 2) { parse_error(state, "actions must have a trigger\n"); return 0; } - if (nargs > 2) { - parse_error(state, "actions may not have extra parameters\n"); - return 0; + + action* act = (action*) calloc(1, sizeof(*act)); + list_init(&act->triggers); + + for (i = 1; i < nargs; i++) { + if (!(i % 2)) { + if (strcmp(args[i], "&&")) { + struct listnode *node; + struct listnode *node2; + parse_error(state, "& is the only symbol allowed to concatenate actions\n"); + list_for_each_safe(node, node2, &act->triggers) { + struct trigger *trigger = node_to_item(node, struct trigger, nlist); + free(trigger); + } + free(act); + return 0; + } else + continue; + } + cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger)); + cur_trigger->name = args[i]; + list_add_tail(&act->triggers, &cur_trigger->nlist); } - act = calloc(1, sizeof(*act)); - act->name = args[1]; + list_init(&act->commands); list_init(&act->qlist); list_add_tail(&action_list, &act->alist); @@ -845,9 +971,7 @@ static void *parse_action(struct parse_state *state, int nargs, char **args) static void parse_line_action(struct parse_state* state, int nargs, char **args) { - struct command *cmd; - struct action *act = state->context; - int (*func)(int nargs, char **args); + struct action *act = (action*) state->context; int kw, n; if (nargs == 0) { @@ -866,7 +990,7 @@ static void parse_line_action(struct parse_state* state, int nargs, char **args) n > 2 ? "arguments" : "argument"); return; } - cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs); + command* cmd = (command*) malloc(sizeof(*cmd) + sizeof(char*) * nargs); cmd->func = kw_func(kw); cmd->line = state->line; cmd->filename = state->filename; diff --git a/init/init_parser.h b/init/init_parser.h index b078cad..6348607 100644 --- a/init/init_parser.h +++ b/init/init_parser.h @@ -20,6 +20,7 @@ #define INIT_PARSER_MAXARGS 64 struct action; +struct service; struct action *action_remove_queue_head(void); void action_add_queue_tail(struct action *act); @@ -28,9 +29,11 @@ void action_for_each_trigger(const char *trigger, int action_queue_empty(void); void queue_property_triggers(const char *name, const char *value); void queue_all_property_triggers(); -void queue_builtin_action(int (*func)(int nargs, char **args), char *name); +void queue_builtin_action(int (*func)(int nargs, char **args), const char *name); int init_parse_config_file(const char *fn); int expand_props(char *dst, const char *src, int len); +service* make_exec_oneshot_service(int argc, char** argv); + #endif diff --git a/init/init_parser_test.cpp b/init/init_parser_test.cpp new file mode 100644 index 0000000..170a73a --- /dev/null +++ b/init/init_parser_test.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2015 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 "init_parser.h" + +#include "init.h" +#include "util.h" + +#include <errno.h> +#include <gtest/gtest.h> + +TEST(init_parser, make_exec_oneshot_service_invalid_syntax) { + char* argv[10]; + memset(argv, 0, sizeof(argv)); + + // Nothing. + ASSERT_EQ(nullptr, make_exec_oneshot_service(0, argv)); + + // No arguments to 'exec'. + argv[0] = const_cast<char*>("exec"); + ASSERT_EQ(nullptr, make_exec_oneshot_service(1, argv)); + + // No command in "exec --". + argv[1] = const_cast<char*>("--"); + ASSERT_EQ(nullptr, make_exec_oneshot_service(2, argv)); +} + +TEST(init_parser, make_exec_oneshot_service_too_many_supplementary_gids) { + int argc = 0; + char* argv[4 + NR_SVC_SUPP_GIDS + 3]; + argv[argc++] = const_cast<char*>("exec"); + argv[argc++] = const_cast<char*>("seclabel"); + argv[argc++] = const_cast<char*>("root"); // uid. + argv[argc++] = const_cast<char*>("root"); // gid. + for (int i = 0; i < NR_SVC_SUPP_GIDS; ++i) { + argv[argc++] = const_cast<char*>("root"); // Supplementary gid. + } + argv[argc++] = const_cast<char*>("--"); + argv[argc++] = const_cast<char*>("/system/bin/id"); + argv[argc] = nullptr; + ASSERT_EQ(nullptr, make_exec_oneshot_service(argc, argv)); +} + +static void Test_make_exec_oneshot_service(bool dash_dash, bool seclabel, bool uid, bool gid, bool supplementary_gids) { + int argc = 0; + char* argv[10]; + argv[argc++] = const_cast<char*>("exec"); + if (seclabel) { + argv[argc++] = const_cast<char*>("u:r:su:s0"); // seclabel + if (uid) { + argv[argc++] = const_cast<char*>("log"); // uid + if (gid) { + argv[argc++] = const_cast<char*>("shell"); // gid + if (supplementary_gids) { + argv[argc++] = const_cast<char*>("system"); // supplementary gid 0 + argv[argc++] = const_cast<char*>("adb"); // supplementary gid 1 + } + } + } + } + if (dash_dash) { + argv[argc++] = const_cast<char*>("--"); + } + argv[argc++] = const_cast<char*>("/system/bin/toybox"); + argv[argc++] = const_cast<char*>("id"); + argv[argc] = nullptr; + service* svc = make_exec_oneshot_service(argc, argv); + ASSERT_NE(nullptr, svc); + + if (seclabel) { + ASSERT_STREQ("u:r:su:s0", svc->seclabel); + } else { + ASSERT_EQ(nullptr, svc->seclabel); + } + if (uid) { + ASSERT_EQ(decode_uid("log"), svc->uid); + } else { + ASSERT_EQ(0U, svc->uid); + } + if (gid) { + ASSERT_EQ(decode_uid("shell"), svc->gid); + } else { + ASSERT_EQ(0U, svc->gid); + } + if (supplementary_gids) { + ASSERT_EQ(2U, svc->nr_supp_gids); + ASSERT_EQ(decode_uid("system"), svc->supp_gids[0]); + ASSERT_EQ(decode_uid("adb"), svc->supp_gids[1]); + } else { + ASSERT_EQ(0U, svc->nr_supp_gids); + } + + ASSERT_EQ(2, svc->nargs); + ASSERT_EQ("/system/bin/toybox", svc->args[0]); + ASSERT_EQ("id", svc->args[1]); + ASSERT_EQ(nullptr, svc->args[2]); +} + +TEST(init_parser, make_exec_oneshot_service_with_everything) { + Test_make_exec_oneshot_service(true, true, true, true, true); +} + +TEST(init_parser, make_exec_oneshot_service_with_seclabel_uid_gid) { + Test_make_exec_oneshot_service(true, true, true, true, false); +} + +TEST(init_parser, make_exec_oneshot_service_with_seclabel_uid) { + Test_make_exec_oneshot_service(true, true, true, false, false); +} + +TEST(init_parser, make_exec_oneshot_service_with_seclabel) { + Test_make_exec_oneshot_service(true, true, false, false, false); +} + +TEST(init_parser, make_exec_oneshot_service_with_just_command) { + Test_make_exec_oneshot_service(true, false, false, false, false); +} + +TEST(init_parser, make_exec_oneshot_service_with_just_command_no_dash) { + Test_make_exec_oneshot_service(false, false, false, false, false); +} diff --git a/init/keychords.c b/init/keychords.cpp index 4a64042..10d9573 100644 --- a/init/keychords.c +++ b/init/keychords.cpp @@ -40,7 +40,7 @@ void add_service_keycodes(struct service *svc) if (svc->keycodes) { /* add a new keychord to the list */ size = sizeof(*keychord) + svc->nkeycodes * sizeof(keychord->keycodes[0]); - keychords = realloc(keychords, keychords_length + size); + keychords = (input_keychord*) realloc(keychords, keychords_length + size); if (!keychords) { ERROR("could not allocate keychords\n"); keychords_length = 0; @@ -62,38 +62,7 @@ void add_service_keycodes(struct service *svc) } } -void keychord_init() -{ - int fd, ret; - - service_for_each(add_service_keycodes); - - /* nothing to do if no services require keychords */ - if (!keychords) - return; - - fd = open("/dev/keychord", O_RDWR); - if (fd < 0) { - ERROR("could not open /dev/keychord\n"); - return; - } - fcntl(fd, F_SETFD, FD_CLOEXEC); - - ret = write(fd, keychords, keychords_length); - if (ret != keychords_length) { - ERROR("could not configure /dev/keychord %d (%d)\n", ret, errno); - close(fd); - fd = -1; - } - - free(keychords); - keychords = 0; - - keychord_fd = fd; -} - -void handle_keychord() -{ +static void handle_keychord() { struct service *svc; char adb_enabled[PROP_VALUE_MAX]; int ret; @@ -110,7 +79,7 @@ void handle_keychord() if (!strcmp(adb_enabled, "running")) { svc = service_find_by_keychord(id); if (svc) { - INFO("starting service %s from keychord\n", svc->name); + INFO("Starting service %s from keychord\n", svc->name); service_start(svc, NULL); } else { ERROR("service for keychord %d not found\n", id); @@ -118,7 +87,28 @@ void handle_keychord() } } -int get_keychord_fd() -{ - return keychord_fd; +void keychord_init() { + service_for_each(add_service_keycodes); + + // Nothing to do if no services require keychords. + if (!keychords) { + return; + } + + keychord_fd = TEMP_FAILURE_RETRY(open("/dev/keychord", O_RDWR | O_CLOEXEC)); + if (keychord_fd == -1) { + ERROR("could not open /dev/keychord: %s\n", strerror(errno)); + return; + } + + int ret = write(keychord_fd, keychords, keychords_length); + if (ret != keychords_length) { + ERROR("could not configure /dev/keychord %d: %s\n", ret, strerror(errno)); + close(keychord_fd); + } + + free(keychords); + keychords = nullptr; + + register_epoll_handler(keychord_fd, handle_keychord); } diff --git a/init/keychords.h b/init/keychords.h index 070b858..d2723b7 100644 --- a/init/keychords.h +++ b/init/keychords.h @@ -19,9 +19,7 @@ struct service; -void add_service_keycodes(struct service *svc); -void keychord_init(void); -void handle_keychord(void); -int get_keychord_fd(void); +void add_service_keycodes(service*); +void keychord_init(); #endif diff --git a/init/keywords.h b/init/keywords.h index 2d97e5b..37f01b8 100644 --- a/init/keywords.h +++ b/init/keywords.h @@ -1,7 +1,5 @@ - #ifndef KEYWORD -int do_chroot(int nargs, char **args); -int do_chdir(int nargs, char **args); +int do_bootchart_init(int nargs, char **args); int do_class_start(int nargs, char **args); int do_class_stop(int nargs, char **args); int do_class_reset(int nargs, char **args); @@ -12,6 +10,7 @@ int do_export(int nargs, char **args); int do_hostname(int nargs, char **args); int do_ifup(int nargs, char **args); int do_insmod(int nargs, char **args); +int do_installkey(int nargs, char **args); int do_mkdir(int nargs, char **args); int do_mount_all(int nargs, char **args); int do_mount(int nargs, char **args); @@ -21,12 +20,8 @@ int do_restorecon(int nargs, char **args); int do_restorecon_recursive(int nargs, char **args); int do_rm(int nargs, char **args); int do_rmdir(int nargs, char **args); -int do_setcon(int nargs, char **args); -int do_setenforce(int nargs, char **args); -int do_setkey(int nargs, char **args); int do_setprop(int nargs, char **args); int do_setrlimit(int nargs, char **args); -int do_setsebool(int nargs, char **args); int do_start(int nargs, char **args); int do_stop(int nargs, char **args); int do_swapon_all(int nargs, char **args); @@ -40,15 +35,14 @@ int do_chmod(int nargs, char **args); int do_loglevel(int nargs, char **args); int do_load_persist_props(int nargs, char **args); int do_load_all_props(int nargs, char **args); +int do_verity_load_state(int nargs, char **args); +int do_verity_update_state(int nargs, char **args); int do_wait(int nargs, char **args); #define __MAKE_KEYWORD_ENUM__ #define KEYWORD(symbol, flags, nargs, func) K_##symbol, enum { K_UNKNOWN, #endif - KEYWORD(capability, OPTION, 0, 0) - KEYWORD(chdir, COMMAND, 1, do_chdir) - KEYWORD(chroot, COMMAND, 1, do_chroot) KEYWORD(class, OPTION, 0, 0) KEYWORD(class_start, COMMAND, 1, do_class_start) KEYWORD(class_stop, COMMAND, 1, do_class_stop) @@ -64,6 +58,7 @@ enum { KEYWORD(hostname, COMMAND, 1, do_hostname) KEYWORD(ifup, COMMAND, 1, do_ifup) KEYWORD(insmod, COMMAND, 1, do_insmod) + KEYWORD(installkey, COMMAND, 1, do_installkey) KEYWORD(import, SECTION, 1, 0) KEYWORD(keycodes, OPTION, 0, 0) KEYWORD(mkdir, COMMAND, 1, do_mkdir) @@ -80,13 +75,9 @@ enum { KEYWORD(rmdir, COMMAND, 1, do_rmdir) KEYWORD(seclabel, OPTION, 0, 0) KEYWORD(service, SECTION, 0, 0) - KEYWORD(setcon, COMMAND, 1, do_setcon) - KEYWORD(setenforce, COMMAND, 1, do_setenforce) KEYWORD(setenv, OPTION, 2, 0) - KEYWORD(setkey, COMMAND, 0, do_setkey) KEYWORD(setprop, COMMAND, 2, do_setprop) KEYWORD(setrlimit, COMMAND, 3, do_setrlimit) - KEYWORD(setsebool, COMMAND, 2, do_setsebool) KEYWORD(socket, OPTION, 0, 0) KEYWORD(start, COMMAND, 1, do_start) KEYWORD(stop, COMMAND, 1, do_stop) @@ -95,6 +86,8 @@ enum { KEYWORD(symlink, COMMAND, 1, do_symlink) KEYWORD(sysclktz, COMMAND, 1, do_sysclktz) KEYWORD(user, OPTION, 0, 0) + KEYWORD(verity_load_state, COMMAND, 0, do_verity_load_state) + KEYWORD(verity_update_state, COMMAND, 0, do_verity_update_state) KEYWORD(wait, COMMAND, 1, do_wait) KEYWORD(write, COMMAND, 2, do_write) KEYWORD(copy, COMMAND, 2, do_copy) @@ -104,10 +97,10 @@ enum { KEYWORD(load_persist_props, COMMAND, 0, do_load_persist_props) KEYWORD(load_all_props, COMMAND, 0, do_load_all_props) KEYWORD(ioprio, OPTION, 0, 0) + KEYWORD(bootchart_init, COMMAND, 0, do_bootchart_init) #ifdef __MAKE_KEYWORD_ENUM__ KEYWORD_COUNT, }; #undef __MAKE_KEYWORD_ENUM__ #undef KEYWORD #endif - diff --git a/init/log.cpp b/init/log.cpp new file mode 100644 index 0000000..d32f2da --- /dev/null +++ b/init/log.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015 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 <stdlib.h> +#include <string.h> +#include <sys/uio.h> + +#include <selinux/selinux.h> + +#include "log.h" + +static void init_klog_vwrite(int level, const char* fmt, va_list ap) { + static const char* tag = basename(getprogname()); + + char prefix[64]; + snprintf(prefix, sizeof(prefix), "<%d>%s: ", level, tag); + + char msg[512]; + vsnprintf(msg, sizeof(msg), fmt, ap); + + iovec iov[2]; + iov[0].iov_base = prefix; + iov[0].iov_len = strlen(prefix); + iov[1].iov_base = msg; + iov[1].iov_len = strlen(msg); + + klog_writev(level, iov, 2); +} + +void init_klog_write(int level, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + init_klog_vwrite(level, fmt, ap); + va_end(ap); +} + +int selinux_klog_callback(int type, const char *fmt, ...) { + int level = KLOG_ERROR_LEVEL; + if (type == SELINUX_WARNING) { + level = KLOG_WARNING_LEVEL; + } else if (type == SELINUX_INFO) { + level = KLOG_INFO_LEVEL; + } + va_list ap; + va_start(ap, fmt); + init_klog_vwrite(level, fmt, ap); + va_end(ap); + return 0; +} @@ -19,10 +19,11 @@ #include <cutils/klog.h> -#define ERROR(x...) KLOG_ERROR("init", x) -#define NOTICE(x...) KLOG_NOTICE("init", x) -#define INFO(x...) KLOG_INFO("init", x) +#define ERROR(x...) init_klog_write(KLOG_ERROR_LEVEL, x) +#define NOTICE(x...) init_klog_write(KLOG_NOTICE_LEVEL, x) +#define INFO(x...) init_klog_write(KLOG_INFO_LEVEL, x) -extern int log_callback(int type, const char *fmt, ...); +void init_klog_write(int level, const char* fmt, ...) __printflike(2, 3); +int selinux_klog_callback(int level, const char* fmt, ...) __printflike(2, 3); #endif diff --git a/init/parser.c b/init/parser.cpp index 48e7aec..8193729 100644 --- a/init/parser.c +++ b/init/parser.cpp @@ -1,59 +1,17 @@ -#include <stdio.h> +#include "parser.h" + #include <stdarg.h> +#include <stdio.h> #include <string.h> -#include "parser.h" #include "log.h" -#define RAW(x...) log_write(6, x) - -void DUMP(void) -{ -#if 0 - struct service *svc; - struct action *act; - struct command *cmd; - struct listnode *node; - struct listnode *node2; - struct socketinfo *si; - int n; - - list_for_each(node, &service_list) { - svc = node_to_item(node, struct service, slist); - RAW("service %s\n", svc->name); - RAW(" class '%s'\n", svc->classname); - RAW(" exec"); - for (n = 0; n < svc->nargs; n++) { - RAW(" '%s'", svc->args[n]); - } - RAW("\n"); - for (si = svc->sockets; si; si = si->next) { - RAW(" socket %s %s 0%o\n", si->name, si->type, si->perm); - } - } - - list_for_each(node, &action_list) { - act = node_to_item(node, struct action, alist); - RAW("on %s\n", act->name); - list_for_each(node2, &act->commands) { - cmd = node_to_item(node2, struct command, clist); - RAW(" %p", cmd->func); - for (n = 0; n < cmd->nargs; n++) { - RAW(" %s", cmd->args[n]); - } - RAW("\n"); - } - RAW("\n"); - } -#endif -} - void parse_error(struct parse_state *state, const char *fmt, ...) { va_list ap; char buf[128]; int off; - + snprintf(buf, 128, "%s: %d: ", state->filename, state->line); buf[127] = 0; off = strlen(buf); diff --git a/init/parser.h b/init/parser.h index a58272a..95e1164 100644 --- a/init/parser.h +++ b/init/parser.h @@ -33,7 +33,7 @@ struct parse_state void *priv; }; -void DUMP(void); +void dump_parser_state(void); int next_token(struct parse_state *state); void parse_error(struct parse_state *state, const char *fmt, ...); diff --git a/init/property_service.c b/init/property_service.cpp index 91ef251..930ef82 100644 --- a/init/property_service.c +++ b/init/property_service.cpp @@ -26,6 +26,8 @@ #include <errno.h> #include <sys/poll.h> +#include <memory> + #include <cutils/misc.h> #include <cutils/sockets.h> #include <cutils/multiuser.h> @@ -52,44 +54,34 @@ #define PERSISTENT_PROPERTY_DIR "/data/property" static int persistent_properties_loaded = 0; -static int property_area_inited = 0; +static bool property_area_initialized = false; static int property_set_fd = -1; -typedef struct { +struct workspace { size_t size; int fd; -} workspace; - -static int init_workspace(workspace *w, size_t size) -{ - void *data; - int fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW); - if (fd < 0) - return -1; - - w->size = size; - w->fd = fd; - return 0; -} +}; static workspace pa_workspace; -static int init_property_area(void) -{ - if (property_area_inited) - return -1; - - if(__system_property_area_init()) - return -1; +void property_init() { + if (property_area_initialized) { + return; + } - if(init_workspace(&pa_workspace, 0)) - return -1; + property_area_initialized = true; - fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC); + if (__system_property_area_init()) { + return; + } - property_area_inited = 1; - return 0; + pa_workspace.size = 0; + pa_workspace.fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); + if (pa_workspace.fd == -1) { + ERROR("Failed to open %s: %s\n", PROP_FILENAME, strerror(errno)); + return; + } } static int check_mac_perms(const char *name, char *sctx) @@ -98,8 +90,6 @@ static int check_mac_perms(const char *name, char *sctx) return 1; char *tctx = NULL; - const char *class = "property_service"; - const char *perm = "set"; int result = 0; if (!sctx) @@ -111,7 +101,7 @@ static int check_mac_perms(const char *name, char *sctx) if (selabel_lookup(sehandle_prop, &tctx, name, 1) != 0) goto err; - if (selinux_check_access(sctx, tctx, class, perm, (void*) name) == 0) + if (selinux_check_access(sctx, tctx, "property_service", "set", (void*) name) == 0) result = 1; freecon(tctx); @@ -142,9 +132,6 @@ static int check_control_mac_perms(const char *name, char *sctx) */ static int check_perms(const char *name, char *sctx) { - int i; - unsigned int app_id; - if(!strncmp(name, "ro.", 3)) name +=3; @@ -165,7 +152,7 @@ static void write_persistent_property(const char *name, const char *value) snprintf(tempPath, sizeof(tempPath), "%s/.temp.XXXXXX", PERSISTENT_PROPERTY_DIR); fd = mkstemp(tempPath); if (fd < 0) { - ERROR("Unable to write persistent property to temp file %s errno: %d\n", tempPath, errno); + ERROR("Unable to write persistent property to temp file %s: %s\n", tempPath, strerror(errno)); return; } write(fd, value, strlen(value)); @@ -205,18 +192,14 @@ static bool is_legal_property_name(const char* name, size_t namelen) return true; } -int property_set(const char *name, const char *value) -{ - prop_info *pi; - int ret; - +static int property_set_impl(const char* name, const char* value) { size_t namelen = strlen(name); size_t valuelen = strlen(value); if (!is_legal_property_name(name, namelen)) return -1; if (valuelen >= PROP_VALUE_MAX) return -1; - pi = (prop_info*) __system_property_find(name); + prop_info* pi = (prop_info*) __system_property_find(name); if(pi != 0) { /* ro.* properties may NEVER be modified once set */ @@ -224,10 +207,9 @@ int property_set(const char *name, const char *value) __system_property_update(pi, value, valuelen); } else { - ret = __system_property_add(name, namelen, value, valuelen); - if (ret < 0) { - ERROR("Failed to set '%s'='%s'\n", name, value); - return ret; + int rc = __system_property_add(name, namelen, value, valuelen); + if (rc < 0) { + return rc; } } /* If name starts with "net." treat as a DNS property. */ @@ -256,12 +238,19 @@ int property_set(const char *name, const char *value) return 0; } -void handle_property_set_fd() +int property_set(const char* name, const char* value) { + int rc = property_set_impl(name, value); + if (rc == -1) { + ERROR("property_set(\"%s\", \"%s\") failed\n", name, value); + } + return rc; +} + +static void handle_property_set_fd() { prop_msg msg; int s; int r; - int res; struct ucred cr; struct sockaddr_un addr; socklen_t addr_size = sizeof(addr); @@ -291,15 +280,15 @@ void handle_property_set_fd() close(s); return; } else if (nr < 0) { - ERROR("sys_prop: error waiting for uid=%d to send property message. err=%d %s\n", cr.uid, errno, strerror(errno)); + ERROR("sys_prop: error waiting for uid=%d to send property message: %s\n", cr.uid, strerror(errno)); close(s); return; } r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), MSG_DONTWAIT)); if(r != sizeof(prop_msg)) { - ERROR("sys_prop: mis-match msg size received: %d expected: %zu errno: %d\n", - r, sizeof(prop_msg), errno); + ERROR("sys_prop: mis-match msg size received: %d expected: %zu: %s\n", + r, sizeof(prop_msg), strerror(errno)); close(s); return; } @@ -421,126 +410,106 @@ static void load_properties(char *data, const char *filter) * Filter is used to decide which properties to load: NULL loads all keys, * "ro.foo.*" is a prefix match, and "ro.foo.bar" is an exact match. */ -static void load_properties_from_file(const char *fn, const char *filter) -{ - char *data; - unsigned sz; +static void load_properties_from_file(const char* filename, const char* filter) { + Timer t; + std::string data; + if (read_file(filename, &data)) { + load_properties(&data[0], filter); + } + NOTICE("(Loading properties from %s took %.2fs.)\n", filename, t.duration()); +} - data = read_file(fn, &sz); +static void load_persistent_properties() { + persistent_properties_loaded = 1; - if(data != 0) { - load_properties(data, filter); - free(data); + std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(PERSISTENT_PROPERTY_DIR), closedir); + if (!dir) { + ERROR("Unable to open persistent property directory \"%s\": %s\n", + PERSISTENT_PROPERTY_DIR, strerror(errno)); + return; } -} -static void load_persistent_properties() -{ - DIR* dir = opendir(PERSISTENT_PROPERTY_DIR); - int dir_fd; - struct dirent* entry; - char value[PROP_VALUE_MAX]; - int fd, length; - struct stat sb; - - if (dir) { - dir_fd = dirfd(dir); - while ((entry = readdir(dir)) != NULL) { - if (strncmp("persist.", entry->d_name, strlen("persist."))) - continue; -#if HAVE_DIRENT_D_TYPE - if (entry->d_type != DT_REG) - continue; -#endif - /* open the file and read the property value */ - fd = openat(dir_fd, entry->d_name, O_RDONLY | O_NOFOLLOW); - if (fd < 0) { - ERROR("Unable to open persistent property file \"%s\" errno: %d\n", - entry->d_name, errno); - continue; - } - if (fstat(fd, &sb) < 0) { - ERROR("fstat on property file \"%s\" failed errno: %d\n", entry->d_name, errno); - close(fd); - continue; - } + struct dirent* entry; + while ((entry = readdir(dir.get())) != NULL) { + if (strncmp("persist.", entry->d_name, strlen("persist."))) { + continue; + } + if (entry->d_type != DT_REG) { + continue; + } - // File must not be accessible to others, be owned by root/root, and - // not be a hard link to any other file. - if (((sb.st_mode & (S_IRWXG | S_IRWXO)) != 0) - || (sb.st_uid != 0) - || (sb.st_gid != 0) - || (sb.st_nlink != 1)) { - ERROR("skipping insecure property file %s (uid=%u gid=%u nlink=%d mode=%o)\n", - entry->d_name, (unsigned int)sb.st_uid, (unsigned int)sb.st_gid, - sb.st_nlink, sb.st_mode); - close(fd); - continue; - } + // Open the file and read the property value. + int fd = openat(dirfd(dir.get()), entry->d_name, O_RDONLY | O_NOFOLLOW); + if (fd == -1) { + ERROR("Unable to open persistent property file \"%s\": %s\n", + entry->d_name, strerror(errno)); + continue; + } - length = read(fd, value, sizeof(value) - 1); - if (length >= 0) { - value[length] = 0; - property_set(entry->d_name, value); - } else { - ERROR("Unable to read persistent property file %s errno: %d\n", - entry->d_name, errno); - } + struct stat sb; + if (fstat(fd, &sb) == -1) { + ERROR("fstat on property file \"%s\" failed: %s\n", entry->d_name, strerror(errno)); close(fd); + continue; } - closedir(dir); - } else { - ERROR("Unable to open persistent property directory %s errno: %d\n", PERSISTENT_PROPERTY_DIR, errno); - } - persistent_properties_loaded = 1; -} + // File must not be accessible to others, be owned by root/root, and + // not be a hard link to any other file. + if (((sb.st_mode & (S_IRWXG | S_IRWXO)) != 0) || (sb.st_uid != 0) || (sb.st_gid != 0) || + (sb.st_nlink != 1)) { + ERROR("skipping insecure property file %s (uid=%u gid=%u nlink=%u mode=%o)\n", + entry->d_name, (unsigned int)sb.st_uid, (unsigned int)sb.st_gid, + (unsigned int)sb.st_nlink, sb.st_mode); + close(fd); + continue; + } -void property_init(void) -{ - init_property_area(); + char value[PROP_VALUE_MAX]; + int length = read(fd, value, sizeof(value) - 1); + if (length >= 0) { + value[length] = 0; + property_set(entry->d_name, value); + } else { + ERROR("Unable to read persistent property file %s: %s\n", + entry->d_name, strerror(errno)); + } + close(fd); + } } -void property_load_boot_defaults(void) -{ +void property_load_boot_defaults() { load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT, NULL); } -int properties_inited(void) -{ - return property_area_inited; +bool properties_initialized() { + return property_area_initialized; } static void load_override_properties() { -#ifdef ALLOW_LOCAL_PROP_OVERRIDE - char debuggable[PROP_VALUE_MAX]; - int ret; - - ret = property_get("ro.debuggable", debuggable); - if (ret && (strcmp(debuggable, "1") == 0)) { - load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE, NULL); + if (ALLOW_LOCAL_PROP_OVERRIDE) { + char debuggable[PROP_VALUE_MAX]; + int ret = property_get("ro.debuggable", debuggable); + if (ret && (strcmp(debuggable, "1") == 0)) { + load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE, NULL); + } } -#endif /* ALLOW_LOCAL_PROP_OVERRIDE */ } - /* When booting an encrypted system, /data is not mounted when the * property service is started, so any properties stored there are * not loaded. Vold triggers init to load these properties once it * has mounted /data. */ -void load_persist_props(void) -{ +void load_persist_props(void) { load_override_properties(); /* Read persistent properties after all default values have been loaded. */ load_persistent_properties(); } -void load_all_props(void) -{ +void load_all_props() { load_properties_from_file(PROP_PATH_SYSTEM_BUILD, NULL); - load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT, NULL); load_properties_from_file(PROP_PATH_VENDOR_BUILD, NULL); + load_properties_from_file(PROP_PATH_BOOTIMAGE_BUILD, NULL); load_properties_from_file(PROP_PATH_FACTORY, "ro.*"); load_override_properties(); @@ -549,20 +518,15 @@ void load_all_props(void) load_persistent_properties(); } -void start_property_service(void) -{ - int fd; - - fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0, NULL); - if(fd < 0) return; - fcntl(fd, F_SETFD, FD_CLOEXEC); - fcntl(fd, F_SETFL, O_NONBLOCK); +void start_property_service() { + property_set_fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + 0666, 0, 0, NULL); + if (property_set_fd == -1) { + ERROR("start_property_service socket creation failed: %s\n", strerror(errno)); + exit(1); + } - listen(fd, 8); - property_set_fd = fd; -} + listen(property_set_fd, 8); -int get_property_set_fd() -{ - return property_set_fd; + register_epoll_handler(property_set_fd, handle_property_set_fd); } diff --git a/init/property_service.h b/init/property_service.h index 730495e..a27053d 100644 --- a/init/property_service.h +++ b/init/property_service.h @@ -17,10 +17,9 @@ #ifndef _INIT_PROPERTY_H #define _INIT_PROPERTY_H -#include <stdbool.h> +#include <stddef.h> #include <sys/system_properties.h> -extern void handle_property_set_fd(void); extern void property_init(void); extern void property_load_boot_defaults(void); extern void load_persist_props(void); @@ -29,16 +28,21 @@ extern void start_property_service(void); void get_property_workspace(int *fd, int *sz); extern int __property_get(const char *name, char *value); extern int property_set(const char *name, const char *value); -extern int properties_inited(); -int get_property_set_fd(void); +extern bool properties_initialized(); +#ifndef __clang__ extern void __property_get_size_error() __attribute__((__error__("property_get called with too small buffer"))); +#else +extern void __property_get_size_error(); +#endif static inline __attribute__ ((always_inline)) __attribute__ ((gnu_inline)) +#ifndef __clang__ __attribute__ ((artificial)) +#endif int property_get(const char *name, char *value) { size_t value_len = __builtin_object_size(value, 0); diff --git a/init/readme.txt b/init/readme.txt index 26be536..6b9c42d 100644 --- a/init/readme.txt +++ b/init/readme.txt @@ -70,11 +70,11 @@ disabled setenv <name> <value> Set the environment variable <name> to <value> in the launched process. -socket <name> <type> <perm> [ <user> [ <group> [ <context> ] ] ] +socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ] Create a unix domain socket named /dev/socket/<name> and pass its fd to the launched process. <type> must be "dgram", "stream" or "seqpacket". User and group default to 0. - Context is the SELinux security context for the socket. + 'seclabel' is the SELinux security context for the socket. It defaults to the service security context, as specified by seclabel or computed based on the service executable file security context. @@ -91,8 +91,8 @@ group <groupname> [ <groupname> ]* supplemental groups of the process (via setgroups()). Currently defaults to root. (??? probably should default to nobody) -seclabel <securitycontext> - Change to securitycontext before exec'ing this service. +seclabel <seclabel> + Change to 'seclabel' before exec'ing this service. Primarily for use by services run from the rootfs, e.g. ueventd, adbd. Services on the system partition can instead use policy-defined transitions based on their file security context. @@ -110,6 +110,7 @@ class <name> onrestart Execute a Command (see below) when service restarts. + Triggers -------- Triggers are strings which can be used to match certain kinds @@ -123,40 +124,22 @@ boot Triggers of this form occur when the property <name> is set to the specific value <value>. -device-added-<path> -device-removed-<path> - Triggers of these forms occur when a device node is added - or removed. + One can also test multiple properties to execute a group + of commands. For example: + + on property:test.a=1 && property:test.b=1 + setprop test.c 1 -service-exited-<name> - Triggers of this form occur when the specified service exits. + The above stub sets test.c to 1 only when + both test.a=1 and test.b=1 Commands -------- -exec <path> [ <argument> ]* - Fork and execute a program (<path>). This will block until - the program completes execution. It is best to avoid exec - as unlike the builtin commands, it runs the risk of getting - init "stuck". (??? maybe there should be a timeout?) - -export <name> <value> - Set the environment variable <name> equal to <value> in the - global environment (which will be inherited by all processes - started after this command is executed) - -ifup <interface> - Bring the network interface <interface> online. - -import <filename> - Parse an init config file, extending the current configuration. - -hostname <name> - Set the host name. - -chdir <directory> - Change working directory. +bootchart_init + Start bootcharting if configured (see below). + This is included in the default init.rc. chmod <octal-mode> <path> Change file access permissions. @@ -164,17 +147,23 @@ chmod <octal-mode> <path> chown <owner> <group> <path> Change file owner and group. -chroot <directory> - Change process root directory. - class_start <serviceclass> Start all services of the specified class if they are not already running. class_stop <serviceclass> - Stop all services of the specified class if they are + Stop and disable all services of the specified class if they are currently running. +class_reset <serviceclass> + Stop all services of the specified class if they are + currently running, without disabling them. They can be restarted + later using class_start. + +copy <src> <dst> + Copies a file. Similar to write, but useful for binary/large + amounts of data. + domainname <name> Set the domain name. @@ -187,20 +176,63 @@ enable <servicename> on property:ro.boot.myfancyhardware=1 enable my_fancy_service_for_my_fancy_hardware +exec [ <seclabel> [ <user> [ <group> ]* ] ] -- <command> [ <argument> ]* + Fork and execute command with the given arguments. The command starts + after "--" so that an optional security context, user, and supplementary + groups can be provided. No other commands will be run until this one + finishes. + +export <name> <value> + Set the environment variable <name> equal to <value> in the + global environment (which will be inherited by all processes + started after this command is executed) + +hostname <name> + Set the host name. + +ifup <interface> + Bring the network interface <interface> online. + +import <filename> + Parse an init config file, extending the current configuration. insmod <path> Install the module at <path> +load_all_props + Loads properties from /system, /vendor, et cetera. + This is included in the default init.rc. + +load_persist_props + Loads persistent properties when /data has been decrypted. + This is included in the default init.rc. + +loglevel <level> + Sets the kernel log level to level. Properties are expanded within <level>. + mkdir <path> [mode] [owner] [group] Create a directory at <path>, optionally with the given mode, owner, and group. If not provided, the directory is created with permissions 755 and - owned by the root user and root group. + owned by the root user and root group. If provided, the mode, owner and group + will be updated if the directory exists already. -mount <type> <device> <dir> [ <mountoption> ]* +mount_all <fstab> + Calls fs_mgr_mount_all on the given fs_mgr-format fstab. + +mount <type> <device> <dir> [ <flag> ]* [<options>] Attempt to mount the named device at the directory <dir> <device> may be of the form mtd@name to specify a mtd block device by name. - <mountoption>s include "ro", "rw", "remount", "noatime", ... + <flag>s include "ro", "rw", "remount", "noatime", ... + <options> include "barrier=1", "noauto_da_alloc", "discard", ... as + a comma separated string, eg: barrier=1,noauto_da_alloc + +powerctl + Internal implementation detail used to respond to changes to the + "sys.powerctl" system property, used to implement rebooting. + +restart <service> + Like stop, but doesn't disable the service. restorecon <path> [ <path> ]* Restore the file named by <path> to the security context specified @@ -211,37 +243,31 @@ restorecon <path> [ <path> ]* restorecon_recursive <path> [ <path> ]* Recursively restore the directory tree named by <path> to the security contexts specified in the file_contexts configuration. - Do NOT use this with paths leading to shell-writable or app-writable - directories, e.g. /data/local/tmp, /data/data or any prefix thereof. - -setcon <securitycontext> - Set the current process security context to the specified string. - This is typically only used from early-init to set the init context - before any other process is started. -setenforce 0|1 - Set the SELinux system-wide enforcing status. - 0 is permissive (i.e. log but do not deny), 1 is enforcing. +rm <path> + Calls unlink(2) on the given path. You might want to + use "exec -- rm ..." instead (provided the system partition is + already mounted). -setkey - TBD +rmdir <path> + Calls rmdir(2) on the given path. setprop <name> <value> - Set system property <name> to <value>. + Set system property <name> to <value>. Properties are expanded + within <value>. setrlimit <resource> <cur> <max> Set the rlimit for a resource. -setsebool <name> <value> - Set SELinux boolean <name> to <value>. - <value> may be 1|true|on or 0|false|off - start <service> Start a service running if it is not already running. stop <service> Stop a service from running if it is currently running. +swapon_all <fstab> + Calls fs_mgr_swapon_all on the given fstab file. + symlink <target> <path> Create a symbolic link at <path> with the value <target> @@ -252,14 +278,23 @@ trigger <event> Trigger an event. Used to queue an action from another action. +verity_load_state + Internal implementation detail used to load dm-verity state. + +verity_update_state <mount_point> + Internal implementation detail used to update dm-verity state and + set the partition.<mount_point>.verified properties used by adb remount + because fs_mgr can't set them directly itself. + wait <path> [ <timeout> ] - Poll for the existence of the given file and return when found, - or the timeout has been reached. If timeout is not specified it - currently defaults to five seconds. + Poll for the existence of the given file and return when found, + or the timeout has been reached. If timeout is not specified it + currently defaults to five seconds. -write <path> <string> - Open the file at <path> and write a string to it with write(2) - without appending. +write <path> <content> + Open the file at <path> and write a string to it with write(2). + If the file does not exist, it will be created. If it does exist, + it will be truncated. Properties are expanded within <content>. Properties @@ -267,7 +302,7 @@ Properties Init updates some system properties to provide some insight into what it's doing: -init.action +init.action Equal to the name of the action currently being executed or "" if none init.command @@ -277,73 +312,62 @@ init.svc.<name> State of a named service ("stopped", "running", "restarting") -Example init.conf ------------------ - -# not complete -- just providing some examples of usage -# -on boot - export PATH /sbin:/system/sbin:/system/bin - export LD_LIBRARY_PATH /system/lib - - mkdir /dev - mkdir /proc - mkdir /sys - - mount tmpfs tmpfs /dev - mkdir /dev/pts - mkdir /dev/socket - mount devpts devpts /dev/pts - mount proc proc /proc - mount sysfs sysfs /sys - - write /proc/cpu/alignment 4 +Bootcharting +------------ +This version of init contains code to perform "bootcharting": generating log +files that can be later processed by the tools provided by www.bootchart.org. - ifup lo +On the emulator, use the -bootchart <timeout> option to boot with bootcharting +activated for <timeout> seconds. - hostname localhost - domainname localhost +On a device, create /data/bootchart/start with a command like the following: - mount yaffs2 mtd@system /system - mount yaffs2 mtd@userdata /data + adb shell 'echo $TIMEOUT > /data/bootchart/start' - import /system/etc/init.conf +Where the value of $TIMEOUT corresponds to the desired bootcharted period in +seconds. Bootcharting will stop after that many seconds have elapsed. +You can also stop the bootcharting at any moment by doing the following: - class_start default + adb shell 'echo 1 > /data/bootchart/stop' -service adbd /sbin/adbd - user adb - group adb +Note that /data/bootchart/stop is deleted automatically by init at the end of +the bootcharting. This is not the case with /data/bootchart/start, so don't +forget to delete it when you're done collecting data. -service usbd /system/bin/usbd -r - user usbd - group usbd - socket usbd 666 +The log files are written to /data/bootchart/. A script is provided to +retrieve them and create a bootchart.tgz file that can be used with the +bootchart command-line utility: -service zygote /system/bin/app_process -Xzygote /system/bin --zygote - socket zygote 666 + sudo apt-get install pybootchartgui + # grab-bootchart.sh uses $ANDROID_SERIAL. + $ANDROID_BUILD_TOP/system/core/init/grab-bootchart.sh -service runtime /system/bin/runtime - user system - group system +One thing to watch for is that the bootchart will show init as if it started +running at 0s. You'll have to look at dmesg to work out when the kernel +actually started init. -on device-added-/dev/compass - start akmd -on device-removed-/dev/compass - stop akmd - -service akmd /sbin/akmd - disabled - user akmd - group akmd - -Debugging notes ---------------- +Debugging init +-------------- By default, programs executed by init will drop stdout and stderr into /dev/null. To help with debugging, you can execute your program via the -Andoird program logwrapper. This will redirect stdout/stderr into the +Android program logwrapper. This will redirect stdout/stderr into the Android logging system (accessed via logcat). For example service akmd /system/bin/logwrapper /sbin/akmd + +For quicker turnaround when working on init itself, use: + + mm -j + m ramdisk-nodeps + m bootimage-nodeps + adb reboot bootloader + fastboot boot $ANDROID_PRODUCT_OUT/boot.img + +Alternatively, use the emulator: + + emulator -partition-size 1024 -verbose -show-kernel -no-window + +You might want to call klog_set_level(6) after the klog_init() call +so you see the kernel logging in dmesg (or the emulator output). diff --git a/init/signal_handler.c b/init/signal_handler.c deleted file mode 100644 index 7e8e1a7..0000000 --- a/init/signal_handler.c +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) 2010 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 <stdio.h> -#include <errno.h> -#include <signal.h> -#include <unistd.h> -#include <fcntl.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <sys/wait.h> -#include <cutils/sockets.h> -#include <cutils/android_reboot.h> -#include <cutils/list.h> - -#include "init.h" -#include "util.h" -#include "log.h" - -static int signal_fd = -1; -static int signal_recv_fd = -1; - -static void sigchld_handler(int s) -{ - write(signal_fd, &s, 1); -} - -#define CRITICAL_CRASH_THRESHOLD 4 /* if we crash >4 times ... */ -#define CRITICAL_CRASH_WINDOW (4*60) /* ... in 4 minutes, goto recovery*/ - -static int wait_for_one_process(int block) -{ - pid_t pid; - int status; - struct service *svc; - struct socketinfo *si; - time_t now; - struct listnode *node; - struct command *cmd; - - while ( (pid = waitpid(-1, &status, block ? 0 : WNOHANG)) == -1 && errno == EINTR ); - if (pid <= 0) return -1; - INFO("waitpid returned pid %d, status = %08x\n", pid, status); - - svc = service_find_by_pid(pid); - if (!svc) { - if (WIFEXITED(status)) { - ERROR("untracked pid %d exited with status %d\n", pid, WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - ERROR("untracked pid %d killed by signal %d\n", pid, WTERMSIG(status)); - } else if (WIFSTOPPED(status)) { - ERROR("untracked pid %d stopped by signal %d\n", pid, WSTOPSIG(status)); - } else { - ERROR("untracked pid %d state changed\n", pid); - } - return 0; - } - - NOTICE("process '%s', pid %d exited\n", svc->name, pid); - - if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) { - kill(-pid, SIGKILL); - NOTICE("process '%s' killing any children in process group\n", svc->name); - } - - /* remove any sockets we may have created */ - for (si = svc->sockets; si; si = si->next) { - char tmp[128]; - snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name); - unlink(tmp); - } - - svc->pid = 0; - svc->flags &= (~SVC_RUNNING); - - /* oneshot processes go into the disabled state on exit, - * except when manually restarted. */ - if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) { - svc->flags |= SVC_DISABLED; - } - - /* disabled and reset processes do not get restarted automatically */ - if (svc->flags & (SVC_DISABLED | SVC_RESET) ) { - notify_service_state(svc->name, "stopped"); - return 0; - } - - now = gettime(); - if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) { - if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) { - if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) { - ERROR("critical process '%s' exited %d times in %d minutes; " - "rebooting into recovery mode\n", svc->name, - CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60); - android_reboot(ANDROID_RB_RESTART2, 0, "recovery"); - return 0; - } - } else { - svc->time_crashed = now; - svc->nr_crashed = 1; - } - } - - svc->flags &= (~SVC_RESTART); - svc->flags |= SVC_RESTARTING; - - /* Execute all onrestart commands for this service. */ - list_for_each(node, &svc->onrestart.commands) { - cmd = node_to_item(node, struct command, clist); - cmd->func(cmd->nargs, cmd->args); - } - notify_service_state(svc->name, "restarting"); - return 0; -} - -void handle_signal(void) -{ - char tmp[32]; - - /* we got a SIGCHLD - reap and restart as needed */ - read(signal_recv_fd, tmp, sizeof(tmp)); - while (!wait_for_one_process(0)) - ; -} - -void signal_init(void) -{ - int s[2]; - - struct sigaction act; - memset(&act, 0, sizeof(act)); - act.sa_handler = sigchld_handler; - act.sa_flags = SA_NOCLDSTOP; - sigaction(SIGCHLD, &act, 0); - - /* create a signalling mechanism for the sigchld handler */ - if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) { - signal_fd = s[0]; - signal_recv_fd = s[1]; - fcntl(s[0], F_SETFD, FD_CLOEXEC); - fcntl(s[0], F_SETFL, O_NONBLOCK); - fcntl(s[1], F_SETFD, FD_CLOEXEC); - fcntl(s[1], F_SETFL, O_NONBLOCK); - } - - handle_signal(); -} - -int get_signal_fd() -{ - return signal_recv_fd; -} diff --git a/init/signal_handler.cpp b/init/signal_handler.cpp new file mode 100644 index 0000000..39a466d --- /dev/null +++ b/init/signal_handler.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2010 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 <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <base/stringprintf.h> +#include <cutils/android_reboot.h> +#include <cutils/list.h> +#include <cutils/sockets.h> + +#include "init.h" +#include "log.h" +#include "util.h" + +#define CRITICAL_CRASH_THRESHOLD 4 /* if we crash >4 times ... */ +#define CRITICAL_CRASH_WINDOW (4*60) /* ... in 4 minutes, goto recovery */ + +static int signal_write_fd = -1; +static int signal_read_fd = -1; + +static std::string DescribeStatus(int status) { + if (WIFEXITED(status)) { + return android::base::StringPrintf("exited with status %d", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + return android::base::StringPrintf("killed by signal %d", WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + return android::base::StringPrintf("stopped by signal %d", WSTOPSIG(status)); + } else { + return "state changed"; + } +} + +static bool wait_for_one_process() { + int status; + pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG)); + if (pid == 0) { + return false; + } else if (pid == -1) { + ERROR("waitpid failed: %s\n", strerror(errno)); + return false; + } + + service* svc = service_find_by_pid(pid); + + std::string name; + if (svc) { + name = android::base::StringPrintf("Service '%s' (pid %d)", svc->name, pid); + } else { + name = android::base::StringPrintf("Untracked pid %d", pid); + } + + NOTICE("%s %s\n", name.c_str(), DescribeStatus(status).c_str()); + + if (!svc) { + return true; + } + + // TODO: all the code from here down should be a member function on service. + + if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) { + NOTICE("Service '%s' (pid %d) killing any children in process group\n", svc->name, pid); + kill(-pid, SIGKILL); + } + + // Remove any sockets we may have created. + for (socketinfo* si = svc->sockets; si; si = si->next) { + char tmp[128]; + snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name); + unlink(tmp); + } + + if (svc->flags & SVC_EXEC) { + INFO("SVC_EXEC pid %d finished...\n", svc->pid); + waiting_for_exec = false; + list_remove(&svc->slist); + free(svc->name); + free(svc); + return true; + } + + svc->pid = 0; + svc->flags &= (~SVC_RUNNING); + + // Oneshot processes go into the disabled state on exit, + // except when manually restarted. + if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) { + svc->flags |= SVC_DISABLED; + } + + // Disabled and reset processes do not get restarted automatically. + if (svc->flags & (SVC_DISABLED | SVC_RESET)) { + svc->NotifyStateChange("stopped"); + return true; + } + + time_t now = gettime(); + if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) { + if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) { + if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) { + ERROR("critical process '%s' exited %d times in %d minutes; " + "rebooting into recovery mode\n", svc->name, + CRITICAL_CRASH_THRESHOLD, CRITICAL_CRASH_WINDOW / 60); + android_reboot(ANDROID_RB_RESTART2, 0, "recovery"); + return true; + } + } else { + svc->time_crashed = now; + svc->nr_crashed = 1; + } + } + + svc->flags &= (~SVC_RESTART); + svc->flags |= SVC_RESTARTING; + + // Execute all onrestart commands for this service. + struct listnode* node; + list_for_each(node, &svc->onrestart.commands) { + command* cmd = node_to_item(node, struct command, clist); + cmd->func(cmd->nargs, cmd->args); + } + svc->NotifyStateChange("restarting"); + return true; +} + +static void reap_any_outstanding_children() { + while (wait_for_one_process()) { + } +} + +static void handle_signal() { + // Clear outstanding requests. + char buf[32]; + read(signal_read_fd, buf, sizeof(buf)); + + reap_any_outstanding_children(); +} + +static void SIGCHLD_handler(int) { + if (TEMP_FAILURE_RETRY(write(signal_write_fd, "1", 1)) == -1) { + ERROR("write(signal_write_fd) failed: %s\n", strerror(errno)); + } +} + +void signal_handler_init() { + // Create a signalling mechanism for SIGCHLD. + int s[2]; + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == -1) { + ERROR("socketpair failed: %s\n", strerror(errno)); + exit(1); + } + + signal_write_fd = s[0]; + signal_read_fd = s[1]; + + // Write to signal_write_fd if we catch SIGCHLD. + struct sigaction act; + memset(&act, 0, sizeof(act)); + act.sa_handler = SIGCHLD_handler; + act.sa_flags = SA_NOCLDSTOP; + sigaction(SIGCHLD, &act, 0); + + reap_any_outstanding_children(); + + register_epoll_handler(signal_read_fd, handle_signal); +} diff --git a/init/signal_handler.h b/init/signal_handler.h index b092ccb..449b4af 100644 --- a/init/signal_handler.h +++ b/init/signal_handler.h @@ -17,8 +17,6 @@ #ifndef _INIT_SIGNAL_HANDLER_H_ #define _INIT_SIGNAL_HANDLER_H_ -void signal_init(void); -void handle_signal(void); -int get_signal_fd(void); +void signal_handler_init(void); #endif diff --git a/init/ueventd.c b/init/ueventd.cpp index 833e4fd..c63fdaa 100644 --- a/init/ueventd.c +++ b/init/ueventd.cpp @@ -14,46 +14,27 @@ * limitations under the License. */ -#include <poll.h> -#include <fcntl.h> -#include <string.h> -#include <stdlib.h> -#include <stdio.h> #include <ctype.h> +#include <fcntl.h> +#include <poll.h> #include <signal.h> -#include <selinux/selinux.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <base/stringprintf.h> #include <private/android_filesystem_config.h> +#include <selinux/selinux.h> #include "ueventd.h" #include "log.h" #include "util.h" #include "devices.h" #include "ueventd_parser.h" - -static char hardware[32]; -static unsigned revision = 0; - -static void import_kernel_nv(char *name, int in_qemu) -{ - if (*name != '\0') { - char *value = strchr(name, '='); - if (value != NULL) { - *value++ = 0; - if (!strcmp(name,"androidboot.hardware")) - { - strlcpy(hardware, value, sizeof(hardware)); - } - } - } -} +#include "property_service.h" int ueventd_main(int argc, char **argv) { - struct pollfd ufd; - int nr; - char tmp[32]; - /* * init sets the umask to 077 for forked processes. We need to * create files with exact permissions, without modification by @@ -70,44 +51,38 @@ int ueventd_main(int argc, char **argv) open_devnull_stdio(); klog_init(); -#if LOG_UEVENTS - /* Ensure we're at a logging level that will show the events */ - if (klog_get_level() < KLOG_INFO_LEVEL) { - klog_set_level(KLOG_INFO_LEVEL); - } -#endif + klog_set_level(KLOG_NOTICE_LEVEL); - union selinux_callback cb; - cb.func_log = log_callback; - selinux_set_callback(SELINUX_CB_LOG, cb); + NOTICE("ueventd started!\n"); - INFO("starting ueventd\n"); - - /* Respect hardware passed in through the kernel cmd line. Here we will look - * for androidboot.hardware param in kernel cmdline, and save its value in - * hardware[]. */ - import_kernel_cmdline(0, import_kernel_nv); + selinux_callback cb; + cb.func_log = selinux_klog_callback; + selinux_set_callback(SELINUX_CB_LOG, cb); - get_hardware_name(hardware, &revision); + char hardware[PROP_VALUE_MAX]; + property_get("ro.hardware", hardware); ueventd_parse_config_file("/ueventd.rc"); - - snprintf(tmp, sizeof(tmp), "/ueventd.%s.rc", hardware); - ueventd_parse_config_file(tmp); + ueventd_parse_config_file(android::base::StringPrintf("/ueventd.%s.rc", hardware).c_str()); device_init(); + pollfd ufd; ufd.events = POLLIN; ufd.fd = get_device_fd(); - while(1) { + while (true) { ufd.revents = 0; - nr = poll(&ufd, 1, -1); - if (nr <= 0) + int nr = poll(&ufd, 1, -1); + if (nr <= 0) { continue; - if (ufd.revents & POLLIN) - handle_device_fd(); + } + if (ufd.revents & POLLIN) { + handle_device_fd(); + } } + + return 0; } static int get_android_id(const char *id) diff --git a/init/ueventd.h b/init/ueventd.h index 0a454c5..d12d7fe 100644 --- a/init/ueventd.h +++ b/init/ueventd.h @@ -20,16 +20,18 @@ #include <cutils/list.h> #include <sys/types.h> +enum devname_src_t { + DEVNAME_UNKNOWN = 0, + DEVNAME_UEVENT_DEVNAME, + DEVNAME_UEVENT_DEVPATH, +}; + struct ueventd_subsystem { struct listnode slist; const char *name; - enum { - DEVNAME_UNKNOWN = 0, - DEVNAME_UEVENT_DEVNAME, - DEVNAME_UEVENT_DEVPATH, - } devname_src; const char *dirname; + devname_src_t devname_src; }; int ueventd_main(int argc, char **argv); diff --git a/init/ueventd_parser.c b/init/ueventd_parser.cpp index e447006..7a4841f 100644 --- a/init/ueventd_parser.c +++ b/init/ueventd_parser.cpp @@ -67,9 +67,7 @@ static int lookup_keyword(const char *s) return K_UNKNOWN; } -static void parse_line_no_op(struct parse_state *state __attribute__((unused)), - int nargs __attribute__((unused)), char **args __attribute__((unused))) -{ +static void parse_line_no_op(struct parse_state*, int, char**) { } static int valid_name(const char *name) @@ -97,24 +95,20 @@ struct ueventd_subsystem *ueventd_subsystem_find_by_name(const char *name) return 0; } -static void *parse_subsystem(struct parse_state *state, - int nargs __attribute__((unused)), char **args) -{ - struct ueventd_subsystem *s; - +static void *parse_subsystem(parse_state* state, int /*nargs*/, char** args) { if (!valid_name(args[1])) { parse_error(state, "invalid subsystem name '%s'\n", args[1]); return 0; } - s = ueventd_subsystem_find_by_name(args[1]); + ueventd_subsystem* s = ueventd_subsystem_find_by_name(args[1]); if (s) { parse_error(state, "ignored duplicate definition of subsystem '%s'\n", args[1]); return 0; } - s = calloc(1, sizeof(*s)); + s = (ueventd_subsystem*) calloc(1, sizeof(*s)); if (!s) { parse_error(state, "out of memory\n"); return 0; @@ -128,7 +122,7 @@ static void *parse_subsystem(struct parse_state *state, static void parse_line_subsystem(struct parse_state *state, int nargs, char **args) { - struct ueventd_subsystem *s = state->context; + struct ueventd_subsystem *s = (ueventd_subsystem*) state->context; int kw; if (nargs == 0) { @@ -197,7 +191,7 @@ static void parse_line(struct parse_state *state, char **args, int nargs) } } -static void parse_config(const char *fn, char *s) +static void parse_config(const char *fn, const std::string& data) { struct parse_state state; char *args[UEVENTD_PARSER_MAXARGS]; @@ -205,7 +199,7 @@ static void parse_config(const char *fn, char *s) nargs = 0; state.filename = fn; state.line = 1; - state.ptr = s; + state.ptr = strdup(data.c_str()); // TODO: fix this code! state.nexttoken = 0; state.parse_line = parse_line_no_op; for (;;) { @@ -232,17 +226,16 @@ static void parse_config(const char *fn, char *s) int ueventd_parse_config_file(const char *fn) { - char *data; - data = read_file(fn, 0); - if (!data) return -1; + std::string data; + if (!read_file(fn, &data)) { + return -1; + } parse_config(fn, data); - DUMP(); + dump_parser_state(); return 0; } -static void parse_line_device(struct parse_state *state __attribute__((unused)), - int nargs, char **args) -{ +static void parse_line_device(parse_state*, int nargs, char** args) { set_device_permission(nargs, args); } diff --git a/init/util.c b/init/util.cpp index 12cb11d..20ce806 100644 --- a/init/util.c +++ b/init/util.cpp @@ -32,6 +32,8 @@ #include <sys/socket.h> #include <sys/un.h> +#include <base/file.h> + /* for ANDROID_SOCKET_* */ #include <cutils/sockets.h> @@ -146,48 +148,46 @@ out_close: return -1; } -/* reads a file, making sure it is terminated with \n \0 */ -void *read_file(const char *fn, unsigned *_sz) -{ - char *data; - int sz; - int fd; - struct stat sb; +bool read_file(const char* path, std::string* content) { + content->clear(); - data = 0; - fd = open(fn, O_RDONLY); - if(fd < 0) return 0; + int fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY|O_NOFOLLOW|O_CLOEXEC)); + if (fd == -1) { + return false; + } - // for security reasons, disallow world-writable - // or group-writable files - if (fstat(fd, &sb) < 0) { - ERROR("fstat failed for '%s'\n", fn); - goto oops; + // For security reasons, disallow world-writable + // or group-writable files. + struct stat sb; + if (fstat(fd, &sb) == -1) { + ERROR("fstat failed for '%s': %s\n", path, strerror(errno)); + return false; } if ((sb.st_mode & (S_IWGRP | S_IWOTH)) != 0) { - ERROR("skipping insecure file '%s'\n", fn); - goto oops; + ERROR("skipping insecure file '%s'\n", path); + return false; } - sz = lseek(fd, 0, SEEK_END); - if(sz < 0) goto oops; - - if(lseek(fd, 0, SEEK_SET) != 0) goto oops; - - data = (char*) malloc(sz + 2); - if(data == 0) goto oops; - - if(read(fd, data, sz) != sz) goto oops; - close(fd); - data[sz] = '\n'; - data[sz+1] = 0; - if(_sz) *_sz = sz; - return data; + bool okay = android::base::ReadFdToString(fd, content); + TEMP_FAILURE_RETRY(close(fd)); + if (okay) { + content->append("\n", 1); + } + return okay; +} -oops: - close(fd); - if(data != 0) free(data); - return 0; +int write_file(const char* path, const char* content) { + int fd = TEMP_FAILURE_RETRY(open(path, O_WRONLY|O_CREAT|O_NOFOLLOW|O_CLOEXEC, 0600)); + if (fd == -1) { + NOTICE("write_file: Unable to open '%s': %s\n", path, strerror(errno)); + return -1; + } + int result = android::base::WriteStringToFd(content, fd) ? 0 : -1; + if (result == -1) { + NOTICE("write_file: Unable to write to '%s': %s\n", path, strerror(errno)); + } + TEMP_FAILURE_RETRY(close(fd)); + return result; } #define MAX_MTD_PARTITIONS 16 @@ -207,7 +207,7 @@ static void find_mtd_partitions(void) ssize_t pmtdsize; int r; - fd = open("/proc/mtd", O_RDONLY); + fd = open("/proc/mtd", O_RDONLY|O_CLOEXEC); if (fd < 0) return; @@ -262,22 +262,16 @@ int mtd_name_to_number(const char *name) return -1; } -/* - * gettime() - returns the time in seconds of the system's monotonic clock or - * zero on error. - */ -time_t gettime(void) -{ - struct timespec ts; - int ret; - - ret = clock_gettime(CLOCK_MONOTONIC, &ts); - if (ret < 0) { - ERROR("clock_gettime(CLOCK_MONOTONIC) failed: %s\n", strerror(errno)); - return 0; - } +time_t gettime() { + timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return now.tv_sec; +} - return ts.tv_sec; +uint64_t gettime_ns() { + timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return static_cast<uint64_t>(now.tv_sec) * UINT64_C(1000000000) + now.tv_nsec; } int mkdir_recursive(const char *pathname, mode_t mode) @@ -385,101 +379,37 @@ int wait_for_file(const char *filename, int timeout) void open_devnull_stdio(void) { - int fd; - static const char *name = "/dev/__null__"; - if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) { - fd = open(name, O_RDWR); - unlink(name); - if (fd >= 0) { - dup2(fd, 0); - dup2(fd, 1); - dup2(fd, 2); - if (fd > 2) { - close(fd); - } - return; - } - } - - exit(1); -} - -void get_hardware_name(char *hardware, unsigned int *revision) -{ - const char *cpuinfo = "/proc/cpuinfo"; - char *data = NULL; - size_t len = 0, limit = 1024; - int fd, n; - char *x, *hw, *rev; - - /* Hardware string was provided on kernel command line */ - if (hardware[0]) - return; - - fd = open(cpuinfo, O_RDONLY); - if (fd < 0) return; - - for (;;) { - x = realloc(data, limit); - if (!x) { - ERROR("Failed to allocate memory to read %s\n", cpuinfo); - goto done; + // Try to avoid the mknod() call if we can. Since SELinux makes + // a /dev/null replacement available for free, let's use it. + int fd = open("/sys/fs/selinux/null", O_RDWR); + if (fd == -1) { + // OOPS, /sys/fs/selinux/null isn't available, likely because + // /sys/fs/selinux isn't mounted. Fall back to mknod. + static const char *name = "/dev/__null__"; + if (mknod(name, S_IFCHR | 0600, (1 << 8) | 3) == 0) { + fd = open(name, O_RDWR); + unlink(name); } - data = x; - - n = read(fd, data + len, limit - len); - if (n < 0) { - ERROR("Failed reading %s: %s (%d)\n", cpuinfo, strerror(errno), errno); - goto done; + if (fd == -1) { + exit(1); } - len += n; - - if (len < limit) - break; - - /* We filled the buffer, so increase size and loop to read more */ - limit *= 2; } - data[len] = 0; - hw = strstr(data, "\nHardware"); - rev = strstr(data, "\nRevision"); - - if (hw) { - x = strstr(hw, ": "); - if (x) { - x += 2; - n = 0; - while (*x && *x != '\n') { - if (!isspace(*x)) - hardware[n++] = tolower(*x); - x++; - if (n == 31) break; - } - hardware[n] = 0; - } - } - - if (rev) { - x = strstr(rev, ": "); - if (x) { - *revision = strtoul(x + 2, 0, 16); - } + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + if (fd > 2) { + close(fd); } - -done: - close(fd); - free(data); } -void import_kernel_cmdline(int in_qemu, - void (*import_kernel_nv)(char *name, int in_qemu)) +void import_kernel_cmdline(bool in_qemu, std::function<void(char*,bool)> import_kernel_nv) { char cmdline[2048]; char *ptr; int fd; - fd = open("/proc/cmdline", O_RDONLY); + fd = open("/proc/cmdline", O_RDONLY | O_CLOEXEC); if (fd >= 0) { int n = read(fd, cmdline, sizeof(cmdline) - 1); if (n < 0) n = 0; diff --git a/init/util.h b/init/util.h index a7e7c8b..1c947ec 100644 --- a/init/util.h +++ b/init/util.h @@ -20,15 +20,36 @@ #include <sys/stat.h> #include <sys/types.h> +#include <string> +#include <functional> + #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) -static const char *coldboot_done = "/dev/.coldboot_done"; +#define COLDBOOT_DONE "/dev/.coldboot_done" int mtd_name_to_number(const char *name); int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid, const char *socketcon); -void *read_file(const char *fn, unsigned *_sz); -time_t gettime(void); + +bool read_file(const char* path, std::string* content); +int write_file(const char* path, const char* content); + +time_t gettime(); +uint64_t gettime_ns(); + +class Timer { + public: + Timer() : t0(gettime_ns()) { + } + + double duration() { + return static_cast<double>(gettime_ns() - t0) / 1000000000.0; + } + + private: + uint64_t t0; +}; + unsigned int decode_uid(const char *s); int mkdir_recursive(const char *pathname, mode_t mode); @@ -37,8 +58,7 @@ void make_link_init(const char *oldpath, const char *newpath); void remove_link(const char *oldpath, const char *newpath); int wait_for_file(const char *filename, int timeout); void open_devnull_stdio(void); -void get_hardware_name(char *hardware, unsigned int *revision); -void import_kernel_cmdline(int in_qemu, void (*import_kernel_nv)(char *name, int in_qemu)); +void import_kernel_cmdline(bool in_qemu, std::function<void(char*,bool)>); int make_dir(const char *path, mode_t mode); int restorecon(const char *pathname); int restorecon_recursive(const char *pathname); diff --git a/init/util_test.cpp b/init/util_test.cpp new file mode 100644 index 0000000..5b3ab50 --- /dev/null +++ b/init/util_test.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015 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 "util.h" + +#include <errno.h> +#include <gtest/gtest.h> + +TEST(util, read_file_ENOENT) { + std::string s("hello"); + errno = 0; + EXPECT_FALSE(read_file("/proc/does-not-exist", &s)); + EXPECT_EQ(ENOENT, errno); + EXPECT_EQ("", s); // s was cleared. +} + +TEST(util, read_file_success) { + std::string s("hello"); + EXPECT_TRUE(read_file("/proc/version", &s)); + EXPECT_GT(s.length(), 6U); + EXPECT_EQ('\n', s[s.length() - 1]); + s[5] = 0; + EXPECT_STREQ("Linux", s.c_str()); +} + +TEST(util, decode_uid) { + EXPECT_EQ(0U, decode_uid("root")); + EXPECT_EQ(-1U, decode_uid("toot")); + EXPECT_EQ(123U, decode_uid("123")); +} diff --git a/init/watchdogd.c b/init/watchdogd.cpp index fb53836..881a4df 100644 --- a/init/watchdogd.c +++ b/init/watchdogd.cpp @@ -18,6 +18,7 @@ #include <fcntl.h> #include <stdlib.h> #include <string.h> +#include <unistd.h> #include <linux/watchdog.h> @@ -26,52 +27,45 @@ #define DEV_NAME "/dev/watchdog" -int watchdogd_main(int argc, char **argv) -{ - int fd; - int ret; - int interval = 10; - int margin = 10; - int timeout; - +int watchdogd_main(int argc, char **argv) { open_devnull_stdio(); klog_init(); + klog_set_level(KLOG_NOTICE_LEVEL); - INFO("Starting watchdogd\n"); - - if (argc >= 2) - interval = atoi(argv[1]); + int interval = 10; + if (argc >= 2) interval = atoi(argv[1]); - if (argc >= 3) - margin = atoi(argv[2]); + int margin = 10; + if (argc >= 3) margin = atoi(argv[2]); - timeout = interval + margin; + NOTICE("watchdogd started (interval %d, margin %d)!\n", interval, margin); - fd = open(DEV_NAME, O_RDWR); - if (fd < 0) { + int fd = open(DEV_NAME, O_RDWR|O_CLOEXEC); + if (fd == -1) { ERROR("watchdogd: Failed to open %s: %s\n", DEV_NAME, strerror(errno)); return 1; } - ret = ioctl(fd, WDIOC_SETTIMEOUT, &timeout); + int timeout = interval + margin; + int ret = ioctl(fd, WDIOC_SETTIMEOUT, &timeout); if (ret) { ERROR("watchdogd: Failed to set timeout to %d: %s\n", timeout, strerror(errno)); ret = ioctl(fd, WDIOC_GETTIMEOUT, &timeout); if (ret) { ERROR("watchdogd: Failed to get timeout: %s\n", strerror(errno)); } else { - if (timeout > margin) + if (timeout > margin) { interval = timeout - margin; - else + } else { interval = 1; + } ERROR("watchdogd: Adjusted interval to timeout returned by driver: timeout %d, interval %d, margin %d\n", timeout, interval, margin); } } - while(1) { + while (true) { write(fd, "", 1); sleep(interval); } } - |