diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
commit | 4f6e8d7a00cbeda1e70cc15be9c4af1018bdad53 (patch) | |
tree | 54fd1b2695a591d2306d41264df67c53077b752c /init | |
download | system_core-4f6e8d7a00cbeda1e70cc15be9c4af1018bdad53.zip system_core-4f6e8d7a00cbeda1e70cc15be9c4af1018bdad53.tar.gz system_core-4f6e8d7a00cbeda1e70cc15be9c4af1018bdad53.tar.bz2 |
Initial Contribution
Diffstat (limited to 'init')
-rw-r--r-- | init/Android.mk | 33 | ||||
-rw-r--r-- | init/MODULE_LICENSE_APACHE2 | 0 | ||||
-rw-r--r-- | init/NOTICE | 190 | ||||
-rw-r--r-- | init/README.BOOTCHART | 34 | ||||
-rw-r--r-- | init/bootchart.c | 337 | ||||
-rw-r--r-- | init/builtins.c | 402 | ||||
-rw-r--r-- | init/devices.c | 622 | ||||
-rw-r--r-- | init/devices.h | 27 | ||||
-rwxr-xr-x | init/grab-bootchart.sh | 22 | ||||
-rw-r--r-- | init/init.c | 891 | ||||
-rw-r--r-- | init/init.h | 167 | ||||
-rw-r--r-- | init/keywords.h | 75 | ||||
-rw-r--r-- | init/logo.c | 163 | ||||
-rw-r--r-- | init/parser.c | 755 | ||||
-rw-r--r-- | init/property_service.c | 502 | ||||
-rw-r--r-- | init/property_service.h | 28 | ||||
-rw-r--r-- | init/readme.txt | 290 | ||||
-rw-r--r-- | init/util.c | 211 |
18 files changed, 4749 insertions, 0 deletions
diff --git a/init/Android.mk b/init/Android.mk new file mode 100644 index 0000000..d3766d4 --- /dev/null +++ b/init/Android.mk @@ -0,0 +1,33 @@ +# 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 \ + logo.c + +ifeq ($(strip $(INIT_BOOTCHART)),true) +LOCAL_SRC_FILES += bootchart.c +LOCAL_CFLAGS += -DBOOTCHART=1 +endif + +LOCAL_MODULE:= init + +LOCAL_FORCE_STATIC_EXECUTABLE := true +LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT) +LOCAL_UNSTRIPPED_PATH := $(TARGET_ROOT_OUT_UNSTRIPPED) + +LOCAL_STATIC_LIBRARIES := libcutils libc + +#LOCAL_STATIC_LIBRARIES := libcutils libc libminui libpixelflinger_static +#LOCAL_STATIC_LIBRARIES += libminzip libunz libamend libmtdutils libmincrypt +#LOCAL_STATIC_LIBRARIES += libstdc++_static + +include $(BUILD_EXECUTABLE) + diff --git a/init/MODULE_LICENSE_APACHE2 b/init/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/init/MODULE_LICENSE_APACHE2 diff --git a/init/NOTICE b/init/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/init/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/init/README.BOOTCHART b/init/README.BOOTCHART new file mode 100644 index 0000000..4899369 --- /dev/null +++ b/init/README.BOOTCHART @@ -0,0 +1,34 @@ +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', then create a file on the /data partition with a command +like the following: + + adb shell 'echo 1 > /data/bootchart' + +if the '/data/bootchart' file doesn't exist, or doesn't contain a '1' in its first +byte, init will proceed normally. + +by default, the bootchart log stops after 2 minutes, but you can stop it earlier +with the following command while the device is booting: + + 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, so don't forget to delete it +when you're done collecting data: + + adb shell rm /data/bootchart + +the log files are placed in /tmp/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 + +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 new file mode 100644 index 0000000..2afe98b --- /dev/null +++ b/init/bootchart.c @@ -0,0 +1,337 @@ +/* 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> + +#define VERSION "0.8" +#define SAMPLE_PERIOD 0.2 +#define LOG_ROOT "/tmp/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" +#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; + return len; +} + +#define FILE_BUFF_SIZE 65536 +#define FILE_BUFF_RESERVE (FILE_BUFF_SIZE - 4096) + +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; + close_on_exec(fd); + 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]; + + buff[0] = 0; + proc_read( LOG_STARTFILE, buff, sizeof(buff) ); + if (buff[0] != '1') + return -1; + + 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 0; +} + +/* 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/builtins.c b/init/builtins.c new file mode 100644 index 0000000..ba34410 --- /dev/null +++ b/init/builtins.c @@ -0,0 +1,402 @@ +/* + * 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 <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.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 <sys/mount.h> +#include <sys/resource.h> + +#include "init.h" +#include "keywords.h" +#include "property_service.h" +#include "devices.h" + +#include <private/android_filesystem_config.h> + +void 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, 0622); + + if (fd < 0) + return -1; + + len = strlen(value); + + do { + ret = write(fd, value, len); + } while (ret < 0 && errno == EINTR); + + close(fd); + if (ret < 0) { + return -1; + } else { + return 0; + } +} + +static int insmod(const char *filename) +{ + void *module; + unsigned size; + int ret; + + module = read_file(filename, &size); + if (!module) + return -1; + + ret = init_module(module, size, ""); + + 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; +} + +static int __ifupdown(const char *interface, int up) +{ + struct ifreq ifr; + int s, ret; + + strlcpy(ifr.ifr_name, interface, IFNAMSIZ); + + s = socket(AF_INET, SOCK_DGRAM, 0); + if (s < 0) + return -1; + + ret = ioctl(s, SIOCGIFFLAGS, &ifr); + if (ret < 0) { + goto done; + } + + if (up) + ifr.ifr_flags |= IFF_UP; + else + ifr.ifr_flags &= ~IFF_UP; + + ret = ioctl(s, SIOCSIFFLAGS, &ifr); + +done: + close(s); + return ret; +} + +static void service_start_if_not_disabled(struct service *svc) +{ + if (!(svc->flags & SVC_DISABLED)) { + service_start(svc); + } +} + +int do_class_start(int nargs, char **args) +{ + /* Starting a class does not start services + * which are explicitly disabled. They must + * be started individually. + */ + service_for_each_class(args[1], service_start_if_not_disabled); + return 0; +} + +int do_class_stop(int nargs, char **args) +{ + service_for_each_class(args[1], service_stop); + return 0; +} + +int do_domainname(int nargs, char **args) +{ + return write_file("/proc/sys/kernel/domainname", args[1]); +} + +int do_exec(int nargs, char **args) +{ + return -1; +} + +int do_export(int nargs, char **args) +{ + add_environment(args[1], args[2]); + return 0; +} + +int do_hostname(int nargs, char **args) +{ + return write_file("/proc/sys/kernel/hostname", args[1]); +} + +int do_ifup(int nargs, char **args) +{ + return __ifupdown(args[1], 1); +} + +int do_insmod(int nargs, char **args) +{ + return insmod(args[1]); +} + +int do_import(int nargs, char **args) +{ + return -1; +} + +int do_mkdir(int nargs, char **args) +{ + mode_t mode = 0755; + + /* mkdir <path> [mode] [owner] [group] */ + + if (nargs >= 3) { + mode = strtoul(args[2], 0, 8); + } + + if (mkdir(args[1], mode)) { + return -errno; + } + + if (nargs >= 4) { + uid_t uid = decode_uid(args[3]); + gid_t gid = -1; + + if (nargs == 5) { + gid = decode_uid(args[4]); + } + + if (chown(args[1], uid, gid)) { + return -errno; + } + } + + return 0; +} + +static struct { + const char *name; + unsigned flag; +} mount_flags[] = { + { "noatime", MS_NOATIME }, + { "nosuid", MS_NOSUID }, + { "nodev", MS_NODEV }, + { "nodiratime", MS_NODIRATIME }, + { "ro", MS_RDONLY }, + { "rw", 0 }, + { "remount", MS_REMOUNT }, + { "defaults", 0 }, + { 0, 0 }, +}; + +/* mount <type> <device> <path> <flags ...> <options> */ +int do_mount(int nargs, char **args) +{ + char tmp[64]; + char *source; + char *options = NULL; + unsigned flags = 0; + int n, i; + + for (n = 4; n < nargs; n++) { + for (i = 0; mount_flags[i].name; i++) { + if (!strcmp(args[n], mount_flags[i].name)) { + flags |= mount_flags[i].flag; + break; + } + } + + /* if our last argument isn't a flag, wolf it up as an option string */ + if (n + 1 == nargs && !mount_flags[i].name) + options = args[n]; + } + + source = args[2]; + if (!strncmp(source, "mtd@", 4)) { + n = mtd_name_to_number(source + 4); + if (n >= 0) { + sprintf(tmp, "/dev/block/mtdblock%d", n); + source = tmp; + } + } + return mount(source, args[3], args[1], flags, options); +} + +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) +{ + property_set(args[1], args[2]); + return 0; +} + +int do_setrlimit(int nargs, char **args) +{ + struct rlimit limit; + int resource; + resource = atoi(args[1]); + limit.rlim_cur = atoi(args[2]); + limit.rlim_max = atoi(args[3]); + return setrlimit(resource, &limit); +} + +int do_start(int nargs, char **args) +{ + struct service *svc; + svc = service_find_by_name(args[1]); + if (svc) { + service_start(svc); + } + return 0; +} + +int do_stop(int nargs, char **args) +{ + struct service *svc; + svc = service_find_by_name(args[1]); + if (svc) { + service_stop(svc); + } + return 0; +} + +int do_restart(int nargs, char **args) +{ + struct service *svc; + svc = service_find_by_name(args[1]); + if (svc) { + service_stop(svc); + service_start(svc); + } + return 0; +} + +int do_trigger(int nargs, char **args) +{ + return 0; +} + +int do_symlink(int nargs, char **args) +{ + return symlink(args[1], args[2]); +} + +int do_write(int nargs, char **args) +{ + return write_file(args[1], args[2]); +} + +int do_chown(int nargs, char **args) { + /* GID is optional. */ + if (nargs == 3) { + if (chown(args[2], decode_uid(args[1]), -1) < 0) + return -errno; + } else if (nargs == 4) { + if (chown(args[3], decode_uid(args[1]), decode_uid(args[2]))) + return -errno; + } else { + return -1; + } + return 0; +} + +static mode_t get_mode(const char *s) { + mode_t mode = 0; + while (*s) { + if (*s >= '0' && *s <= '7') { + mode = (mode<<3) | (*s-'0'); + } else { + return -1; + } + s++; + } + return mode; +} + +int do_chmod(int nargs, char **args) { + mode_t mode = get_mode(args[1]); + if (chmod(args[2], mode) < 0) { + return -errno; + } + return 0; +} + +int do_loglevel(int nargs, char **args) { + if (nargs == 2) { + log_set_level(atoi(args[1])); + return 0; + } + return -1; +} + +int do_device(int nargs, char **args) { + int len; + char tmp[64]; + char *source = args[1]; + int prefix = 0; + + if (nargs != 5) + return -1; + /* Check for wildcard '*' at the end which indicates a prefix. */ + len = strlen(args[1]) - 1; + if (args[1][len] == '*') { + args[1][len] = '\0'; + prefix = 1; + } + /* If path starts with mtd@ lookup the mount number. */ + if (!strncmp(source, "mtd@", 4)) { + int n = mtd_name_to_number(source + 4); + if (n >= 0) { + snprintf(tmp, sizeof(tmp), "/dev/mtd/mtd%d", n); + source = tmp; + } + } + add_devperms_partners(source, get_mode(args[2]), decode_uid(args[3]), + decode_uid(args[4]), prefix); + return 0; +} diff --git a/init/devices.c b/init/devices.c new file mode 100644 index 0000000..7aea246 --- /dev/null +++ b/init/devices.c @@ -0,0 +1,622 @@ +/* + * Copyright (C) 2007 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 <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <fcntl.h> +#include <dirent.h> +#include <unistd.h> +#include <string.h> + +#include <sys/socket.h> +#include <sys/un.h> +#include <linux/netlink.h> +#include <private/android_filesystem_config.h> +#include <sys/time.h> +#include <asm/page.h> + +#include "init.h" +#include "devices.h" + +#define CMDLINE_PREFIX "/dev" +#define SYSFS_PREFIX "/sys" +#define FIRMWARE_DIR "/etc/firmware" +#define MAX_QEMU_PERM 6 + +struct uevent { + const char *action; + const char *path; + const char *subsystem; + const char *firmware; + int major; + int minor; +}; + +static int open_uevent_socket(void) +{ + struct sockaddr_nl addr; + int sz = 64*1024; // XXX larger? udev uses 16MB! + int s; + + memset(&addr, 0, sizeof(addr)); + addr.nl_family = AF_NETLINK; + addr.nl_pid = getpid(); + addr.nl_groups = 0xffffffff; + + s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); + if(s < 0) + return -1; + + setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)); + + if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + close(s); + return -1; + } + + return s; +} + +struct perms_ { + char *name; + mode_t perm; + unsigned int uid; + unsigned int gid; + unsigned short prefix; +}; +static struct perms_ devperms[] = { + { "/dev/null", 0666, AID_ROOT, AID_ROOT, 0 }, + { "/dev/zero", 0666, AID_ROOT, AID_ROOT, 0 }, + { "/dev/full", 0666, AID_ROOT, AID_ROOT, 0 }, + { "/dev/ptmx", 0666, AID_ROOT, AID_ROOT, 0 }, + { "/dev/tty", 0666, AID_ROOT, AID_ROOT, 0 }, + { "/dev/random", 0666, AID_ROOT, AID_ROOT, 0 }, + { "/dev/urandom", 0666, AID_ROOT, AID_ROOT, 0 }, + { "/dev/ashmem", 0666, AID_ROOT, AID_ROOT, 0 }, + { "/dev/binder", 0666, AID_ROOT, AID_ROOT, 0 }, + + /* logger should be world writable (for logging) but not readable */ + { "/dev/log/", 0662, AID_ROOT, AID_LOG, 1 }, + + /* these should not be world writable */ + { "/dev/android_adb", 0660, AID_ADB, AID_ADB, 0 }, + { "/dev/android_adb_enable", 0660, AID_ADB, AID_ADB, 0 }, + { "/dev/ttyMSM0", 0660, AID_BLUETOOTH, AID_BLUETOOTH, 0 }, + { "/dev/alarm", 0664, AID_SYSTEM, AID_RADIO, 0 }, + { "/dev/tty0", 0666, AID_ROOT, AID_SYSTEM, 0 }, + { "/dev/graphics/", 0660, AID_ROOT, AID_GRAPHICS, 1 }, + { "/dev/hw3d", 0660, AID_SYSTEM, AID_GRAPHICS, 0 }, + { "/dev/input/", 0660, AID_ROOT, AID_INPUT, 1 }, + { "/dev/eac", 0660, AID_ROOT, AID_AUDIO, 0 }, + { "/dev/cam", 0660, AID_ROOT, AID_CAMERA, 0 }, + { "/dev/pmem", 0660, AID_SYSTEM, AID_GRAPHICS, 0 }, + { "/dev/pmem_gpu", 0660, AID_SYSTEM, AID_GRAPHICS, 1 }, + { "/dev/pmem_adsp", 0660, AID_SYSTEM, AID_AUDIO, 1 }, + { "/dev/pmem_camera", 0660, AID_SYSTEM, AID_CAMERA, 1 }, + { "/dev/oncrpc/", 0660, AID_ROOT, AID_SYSTEM, 1 }, + { "/dev/adsp/", 0660, AID_SYSTEM, AID_AUDIO, 1 }, + { "/dev/mt9t013", 0660, AID_SYSTEM, AID_SYSTEM, 0 }, + { "/dev/akm8976_daemon",0640, AID_COMPASS, AID_SYSTEM, 0 }, + { "/dev/akm8976_aot", 0640, AID_COMPASS, AID_SYSTEM, 0 }, + { "/dev/akm8976_pffd", 0640, AID_COMPASS, AID_SYSTEM, 0 }, + { "/dev/msm_pcm_out", 0660, AID_SYSTEM, AID_AUDIO, 1 }, + { "/dev/msm_pcm_in", 0660, AID_SYSTEM, AID_AUDIO, 1 }, + { "/dev/msm_pcm_ctl", 0660, AID_SYSTEM, AID_AUDIO, 1 }, + { "/dev/msm_mp3", 0660, AID_SYSTEM, AID_AUDIO, 1 }, + { "/dev/smd0", 0640, AID_RADIO, AID_RADIO, 0 }, + { "/dev/qmi", 0640, AID_RADIO, AID_RADIO, 0 }, + { "/dev/qmi0", 0640, AID_RADIO, AID_RADIO, 0 }, + { "/dev/qmi1", 0640, AID_RADIO, AID_RADIO, 0 }, + { "/dev/qmi2", 0640, AID_RADIO, AID_RADIO, 0 }, + { "/dev/htc-acoustic", 0640, AID_RADIO, AID_RADIO, 0 }, + { NULL, 0, 0, 0, 0 }, +}; + +/* devperms_partners list and perm_node are for hardware specific /dev entries */ +struct perm_node { + struct perms_ dp; + struct listnode plist; +}; +list_declare(devperms_partners); + +/* + * Permission override when in emulator mode, must be parsed before + * system properties is initalized. + */ +static int qemu_perm_count; +static struct perms_ qemu_perms[MAX_QEMU_PERM + 1]; + +int add_devperms_partners(const char *name, mode_t perm, unsigned int uid, + unsigned int gid, unsigned short prefix) { + int size; + struct perm_node *node = malloc(sizeof (struct perm_node)); + if (!node) + return -ENOMEM; + + size = strlen(name) + 1; + if ((node->dp.name = malloc(size)) == NULL) + return -ENOMEM; + + memcpy(node->dp.name, name, size); + node->dp.perm = perm; + node->dp.uid = uid; + node->dp.gid = gid; + node->dp.prefix = prefix; + + list_add_tail(&devperms_partners, &node->plist); + return 0; +} + +void qemu_init(void) { + qemu_perm_count = 0; + memset(&qemu_perms, 0, sizeof(qemu_perms)); +} + +static int qemu_perm(const char* name, mode_t perm, unsigned int uid, + unsigned int gid, unsigned short prefix) +{ + char *buf; + if (qemu_perm_count == MAX_QEMU_PERM) + return -ENOSPC; + + buf = malloc(strlen(name) + 1); + if (!buf) + return -errno; + + strlcpy(buf, name, strlen(name) + 1); + qemu_perms[qemu_perm_count].name = buf; + qemu_perms[qemu_perm_count].perm = perm; + qemu_perms[qemu_perm_count].uid = uid; + qemu_perms[qemu_perm_count].gid = gid; + qemu_perms[qemu_perm_count].prefix = prefix; + + qemu_perm_count++; + return 0; +} + +/* Permission overrides for emulator that are parsed from /proc/cmdline. */ +void qemu_cmdline(const char* name, const char *value) +{ + char *buf; + if (!strcmp(name, "android.ril")) { + /* cmd line params currently assume /dev/ prefix */ + if (asprintf(&buf, CMDLINE_PREFIX"/%s", value) == -1) { + return; + } + INFO("nani- buf:: %s\n", buf); + qemu_perm(buf, 0660, AID_RADIO, AID_ROOT, 0); + } +} + +static int get_device_perm_inner(struct perms_ *perms, const char *path, + unsigned *uid, unsigned *gid, mode_t *perm) +{ + int i; + for(i = 0; perms[i].name; i++) { + + if(perms[i].prefix) { + if(strncmp(path, perms[i].name, strlen(perms[i].name))) + continue; + } else { + if(strcmp(path, perms[i].name)) + continue; + } + *uid = perms[i].uid; + *gid = perms[i].gid; + *perm = perms[i].perm; + return 0; + } + return -1; +} + +/* First checks for emulator specific permissions specified in /proc/cmdline. */ +static mode_t get_device_perm(const char *path, unsigned *uid, unsigned *gid) +{ + mode_t perm; + + if (get_device_perm_inner(qemu_perms, path, uid, gid, &perm) == 0) { + return perm; + } else if (get_device_perm_inner(devperms, path, uid, gid, &perm) == 0) { + return perm; + } else { + struct listnode *node; + struct perm_node *perm_node; + struct perms_ *dp; + + /* Check partners list. */ + list_for_each(node, &devperms_partners) { + perm_node = node_to_item(node, struct perm_node, plist); + dp = &perm_node->dp; + + if (dp->prefix) { + if (strncmp(path, dp->name, strlen(dp->name))) + continue; + } else { + if (strcmp(path, dp->name)) + continue; + } + /* Found perm in partner list. */ + *uid = dp->uid; + *gid = dp->gid; + return dp->perm; + } + /* Default if nothing found. */ + *uid = 0; + *gid = 0; + return 0600; + } +} + +static void make_device(const char *path, int block, int major, int minor) +{ + unsigned uid; + unsigned gid; + mode_t mode; + dev_t dev; + + if(major > 255 || minor > 255) + return; + + mode = get_device_perm(path, &uid, &gid) | (block ? S_IFBLK : S_IFCHR); + dev = (major << 8) | minor; + mknod(path, mode, dev); + chown(path, uid, gid); +} + +#ifdef 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 = ""; + uevent->path = ""; + uevent->subsystem = ""; + uevent->firmware = ""; + uevent->major = -1; + uevent->minor = -1; + + /* currently ignoring SEQNUM */ + while(*msg) { + if(!strncmp(msg, "ACTION=", 7)) { + msg += 7; + uevent->action = msg; + } else if(!strncmp(msg, "DEVPATH=", 8)) { + msg += 8; + uevent->path = msg; + } else if(!strncmp(msg, "SUBSYSTEM=", 10)) { + msg += 10; + uevent->subsystem = msg; + } else if(!strncmp(msg, "FIRMWARE=", 9)) { + msg += 9; + uevent->firmware = msg; + } else if(!strncmp(msg, "MAJOR=", 6)) { + msg += 6; + uevent->major = atoi(msg); + } else if(!strncmp(msg, "MINOR=", 6)) { + msg += 6; + uevent->minor = atoi(msg); + } + + /* advance to after the next \0 */ + while(*msg++) + ; + } + + log_event_print("event { '%s', '%s', '%s', '%s', %d, %d }\n", + uevent->action, uevent->path, uevent->subsystem, + uevent->firmware, uevent->major, uevent->minor); +} + +static void handle_device_event(struct uevent *uevent) +{ + char devpath[96]; + char *base, *name; + int block; + + /* if it's not a /dev device, nothing to do */ + if((uevent->major < 0) || (uevent->minor < 0)) + return; + + /* do we have a name? */ + name = strrchr(uevent->path, '/'); + if(!name) + return; + name++; + + /* too-long names would overrun our buffer */ + if(strlen(name) > 64) + return; + + /* are we block or char? where should we live? */ + if(!strncmp(uevent->path, "/block", 6)) { + block = 1; + base = "/dev/block/"; + mkdir(base, 0755); + } else { + block = 0; + /* this should probably be configurable somehow */ + if(!strncmp(uevent->path, "/class/graphics/", 16)) { + base = "/dev/graphics/"; + mkdir(base, 0755); + } else if (!strncmp(uevent->path, "/class/oncrpc/", 14)) { + base = "/dev/oncrpc/"; + mkdir(base, 0755); + } else if (!strncmp(uevent->path, "/class/adsp/", 12)) { + base = "/dev/adsp/"; + mkdir(base, 0755); + } else if(!strncmp(uevent->path, "/class/input/", 13)) { + base = "/dev/input/"; + mkdir(base, 0755); + } else if(!strncmp(uevent->path, "/class/mtd/", 11)) { + base = "/dev/mtd/"; + mkdir(base, 0755); + } else if(!strncmp(uevent->path, "/class/misc/", 12) && + !strncmp(name, "log_", 4)) { + base = "/dev/log/"; + mkdir(base, 0755); + name += 4; + } else + base = "/dev/"; + } + + snprintf(devpath, sizeof(devpath), "%s%s", base, name); + + if(!strcmp(uevent->action, "add")) { + make_device(devpath, block, uevent->major, uevent->minor); + return; + } + + if(!strcmp(uevent->action, "remove")) { + unlink(devpath); + return; + } +} + +static int load_firmware(int fw_fd, int loading_fd, int data_fd) +{ + struct stat st; + long len_to_copy; + int ret = 0; + + if(fstat(fw_fd, &st) < 0) + return -1; + len_to_copy = st.st_size; + + write(loading_fd, "1", 1); /* start transfer */ + + while (len_to_copy > 0) { + char buf[PAGE_SIZE]; + ssize_t nr; + + nr = read(fw_fd, buf, sizeof(buf)); + if(!nr) + break; + if(nr < 0) { + ret = -1; + break; + } + + len_to_copy -= nr; + while (nr > 0) { + ssize_t nw = 0; + + nw = write(data_fd, buf + nw, nr); + if(nw <= 0) { + ret = -1; + goto out; + } + nr -= nw; + } + } + +out: + if(!ret) + write(loading_fd, "0", 1); /* successful end of transfer */ + else + write(loading_fd, "-1", 2); /* abort transfer */ + + return ret; +} + +static void process_firmware_event(struct uevent *uevent) +{ + char *root, *loading, *data, *file; + int l, loading_fd, data_fd, fw_fd; + + log_event_print("firmware event { '%s', '%s' }\n", + uevent->path, uevent->firmware); + + l = asprintf(&root, SYSFS_PREFIX"%s/", uevent->path); + if (l == -1) + return; + + l = asprintf(&loading, "%sloading", root); + if (l == -1) + goto root_free_out; + + l = asprintf(&data, "%sdata", root); + if (l == -1) + goto loading_free_out; + + l = asprintf(&file, FIRMWARE_DIR"/%s", uevent->firmware); + if (l == -1) + goto data_free_out; + + loading_fd = open(loading, O_WRONLY); + if(loading_fd < 0) + goto file_free_out; + + data_fd = open(data, O_WRONLY); + if(data_fd < 0) + goto loading_close_out; + + fw_fd = open(file, O_RDONLY); + if(fw_fd < 0) + goto data_close_out; + + if(!load_firmware(fw_fd, loading_fd, data_fd)) + log_event_print("firmware copy success { '%s', '%s' }\n", root, file); + else + log_event_print("firmware copy failure { '%s', '%s' }\n", root, file); + + close(fw_fd); +data_close_out: + close(data_fd); +loading_close_out: + close(loading_fd); +file_free_out: + free(file); +data_free_out: + free(data); +loading_free_out: + free(loading); +root_free_out: + free(root); +} + +static void handle_firmware_event(struct uevent *uevent) +{ + pid_t pid; + + if(strcmp(uevent->subsystem, "firmware")) + return; + + if(strcmp(uevent->action, "add")) + return; + + /* we fork, to avoid making large memory allocations in init proper */ + pid = fork(); + if (!pid) { + process_firmware_event(uevent); + exit(EXIT_SUCCESS); + } +} + +#define UEVENT_MSG_LEN 1024 +void handle_device_fd(int fd) +{ + char msg[UEVENT_MSG_LEN+2]; + int n; + + while((n = recv(fd, msg, UEVENT_MSG_LEN, 0)) > 0) { + struct uevent uevent; + + if(n == UEVENT_MSG_LEN) /* overflow -- discard */ + continue; + + msg[n] = '\0'; + msg[n+1] = '\0'; + + parse_event(msg, &uevent); + + handle_device_event(&uevent); + handle_firmware_event(&uevent); + } +} + +/* Coldboot walks parts of the /sys tree and pokes the uevent files +** to cause the kernel to regenerate device add events that happened +** before init's device manager was started +** +** 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. +*/ + +static void do_coldboot(int event_fd, DIR *d) +{ + struct dirent *de; + int dfd, fd; + + dfd = dirfd(d); + + fd = openat(dfd, "uevent", O_WRONLY); + if(fd >= 0) { + write(fd, "add\n", 4); + close(fd); + handle_device_fd(event_fd); + } + + while((de = readdir(d))) { + DIR *d2; + + if(de->d_type != DT_DIR || de->d_name[0] == '.') + continue; + + fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY); + if(fd < 0) + continue; + + d2 = fdopendir(fd); + if(d2 == 0) + close(fd); + else { + do_coldboot(event_fd, d2); + closedir(d2); + } + } +} + +static void coldboot(int event_fd, const char *path) +{ + DIR *d = opendir(path); + if(d) { + do_coldboot(event_fd, d); + closedir(d); + } +} + +int device_init(void) +{ + suseconds_t t0, t1; + int fd; + + fd = open_uevent_socket(); + if(fd < 0) + return -1; + + fcntl(fd, F_SETFD, FD_CLOEXEC); + fcntl(fd, F_SETFL, O_NONBLOCK); + + t0 = get_usecs(); + coldboot(fd, "/sys/class"); + coldboot(fd, "/sys/block"); + coldboot(fd, "/sys/devices"); + t1 = get_usecs(); + + log_event_print("coldboot %ld uS\n", ((long) (t1 - t0))); + + return fd; +} diff --git a/init/devices.h b/init/devices.h new file mode 100644 index 0000000..b484da4 --- /dev/null +++ b/init/devices.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2007 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. + */ + +#ifndef _INIT_DEVICES_H +#define _INIT_DEVICES_H + +extern void handle_device_fd(int fd); +extern int device_init(void); +extern void qemu_init(void); +extern void qemu_cmdline(const char* name, const char *value); +extern int add_devperms_partners(const char *name, mode_t perm, unsigned int uid, + unsigned int gid, unsigned short prefix); + +#endif /* _INIT_DEVICES_H */ diff --git a/init/grab-bootchart.sh b/init/grab-bootchart.sh new file mode 100755 index 0000000..57c9556 --- /dev/null +++ b/init/grab-bootchart.sh @@ -0,0 +1,22 @@ +#!/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 +# +TMPDIR=/tmp/android-bootchart +rm -rf $TMPDIR +mkdir -p $TMPDIR + +LOGROOT=/tmp/bootchart +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 &> /dev/null +done +(cd $TMPDIR && tar -czf $TARBALL $FILES) +cp -f $TMPDIR/$TARBALL ./$TARBALL +echo "look at $TARBALL" diff --git a/init/init.c b/init/init.c new file mode 100644 index 0000000..f6e9b39 --- /dev/null +++ b/init/init.c @@ -0,0 +1,891 @@ +/* + * 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 <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/mount.h> +#include <sys/stat.h> +#include <sys/poll.h> +#include <time.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/reboot.h> + +#include <cutils/sockets.h> +#include <termios.h> +#include <linux/kd.h> + +#include <sys/system_properties.h> + +#include "devices.h" +#include "init.h" +#include "property_service.h" + +#ifndef BOOTCHART +# define BOOTCHART 0 +#endif + +static int property_triggers_enabled = 0; + +#if BOOTCHART +static int bootchart_count; +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_MAX_TIME_MS (2*60*1000) /* max polling time from boot */ +# define BOOTCHART_MAX_COUNT (BOOTCHART_MAX_TIME_MS/BOOTCHART_POLLING_MS) +#endif + +static char console[32]; +static char serialno[32]; +static char bootmode[32]; +static char baseband[32]; +static char carrier[32]; +static char bootloader[32]; +static char hardware[32]; +static unsigned revision = 0; +static char qemu[32]; + +static void drain_action_queue(void); + +static 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 = "/dev/console"; +static time_t process_needs_restart; + +static const char *ENV[32]; + +/* add_environment - add "key=value" to the current environment */ +int add_environment(const char *key, const char *val) +{ + int n; + + for (n = 0; n < 31; n++) { + if (!ENV[n]) { + size_t len = strlen(key) + strlen(val) + 2; + char *entry = malloc(len); + snprintf(entry, len, "%s=%s", key, val); + ENV[n] = entry; + return 0; + } + } + + return 1; +} + +static void zap_stdio(void) +{ + int fd; + fd = open("/dev/null", O_RDWR); + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + close(fd); +} + +static void open_console() +{ + int fd; + if ((fd = open(console_name, O_RDWR)) < 0) { + fd = open("/dev/null", O_RDWR); + } + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + close(fd); +} + +/* + * gettime() - returns the time in seconds of the system's monotonic clock or + * zero on error. + */ +static 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; + } + + return ts.tv_sec; +} + +static void publish_socket(const char *name, int fd) +{ + char key[64] = ANDROID_SOCKET_ENV_PREFIX; + char val[64]; + + strlcpy(key + sizeof(ANDROID_SOCKET_ENV_PREFIX) - 1, + name, + sizeof(key) - sizeof(ANDROID_SOCKET_ENV_PREFIX)); + snprintf(val, sizeof(val), "%d", fd); + add_environment(key, val); + + /* make sure we don't close-on-exec */ + fcntl(fd, F_SETFD, 0); +} + +void service_start(struct service *svc) +{ + struct stat s; + pid_t pid; + int needs_console; + int n; + + /* starting a service removes it from the disabled + * state and immediately takes it out of the restarting + * state if it was in there + */ + svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING)); + svc->time_started = 0; + + /* running processes require no additional work -- if + * they're in the process of exiting, we've ensured + * that they will immediately restart on exit, unless + * they are ONESHOT + */ + if (svc->flags & SVC_RUNNING) { + return; + } + + needs_console = (svc->flags & SVC_CONSOLE) ? 1 : 0; + if (needs_console && (!have_console)) { + ERROR("service '%s' requires console\n", svc->name); + svc->flags |= SVC_DISABLED; + return; + } + + if (stat(svc->args[0], &s) != 0) { + ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name); + svc->flags |= SVC_DISABLED; + return; + } + + NOTICE("starting '%s'\n", svc->name); + + pid = fork(); + + if (pid == 0) { + struct socketinfo *si; + struct svcenvinfo *ei; + char tmp[32]; + int fd, sz; + + get_property_workspace(&fd, &sz); + sprintf(tmp, "%d,%d", dup(fd), sz); + add_environment("ANDROID_PROPERTY_WORKSPACE", tmp); + + for (ei = svc->envvars; ei; ei = ei->next) + add_environment(ei->name, ei->value); + + for (si = svc->sockets; si; si = si->next) { + int s = create_socket(si->name, + !strcmp(si->type, "dgram") ? + SOCK_DGRAM : SOCK_STREAM, + si->perm, si->uid, si->gid); + if (s >= 0) { + publish_socket(si->name, s); + } + } + + if (needs_console) { + setsid(); + open_console(); + } else { + 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]); + } +#endif + + setpgid(0, getpid()); + + /* as requested, set our gid, supplemental gids, and uid */ + if (svc->gid) { + setgid(svc->gid); + } + if (svc->nr_supp_gids) { + setgroups(svc->nr_supp_gids, svc->supp_gids); + } + if (svc->uid) { + setuid(svc->uid); + } + + execve(svc->args[0], (char**) svc->args, (char**) ENV); + _exit(127); + } + + if (pid < 0) { + ERROR("failed to start '%s'\n", svc->name); + svc->pid = 0; + return; + } + + svc->time_started = gettime(); + svc->pid = pid; + svc->flags |= SVC_RUNNING; + + notify_service_state(svc->name, "running"); +} + +void service_stop(struct service *svc) +{ + /* we are no longer running, nor should we + * attempt to restart + */ + svc->flags &= (~(SVC_RUNNING|SVC_RESTARTING)); + + /* if the service has not yet started, prevent + * it from auto-starting with its class + */ + svc->flags |= SVC_DISABLED; + + if (svc->pid) { + NOTICE("service '%s' is being killed\n", svc->name); + kill(-svc->pid, SIGTERM); + notify_service_state(svc->name, "stopping"); + } else { + notify_service_state(svc->name, "stopped"); + } +} + +void property_changed(const char *name, const char *value) +{ + if (property_triggers_enabled) { + queue_property_triggers(name, value); + drain_action_queue(); + } +} + +#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) { + ERROR("untracked pid %d exited\n", pid); + return 0; + } + + NOTICE("process '%s', pid %d exited\n", svc->name, pid); + + if (!(svc->flags & SVC_ONESHOT)) { + 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 */ + if (svc->flags & SVC_ONESHOT) { + svc->flags |= SVC_DISABLED; + } + + /* disabled processes do not get restarted automatically */ + if (svc->flags & SVC_DISABLED) { + notify_service_state(svc->name, "stopped"); + return 0; + } + + now = gettime(); + if (svc->flags & SVC_CRITICAL) { + 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); + sync(); + __reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, + LINUX_REBOOT_CMD_RESTART2, "recovery"); + return 0; + } + } else { + svc->time_crashed = now; + svc->nr_crashed = 1; + } + } + + /* 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); + } + svc->flags |= SVC_RESTARTING; + notify_service_state(svc->name, "restarting"); + return 0; +} + +static void restart_service_if_needed(struct service *svc) +{ + time_t next_start_time = svc->time_started + 5; + + if (next_start_time <= gettime()) { + svc->flags &= (~SVC_RESTARTING); + service_start(svc); + return; + } + + if ((next_start_time < process_needs_restart) || + (process_needs_restart == 0)) { + process_needs_restart = next_start_time; + } +} + +static void restart_processes() +{ + process_needs_restart = 0; + service_for_each_flags(SVC_RESTARTING, + restart_service_if_needed); +} + +static int signal_fd = -1; + +static void sigchld_handler(int s) +{ + write(signal_fd, &s, 1); +} + +static void msg_start(const char *name) +{ + struct service *svc = service_find_by_name(name); + + if (svc) { + service_start(svc); + } else { + ERROR("no such service '%s'\n", name); + } +} + +static void msg_stop(const char *name) +{ + struct service *svc = service_find_by_name(name); + + if (svc) { + service_stop(svc); + } else { + ERROR("no such service '%s'\n"); + } +} + +void handle_control_message(const char *msg, const char *arg) +{ + if (!strcmp(msg,"start")) { + msg_start(arg); + } else if (!strcmp(msg,"stop")) { + msg_stop(arg); + } else { + ERROR("unknown control msg '%s'\n", msg); + } +} + +#define MAX_MTD_PARTITIONS 16 + +static struct { + char name[16]; + int number; +} mtd_part_map[MAX_MTD_PARTITIONS]; + +static int mtd_part_count = -1; + +static void find_mtd_partitions(void) +{ + int fd; + char buf[1024]; + char *pmtdbufp; + ssize_t pmtdsize; + int r; + + fd = open("/proc/mtd", O_RDONLY); + if (fd < 0) + return; + + buf[sizeof(buf) - 1] = '\0'; + pmtdsize = read(fd, buf, sizeof(buf) - 1); + pmtdbufp = buf; + while (pmtdsize > 0) { + int mtdnum, mtdsize, mtderasesize; + char mtdname[16]; + mtdname[0] = '\0'; + mtdnum = -1; + r = sscanf(pmtdbufp, "mtd%d: %x %x %15s", + &mtdnum, &mtdsize, &mtderasesize, mtdname); + if ((r == 4) && (mtdname[0] == '"')) { + char *x = strchr(mtdname + 1, '"'); + if (x) { + *x = 0; + } + INFO("mtd partition %d, %s\n", mtdnum, mtdname + 1); + if (mtd_part_count < MAX_MTD_PARTITIONS) { + strcpy(mtd_part_map[mtd_part_count].name, mtdname + 1); + mtd_part_map[mtd_part_count].number = mtdnum; + mtd_part_count++; + } else { + ERROR("too many mtd partitions\n"); + } + } + while (pmtdsize > 0 && *pmtdbufp != '\n') { + pmtdbufp++; + pmtdsize--; + } + if (pmtdsize > 0) { + pmtdbufp++; + pmtdsize--; + } + } + close(fd); +} + +int mtd_name_to_number(const char *name) +{ + int n; + if (mtd_part_count < 0) { + mtd_part_count = 0; + find_mtd_partitions(); + } + for (n = 0; n < mtd_part_count; n++) { + if (!strcmp(name, mtd_part_map[n].name)) { + return mtd_part_map[n].number; + } + } + return -1; +} + +static void import_kernel_nv(char *name, int in_qemu) +{ + char *value = strchr(name, '='); + + if (value == 0) return; + *value++ = 0; + if (*name == 0) return; + + if (!in_qemu) + { + /* on a real device, white-list the kernel options */ + if (!strcmp(name,"qemu")) { + strlcpy(qemu, value, sizeof(qemu)); + } else if (!strcmp(name,"androidboot.console")) { + strlcpy(console, value, sizeof(console)); + } else if (!strcmp(name,"androidboot.mode")) { + strlcpy(bootmode, value, sizeof(bootmode)); + } else if (!strcmp(name,"androidboot.serialno")) { + strlcpy(serialno, value, sizeof(serialno)); + } else if (!strcmp(name,"androidboot.baseband")) { + strlcpy(baseband, value, sizeof(baseband)); + } else if (!strcmp(name,"androidboot.carrier")) { + strlcpy(carrier, value, sizeof(carrier)); + } else if (!strcmp(name,"androidboot.bootloader")) { + strlcpy(bootloader, value, sizeof(bootloader)); + } else if (!strcmp(name,"androidboot.hardware")) { + strlcpy(hardware, value, sizeof(hardware)); + } else { + qemu_cmdline(name, value); + } + } else { + /* in the emulator, export any kernel option with the + * ro.kernel. prefix */ + char buff[32]; + int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name ); + if (len < (int)sizeof(buff)) { + property_set( buff, value ); + } + } +} + +static void import_kernel_cmdline(int in_qemu) +{ + char cmdline[1024]; + char *ptr; + int fd; + + fd = open("/proc/cmdline", O_RDONLY); + if (fd >= 0) { + int n = read(fd, cmdline, 1023); + if (n < 0) n = 0; + + /* get rid of trailing newline, it happens */ + if (n > 0 && cmdline[n-1] == '\n') n--; + + cmdline[n] = 0; + close(fd); + } else { + cmdline[0] = 0; + } + + ptr = cmdline; + while (ptr && *ptr) { + char *x = strchr(ptr, ' '); + if (x != 0) *x++ = 0; + import_kernel_nv(ptr, in_qemu); + ptr = x; + } + + /* don't expose the raw commandline to nonpriv processes */ + chmod("/proc/cmdline", 0440); +} + +static void get_hardware_name(void) +{ + char data[1024]; + int fd, n; + char *x, *hw, *rev; + + /* Hardware string was provided on kernel command line */ + if (hardware[0]) + return; + + fd = open("/proc/cpuinfo", O_RDONLY); + if (fd < 0) return; + + n = read(fd, data, 1023); + close(fd); + if (n < 0) return; + + data[n] = 0; + hw = strstr(data, "\nHardware"); + rev = strstr(data, "\nRevision"); + + if (hw) { + x = strstr(hw, ": "); + if (x) { + x += 2; + n = 0; + while (*x && !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); + } + } +} + +static void drain_action_queue(void) +{ + struct listnode *node; + struct command *cmd; + struct action *act; + int ret; + + while ((act = action_remove_queue_head())) { + INFO("processing action %p (%s)\n", act, act->name); + list_for_each(node, &act->commands) { + cmd = node_to_item(node, struct command, clist); + ret = cmd->func(cmd->nargs, cmd->args); + INFO("command '%s' r=%d\n", cmd->args[0], ret); + } + } +} + +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); +} + +int main(int argc, char **argv) +{ + int device_fd = -1; + int property_set_fd = -1; + int signal_recv_fd = -1; + int s[2]; + int fd; + struct sigaction act; + char tmp[PROP_VALUE_MAX]; + struct pollfd ufds[4]; + char *tmpdev; + + act.sa_handler = sigchld_handler; + act.sa_flags = SA_NOCLDSTOP; + act.sa_mask = 0; + act.sa_restorer = NULL; + sigaction(SIGCHLD, &act, 0); + + /* 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", 0, "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(); + log_init(); + + INFO("reading config file\n"); + parse_config_file("/init.rc"); + + /* pull the kernel commandline and ramdisk properties file in */ + qemu_init(); + import_kernel_cmdline(0); + + get_hardware_name(); + snprintf(tmp, sizeof(tmp), "/init.%s.rc", hardware); + parse_config_file(tmp); + + action_for_each_trigger("early-init", action_add_queue_tail); + drain_action_queue(); + + INFO("device init\n"); + device_fd = device_init(); + + property_init(); + + if (console[0]) { + snprintf(tmp, sizeof(tmp), "/dev/%s", console); + console_name = strdup(tmp); + } + + fd = open(console_name, O_RDWR); + if (fd >= 0) + have_console = 1; + close(fd); + + if( load_565rle_image(INIT_IMAGE_FILE) ) { + fd = open("/dev/tty0", O_WRONLY); + if (fd >= 0) { + const char *msg; + msg = "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" // console is 40 cols x 30 lines + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + " A N D R O I D "; + write(fd, msg, strlen(msg)); + close(fd); + } + } + + if (qemu[0]) + import_kernel_cmdline(1); + + 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"); + + property_set("ro.serialno", serialno[0] ? serialno : ""); + property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown"); + property_set("ro.baseband", baseband[0] ? baseband : "unknown"); + property_set("ro.carrier", carrier[0] ? carrier : "unknown"); + property_set("ro.bootloader", bootloader[0] ? bootloader : "unknown"); + + property_set("ro.hardware", hardware); + snprintf(tmp, PROP_VALUE_MAX, "%d", revision); + property_set("ro.revision", tmp); + + /* execute all the boot actions to get us started */ + action_for_each_trigger("init", action_add_queue_tail); + drain_action_queue(); + + /* 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. + */ + property_set_fd = start_property_service(); + + /* 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); + } + + /* make sure we actually have all the pieces we need */ + if ((device_fd < 0) || + (property_set_fd < 0) || + (signal_recv_fd < 0)) { + ERROR("init startup failure\n"); + return 1; + } + + /* execute all the boot actions to get us started */ + action_for_each_trigger("early-boot", action_add_queue_tail); + action_for_each_trigger("boot", action_add_queue_tail); + drain_action_queue(); + + /* run all property triggers based on current state of the properties */ + queue_all_property_triggers(); + drain_action_queue(); + + /* enable property triggers */ + property_triggers_enabled = 1; + + ufds[0].fd = device_fd; + ufds[0].events = POLLIN; + ufds[1].fd = property_set_fd; + ufds[1].events = POLLIN; + ufds[2].fd = signal_recv_fd; + ufds[2].events = POLLIN; + +#if BOOTCHART + if (bootchart_init() < 0) + ERROR("bootcharting init failure\n"); + else { + NOTICE("bootcharting started\n"); + bootchart_count = BOOTCHART_MAX_COUNT; + } +#endif + + for(;;) { + int nr, timeout = -1; + + ufds[0].revents = 0; + ufds[1].revents = 0; + ufds[2].revents = 0; + + drain_action_queue(); + restart_processes(); + + if (process_needs_restart) { + timeout = (process_needs_restart - gettime()) * 1000; + if (timeout < 0) + 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, 3, timeout); + if (nr <= 0) + continue; + + if (ufds[2].revents == POLLIN) { + /* we got a SIGCHLD - reap and restart as needed */ + read(signal_recv_fd, tmp, sizeof(tmp)); + while (!wait_for_one_process(0)) + ; + continue; + } + + if (ufds[0].revents == POLLIN) + handle_device_fd(device_fd); + + if (ufds[1].revents == POLLIN) + handle_property_set_fd(property_set_fd); + } + + return 0; +} diff --git a/init/init.h b/init/init.h new file mode 100644 index 0000000..4ff0c69 --- /dev/null +++ b/init/init.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2007 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. + */ + +#ifndef _INIT_INIT_H +#define _INIT_INIT_H + +int mtd_name_to_number(const char *name); + +void handle_control_message(const char *msg, const char *arg); + +int create_socket(const char *name, int type, mode_t perm, + uid_t uid, gid_t gid); + +void *read_file(const char *fn, unsigned *_sz); + +void log_init(void); +void log_set_level(int level); +void log_close(void); +void log_write(int level, const char *fmt, ...); + +#define ERROR(x...) log_write(3, "<3>init: " x) +#define NOTICE(x...) log_write(5, "<5>init: " x) +#define INFO(x...) log_write(6, "<6>init: " x) + +#define LOG_DEFAULT_LEVEL 3 /* messages <= this level are logged */ +#define LOG_UEVENTS 0 /* log uevent messages if 1. verbose */ + +unsigned int decode_uid(const char *s); + +struct listnode +{ + struct listnode *next; + struct listnode *prev; +}; + +#define node_to_item(node, container, member) \ + (container *) (((char*) (node)) - offsetof(container, member)) + +#define list_declare(name) \ + struct listnode name = { \ + .next = &name, \ + .prev = &name, \ + } + +#define list_for_each(node, list) \ + for (node = (list)->next; node != (list); node = node->next) + +void list_init(struct listnode *list); +void list_add_tail(struct listnode *list, struct listnode *item); +void list_remove(struct listnode *item); + +#define list_empty(list) ((list) == (list)->next) +#define list_head(list) ((list)->next) +#define list_tail(list) ((list)->prev) + +struct command +{ + /* list of commands in an action */ + struct listnode clist; + + int (*func)(int nargs, char **args); + int nargs; + char *args[1]; +}; + +struct action { + /* node in list of all actions */ + struct listnode alist; + /* node in the queue of pending actions */ + struct listnode qlist; + /* node in list of actions for a trigger */ + struct listnode tlist; + + unsigned hash; + const char *name; + + struct listnode commands; + struct command *current; +}; + +struct socketinfo { + struct socketinfo *next; + const char *name; + const char *type; + uid_t uid; + gid_t gid; + int perm; +}; + +struct svcenvinfo { + struct svcenvinfo *next; + const char *name; + 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 NR_SVC_SUPP_GIDS 6 /* six supplementary groups */ + +struct service { + /* list of all services */ + struct listnode slist; + + const char *name; + const char *classname; + + unsigned flags; + pid_t pid; + 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; + + struct socketinfo *sockets; + struct svcenvinfo *envvars; + + int nargs; + char *args[1]; + struct action onrestart; /* Actions to execute on restart. */ +}; + +int parse_config_file(const char *fn); + +struct service *service_find_by_name(const char *name); +struct service *service_find_by_pid(pid_t pid); +void service_for_each_class(const char *classname, + void (*func)(struct service *svc)); +void service_for_each_flags(unsigned matchflags, + void (*func)(struct service *svc)); +void service_stop(struct service *svc); +void service_start(struct service *svc); +void property_changed(const char *name, const char *value); + +struct action *action_remove_queue_head(void); +void action_add_queue_tail(struct action *act); +void action_for_each_trigger(const char *trigger, + void (*func)(struct action *act)); +void queue_property_triggers(const char *name, const char *value); +void queue_all_property_triggers(); + +#define INIT_IMAGE_FILE "/initlogo.rle" + +int load_565rle_image( char *file_name ); + +#endif /* _INIT_INIT_H */ diff --git a/init/keywords.h b/init/keywords.h new file mode 100644 index 0000000..f09bad2 --- /dev/null +++ b/init/keywords.h @@ -0,0 +1,75 @@ + +#ifndef KEYWORD +int do_class_start(int nargs, char **args); +int do_class_stop(int nargs, char **args); +int do_domainname(int nargs, char **args); +int do_exec(int nargs, char **args); +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_import(int nargs, char **args); +int do_mkdir(int nargs, char **args); +int do_mount(int nargs, char **args); +int do_restart(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_start(int nargs, char **args); +int do_stop(int nargs, char **args); +int do_trigger(int nargs, char **args); +int do_symlink(int nargs, char **args); +int do_write(int nargs, char **args); +int do_chown(int nargs, char **args); +int do_chmod(int nargs, char **args); +int do_loglevel(int nargs, char **args); +int do_device(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(class, OPTION, 0, 0) + KEYWORD(class_start, COMMAND, 1, do_class_start) + KEYWORD(class_stop, COMMAND, 1, do_class_stop) + KEYWORD(console, OPTION, 0, 0) + KEYWORD(critical, OPTION, 0, 0) + KEYWORD(disabled, OPTION, 0, 0) + KEYWORD(domainname, COMMAND, 1, do_domainname) + KEYWORD(exec, COMMAND, 1, do_exec) + KEYWORD(export, COMMAND, 2, do_export) + KEYWORD(group, OPTION, 0, 0) + KEYWORD(hostname, COMMAND, 1, do_hostname) + KEYWORD(ifup, COMMAND, 1, do_ifup) + KEYWORD(insmod, COMMAND, 1, do_insmod) + KEYWORD(import, COMMAND, 1, do_import) + KEYWORD(mkdir, COMMAND, 1, do_mkdir) + KEYWORD(mount, COMMAND, 3, do_mount) + KEYWORD(on, SECTION, 0, 0) + KEYWORD(oneshot, OPTION, 0, 0) + KEYWORD(onrestart, OPTION, 0, 0) + KEYWORD(restart, COMMAND, 1, do_restart) + KEYWORD(service, SECTION, 0, 0) + KEYWORD(setenv, OPTION, 2, 0) + KEYWORD(setkey, COMMAND, 0, do_setkey) + KEYWORD(setprop, COMMAND, 2, do_setprop) + KEYWORD(setrlimit, COMMAND, 3, do_setrlimit) + KEYWORD(socket, OPTION, 0, 0) + KEYWORD(start, COMMAND, 1, do_start) + KEYWORD(stop, COMMAND, 1, do_stop) + KEYWORD(trigger, COMMAND, 1, do_trigger) + KEYWORD(symlink, COMMAND, 1, do_symlink) + KEYWORD(user, OPTION, 0, 0) + KEYWORD(write, COMMAND, 2, do_write) + KEYWORD(chown, COMMAND, 2, do_chown) + KEYWORD(chmod, COMMAND, 2, do_chmod) + KEYWORD(loglevel, COMMAND, 1, do_loglevel) + KEYWORD(device, COMMAND, 4, do_device) +#ifdef __MAKE_KEYWORD_ENUM__ + KEYWORD_COUNT, +}; +#undef __MAKE_KEYWORD_ENUM__ +#undef KEYWORD +#endif + diff --git a/init/logo.c b/init/logo.c new file mode 100644 index 0000000..6a740bf --- /dev/null +++ b/init/logo.c @@ -0,0 +1,163 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <linux/fb.h> +#include <linux/kd.h> + +#include "init.h" + +#ifdef ANDROID +#include <cutils/memory.h> +#else +void android_memset16(void *_ptr, unsigned short val, unsigned count) +{ + unsigned short *ptr = _ptr; + count >>= 1; + while(count--) + *ptr++ = val; +} +#endif + +struct FB { + unsigned short *bits; + unsigned size; + int fd; + struct fb_fix_screeninfo fi; + struct fb_var_screeninfo vi; +}; + +#define fb_width(fb) ((fb)->vi.xres) +#define fb_height(fb) ((fb)->vi.yres) +#define fb_size(fb) ((fb)->vi.xres * (fb)->vi.yres * 2) + +static int fb_open(struct FB *fb) +{ + fb->fd = open("/dev/graphics/fb0", O_RDWR); + if (fb->fd < 0) + return -1; + + if (ioctl(fb->fd, FBIOGET_FSCREENINFO, &fb->fi) < 0) + goto fail; + if (ioctl(fb->fd, FBIOGET_VSCREENINFO, &fb->vi) < 0) + goto fail; + + fb->bits = mmap(0, fb_size(fb), PROT_READ | PROT_WRITE, + MAP_SHARED, fb->fd, 0); + if (fb->bits == MAP_FAILED) + goto fail; + + return 0; + +fail: + close(fb->fd); + return -1; +} + +static void fb_close(struct FB *fb) +{ + munmap(fb->bits, fb_size(fb)); + close(fb->fd); +} + +/* there's got to be a more portable way to do this ... */ +static void fb_update(struct FB *fb) +{ + fb->vi.yoffset = 1; + ioctl(fb->fd, FBIOPUT_VSCREENINFO, &fb->vi); + fb->vi.yoffset = 0; + ioctl(fb->fd, FBIOPUT_VSCREENINFO, &fb->vi); +} + +static int vt_set_mode(int graphics) +{ + int fd, r; + fd = open("/dev/tty0", O_RDWR | O_SYNC); + if (fd < 0) + return -1; + r = ioctl(fd, KDSETMODE, (void*) (graphics ? KD_GRAPHICS : KD_TEXT)); + close(fd); + return r; +} + +/* 565RLE image format: [count(2 bytes), rle(2 bytes)] */ + +int load_565rle_image(char *fn) +{ + struct FB fb; + struct stat s; + unsigned short *data, *bits, *ptr; + unsigned count, max; + int fd; + + if (vt_set_mode(1)) + return -1; + + fd = open(fn, O_RDONLY); + if (fd < 0) { + ERROR("cannot open '%s'\n", fn); + goto fail_restore_text; + } + + if (fstat(fd, &s) < 0) { + goto fail_close_file; + } + + data = mmap(0, s.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) + goto fail_close_file; + + if (fb_open(&fb)) + goto fail_unmap_data; + + max = fb_width(&fb) * fb_height(&fb); + ptr = data; + count = s.st_size; + bits = fb.bits; + while (count > 3) { + unsigned n = ptr[0]; + if (n > max) + break; + android_memset16(bits, ptr[1], n << 1); + bits += n; + max -= n; + ptr += 2; + count -= 4; + } + + munmap(data, s.st_size); + fb_update(&fb); + fb_close(&fb); + close(fd); + unlink(fn); + return 0; + +fail_unmap_data: + munmap(data, s.st_size); +fail_close_file: + close(fd); +fail_restore_text: + vt_set_mode(0); + return -1; +} + diff --git a/init/parser.c b/init/parser.c new file mode 100644 index 0000000..95bf017 --- /dev/null +++ b/init/parser.c @@ -0,0 +1,755 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdarg.h> +#include <string.h> +#include <stddef.h> +#include <ctype.h> + +#include "init.h" +#include "property_service.h" + +#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_ +#include <sys/_system_properties.h> + +static list_declare(service_list); +static list_declare(action_list); +static list_declare(action_queue); + +#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 +} + +#define MAXARGS 64 + +#define T_EOF 0 +#define T_TEXT 1 +#define T_NEWLINE 2 + +struct parse_state +{ + char *ptr; + char *text; + int line; + int nexttoken; + void *context; + void (*parse_line)(struct parse_state *state, int nargs, char **args); + const char *filename; +}; + +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); + +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); + +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); + + va_start(ap, fmt); + vsnprintf(buf + off, 128 - off, fmt, ap); + va_end(ap); + buf[127] = 0; + ERROR("%s", buf); +} + +#define SECTION 0x01 +#define COMMAND 0x02 +#define OPTION 0x04 + +#include "keywords.h" + +#define KEYWORD(symbol, flags, nargs, func) \ + [ K_##symbol ] = { #symbol, func, nargs + 1, flags, }, + +struct { + const char *name; + int (*func)(int nargs, char **args); + unsigned char nargs; + unsigned char flags; +} keyword_info[KEYWORD_COUNT] = { + [ K_UNKNOWN ] = { "unknown", 0, 0, 0 }, +#include "keywords.h" +}; +#undef KEYWORD + +#define kw_is(kw, type) (keyword_info[kw].flags & (type)) +#define kw_name(kw) (keyword_info[kw].name) +#define kw_func(kw) (keyword_info[kw].func) +#define kw_nargs(kw) (keyword_info[kw].nargs) + +int lookup_keyword(const char *s) +{ + switch (*s++) { + case 'c': + if (!strcmp(s, "apability")) return K_capability; + 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; + if (!strcmp(s, "onsole")) return K_console; + if (!strcmp(s, "hown")) return K_chown; + if (!strcmp(s, "hmod")) return K_chmod; + if (!strcmp(s, "ritical")) return K_critical; + break; + case 'd': + if (!strcmp(s, "isabled")) return K_disabled; + if (!strcmp(s, "omainname")) return K_domainname; + if (!strcmp(s, "evice")) return K_device; + break; + case 'e': + if (!strcmp(s, "xec")) return K_exec; + if (!strcmp(s, "xport")) return K_export; + break; + case 'g': + if (!strcmp(s, "roup")) return K_group; + break; + case 'h': + if (!strcmp(s, "ostname")) return K_hostname; + break; + case 'i': + if (!strcmp(s, "fup")) return K_ifup; + if (!strcmp(s, "nsmod")) return K_insmod; + if (!strcmp(s, "mport")) return K_import; + break; + case 'l': + if (!strcmp(s, "oglevel")) return K_loglevel; + break; + case 'm': + if (!strcmp(s, "kdir")) return K_mkdir; + if (!strcmp(s, "ount")) return K_mount; + break; + case 'o': + if (!strcmp(s, "n")) return K_on; + if (!strcmp(s, "neshot")) return K_oneshot; + if (!strcmp(s, "nrestart")) return K_onrestart; + break; + case 'r': + if (!strcmp(s, "estart")) return K_restart; + break; + case 's': + if (!strcmp(s, "ervice")) return K_service; + 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, "ocket")) return K_socket; + if (!strcmp(s, "tart")) return K_start; + if (!strcmp(s, "top")) return K_stop; + if (!strcmp(s, "ymlink")) return K_symlink; + break; + case 't': + if (!strcmp(s, "rigger")) return K_trigger; + break; + case 'u': + if (!strcmp(s, "ser")) return K_user; + break; + case 'w': + if (!strcmp(s, "rite")) return K_write; + break; + } + return K_UNKNOWN; +} + +void parse_line_no_op(struct parse_state *state, int nargs, char **args) +{ +} + +int next_token(struct parse_state *state) +{ + char *x = state->ptr; + char *s; + + if (state->nexttoken) { + int t = state->nexttoken; + state->nexttoken = 0; + return t; + } + + for (;;) { + switch (*x) { + case 0: + state->ptr = x; + return T_EOF; + case '\n': + state->line++; + x++; + state->ptr = x; + return T_NEWLINE; + case ' ': + case '\t': + case '\r': + x++; + continue; + case '#': + while (*x && (*x != '\n')) x++; + state->line++; + state->ptr = x; + return T_NEWLINE; + default: + goto text; + } + } + +textdone: + state->ptr = x; + *s = 0; + return T_TEXT; +text: + state->text = s = x; +textresume: + for (;;) { + switch (*x) { + case 0: + goto textdone; + case ' ': + case '\t': + case '\r': + x++; + goto textdone; + case '\n': + state->nexttoken = T_NEWLINE; + x++; + goto textdone; + case '"': + x++; + for (;;) { + switch (*x) { + case 0: + /* unterminated quoted thing */ + state->ptr = x; + return T_EOF; + case '"': + x++; + goto textresume; + default: + *s++ = *x++; + } + } + break; + case '\\': + x++; + switch (*x) { + case 0: + goto textdone; + case 'n': + *s++ = '\n'; + break; + case 'r': + *s++ = '\r'; + break; + case 't': + *s++ = '\t'; + break; + case '\\': + *s++ = '\\'; + break; + case '\r': + /* \ <cr> <lf> -> line continuation */ + if (x[1] != '\n') { + x++; + continue; + } + case '\n': + /* \ <lf> -> line continuation */ + state->line++; + x++; + /* eat any extra whitespace */ + while((*x == ' ') || (*x == '\t')) x++; + continue; + default: + /* unknown escape -- just copy */ + *s++ = *x++; + } + continue; + default: + *s++ = *x++; + } + } + return T_EOF; +} + +void parse_line(int nargs, char **args) +{ + int n; + int id = lookup_keyword(args[0]); + printf("%s(%d)", args[0], id); + for (n = 1; n < nargs; n++) { + printf(" '%s'", args[n]); + } + printf("\n"); +} + +void parse_new_section(struct parse_state *state, int kw, + int nargs, char **args) +{ + printf("[ %s %s ]\n", args[0], + nargs > 1 ? args[1] : ""); + switch(kw) { + case K_service: + state->context = parse_service(state, nargs, args); + if (state->context) { + state->parse_line = parse_line_service; + return; + } + break; + case K_on: + state->context = parse_action(state, nargs, args); + if (state->context) { + state->parse_line = parse_line_action; + return; + } + break; + } + state->parse_line = parse_line_no_op; +} + +static void parse_config(const char *fn, char *s) +{ + struct parse_state state; + char *args[MAXARGS]; + int nargs; + + nargs = 0; + state.filename = fn; + state.line = 1; + state.ptr = s; + state.nexttoken = 0; + state.parse_line = parse_line_no_op; + for (;;) { + switch (next_token(&state)) { + case T_EOF: + state.parse_line(&state, 0, 0); + return; + case T_NEWLINE: + if (nargs) { + int kw = lookup_keyword(args[0]); + if (kw_is(kw, SECTION)) { + state.parse_line(&state, 0, 0); + parse_new_section(&state, kw, nargs, args); + } else { + state.parse_line(&state, nargs, args); + } + nargs = 0; + } + break; + case T_TEXT: + if (nargs < MAXARGS) { + args[nargs++] = state.text; + } + break; + } + } +} + +int parse_config_file(const char *fn) +{ + char *data; + data = read_file(fn, 0); + if (!data) return -1; + + parse_config(fn, data); + DUMP(); + return 0; +} + +static int valid_name(const char *name) +{ + if (strlen(name) > 16) { + return 0; + } + while (*name) { + if (!isalnum(*name) && (*name != '_') && (*name != '-')) { + return 0; + } + name++; + } + return 1; +} + +struct service *service_find_by_name(const char *name) +{ + struct listnode *node; + struct service *svc; + list_for_each(node, &service_list) { + svc = node_to_item(node, struct service, slist); + if (!strcmp(svc->name, name)) { + return svc; + } + } + return 0; +} + +struct service *service_find_by_pid(pid_t pid) +{ + struct listnode *node; + struct service *svc; + list_for_each(node, &service_list) { + svc = node_to_item(node, struct service, slist); + if (svc->pid == pid) { + return svc; + } + } + return 0; +} + +void service_for_each_class(const char *classname, + void (*func)(struct service *svc)) +{ + struct listnode *node; + struct service *svc; + list_for_each(node, &service_list) { + svc = node_to_item(node, struct service, slist); + if (!strcmp(svc->classname, classname)) { + func(svc); + } + } +} + +void service_for_each_flags(unsigned matchflags, + void (*func)(struct service *svc)) +{ + struct listnode *node; + struct service *svc; + list_for_each(node, &service_list) { + svc = node_to_item(node, struct service, slist); + if (svc->flags & matchflags) { + func(svc); + } + } +} + +void action_for_each_trigger(const char *trigger, + void (*func)(struct action *act)) +{ + struct listnode *node; + struct action *act; + list_for_each(node, &action_list) { + act = node_to_item(node, struct action, alist); + if (!strcmp(act->name, trigger)) { + func(act); + } + } +} + +void queue_property_triggers(const char *name, const char *value) +{ + 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:"))) { + 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)) { + 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]; + const char* value; + int length = equals - name; + if (length > PROP_NAME_MAX) { + ERROR("property name too long in trigger %s", act->name); + } else { + memcpy(prop_name, name, length); + prop_name[length] = 0; + + /* does the property exist, and match the trigger value? */ + value = property_get((const char *)&prop_name[0]); + if (value && !strcmp(equals + 1, value)) { + action_add_queue_tail(act); + } + } + } + } + } +} + +void action_add_queue_tail(struct action *act) +{ + list_add_tail(&action_queue, &act->qlist); +} + +struct action *action_remove_queue_head(void) +{ + if (list_empty(&action_queue)) { + return 0; + } else { + struct listnode *node = list_head(&action_queue); + struct action *act = node_to_item(node, struct action, qlist); + list_remove(node); + return act; + } +} + +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; + } + if (!valid_name(args[1])) { + parse_error(state, "invalid service name '%s'\n", args[1]); + return 0; + } + + svc = 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); + if (!svc) { + parse_error(state, "out of memory\n"); + return 0; + } + svc->name = args[1]; + svc->classname = "default"; + memcpy(svc->args, args + 2, sizeof(char*) * nargs); + svc->args[nargs] = 0; + svc->nargs = nargs; + svc->onrestart.name = "onrestart"; + list_init(&svc->onrestart.commands); + list_add_tail(&service_list, &svc->slist); + return svc; +} + +static void parse_line_service(struct parse_state *state, int nargs, char **args) +{ + struct service *svc = state->context; + struct command *cmd; + int kw, kw_nargs; + + if (nargs == 0) { + return; + } + + 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"); + } else { + svc->classname = args[1]; + } + break; + case K_console: + svc->flags |= SVC_CONSOLE; + break; + case K_disabled: + svc->flags |= SVC_DISABLED; + break; + case K_group: + if (nargs < 2) { + parse_error(state, "group option requires a group id\n"); + } else if (nargs > NR_SVC_SUPP_GIDS + 2) { + parse_error(state, "group option accepts at most %d supp. groups\n", + NR_SVC_SUPP_GIDS); + } else { + int n; + svc->gid = decode_uid(args[1]); + for (n = 2; n < nargs; n++) { + svc->supp_gids[n-2] = decode_uid(args[n]); + } + svc->nr_supp_gids = n - 2; + } + break; + case K_oneshot: + svc->flags |= SVC_ONESHOT; + break; + case K_onrestart: + nargs--; + args++; + kw = lookup_keyword(args[0]); + if (!kw_is(kw, COMMAND)) { + parse_error(state, "invalid command '%s'\n", args[0]); + break; + } + kw_nargs = kw_nargs(kw); + if (nargs < kw_nargs) { + parse_error(state, "%s requires %d %s\n", args[0], kw_nargs - 1, + kw_nargs > 2 ? "arguments" : "argument"); + break; + } + + cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs); + cmd->func = kw_func(kw); + cmd->nargs = nargs; + memcpy(cmd->args, args, sizeof(char*) * nargs); + list_add_tail(&svc->onrestart.commands, &cmd->clist); + break; + case K_critical: + svc->flags |= SVC_CRITICAL; + break; + case K_setenv: { /* name value */ + struct svcenvinfo *ei; + if (nargs < 2) { + parse_error(state, "setenv option requires name and value arguments\n"); + break; + } + ei = calloc(1, sizeof(*ei)); + if (!ei) { + parse_error(state, "out of memory\n"); + break; + } + ei->name = args[1]; + ei->value = args[2]; + ei->next = svc->envvars; + svc->envvars = ei; + break; + } + case K_socket: {/* name type perm [ uid gid ] */ + struct socketinfo *si; + if (nargs < 4) { + parse_error(state, "socket option requires name, type, perm arguments\n"); + break; + } + if (strcmp(args[2],"dgram") && strcmp(args[2],"stream")) { + parse_error(state, "socket type must be 'dgram' or 'stream'\n"); + break; + } + si = calloc(1, sizeof(*si)); + if (!si) { + parse_error(state, "out of memory\n"); + break; + } + si->name = args[1]; + si->type = args[2]; + si->perm = strtoul(args[3], 0, 8); + if (nargs > 4) + si->uid = decode_uid(args[4]); + if (nargs > 5) + si->gid = decode_uid(args[5]); + si->next = svc->sockets; + svc->sockets = si; + break; + } + case K_user: + if (nargs != 2) { + parse_error(state, "user option requires a user id\n"); + } else { + svc->uid = decode_uid(args[1]); + } + break; + default: + parse_error(state, "invalid option '%s'\n", args[0]); + } +} + +static void *parse_action(struct parse_state *state, int nargs, char **args) +{ + struct action *act; + 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; + } + act = calloc(1, sizeof(*act)); + act->name = args[1]; + list_init(&act->commands); + list_add_tail(&action_list, &act->alist); + /* XXX add to hash */ + return act; +} + +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); + int kw, n; + + if (nargs == 0) { + return; + } + + kw = lookup_keyword(args[0]); + if (!kw_is(kw, COMMAND)) { + parse_error(state, "invalid command '%s'\n", args[0]); + return; + } + + n = kw_nargs(kw); + if (nargs < n) { + parse_error(state, "%s requires %d %s\n", args[0], n - 1, + n > 2 ? "arguments" : "argument"); + return; + } + cmd = malloc(sizeof(*cmd) + sizeof(char*) * nargs); + cmd->func = kw_func(kw); + cmd->nargs = nargs; + memcpy(cmd->args, args, sizeof(char*) * nargs); + list_add_tail(&act->commands, &cmd->clist); +} diff --git a/init/property_service.c b/init/property_service.c new file mode 100644 index 0000000..0bc403f --- /dev/null +++ b/init/property_service.c @@ -0,0 +1,502 @@ +/* + * Copyright (C) 2007 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 <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <fcntl.h> +#include <stdarg.h> +#include <dirent.h> +#include <limits.h> +#include <errno.h> + +#include <cutils/misc.h> +#include <cutils/sockets.h> +#include <cutils/ashmem.h> + +#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_ +#include <sys/_system_properties.h> + +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/select.h> +#include <sys/types.h> +#include <netinet/in.h> +#include <sys/mman.h> +#include <sys/atomics.h> +#include <private/android_filesystem_config.h> + +#include "property_service.h" +#include "init.h" + +#define PERSISTENT_PROPERTY_DIR "/data/property" + +static int persistent_properties_loaded = 0; + +/* White list of permissions for setting property services. */ +struct { + const char *prefix; + unsigned int uid; +} property_perms[] = { + { "net.rmnet0.", AID_RADIO }, + { "net.gprs.", AID_RADIO }, + { "ril.", AID_RADIO }, + { "gsm.", AID_RADIO }, + { "net.dns", AID_RADIO }, + { "net.", AID_SYSTEM }, + { "dev.", AID_SYSTEM }, + { "runtime.", AID_SYSTEM }, + { "hw.", AID_SYSTEM }, + { "sys.", AID_SYSTEM }, + { "service.", AID_SYSTEM }, + { "wlan.", AID_SYSTEM }, + { "dhcp.", AID_SYSTEM }, + { "dhcp.", AID_DHCP }, + { "debug.", AID_SHELL }, + { "log.", AID_SHELL }, + { "persist.sys.", AID_SYSTEM }, + { "persist.service.", AID_SYSTEM }, + { NULL, 0 } +}; + +/* + * White list of UID that are allowed to start/stop services. + * Currently there are no user apps that require. + */ +struct { + const char *service; + unsigned int uid; +} control_perms[] = { + {NULL, 0 } +}; + +typedef struct { + void *data; + size_t size; + int fd; +} workspace; + +static int init_workspace(workspace *w, size_t size) +{ + void *data; + int fd; + + fd = ashmem_create_region("system_properties", size); + if(fd < 0) + return -1; + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if(data == MAP_FAILED) + goto out; + + /* allow the wolves we share with to do nothing but read */ + ashmem_set_prot_region(fd, PROT_READ); + + w->data = data; + w->size = size; + w->fd = fd; + + return 0; + +out: + close(fd); + return -1; +} + +/* (8 header words + 247 toc words) = 1020 bytes */ +/* 1024 bytes header and toc + 247 prop_infos @ 128 bytes = 32640 bytes */ + +#define PA_COUNT_MAX 247 +#define PA_INFO_START 1024 +#define PA_SIZE 32768 + +static workspace pa_workspace; +static prop_info *pa_info_array; + +extern prop_area *__system_property_area__; + +static int init_property_area(void) +{ + prop_area *pa; + + if(pa_info_array) + return -1; + + if(init_workspace(&pa_workspace, PA_SIZE)) + return -1; + + fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC); + + pa_info_array = (void*) (((char*) pa_workspace.data) + PA_INFO_START); + + pa = pa_workspace.data; + memset(pa, 0, PA_SIZE); + pa->magic = PROP_AREA_MAGIC; + pa->version = PROP_AREA_VERSION; + + /* plug into the lib property services */ + __system_property_area__ = pa; + + return 0; +} + +static void update_prop_info(prop_info *pi, const char *value, unsigned len) +{ + pi->serial = pi->serial | 1; + memcpy(pi->value, value, len + 1); + pi->serial = (len << 24) | ((pi->serial + 1) & 0xffffff); + __futex_wake(&pi->serial, INT32_MAX); +} + +static int property_write(prop_info *pi, const char *value) +{ + int valuelen = strlen(value); + if(valuelen >= PROP_VALUE_MAX) return -1; + update_prop_info(pi, value, valuelen); + return 0; +} + + +/* + * Checks permissions for starting/stoping system services. + * AID_SYSTEM and AID_ROOT are always allowed. + * + * Returns 1 if uid allowed, 0 otherwise. + */ +static int check_control_perms(const char *name, int uid) { + int i; + if (uid == AID_SYSTEM || uid == AID_ROOT) + return 1; + + /* Search the ACL */ + for (i = 0; control_perms[i].service; i++) { + if (strcmp(control_perms[i].service, name) == 0) { + if (control_perms[i].uid == uid) + return 1; + } + } + return 0; +} + +/* + * Checks permissions for setting system properties. + * Returns 1 if uid allowed, 0 otherwise. + */ +static int check_perms(const char *name, unsigned int uid) +{ + int i; + if (uid == 0) + return 1; + + if(!strncmp(name, "ro.", 3)) + name +=3; + + for (i = 0; property_perms[i].prefix; i++) { + int tmp; + if (strncmp(property_perms[i].prefix, name, + strlen(property_perms[i].prefix)) == 0) { + if (property_perms[i].uid == uid) { + return 1; + } + } + } + + return 0; +} + +const char* property_get(const char *name) +{ + prop_info *pi; + + if(strlen(name) >= PROP_NAME_MAX) return 0; + + pi = (prop_info*) __system_property_find(name); + + if(pi != 0) { + return pi->value; + } else { + return 0; + } +} + +static void write_peristent_property(const char *name, const char *value) +{ + const char *tempPath = PERSISTENT_PROPERTY_DIR "/.temp"; + char path[PATH_MAX]; + int fd, length; + + snprintf(path, sizeof(path), "%s/%s", PERSISTENT_PROPERTY_DIR, name); + + fd = open(tempPath, O_WRONLY|O_CREAT|O_TRUNC, 0600); + if (fd < 0) { + ERROR("Unable to write persistent property to temp file %s errno: %d\n", tempPath, errno); + return; + } + write(fd, value, strlen(value)); + close(fd); + + if (rename(tempPath, path)) { + unlink(tempPath); + ERROR("Unable to rename persistent property file %s to %s\n", tempPath, path); + } +} + +int property_set(const char *name, const char *value) +{ + prop_area *pa; + prop_info *pi; + + int namelen = strlen(name); + int valuelen = strlen(value); + + if(namelen >= PROP_NAME_MAX) return -1; + if(valuelen >= PROP_VALUE_MAX) return -1; + if(namelen < 1) return -1; + + pi = (prop_info*) __system_property_find(name); + + if(pi != 0) { + /* ro.* properties may NEVER be modified once set */ + if(!strncmp(name, "ro.", 3)) return -1; + + pa = __system_property_area__; + update_prop_info(pi, value, valuelen); + pa->serial++; + __futex_wake(&pa->serial, INT32_MAX); + } else { + pa = __system_property_area__; + if(pa->count == PA_COUNT_MAX) return -1; + + pi = pa_info_array + pa->count; + pi->serial = (valuelen << 24); + memcpy(pi->name, name, namelen + 1); + memcpy(pi->value, value, valuelen + 1); + + pa->toc[pa->count] = + (namelen << 24) | (((unsigned) pi) - ((unsigned) pa)); + + pa->count++; + pa->serial++; + __futex_wake(&pa->serial, INT32_MAX); + } + /* If name starts with "net." treat as a DNS property. */ + if (strncmp("net.", name, sizeof("net.") - 1) == 0) { + if (strcmp("net.change", name) == 0) { + return 0; + } + /* + * The 'net.change' property is a special property used track when any + * 'net.*' property name is updated. It is _ONLY_ updated here. Its value + * contains the last updated 'net.*' property. + */ + property_set("net.change", name); + } else if (persistent_properties_loaded && + strncmp("persist.", name, sizeof("persist.") - 1) == 0) { + /* + * Don't write properties to disk until after we have read all default properties + * to prevent them from being overwritten by default values. + */ + write_peristent_property(name, value); + } + property_changed(name, value); + return 0; +} + +static int property_list(void (*propfn)(const char *key, const char *value, void *cookie), + void *cookie) +{ + char name[PROP_NAME_MAX]; + char value[PROP_VALUE_MAX]; + const prop_info *pi; + unsigned n; + + for(n = 0; (pi = __system_property_find_nth(n)); n++) { + __system_property_read(pi, name, value); + propfn(name, value, cookie); + } + return 0; +} + +void handle_property_set_fd(int fd) +{ + prop_msg msg; + int s; + int r; + int res; + struct ucred cr; + struct sockaddr_un addr; + socklen_t addr_size = sizeof(addr); + socklen_t cr_size = sizeof(cr); + + if ((s = accept(fd, &addr, &addr_size)) < 0) { + return; + } + + /* Check socket options here */ + if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) { + close(s); + ERROR("Unable to recieve socket options\n"); + return; + } + + r = recv(s, &msg, sizeof(msg), 0); + close(s); + if(r != sizeof(prop_msg)) { + ERROR("sys_prop: mis-match msg size recieved: %d expected: %d\n", + r, sizeof(prop_msg)); + return; + } + + switch(msg.cmd) { + case PROP_MSG_SETPROP: + msg.name[PROP_NAME_MAX-1] = 0; + msg.value[PROP_VALUE_MAX-1] = 0; + + if(memcmp(msg.name,"ctl.",4) == 0) { + if (check_control_perms(msg.value, cr.uid)) { + handle_control_message((char*) msg.name + 4, (char*) msg.value); + } else { + ERROR("sys_prop: Unable to %s service ctl [%s] uid: %d pid:%d\n", + msg.name + 4, msg.value, cr.uid, cr.pid); + } + } else { + if (check_perms(msg.name, cr.uid)) { + property_set((char*) msg.name, (char*) msg.value); + } else { + ERROR("sys_prop: permission denied uid:%d name:%s\n", + cr.uid, msg.name); + } + } + break; + + default: + break; + } +} + +void get_property_workspace(int *fd, int *sz) +{ + *fd = pa_workspace.fd; + *sz = pa_workspace.size; +} + +static void load_properties(char *data) +{ + char *key, *value, *eol, *sol, *tmp; + + sol = data; + while((eol = strchr(sol, '\n'))) { + key = sol; + *eol++ = 0; + sol = eol; + + value = strchr(key, '='); + if(value == 0) continue; + *value++ = 0; + + while(isspace(*key)) key++; + if(*key == '#') continue; + tmp = value - 2; + while((tmp > key) && isspace(*tmp)) *tmp-- = 0; + + while(isspace(*value)) value++; + tmp = eol - 2; + while((tmp > value) && isspace(*tmp)) *tmp-- = 0; + + property_set(key, value); + } +} + +static void load_properties_from_file(const char *fn) +{ + char *data; + unsigned sz; + + data = read_file(fn, &sz); + + if(data != 0) { + load_properties(data); + free(data); + } +} + +static void load_persistent_properties() +{ + DIR* dir = opendir(PERSISTENT_PROPERTY_DIR); + struct dirent* entry; + char path[PATH_MAX]; + char value[PROP_VALUE_MAX]; + int fd, length; + + if (dir) { + while ((entry = readdir(dir)) != NULL) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..") || + strncmp("persist.", entry->d_name, sizeof("persist.") - 1)) + continue; +#if HAVE_DIRENT_D_TYPE + if (entry->d_type != DT_REG) + continue; +#endif + /* open the file and read the property value */ + snprintf(path, sizeof(path), "%s/%s", PERSISTENT_PROPERTY_DIR, entry->d_name); + fd = open(path, O_RDONLY); + if (fd >= 0) { + 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", path, errno); + } + close(fd); + } else { + ERROR("Unable to open persistent property file %s errno: %d\n", path, errno); + } + } + closedir(dir); + } else { + ERROR("Unable to open persistent property directory %s errno: %d\n", PERSISTENT_PROPERTY_DIR, errno); + } + + persistent_properties_loaded = 1; +} + +void property_init(void) +{ + init_property_area(); + load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT); +} + +int start_property_service(void) +{ + int fd; + + load_properties_from_file(PROP_PATH_SYSTEM_BUILD); + load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT); + load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE); + /* Read persistent properties after all default values have been loaded. */ + load_persistent_properties(); + + fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0); + if(fd < 0) return -1; + fcntl(fd, F_SETFD, FD_CLOEXEC); + fcntl(fd, F_SETFL, O_NONBLOCK); + + listen(fd, 8); + return fd; +} diff --git a/init/property_service.h b/init/property_service.h new file mode 100644 index 0000000..d12f1f3 --- /dev/null +++ b/init/property_service.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2007 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. + */ + +#ifndef _INIT_PROPERTY_H +#define _INIT_PROPERTY_H + +extern void handle_property_fd(int fd); +extern void handle_property_set_fd(int fd); +extern void property_init(void); +extern int start_property_service(void); +void get_property_workspace(int *fd, int *sz); +extern const char* property_get(const char *name); +extern int property_set(const char *name, const char *value); + +#endif /* _INIT_PROPERTY_H */ diff --git a/init/readme.txt b/init/readme.txt new file mode 100644 index 0000000..360a1b7 --- /dev/null +++ b/init/readme.txt @@ -0,0 +1,290 @@ + +Android Init Language +--------------------- + +The Android Init Language consists of four broad classes of statements, +which are Actions, Commands, Services, and Options. + +All of these are line-oriented, consisting of tokens separated by +whitespace. The c-style backslash escapes may be used to insert +whitespace into a token. Double quotes may also be used to prevent +whitespace from breaking text into multiple tokens. The backslash, +when it is the last character on a line, may be used for line-folding. + +Lines which start with a # (leading whitespace allowed) are comments. + +Actions and Services implicitly declare a new section. All commands +or options belong to the section most recently declared. Commands +or options before the first section are ignored. + +Actions and Services have unique names. If a second Action or Service +is declared with the same name as an existing one, it is ignored as +an error. (??? should we override instead) + + +Actions +------- +Actions are named sequences of commands. Actions have a trigger which +is used to determine when the action should occur. When an event +occurs which matches an action's trigger, that action is added to +the tail of a to-be-executed queue (unless it is already on the +queue). + +Each action in the queue is dequeued in sequence and each command in +that action is executed in sequence. Init handles other activities +(device creation/destruction, property setting, process restarting) +"between" the execution of the commands in activities. + +Actions take the form of: + +on <trigger> + <command> + <command> + <command> + + +Services +-------- +Services are programs which init launches and (optionally) restarts +when they exit. Services take the form of: + +service <name> <pathname> [ <argument> ]* + <option> + <option> + ... + + +Options +------- +Options are modifiers to services. They affect how and when init +runs the service. + +critical + This is a device-critical service. If it exits more than four times in + four minutes, the device will reboot into recovery mode. + +disabled + This service will not automatically start with its class. + It must be explicitly started by name. + +setenv <name> <value> + Set the environment variable <name> to <value> in the launched process. + +socket <name> <type> <perm> [ <user> [ <group> ] ] + Create a unix domain socket named /dev/socket/<name> and pass + its fd to the launched process. <type> must be "dgram" or "stream". + User and group default to 0. + +user <username> + Change to username before exec'ing this service. + Currently defaults to root. (??? probably should default to nobody) + Currently, if your process requires linux capabilities then you cannot use + this command. You must instead request the capabilities in-process while + still root, and then drop to your desired uid. + +group <groupname> [ <groupname> ]* + Change to groupname before exec'ing this service. Additional + groupnames beyond the (required) first one are used to set the + supplemental groups of the process (via setgroups()). + Currently defaults to root. (??? probably should default to nobody) + +oneshot + Do not restart the service when it exits. + +class <name> + Specify a class name for the service. All services in a + named class may be started or stopped together. A service + is in the class "default" if one is not specified via the + class option. + +onrestart + Execute a Command (see below) when service restarts. + +Triggers +-------- + Triggers are strings which can be used to match certain kinds + of events and used to cause an action to occur. + +boot + This is the first trigger that will occur when init starts + (after /init.conf is loaded) + +<name>=<value> + 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. + +service-exited-<name> + Triggers of this form occur when the specified service exits. + + +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. + +chmod <octal-mode> <path> + Change file access permissions. + +chown <owner> <group> <path> + Change file owner and group. + +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 + currently running. + +domainname <name> + Set the domain name. + +insmod <path> + Install the module at <path> + +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. + +mount <type> <device> <dir> [ <mountoption> ]* + 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", ... + +setkey + TBD + +setprop <name> <value> + Set system property <name> to <value>. + +setrlimit <resource> <cur> <max> + Set the rlimit for a resource. + +start <service> + Start a service running if it is not already running. + +stop <service> + Stop a service from running if it is currently running. + +symlink <target> <path> + Create a symbolic link at <path> with the value <target> + +trigger <event> + Trigger an event. Used to queue an action from another + action. + +write <path> <string> [ <string> ]* + Open the file at <path> and write one or more strings + to it with write(2) + + +Properties +---------- +Init updates some system properties to provide some insight into +what it's doing: + +init.action + Equal to the name of the action currently being executed or "" if none + +init.command + Equal to the command being executed or "" if none. + +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 + + ifup lo + + hostname localhost + domainname localhost + + mount yaffs2 mtd@system /system + mount yaffs2 mtd@userdata /data + + import /system/etc/init.conf + + class_start default + +service adbd /sbin/adbd + user adb + group adb + +service usbd /system/bin/usbd -r + user usbd + group usbd + socket usbd 666 + +service zygote /system/bin/app_process -Xzygote /system/bin --zygote + socket zygote 666 + +service runtime /system/bin/runtime + user system + group system + +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 +--------------- +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 logging system (accessed via logcat). + +For example +service akmd /system/bin/logwrapper /sbin/akmd diff --git a/init/util.c b/init/util.c new file mode 100644 index 0000000..0b7667d --- /dev/null +++ b/init/util.c @@ -0,0 +1,211 @@ +/* + * 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 <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <ctype.h> +#include <errno.h> + +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> + +/* for ANDROID_SOCKET_* */ +#include <cutils/sockets.h> + +#include <private/android_filesystem_config.h> + +#include "init.h" + +static int log_fd = -1; +/* Inital log level before init.rc is parsed and this this is reset. */ +static int log_level = LOG_DEFAULT_LEVEL; + + +void log_set_level(int level) { + log_level = level; +} + +void log_init(void) +{ + static const char *name = "/dev/__kmsg__"; + if (mknod(name, S_IFCHR | 0600, (1 << 8) | 11) == 0) { + log_fd = open(name, O_WRONLY); + fcntl(log_fd, F_SETFD, FD_CLOEXEC); + unlink(name); + } +} + +#define LOG_BUF_MAX 512 + +void log_write(int level, const char *fmt, ...) +{ + char buf[LOG_BUF_MAX]; + va_list ap; + + if (level > log_level) return; + if (log_fd < 0) return; + + va_start(ap, fmt); + vsnprintf(buf, LOG_BUF_MAX, fmt, ap); + buf[LOG_BUF_MAX - 1] = 0; + va_end(ap); + write(log_fd, buf, strlen(buf)); +} + +/* + * android_name_to_id - returns the integer uid/gid associated with the given + * name, or -1U on error. + */ +static unsigned int android_name_to_id(const char *name) +{ + struct android_id_info *info = android_ids; + unsigned int n; + + for (n = 0; n < android_id_count; n++) { + if (!strcmp(info[n].name, name)) + return info[n].aid; + } + + return -1U; +} + +/* + * decode_uid - decodes and returns the given string, which can be either the + * numeric or name representation, into the integer uid or gid. Returns -1U on + * error. + */ +unsigned int decode_uid(const char *s) +{ + unsigned int v; + + if (!s || *s == '\0') + return -1U; + if (isalpha(s[0])) + return android_name_to_id(s); + + errno = 0; + v = (unsigned int) strtoul(s, 0, 0); + if (errno) + return -1U; + return v; +} + +/* + * create_socket - creates a Unix domain socket in ANDROID_SOCKET_DIR + * ("/dev/socket") as dictated in init.rc. This socket is inherited by the + * daemon. We communicate the file descriptor's value via the environment + * variable ANDROID_SOCKET_ENV_PREFIX<name> ("ANDROID_SOCKET_foo"). + */ +int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid) +{ + struct sockaddr_un addr; + int fd, ret; + + fd = socket(PF_UNIX, type, 0); + if (fd < 0) { + ERROR("Failed to open socket '%s': %s\n", name, strerror(errno)); + return -1; + } + + memset(&addr, 0 , sizeof(addr)); + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s", + name); + + ret = unlink(addr.sun_path); + if (ret != 0 && errno != ENOENT) { + ERROR("Failed to unlink old socket '%s': %s\n", name, strerror(errno)); + goto out_close; + } + + ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr)); + if (ret) { + ERROR("Failed to bind socket '%s': %s\n", name, strerror(errno)); + goto out_unlink; + } + + chown(addr.sun_path, uid, gid); + chmod(addr.sun_path, perm); + + INFO("Created socket '%s' with mode '%o', user '%d', group '%d'\n", + addr.sun_path, perm, uid, gid); + + return fd; + +out_unlink: + unlink(addr.sun_path); +out_close: + close(fd); + 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; + + data = 0; + fd = open(fn, O_RDONLY); + if(fd < 0) return 0; + + 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; + +oops: + close(fd); + if(data != 0) free(data); + return 0; +} + +void list_init(struct listnode *node) +{ + node->next = node; + node->prev = node; +} + +void list_add_tail(struct listnode *head, struct listnode *item) +{ + item->next = head; + item->prev = head->prev; + head->prev->next = item; + head->prev = item; +} + +void list_remove(struct listnode *item) +{ + item->next->prev = item->prev; + item->prev->next = item->next; +} + |