preface

This article will cover the second phase of the init process, but both phases cover selinux. This part is relatively complex, so we’ll skip it for a separate article on Selinux. In the second phase, the init process does the following:

  1. Initialize the properties service
  2. Initializes the subprocess termination signal handler
  3. Parse the rc file and start the Zygote process

Initialize the properties service

A property service is a system or some system application that stores configuration properties, usually key-value pairs, that are stored and read by a property service. We passed:

adb shell getprop 
Copy the code

Command to view the property value of the current device.

int main(int argc, char** argv) {...bool is_first_stage = (getenv("INIT_SECOND_STAGE") = =nullptr);

    if(is_first_stage) { ... }...// Initialize the properties serviceproperty_init(); .return 0;
}
Copy the code

When property_set() is used by A process to modify the property value, the init process checks the access permission, and changes the property value when the permission is satisfied. Once the property value changes, the trigger (the statement starting with on in the RC file) is triggered. In Android Shared Memmory, there is a _system_property_area_ area where all property values are recorded. For process A through the property_get () method, the property value of the shared memory region is also obtained.

property_init

void property_init(a) {
    mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
    CreateSerializedPropertyInfo();// Load the property values from the file
    if (__system_property_area_init()) {// Create shared memory
        LOG(FATAL) << "Failed to initialize property area";
    }
    if(! property_info_area.LoadDefaultPath()) { LOG(FATAL) <<"Failed to load serialized property info file"; }}Copy the code

CreateSerializedPropertyInfo

void CreateSerializedPropertyInfo(a) {
    auto property_infos = std: :vector<PropertyInfoEntry>();
    /** * access() checks whether an existing file can be read/written * return value: 0 if all permissions passed the check, -1 if any permissions were denied. * /
    if (access("/system/etc/selinux/plat_property_contexts", R_OK) ! =- 1) {
        if(! LoadPropertyInfoFromFile("/system/etc/selinux/plat_property_contexts",
                                      &property_infos)) {// Load the property information from the file
            return;
        }
        // Don't check for failure here, so we always have a sane list of properties.
        // E.g. In case of recovery, the vendor partition will not have mounted and we
        // still need the system / platform properties to function.
        if(! LoadPropertyInfoFromFile("/vendor/etc/selinux/vendor_property_contexts",
                                      &property_infos)) {
            // Fallback to nonplat_* if vendor_* doesn't exist.
            LoadPropertyInfoFromFile("/vendor/etc/selinux/nonplat_property_contexts", &property_infos); }}else {
        if(! LoadPropertyInfoFromFile("/plat_property_contexts", &property_infos)) {
            return;
        }
        if(! LoadPropertyInfoFromFile("/vendor_property_contexts", &property_infos)) {
            // Fallback to nonplat_* if vendor_* doesn't exist.
            LoadPropertyInfoFromFile("/nonplat_property_contexts", &property_infos); }}auto serialized_contexts = std: :string(a);auto error = std: :string(a);if(! BuildTrie(property_infos,"u:object_r:default_prop:s0"."string", &serialized_contexts,
                   &error)) {
        LOG(ERROR) << "Unable to serialize property contexts: " << error;
        return;
    }

    constexpr static const char kPropertyInfosPath[] = "/dev/__properties__/property_info";
    if(! WriteStringToFile(serialized_contexts, kPropertyInfosPath,0444.0.0.false)) {
        PLOG(ERROR) << "Unable to write serialized property infos to file";
    }
    selinux_android_restorecon(kPropertyInfosPath, 0);
}
Copy the code

CreateSerializedPropertyInfo is reading some documents, will read the attribute value is stored in a dynamic array.

LoadPropertyInfoFromFile

bool LoadPropertyInfoFromFile(const std: :string& filename,
                              std: :vector<PropertyInfoEntry>* property_infos) {
    auto file_contents = std: :string(a);if(! ReadFileToString(filename, &file_contents)) { PLOG(ERROR) <<"Could not read properties from '" << filename << "'";
        return false;
    }

    auto errors = std: :vector<std: :string> {}; ParsePropertyInfoFile(file_contents, property_infos, &errors);// Parse the Propertiy file
    // Individual parsing errors are reported but do not cause a failed boot, which is what
    // returning false would do here.
    for (const auto& error : errors) {
        LOG(ERROR) << "Could not read line from '" << filename << "'." << error;
    }

    return true;
}
Copy the code

ParsePropertyInfoFile

void ParsePropertyInfoFile(const std: :string& file_contents,
                           std: :vector<PropertyInfoEntry>* property_infos,
                           std: :vector<std: :string>* errors) {
  // Do not clear property_infos to allow this function to be called on multiple files, with
  // their results concatenated.
  errors->clear();

  for (const auto& line : Split(file_contents, "\n")) {// Parse the property file line by line
    auto trimmed_line = Trim(line);
    if (trimmed_line.empty() || StartsWith(trimmed_line, "#")) {
      continue;
    }

    auto property_info_entry = PropertyInfoEntry{};
    auto parse_error = std: :string{};
    if(! ParsePropertyInfoLine(trimmed_line, &property_info_entry, &parse_error)) { errors->emplace_back(parse_error);continue; } property_infos->emplace_back(property_info_entry); }}Copy the code

_system_property_area_init

int __system_property_area_init() {
  bool fsetxattr_failed = false;
  returnsystem_properties.AreaInit(PROP_FILENAME, &fsetxattr_failed) && ! fsetxattr_failed ?0 : - 1;
}
Copy the code

Initialize the property memory area

SystemProperties::AreaInit

bool SystemProperties::AreaInit(const char* filename, bool* fsetxattr_failed) {
  if (strlen(filename) > PROP_FILENAME_MAX) {
    return false;
  }
  strcpy(property_filename_, filename);

  contexts_ = new (contexts_data_) ContextsSerialized();
  if(! contexts_->Initialize(true, property_filename_, fsetxattr_failed)) {
    return false;
  }
  initialized_ = true;
  return true;
}
Copy the code

ContextsSerialized::Initialize

bool ContextsSerialized::Initialize(bool writable, const char* filename, bool* fsetxattr_failed) {
  filename_ = filename;
  if(! InitializeProperties()) {return false;
  }

  if (writable) {
    mkdir(filename_, S_IRWXU | S_IXGRP | S_IXOTH);
    bool open_failed = false;
    if (fsetxattr_failed) {
      *fsetxattr_failed = false;
    }

    for (size_t i = 0; i < num_context_nodes_; ++i) {
      if(! context_nodes_[i].Open(true, fsetxattr_failed)) {
        open_failed = true; }}if(open_failed || ! MapSerialPropertyArea(true, fsetxattr_failed)) {
      FreeAndUnmap();
      return false; }}else {
    if(! MapSerialPropertyArea(false.nullptr)) {
      FreeAndUnmap();
      return false; }}return true;
}
Copy the code

Starting the Properties service

int main(int argc, char** argv) {

   ...
    bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);

    if(is_first_stage) { ... }... // Start the property service start_property_service(); .return 0;
}
Copy the code

In fact, the Socket service is a Socket service. We need to use it to implement the setting of the Socket service, so why start a Socket service, because not all processes can arbitrarily modify these system properties. Android leaves this task to the init process. If other processes want to modify properties, the service needs to tell the init process to do so through the Socket. Let’s look at this process in code.

start_property_service

void start_property_service(a) {
    selinux_callback cb;
    cb.func_audit = SelinuxAuditCallback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    property_set("ro.property_service.version"."2");
    // Create the socket and return the socket fd
    property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
                                   false.0666.0.0.nullptr);
    if (property_set_fd == - 1) {
        PLOG(FATAL) << "start_property_service socket creation failed";
    }

    listen(property_set_fd, 8);// The number of socket connections is 8
    /** * register the epoll event, which calls handle_property_set_fd */ when listening to property_set_fd change
    register_epoll_handler(property_set_fd, handle_property_set_fd);
}
Copy the code

After the Socket descriptor property_set_fd is created, it is put into epoll, and epoll is used to listen to property_set_fd. When data comes to property_set_fd, The init process will call the handle_property_set_fd function to handle this.

Epoll is an extensible I/O event notification mechanism of the Linux kernel. That is, it can effectively listen to file descriptors. Increase the CPU usage.

handle_property_set_fd

static void handle_property_set_fd(a) {
    static constexpr uint32_t kDefaultSocketTimeout = 2000; /* ms */

    int s = accept4(property_set_fd, nullptr.nullptr, SOCK_CLOEXEC);
    if (s == - 1) {
        return;
    }

    ucred cr;
    socklen_t cr_size = sizeof(cr);
    if (getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &cr_size) < 0) {
        close(s);
        PLOG(ERROR) << "sys_prop: unable to get SO_PEERCRED";
        return;
    }

    SocketConnection socket(s, cr);
    uint32_t timeout_ms = kDefaultSocketTimeout;

    uint32_t cmd = 0;
    if(! socket.RecvUint32(&cmd, &timeout_ms)) { PLOG(ERROR) <<"sys_prop: error while reading command from the socket";
        socket.SendUint32(PROP_ERROR_READ_CMD);
        return;
    }

    switch (cmd) {
    // Set the prop properties
    case PROP_MSG_SETPROP: {
        char prop_name[PROP_NAME_MAX];
        char prop_value[PROP_VALUE_MAX];

        if(! socket.RecvChars(prop_name, PROP_NAME_MAX, &timeout_ms) || ! socket.RecvChars(prop_value, PROP_VALUE_MAX, &timeout_ms)) { PLOG(ERROR) <<"sys_prop(PROP_MSG_SETPROP): error while reading name/value from the socket";
          return;
        }

        prop_name[PROP_NAME_MAX- 1] = 0;
        prop_value[PROP_VALUE_MAX- 1] = 0;

        const auto& cr = socket.cred();
        std: :string error;
        uint32_t result =
            HandlePropertySet(prop_name, prop_value, socket.source_context(), cr, &error);
        if(result ! = PROP_SUCCESS) { LOG(ERROR) <<"Unable to set property '" << prop_name << "' to '" << prop_value
                       << "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ":"
                       << error;
        }

        break;
      }

    case PROP_MSG_SETPROP2: {
        std: :string name;
        std: :string value;
        if(! socket.RecvString(&name, &timeout_ms) || ! socket.RecvString(&value, &timeout_ms)) { PLOG(ERROR) <<"sys_prop(PROP_MSG_SETPROP2): error while reading name/value from the socket";
          socket.SendUint32(PROP_ERROR_READ_DATA);
          return;
        }

        const auto& cr = socket.cred();
        std: :string error;
        uint32_t result = HandlePropertySet(name, value, socket.source_context(), cr, &error);
        if(result ! = PROP_SUCCESS) { LOG(ERROR) <<"Unable to set property '" << name << "' to '" << value
                       << "' from uid:" << cr.uid << " gid:" << cr.gid << " pid:" << cr.pid << ":"
                       << error;
        }
        socket.SendUint32(result);
        break;
      }

    default:
        LOG(ERROR) << "sys_prop: invalid command " << cmd;
        socket.SendUint32(PROP_ERROR_INVALID_CMD);
        break; }}Copy the code

HandlePropertySet

uint32_t HandlePropertySet(const std: :string& name, const std: :string& value,
                           const std: :string& source_context, const ucred& cr, std: :string* error) {
    if(! IsLegalPropertyName(name)) { *error ="Illegal property name";
        return PROP_ERROR_INVALID_NAME;
    }

    if (StartsWith(name, "ctl.")) {// Set the control properties
        if(! CheckControlPropertyPerms(name, value, source_context, cr)) { *error = StringPrintf("Invalid permissions to perform '%s' on '%s'", name.c_str() + 4,
                                  value.c_str());
            return PROP_ERROR_HANDLE_CONTROL_MESSAGE;
        }

        HandleControlMessage(name.c_str() + 4, value, cr.pid);
        return PROP_SUCCESS;
    }

    const char* target_context = nullptr;
    const char* type = nullptr;
    property_info_area->GetPropertyInfo(name.c_str(), &target_context, &type);

    if(! CheckMacPerms(name, target_context, source_context.c_str(), cr)) { *error ="SELinux permission check failed";
        return PROP_ERROR_PERMISSION_DENIED;
    }

    if (type == nullptr| |! CheckType(type, value)) { *error = StringPrintf("Property type check failed, value doesn't match expected type '%s'", (type ? :"(null)"));
        return PROP_ERROR_INVALID_VALUE;
    }

    // sys.powerctl is a special property that is used to make the device reboot. We want to log
    // any process that sets this property to be able to accurately blame the cause of a shutdown.
    if (name == "sys.powerctl") {
        std: :string cmdline_path = StringPrintf("proc/%d/cmdline", cr.pid);
        std: :string process_cmdline;
        std: :string process_log_string;
        if (ReadFileToString(cmdline_path, &process_cmdline)) {
            // Since cmdline is null deliminated, .c_str() conveniently gives us just the process
            // path.
            process_log_string = StringPrintf(" (%s)", process_cmdline.c_str());
        }
        LOG(INFO) << "Received sys.powerctl='" << value << "' from pid: " << cr.pid
                  << process_log_string;
    }

    if (name == "selinux.restorecon_recursive") {
        return PropertySetAsync(name, value, RestoreconRecursiveAsync, error);
    }

    return PropertySet(name, value, error);// Set the properties
}
Copy the code

PropertySet

static uint32_t PropertySet(const std: :string& name, const std: :string& value, std: :string* error) {
    size_t valuelen = value.size();

    if(! IsLegalPropertyName(name)) {// Check whether the attribute name is valid
        *error = "Illegal property name";
        return PROP_ERROR_INVALID_NAME;
    }

    if(valuelen >= PROP_VALUE_MAX && ! StartsWith(name,"ro.")) {
        *error = "Property value too long";
        return PROP_ERROR_INVALID_VALUE;
    }

    if (mbstowcs(nullptr, value.data(), 0) = =static_cast<std: :size_t> (- 1)) {
        *error = "Value is not a UTF8 encoded string";
        return PROP_ERROR_INVALID_VALUE;
    }

    prop_info* pi = (prop_info*) __system_property_find(name.c_str());// Check whether the current attribute already exists
    if(pi ! =nullptr) {
        // ro.* properties are actually "write-once".
        if (StartsWith(name, "ro.")) { // If it already exists and is read-only, it cannot be set again.
            *error = "Read-only property was already set";
            return PROP_ERROR_READ_ONLY_PROPERTY;
        }

        __system_property_update(pi, value.c_str(), valuelen);// Update the value if it already exists and is not read-only
    } else {
        int rc = __system_property_add(name.c_str(), name.size(), value.c_str(), valuelen);
        if (rc < 0) {
            *error = "__system_property_add failed";
            returnPROP_ERROR_SET_FAILED; }}// Don't write properties to disk until after we have read all default
    // properties to prevent them from being overwritten by default values.
    if (persistent_properties_loaded && StartsWith(name, "persist.")) {// Persist the attributes starting with persist
        WritePersistentProperty(name, value);
    }
    property_changed(name, value);
    return PROP_SUCCESS;
}
Copy the code

Explanation of each prefix:

  • Persist prefix. It is saved in the /data/property directory and still takes effect after the system restarts. Load after data decryption or late init phase.
  • Ro prefixes. Read-only properties, similar to constant definitions in C/CPP, that can and can only be set once. These properties are usually set as early as possible. When there are two properties with the same ro prefix, the property loaded earlier takes effect, not the property loaded later.
  • CTL prefix. This is created to facilitate the control of services in init by setting the names of the related services to the values of ctl.start and ctl.stop. (Start and stop on the command line are actually set using this property.) This ability is controlled by Selinux. The specific permissions are defined in property.te. If you are interested, all property-related permissions are defined in this file.

System property

void property_init() {
    mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
    CreateSerializedPropertyInfo();
    if (__system_property_area_init()) {
        LOG(FATAL) << "Failed to initialize property area";
    }
    if(! property_info_area.LoadDefaultPath()) { LOG(FATAL) <<"Failed to load serialized property info file"; }}Copy the code
  1. Android’s System Properties provides a globally accessible repository of configuration Settings
  2. The order in which files are loaded is very important. Properties with the ro prefix are loaded early, while properties with non-ro prefix are loaded late.
  3. As we saw above, the persist property is written to /data/property, which is why the persist property is still in effect after the restart.

Explanation of each prefix:

  • Persist prefix. Stored in the/data/property/persistent_properties, still take effect after restart. Load after data decryption or late init phase.
  • Ro prefixes. Read-only properties, similar to constant definitions in C/CPP, that can and can only be set once. These properties are usually set as early as possible. When there are two properties with the same ro prefix, the property loaded earlier takes effect, not the property loaded later.
  • CTL prefix. This is created to facilitate the control of services in init by setting the names of the related services to the values of ctl.start and ctl.stop. (Start and stop on the command line are actually set using this property.) This ability is controlled by Selinux. The specific permissions are defined in property.te. If you are interested, all property-related permissions are defined in this file.

Parse the init.rc file

int main(int argc, char** argv) {...bool is_first_stage = (getenv("INIT_SECOND_STAGE") = =nullptr);

    if(is_first_stage) { ... }... LoadBootScripts(am, sm);// Parse the init.rc file.return 0;
}
Copy the code
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
    Parser parser = CreateParser(action_manager, service_list);

    std: :string bootscript = GetProperty("ro.boot.init_rc"."");
    if (bootscript.empty()) {
        parser.ParseConfig("/init.rc");// Parse the init.rc file
        if(! parser.ParseConfig("/system/etc/init")) {
            late_import_paths.emplace_back("/system/etc/init");
        }
        if(! parser.ParseConfig("/product/etc/init")) {
            late_import_paths.emplace_back("/product/etc/init");
        }
        if(! parser.ParseConfig("/odm/etc/init")) {
            late_import_paths.emplace_back("/odm/etc/init");
        }
        if(! parser.ParseConfig("/vendor/etc/init")) {
            late_import_paths.emplace_back("/vendor/etc/init"); }}else{ parser.ParseConfig(bootscript); }}Copy the code

Rc is a configuration file written in Android Init Language. There are five actions, services, commands, Options, and Imports

Action

The trigger, which starts with on, determines the time to execute the corresponding service. The timing is as follows:

  • on early-init; Triggered in the early stages of initialization;
  • on init; Triggered during the initialization phase;
  • on late-init; Triggered late in initialization;
  • On boot/ Charger: Triggered when the system starts/charges
  • On property:=: Fires when the property value meets the condition;

Service

Service starts with “Service” and is started by init process. Generally, it runs in a child process of init. Therefore, before starting Service, we need to check whether the corresponding executable file exists. Init generates a child process defined in the RC file, where each service is forked upon startup.

For example, service ueventd /sbin/ueventd The process name is ueventd and the path of the executable file is /sbin/ueventd

Command

The command

  • Chmod Modifies the file permission
  • Chown Changes the file owner
  • Class_start Starts all services belonging to the same class
  • Start Starts a specific service

For example, chmod 0773 /data/misc/trace modifiers permission

Options

Options is optional for Service and is used with Service

Critical: If the service is restarted four times within four minutes, the system will restart and enter recovery mode

Disabled: The service is enabled only by the service name

Imports

Import other RC files

import /init.environ.rc
import /init.usb.rc
import /init.${ro.hardware}.rc
import /vendor/etc/init/hw/init.${ro.hardware}.rc
import /init.usb.configfs.rc
import /init.${ro.zygote}.rc
Copy the code

Start the Zygote service

Import in the init.rc file

/init.${ro.zygote}.rc
Copy the code

If you are on a 32-bit system, the corresponding file is init.zygote32.rc

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc reserved_disk
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks
Copy the code

The rc file is the configuration file used to start the Zygote process, which will be described in the next article.

conclusion

As a template for most daemons, init code follows the classic pattern of building services: initialize, then get stuck in a loop, and never exit.