Merge "Implement exec."

This commit is contained in:
Elliott Hughes 2015-03-11 18:20:28 +00:00 committed by Gerrit Code Review
commit a4d98484a2
10 changed files with 392 additions and 174 deletions

View file

@ -75,6 +75,7 @@ include $(BUILD_EXECUTABLE)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := init_tests LOCAL_MODULE := init_tests
LOCAL_SRC_FILES := \ LOCAL_SRC_FILES := \
init_parser_test.cpp \
util_test.cpp \ util_test.cpp \
LOCAL_SHARED_LIBRARIES += \ LOCAL_SHARED_LIBRARIES += \

View file

@ -172,11 +172,16 @@ int do_enable(int nargs, char **args)
return 0; return 0;
} }
int do_exec(int nargs, char **args) int do_exec(int nargs, char** args) {
{ service* svc = make_exec_oneshot_service(nargs, args);
return -1; if (svc == NULL) {
return -1;
}
service_start(svc, NULL);
return 0;
} }
// TODO: remove execonce when exec is available.
int do_execonce(int nargs, char **args) int do_execonce(int nargs, char **args)
{ {
pid_t child; pid_t child;

View file

@ -14,37 +14,37 @@
* limitations under the License. * limitations under the License.
*/ */
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.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/mount.h>
#include <sys/stat.h>
#include <sys/poll.h> #include <sys/poll.h>
#include <errno.h>
#include <stdarg.h>
#include <mtd/mtd-user.h>
#include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h> #include <sys/un.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#include <mtd/mtd-user.h>
#include <selinux/selinux.h> #include <selinux/selinux.h>
#include <selinux/label.h> #include <selinux/label.h>
#include <selinux/android.h> #include <selinux/android.h>
#include <libgen.h>
#include <cutils/list.h>
#include <cutils/android_reboot.h> #include <cutils/android_reboot.h>
#include <cutils/sockets.h>
#include <cutils/iosched_policy.h>
#include <cutils/fs.h> #include <cutils/fs.h>
#include <cutils/iosched_policy.h>
#include <cutils/list.h>
#include <cutils/sockets.h>
#include <private/android_filesystem_config.h> #include <private/android_filesystem_config.h>
#include <termios.h>
#include "devices.h" #include "devices.h"
#include "init.h" #include "init.h"
@ -72,22 +72,35 @@ static char qemu[32];
static struct action *cur_action = NULL; static struct action *cur_action = NULL;
static struct command *cur_command = NULL; static struct command *cur_command = NULL;
void notify_service_state(const char *name, const char *state)
{
char pname[PROP_NAME_MAX];
int len = strlen(name);
if ((len + 10) > PROP_NAME_MAX)
return;
snprintf(pname, sizeof(pname), "init.svc.%s", name);
property_set(pname, state);
}
static int have_console; static int have_console;
static char console_name[PROP_VALUE_MAX] = "/dev/console"; static char console_name[PROP_VALUE_MAX] = "/dev/console";
static time_t process_needs_restart; static time_t process_needs_restart;
static const char *ENV[32]; static const char *ENV[32];
bool waiting_for_exec = false;
void service::NotifyStateChange(const char* new_state) {
if (!properties_inited()) {
// If properties aren't available yet, we can't set them.
return;
}
if ((flags & SVC_EXEC) != 0) {
// 'exec' commands don't have properties tracking their state.
return;
}
char prop_name[PROP_NAME_MAX];
if (snprintf(prop_name, sizeof(prop_name), "init.svc.%s", name) >= PROP_NAME_MAX) {
// If the property name would be too long, we can't set it.
ERROR("Property name \"init.svc.%s\" too long; not setting to %s\n", name, new_state);
return;
}
property_set(prop_name, new_state);
}
/* add_environment - add "key=value" to the current environment */ /* add_environment - add "key=value" to the current environment */
int add_environment(const char *key, const char *val) int add_environment(const char *key, const char *val)
{ {
@ -160,35 +173,26 @@ static void publish_socket(const char *name, int fd)
void service_start(struct service *svc, const char *dynamic_args) void service_start(struct service *svc, const char *dynamic_args)
{ {
struct stat s; // Starting a service removes it from the disabled or reset state and
pid_t pid; // immediately takes it out of the restarting state if it was in there.
int needs_console;
char *scon = NULL;
int rc;
/* starting a service removes it from the disabled or reset
* state and immediately takes it out of the restarting
* state if it was in there
*/
svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START)); svc->flags &= (~(SVC_DISABLED|SVC_RESTARTING|SVC_RESET|SVC_RESTART|SVC_DISABLED_START));
svc->time_started = 0; svc->time_started = 0;
/* running processes require no additional work -- if // Running processes require no additional work --- if they're in the
* they're in the process of exiting, we've ensured // process of exiting, we've ensured that they will immediately restart
* that they will immediately restart on exit, unless // on exit, unless they are ONESHOT.
* they are ONESHOT
*/
if (svc->flags & SVC_RUNNING) { if (svc->flags & SVC_RUNNING) {
return; return;
} }
needs_console = (svc->flags & SVC_CONSOLE) ? 1 : 0; bool needs_console = (svc->flags & SVC_CONSOLE);
if (needs_console && (!have_console)) { if (needs_console && !have_console) {
ERROR("service '%s' requires console\n", svc->name); ERROR("service '%s' requires console\n", svc->name);
svc->flags |= SVC_DISABLED; svc->flags |= SVC_DISABLED;
return; return;
} }
struct stat s;
if (stat(svc->args[0], &s) != 0) { if (stat(svc->args[0], &s) != 0) {
ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name); ERROR("cannot find '%s', disabling '%s'\n", svc->args[0], svc->name);
svc->flags |= SVC_DISABLED; svc->flags |= SVC_DISABLED;
@ -202,6 +206,7 @@ void service_start(struct service *svc, const char *dynamic_args)
return; return;
} }
char* scon = NULL;
if (is_selinux_enabled() > 0) { if (is_selinux_enabled() > 0) {
if (svc->seclabel) { if (svc->seclabel) {
scon = strdup(svc->seclabel); scon = strdup(svc->seclabel);
@ -213,7 +218,7 @@ void service_start(struct service *svc, const char *dynamic_args)
char *mycon = NULL, *fcon = NULL; char *mycon = NULL, *fcon = NULL;
INFO("computing context for service '%s'\n", svc->args[0]); INFO("computing context for service '%s'\n", svc->args[0]);
rc = getcon(&mycon); int rc = getcon(&mycon);
if (rc < 0) { if (rc < 0) {
ERROR("could not get context while starting '%s'\n", svc->name); ERROR("could not get context while starting '%s'\n", svc->name);
return; return;
@ -241,8 +246,7 @@ void service_start(struct service *svc, const char *dynamic_args)
NOTICE("starting '%s'\n", svc->name); NOTICE("starting '%s'\n", svc->name);
pid = fork(); pid_t pid = fork();
if (pid == 0) { if (pid == 0) {
struct socketinfo *si; struct socketinfo *si;
struct svcenvinfo *ei; struct svcenvinfo *ei;
@ -298,7 +302,7 @@ void service_start(struct service *svc, const char *dynamic_args)
setpgid(0, getpid()); setpgid(0, getpid());
/* as requested, set our gid, supplemental gids, and uid */ // As requested, set our gid, supplemental gids, and uid.
if (svc->gid) { if (svc->gid) {
if (setgid(svc->gid) != 0) { if (setgid(svc->gid) != 0) {
ERROR("setgid failed: %s\n", strerror(errno)); ERROR("setgid failed: %s\n", strerror(errno));
@ -361,8 +365,13 @@ void service_start(struct service *svc, const char *dynamic_args)
svc->pid = pid; svc->pid = pid;
svc->flags |= SVC_RUNNING; svc->flags |= SVC_RUNNING;
if (properties_inited()) if ((svc->flags & SVC_EXEC) != 0) {
notify_service_state(svc->name, "running"); INFO("SVC_EXEC pid %d (uid %d gid %d+%d context %s) started; waiting...\n",
svc->pid, svc->uid, svc->gid, svc->nr_supp_gids, svc->seclabel);
waiting_for_exec = true;
}
svc->NotifyStateChange("running");
} }
/* The how field should be either SVC_DISABLED, SVC_RESET, or SVC_RESTART */ /* The how field should be either SVC_DISABLED, SVC_RESET, or SVC_RESTART */
@ -388,9 +397,9 @@ static void service_stop_or_reset(struct service *svc, int how)
if (svc->pid) { if (svc->pid) {
NOTICE("service '%s' is being killed\n", svc->name); NOTICE("service '%s' is being killed\n", svc->name);
kill(-svc->pid, SIGKILL); kill(-svc->pid, SIGKILL);
notify_service_state(svc->name, "stopping"); svc->NotifyStateChange("stopping");
} else { } else {
notify_service_state(svc->name, "stopped"); svc->NotifyStateChange("stopped");
} }
} }
@ -969,28 +978,18 @@ static void selinux_initialize(void)
security_setenforce(is_enforcing); security_setenforce(is_enforcing);
} }
int main(int argc, char **argv) int main(int argc, char** argv) {
{
size_t fd_count = 0;
struct pollfd ufds[4];
int property_set_fd_init = 0;
int signal_fd_init = 0;
int keychord_fd_init = 0;
bool is_charger = false;
if (!strcmp(basename(argv[0]), "ueventd")) if (!strcmp(basename(argv[0]), "ueventd"))
return ueventd_main(argc, argv); return ueventd_main(argc, argv);
if (!strcmp(basename(argv[0]), "watchdogd")) if (!strcmp(basename(argv[0]), "watchdogd"))
return watchdogd_main(argc, argv); return watchdogd_main(argc, argv);
/* clear the umask */ // Clear the umask.
umask(0); umask(0);
/* Get the basic filesystem setup we need put // Get the basic filesystem setup we need put together in the initramdisk
* together in the initramdisk on / and then we'll // on / and then we'll let the rc file figure out the rest.
* let the rc file figure out the rest.
*/
mkdir("/dev", 0755); mkdir("/dev", 0755);
mkdir("/proc", 0755); mkdir("/proc", 0755);
mkdir("/sys", 0755); mkdir("/sys", 0755);
@ -1002,15 +1001,13 @@ int main(int argc, char **argv)
mount("proc", "/proc", "proc", 0, NULL); mount("proc", "/proc", "proc", 0, NULL);
mount("sysfs", "/sys", "sysfs", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL);
/* indicate that booting is in progress to background fw loaders, etc */ // Indicate that booting is in progress to background fw loaders, etc.
close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000)); close(open("/dev/.booting", O_WRONLY | O_CREAT | O_CLOEXEC, 0000));
/* We must have some place other than / to create the // We must have some place other than / to create the device nodes for
* device nodes for kmsg and null, otherwise we won't // kmsg and null, otherwise we won't be able to remount / read-only
* be able to remount / read-only later on. // later on. Now that tmpfs is mounted on /dev, we can actually talk
* Now that tmpfs is mounted on /dev, we can actually // to the outside world.
* talk to the outside world.
*/
open_devnull_stdio(); open_devnull_stdio();
klog_init(); klog_init();
property_init(); property_init();
@ -1019,25 +1016,22 @@ int main(int argc, char **argv)
process_kernel_cmdline(); process_kernel_cmdline();
union selinux_callback cb; selinux_callback cb;
cb.func_log = log_callback; cb.func_log = log_callback;
selinux_set_callback(SELINUX_CB_LOG, cb); selinux_set_callback(SELINUX_CB_LOG, cb);
cb.func_audit = audit_callback; cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb); selinux_set_callback(SELINUX_CB_AUDIT, cb);
selinux_initialize(); selinux_initialize();
/* These directories were necessarily created before initial policy load
* and therefore need their security context restored to the proper value. // These directories were necessarily created before initial policy load
* This must happen before /dev is populated by ueventd. // and therefore need their security context restored to the proper value.
*/ // This must happen before /dev is populated by ueventd.
restorecon("/dev"); restorecon("/dev");
restorecon("/dev/socket"); restorecon("/dev/socket");
restorecon("/dev/__properties__"); restorecon("/dev/__properties__");
restorecon_recursive("/sys"); restorecon_recursive("/sys");
is_charger = !strcmp(bootmode, "charger");
INFO("property init\n"); INFO("property init\n");
property_load_boot_defaults(); property_load_boot_defaults();
@ -1050,50 +1044,58 @@ int main(int argc, char **argv)
queue_builtin_action(keychord_init_action, "keychord_init"); queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init"); queue_builtin_action(console_init_action, "console_init");
/* execute all the boot actions to get us started */ // Execute all the boot actions to get us started.
action_for_each_trigger("init", action_add_queue_tail); action_for_each_trigger("init", action_add_queue_tail);
/* Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random
* wasn't ready immediately after wait_for_coldboot_done // wasn't ready immediately after wait_for_coldboot_done
*/
queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); queue_builtin_action(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng");
queue_builtin_action(property_service_init_action, "property_service_init"); queue_builtin_action(property_service_init_action, "property_service_init");
queue_builtin_action(signal_init_action, "signal_init"); queue_builtin_action(signal_init_action, "signal_init");
/* Don't mount filesystems or start core system services if in charger mode. */ // Don't mount filesystems or start core system services in charger mode.
if (is_charger) { if (strcmp(bootmode, "charger") == 0) {
action_for_each_trigger("charger", action_add_queue_tail); action_for_each_trigger("charger", action_add_queue_tail);
} else { } else {
action_for_each_trigger("late-init", action_add_queue_tail); action_for_each_trigger("late-init", action_add_queue_tail);
} }
/* run all property triggers based on current state of the properties */ // Run all property triggers based on current state of the properties.
queue_builtin_action(queue_property_triggers_action, "queue_property_triggers"); queue_builtin_action(queue_property_triggers_action, "queue_property_triggers");
// TODO: why do we only initialize ufds after execute_one_command and restart_processes?
size_t fd_count = 0;
struct pollfd ufds[3];
bool property_set_fd_init = false;
bool signal_fd_init = false;
bool keychord_fd_init = false;
for (;;) { for (;;) {
execute_one_command(); if (!waiting_for_exec) {
restart_processes(); execute_one_command();
restart_processes();
}
if (!property_set_fd_init && get_property_set_fd() > 0) { if (!property_set_fd_init && get_property_set_fd() > 0) {
ufds[fd_count].fd = get_property_set_fd(); ufds[fd_count].fd = get_property_set_fd();
ufds[fd_count].events = POLLIN; ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0; ufds[fd_count].revents = 0;
fd_count++; fd_count++;
property_set_fd_init = 1; property_set_fd_init = true;
} }
if (!signal_fd_init && get_signal_fd() > 0) { if (!signal_fd_init && get_signal_fd() > 0) {
ufds[fd_count].fd = get_signal_fd(); ufds[fd_count].fd = get_signal_fd();
ufds[fd_count].events = POLLIN; ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0; ufds[fd_count].revents = 0;
fd_count++; fd_count++;
signal_fd_init = 1; signal_fd_init = true;
} }
if (!keychord_fd_init && get_keychord_fd() > 0) { if (!keychord_fd_init && get_keychord_fd() > 0) {
ufds[fd_count].fd = get_keychord_fd(); ufds[fd_count].fd = get_keychord_fd();
ufds[fd_count].events = POLLIN; ufds[fd_count].events = POLLIN;
ufds[fd_count].revents = 0; ufds[fd_count].revents = 0;
fd_count++; fd_count++;
keychord_fd_init = 1; keychord_fd_init = true;
} }
int timeout = -1; int timeout = -1;

View file

@ -17,13 +17,11 @@
#ifndef _INIT_INIT_H #ifndef _INIT_INIT_H
#define _INIT_INIT_H #define _INIT_INIT_H
#include <sys/types.h>
#include <cutils/list.h> #include <cutils/list.h>
#include <cutils/iosched_policy.h> #include <cutils/iosched_policy.h>
#include <sys/stat.h>
void handle_control_message(const char *msg, const char *arg);
struct command struct command
{ {
/* list of commands in an action */ /* list of commands in an action */
@ -59,8 +57,6 @@ struct action {
struct command *current; struct command *current;
}; };
void build_triggers_string(char *name_str, int length, struct action *cur_action);
struct socketinfo { struct socketinfo {
struct socketinfo *next; struct socketinfo *next;
const char *name; const char *name;
@ -77,27 +73,29 @@ struct svcenvinfo {
const char *value; const char *value;
}; };
#define SVC_DISABLED 0x01 /* do not autostart with class */ #define SVC_DISABLED 0x001 // do not autostart with class
#define SVC_ONESHOT 0x02 /* do not restart on exit */ #define SVC_ONESHOT 0x002 // do not restart on exit
#define SVC_RUNNING 0x04 /* currently active */ #define SVC_RUNNING 0x004 // currently active
#define SVC_RESTARTING 0x08 /* waiting to restart */ #define SVC_RESTARTING 0x008 // waiting to restart
#define SVC_CONSOLE 0x10 /* requires console */ #define SVC_CONSOLE 0x010 // requires console
#define SVC_CRITICAL 0x20 /* will reboot into recovery if keeps crashing */ #define SVC_CRITICAL 0x020 // will reboot into recovery if keeps crashing
#define SVC_RESET 0x40 /* Use when stopping a process, but not disabling #define SVC_RESET 0x040 // Use when stopping a process, but not disabling so it can be restarted with its class.
so it can be restarted with its class */ #define SVC_RC_DISABLED 0x080 // Remember if the disabled flag was set in the rc script.
#define SVC_RC_DISABLED 0x80 /* Remember if the disabled flag was set in the rc script */ #define SVC_RESTART 0x100 // Use to safely restart (stop, wait, start) a service.
#define SVC_RESTART 0x100 /* Use to safely restart (stop, wait, start) a service */ #define SVC_DISABLED_START 0x200 // A start was requested but it was disabled at the time.
#define SVC_DISABLED_START 0x200 /* a start was requested but it was disabled at the time */ #define SVC_EXEC 0x400 // This synthetic service corresponds to an 'exec'.
#define NR_SVC_SUPP_GIDS 12 /* twelve supplementary groups */ #define NR_SVC_SUPP_GIDS 12 /* twelve supplementary groups */
#define COMMAND_RETRY_TIMEOUT 5 #define COMMAND_RETRY_TIMEOUT 5
struct service { struct service {
void NotifyStateChange(const char* new_state);
/* list of all services */ /* list of all services */
struct listnode slist; struct listnode slist;
const char *name; char *name;
const char *classname; const char *classname;
unsigned flags; unsigned flags;
@ -111,7 +109,7 @@ struct service {
gid_t supp_gids[NR_SVC_SUPP_GIDS]; gid_t supp_gids[NR_SVC_SUPP_GIDS];
size_t nr_supp_gids; size_t nr_supp_gids;
char *seclabel; const char* seclabel;
struct socketinfo *sockets; struct socketinfo *sockets;
struct svcenvinfo *envvars; struct svcenvinfo *envvars;
@ -131,7 +129,13 @@ struct service {
char *args[1]; char *args[1];
}; /* ^-------'args' MUST be at the end of this struct! */ }; /* ^-------'args' MUST be at the end of this struct! */
void notify_service_state(const char *name, const char *state); extern bool waiting_for_exec;
extern struct selabel_handle *sehandle;
extern struct selabel_handle *sehandle_prop;
void build_triggers_string(char *name_str, int length, struct action *cur_action);
void handle_control_message(const char *msg, const char *arg);
struct service *service_find_by_name(const char *name); struct service *service_find_by_name(const char *name);
struct service *service_find_by_pid(pid_t pid); struct service *service_find_by_pid(pid_t pid);
@ -147,9 +151,8 @@ void service_restart(struct service *svc);
void service_start(struct service *svc, const char *dynamic_args); void service_start(struct service *svc, const char *dynamic_args);
void property_changed(const char *name, const char *value); void property_changed(const char *name, const char *value);
extern struct selabel_handle *sehandle; int selinux_reload_policy(void);
extern struct selabel_handle *sehandle_prop;
extern int selinux_reload_policy(void);
void zap_stdio(void); void zap_stdio(void);
#endif /* _INIT_INIT_H */ #endif /* _INIT_INIT_H */

View file

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
#include <errno.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
@ -440,7 +441,7 @@ parser_done:
} }
int init_parse_config_file(const char* path) { int init_parse_config_file(const char* path) {
INFO("Parsing %s...", path); INFO("Parsing %s...\n", path);
std::string data; std::string data;
if (!read_file(path, &data)) { if (!read_file(path, &data)) {
return -1; return -1;
@ -663,6 +664,65 @@ int action_queue_empty()
return list_empty(&action_queue); return list_empty(&action_queue);
} }
service* make_exec_oneshot_service(int nargs, char** args) {
// Parse the arguments: exec [SECLABEL [UID [GID]*] --] COMMAND ARGS...
int command_arg = 1;
for (int i = 1; i < nargs; ++i) {
if (strcmp(args[i], "--") == 0) {
command_arg = i + 1;
break;
}
}
if (command_arg > 4 + NR_SVC_SUPP_GIDS) {
ERROR("exec called with too many supplementary group ids\n");
return NULL;
}
int argc = nargs - command_arg;
char** argv = (args + command_arg);
if (argc < 1) {
ERROR("exec called without command\n");
return NULL;
}
service* svc = (service*) calloc(1, sizeof(*svc) + sizeof(char*) * argc);
if (svc == NULL) {
ERROR("Couldn't allocate service for exec of '%s': %s", argv[0], strerror(errno));
return NULL;
}
if (command_arg > 2) {
svc->seclabel = args[1];
}
if (command_arg > 3) {
svc->uid = decode_uid(args[2]);
}
if (command_arg > 4) {
svc->gid = decode_uid(args[3]);
svc->nr_supp_gids = command_arg - 1 /* -- */ - 4 /* exec SECLABEL UID GID */;
for (size_t i = 0; i < svc->nr_supp_gids; ++i) {
svc->supp_gids[i] = decode_uid(args[4 + i]);
}
}
static int exec_count; // Every service needs a unique name.
char* name = NULL;
asprintf(&name, "exec %d (%s)", exec_count++, argv[0]);
if (name == NULL) {
ERROR("Couldn't allocate name for exec service '%s'\n", argv[0]);
free(svc);
return NULL;
}
svc->name = name;
svc->classname = "default";
svc->flags = SVC_EXEC | SVC_ONESHOT;
svc->nargs = argc;
memcpy(svc->args, argv, sizeof(char*) * svc->nargs);
svc->args[argc] = NULL;
list_add_tail(&service_list, &svc->slist);
return svc;
}
static void *parse_service(struct parse_state *state, int nargs, char **args) static void *parse_service(struct parse_state *state, int nargs, char **args)
{ {
if (nargs < 3) { if (nargs < 3) {
@ -686,7 +746,7 @@ static void *parse_service(struct parse_state *state, int nargs, char **args)
parse_error(state, "out of memory\n"); parse_error(state, "out of memory\n");
return 0; return 0;
} }
svc->name = args[1]; svc->name = strdup(args[1]);
svc->classname = "default"; svc->classname = "default";
memcpy(svc->args, args + 2, sizeof(char*) * nargs); memcpy(svc->args, args + 2, sizeof(char*) * nargs);
trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger)); trigger* cur_trigger = (trigger*) calloc(1, sizeof(*cur_trigger));

View file

@ -20,6 +20,7 @@
#define INIT_PARSER_MAXARGS 64 #define INIT_PARSER_MAXARGS 64
struct action; struct action;
struct service;
struct action *action_remove_queue_head(void); struct action *action_remove_queue_head(void);
void action_add_queue_tail(struct action *act); void action_add_queue_tail(struct action *act);
@ -33,4 +34,6 @@ void queue_builtin_action(int (*func)(int nargs, char **args), const char *name)
int init_parse_config_file(const char *fn); int init_parse_config_file(const char *fn);
int expand_props(char *dst, const char *src, int len); int expand_props(char *dst, const char *src, int len);
service* make_exec_oneshot_service(int argc, char** argv);
#endif #endif

134
init/init_parser_test.cpp Normal file
View file

@ -0,0 +1,134 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "init_parser.h"
#include "init.h"
#include "util.h"
#include <errno.h>
#include <gtest/gtest.h>
TEST(init_parser, make_exec_oneshot_service_invalid_syntax) {
char* argv[10];
memset(argv, 0, sizeof(argv));
// Nothing.
ASSERT_EQ(nullptr, make_exec_oneshot_service(0, argv));
// No arguments to 'exec'.
argv[0] = const_cast<char*>("exec");
ASSERT_EQ(nullptr, make_exec_oneshot_service(1, argv));
// No command in "exec --".
argv[1] = const_cast<char*>("--");
ASSERT_EQ(nullptr, make_exec_oneshot_service(2, argv));
}
TEST(init_parser, make_exec_oneshot_service_too_many_supplementary_gids) {
int argc = 0;
char* argv[4 + NR_SVC_SUPP_GIDS + 3];
argv[argc++] = const_cast<char*>("exec");
argv[argc++] = const_cast<char*>("seclabel");
argv[argc++] = const_cast<char*>("root"); // uid.
argv[argc++] = const_cast<char*>("root"); // gid.
for (int i = 0; i < NR_SVC_SUPP_GIDS; ++i) {
argv[argc++] = const_cast<char*>("root"); // Supplementary gid.
}
argv[argc++] = const_cast<char*>("--");
argv[argc++] = const_cast<char*>("/system/bin/id");
argv[argc] = nullptr;
ASSERT_EQ(nullptr, make_exec_oneshot_service(argc, argv));
}
static void Test_make_exec_oneshot_service(bool dash_dash, bool seclabel, bool uid, bool gid, bool supplementary_gids) {
int argc = 0;
char* argv[10];
argv[argc++] = const_cast<char*>("exec");
if (seclabel) {
argv[argc++] = const_cast<char*>("u:r:su:s0"); // seclabel
if (uid) {
argv[argc++] = const_cast<char*>("log"); // uid
if (gid) {
argv[argc++] = const_cast<char*>("shell"); // gid
if (supplementary_gids) {
argv[argc++] = const_cast<char*>("system"); // supplementary gid 0
argv[argc++] = const_cast<char*>("adb"); // supplementary gid 1
}
}
}
}
if (dash_dash) {
argv[argc++] = const_cast<char*>("--");
}
argv[argc++] = const_cast<char*>("/system/bin/toybox");
argv[argc++] = const_cast<char*>("id");
argv[argc] = nullptr;
service* svc = make_exec_oneshot_service(argc, argv);
ASSERT_NE(nullptr, svc);
if (seclabel) {
ASSERT_STREQ("u:r:su:s0", svc->seclabel);
} else {
ASSERT_EQ(nullptr, svc->seclabel);
}
if (uid) {
ASSERT_EQ(decode_uid("log"), svc->uid);
} else {
ASSERT_EQ(0U, svc->uid);
}
if (gid) {
ASSERT_EQ(decode_uid("shell"), svc->gid);
} else {
ASSERT_EQ(0U, svc->gid);
}
if (supplementary_gids) {
ASSERT_EQ(2U, svc->nr_supp_gids);
ASSERT_EQ(decode_uid("system"), svc->supp_gids[0]);
ASSERT_EQ(decode_uid("adb"), svc->supp_gids[1]);
} else {
ASSERT_EQ(0U, svc->nr_supp_gids);
}
ASSERT_EQ(2, svc->nargs);
ASSERT_EQ("/system/bin/toybox", svc->args[0]);
ASSERT_EQ("id", svc->args[1]);
ASSERT_EQ(nullptr, svc->args[2]);
}
TEST(init_parser, make_exec_oneshot_service_with_everything) {
Test_make_exec_oneshot_service(true, true, true, true, true);
}
TEST(init_parser, make_exec_oneshot_service_with_seclabel_uid_gid) {
Test_make_exec_oneshot_service(true, true, true, true, false);
}
TEST(init_parser, make_exec_oneshot_service_with_seclabel_uid) {
Test_make_exec_oneshot_service(true, true, true, false, false);
}
TEST(init_parser, make_exec_oneshot_service_with_seclabel) {
Test_make_exec_oneshot_service(true, true, false, false, false);
}
TEST(init_parser, make_exec_oneshot_service_with_just_command) {
Test_make_exec_oneshot_service(true, false, false, false, false);
}
TEST(init_parser, make_exec_oneshot_service_with_just_command_no_dash) {
Test_make_exec_oneshot_service(false, false, false, false, false);
}

View file

@ -70,11 +70,11 @@ disabled
setenv <name> <value> setenv <name> <value>
Set the environment variable <name> to <value> in the launched process. Set the environment variable <name> to <value> in the launched process.
socket <name> <type> <perm> [ <user> [ <group> [ <context> ] ] ] socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]
Create a unix domain socket named /dev/socket/<name> and pass Create a unix domain socket named /dev/socket/<name> and pass
its fd to the launched process. <type> must be "dgram", "stream" or "seqpacket". its fd to the launched process. <type> must be "dgram", "stream" or "seqpacket".
User and group default to 0. User and group default to 0.
Context is the SELinux security context for the socket. 'seclabel' is the SELinux security context for the socket.
It defaults to the service security context, as specified by seclabel or It defaults to the service security context, as specified by seclabel or
computed based on the service executable file security context. computed based on the service executable file security context.
@ -91,8 +91,8 @@ group <groupname> [ <groupname> ]*
supplemental groups of the process (via setgroups()). supplemental groups of the process (via setgroups()).
Currently defaults to root. (??? probably should default to nobody) Currently defaults to root. (??? probably should default to nobody)
seclabel <securitycontext> seclabel <seclabel>
Change to securitycontext before exec'ing this service. Change to 'seclabel' before exec'ing this service.
Primarily for use by services run from the rootfs, e.g. ueventd, adbd. Primarily for use by services run from the rootfs, e.g. ueventd, adbd.
Services on the system partition can instead use policy-defined transitions Services on the system partition can instead use policy-defined transitions
based on their file security context. based on their file security context.
@ -137,14 +137,17 @@ boot
Commands Commands
-------- --------
exec <path> [ <argument> ]* exec [ <seclabel> [ <user> [ <group> ]* ] ] -- <command> [ <argument> ]*
This command is not implemented. Fork and execute command with the given arguments. The command starts
after "--" so that an optional security context, user, and supplementary
groups can be provided. No other commands will be run until this one
finishes.
execonce <path> [ <argument> ]* execonce <path> [ <argument> ]*
Fork and execute a program (<path>). This will block until Fork and execute a program (<path>). This will block until
the program completes execution. This command can be run at most the program completes execution. This command can be run at most
once during init's lifetime. Subsequent invocations are ignored. once during init's lifetime. Subsequent invocations are ignored.
It is best to avoid exec as unlike the builtin commands, it runs It is best to avoid execonce as unlike the builtin commands, it runs
the risk of getting init "stuck". the risk of getting init "stuck".
export <name> <value> export <name> <value>
@ -220,7 +223,7 @@ restorecon_recursive <path> [ <path> ]*
Recursively restore the directory tree named by <path> to the Recursively restore the directory tree named by <path> to the
security contexts specified in the file_contexts configuration. security contexts specified in the file_contexts configuration.
setcon <securitycontext> setcon <seclabel>
Set the current process security context to the specified string. Set the current process security context to the specified string.
This is typically only used from early-init to set the init context This is typically only used from early-init to set the init context
before any other process is started. before any other process is started.

View file

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
#include <stdio.h>
#include <errno.h> #include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/wait.h> #include <sys/wait.h>
@ -27,34 +27,28 @@
#include <cutils/list.h> #include <cutils/list.h>
#include "init.h" #include "init.h"
#include "util.h"
#include "log.h" #include "log.h"
#include "util.h"
static int signal_fd = -1; static int signal_fd = -1;
static int signal_recv_fd = -1; static int signal_recv_fd = -1;
static void sigchld_handler(int s) static void sigchld_handler(int s) {
{
write(signal_fd, &s, 1); write(signal_fd, &s, 1);
} }
#define CRITICAL_CRASH_THRESHOLD 4 /* if we crash >4 times ... */ #define CRITICAL_CRASH_THRESHOLD 4 /* if we crash >4 times ... */
#define CRITICAL_CRASH_WINDOW (4*60) /* ... in 4 minutes, goto recovery*/ #define CRITICAL_CRASH_WINDOW (4*60) /* ... in 4 minutes, goto recovery */
static int wait_for_one_process(int block) static int wait_for_one_process() {
{
int status; int status;
struct service *svc; pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, WNOHANG));
struct socketinfo *si; if (pid <= 0) {
time_t now; return -1;
struct listnode *node; }
struct command *cmd;
pid_t pid = TEMP_FAILURE_RETRY(waitpid(-1, &status, block ? 0 : WNOHANG));
if (pid <= 0) return -1;
INFO("waitpid returned pid %d, status = %08x\n", pid, status); INFO("waitpid returned pid %d, status = %08x\n", pid, status);
svc = service_find_by_pid(pid); service* svc = service_find_by_pid(pid);
if (!svc) { if (!svc) {
if (WIFEXITED(status)) { if (WIFEXITED(status)) {
ERROR("untracked pid %d exited with status %d\n", pid, WEXITSTATUS(status)); ERROR("untracked pid %d exited with status %d\n", pid, WEXITSTATUS(status));
@ -68,36 +62,47 @@ static int wait_for_one_process(int block)
return 0; return 0;
} }
// TODO: all the code from here down should be a member function on service.
NOTICE("process '%s', pid %d exited\n", svc->name, pid); NOTICE("process '%s', pid %d exited\n", svc->name, pid);
if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) { if (!(svc->flags & SVC_ONESHOT) || (svc->flags & SVC_RESTART)) {
kill(-pid, SIGKILL);
NOTICE("process '%s' killing any children in process group\n", svc->name); NOTICE("process '%s' killing any children in process group\n", svc->name);
kill(-pid, SIGKILL);
} }
/* remove any sockets we may have created */ // Remove any sockets we may have created.
for (si = svc->sockets; si; si = si->next) { for (socketinfo* si = svc->sockets; si; si = si->next) {
char tmp[128]; char tmp[128];
snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name); snprintf(tmp, sizeof(tmp), ANDROID_SOCKET_DIR"/%s", si->name);
unlink(tmp); unlink(tmp);
} }
if (svc->flags & SVC_EXEC) {
INFO("SVC_EXEC pid %d finished...\n", svc->pid);
waiting_for_exec = false;
list_remove(&svc->slist);
free(svc->name);
free(svc);
return 0;
}
svc->pid = 0; svc->pid = 0;
svc->flags &= (~SVC_RUNNING); svc->flags &= (~SVC_RUNNING);
/* oneshot processes go into the disabled state on exit, // Oneshot processes go into the disabled state on exit,
* except when manually restarted. */ // except when manually restarted.
if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) { if ((svc->flags & SVC_ONESHOT) && !(svc->flags & SVC_RESTART)) {
svc->flags |= SVC_DISABLED; svc->flags |= SVC_DISABLED;
} }
/* disabled and reset processes do not get restarted automatically */ // Disabled and reset processes do not get restarted automatically.
if (svc->flags & (SVC_DISABLED | SVC_RESET) ) { if (svc->flags & (SVC_DISABLED | SVC_RESET)) {
notify_service_state(svc->name, "stopped"); svc->NotifyStateChange("stopped");
return 0; return 0;
} }
now = gettime(); time_t now = gettime();
if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) { if ((svc->flags & SVC_CRITICAL) && !(svc->flags & SVC_RESTART)) {
if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) { if (svc->time_crashed + CRITICAL_CRASH_WINDOW >= now) {
if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) { if (++svc->nr_crashed > CRITICAL_CRASH_THRESHOLD) {
@ -116,36 +121,33 @@ static int wait_for_one_process(int block)
svc->flags &= (~SVC_RESTART); svc->flags &= (~SVC_RESTART);
svc->flags |= SVC_RESTARTING; svc->flags |= SVC_RESTARTING;
/* Execute all onrestart commands for this service. */ // Execute all onrestart commands for this service.
struct listnode* node;
list_for_each(node, &svc->onrestart.commands) { list_for_each(node, &svc->onrestart.commands) {
cmd = node_to_item(node, struct command, clist); command* cmd = node_to_item(node, struct command, clist);
cmd->func(cmd->nargs, cmd->args); cmd->func(cmd->nargs, cmd->args);
} }
notify_service_state(svc->name, "restarting"); svc->NotifyStateChange("restarting");
return 0; return 0;
} }
void handle_signal(void) void handle_signal() {
{ // We got a SIGCHLD - reap and restart as needed.
char tmp[32]; char tmp[32];
/* we got a SIGCHLD - reap and restart as needed */
read(signal_recv_fd, tmp, sizeof(tmp)); read(signal_recv_fd, tmp, sizeof(tmp));
while (!wait_for_one_process(0)) while (!wait_for_one_process()) {
; }
} }
void signal_init(void) void signal_init() {
{
int s[2];
struct sigaction act; struct sigaction act;
memset(&act, 0, sizeof(act)); memset(&act, 0, sizeof(act));
act.sa_handler = sigchld_handler; act.sa_handler = sigchld_handler;
act.sa_flags = SA_NOCLDSTOP; act.sa_flags = SA_NOCLDSTOP;
sigaction(SIGCHLD, &act, 0); sigaction(SIGCHLD, &act, 0);
/* create a signalling mechanism for the sigchld handler */ // Create a signalling mechanism for the sigchld handler.
int s[2];
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == 0) { if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, s) == 0) {
signal_fd = s[0]; signal_fd = s[0];
signal_recv_fd = s[1]; signal_recv_fd = s[1];
@ -154,7 +156,6 @@ void signal_init(void)
handle_signal(); handle_signal();
} }
int get_signal_fd() int get_signal_fd() {
{
return signal_recv_fd; return signal_recv_fd;
} }

View file

@ -35,3 +35,9 @@ TEST(util, read_file_success) {
s[5] = 0; s[5] = 0;
EXPECT_STREQ("Linux", s.c_str()); EXPECT_STREQ("Linux", s.c_str());
} }
TEST(util, decode_uid) {
EXPECT_EQ(0U, decode_uid("root"));
EXPECT_EQ(-1U, decode_uid("toot"));
EXPECT_EQ(123U, decode_uid("123"));
}