In depth analysis of Android init process implementation of C language source code

  • 2020-04-02 03:12:50
  • OfStack

An overview of the

Init is a process, or rather, the first user-space process on a Linux system. Since Android is based on the Linux kernel, init is also the first user-space process on the Android system. Init has a process number of 1. As the number one process, init has a lot of important work:

      Init provides a property service to manage properties on the Android system.       Init is responsible for creating key processes in the system, including zygote.

Previous articles have started with the source code for init, but I'll start with these two main things. To figure out how the two main things work, let's go back to the source code for init.

This article focuses on the property service of the init process.

      The source directory related to the init property service is as follows:


    system/core/init/
    bionic/libc/bionic/
    system/core/libcutils/

Property services

On the Windows platform there is something called a registry, which stores key-value pairs like key/value. In general, the system or some applications store some of their properties in the registry, and even if the system is restarted or the application is restarted, it can initialize the properties based on the values previously set in the registry.

Android offers a similar mechanism, called a property service. Applications can query or set properties through this service. We can get the property key value pair in the phone through the following command.


adb shell getprop

For example, the property value of redmi Note phone is as follows:


[ro.product.device]: [lcsh92_wet_jb9]
[ro.product.locale.language]: [zh]
[ro.product.locale.region]: [CN]
[ro.product.manufacturer]: [Xiaomi]


In the main function of system/core/init/init.c file, the code related to the property service is as follows:


property_init();
queue_builtin_action(property_service_init_action, "property_service_init");

Next, we'll take a look at each of these code implementations.
Property service initialization
Create storage space

First of all, let's look at some of the property_init function source code (/ system/core/init/property_service. C) :


void property_init(void)
{
  init_property_area();
}


The property_init function simply calls the init_property_area method. Next, let's take a look at the implementation of this method:


static int property_area_inited = 0;
static workspace pa_workspace;
static int init_property_area(void)
{
  //Whether the property space has been initialized
  if (property_area_inited)
    return -1;

  if (__system_property_area_init())
    return -1;

  if (init_workspace(&pa_workspace, 0))
    return -1;

  fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);

  property_area_inited = 1;
  return 0;
}

From the init_property_area function, we can see that the function first determines whether the property memory area has been initialized, and if so, returns -1. If there is no initialization, then we will find that there are two key function __system_property_area_init and init_workspace should be associated with initialized memory area. So let's analyze the implementation of these two functions separately.


__system_property_area_init

__system_property_area_init Function in /bionic/libc/bionic/system_properties.c In the file, the specific code is as follows: 

struct prop_area {
  unsigned bytes_used;
  unsigned volatile serial;
  unsigned magic;
  unsigned version;
  unsigned reserved[28];
  char data[0];
};
typedef struct prop_area prop_area;
prop_area *__system_property_area__ = NULL;

#define PROP_FILENAME "/dev/__properties__"
static char property_filename[PATH_MAX] = PROP_FILENAME; 

#define PA_SIZE (128 * 1024)


static int map_prop_area_rw()
{
  prop_area *pa;
  int fd;
  int ret;

  
  fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);
  if (fd < 0) {
    if (errno == EACCES) {
      abort();
    }
    return -1;
  }

  ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
  if (ret < 0)
    goto out;

  if (ftruncate(fd, PA_SIZE) < 0)
    goto out;

  pa_size = PA_SIZE;
  pa_data_size = pa_size - sizeof(prop_area);
  compat_mode = false;

  //The mmap map file implements Shared memory
  pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  if (pa == MAP_FAILED)
    goto out;

  
  memset(pa, 0, pa_size);
  pa->magic = PROP_AREA_MAGIC;
  pa->version = PROP_AREA_VERSION;
  pa->bytes_used = sizeof(prop_bt);

  __system_property_area__ = pa;

  close(fd);
  return 0;

out:
  close(fd);
  return -1;
}

int __system_property_area_init()
{
  return map_prop_area_rw();
}

 
The code is easy to understand. The main thing is to create a Shared memory region with the mmap map property_filename and assign the first address of the Shared memory to the global variable, s/s.

For the mmap map file implementation of Shared memory IPC communication mechanism, you can refer to this article: mmap implementation of IPC communication mechanism
init_workspace

Next, let's take a look at init_workspace function source code (/ system/core/init/property_service. C) :


typedef struct {
  void *data;
  size_t size;
  int fd;
}workspace;

static int init_workspace(workspace *w, size_t size)
{
  void *data;
  int fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW);
  if (fd < 0)
    return -1;

  w->size = size;
  w->fd = fd;
  return 0;
}

The client process accesses the property memory area

Although the property memory area was created by the init process, the Android system expects other processes to be able to read the contents of this memory area as well. To do this, the init process does two things during the property region initialization:

      The attribute memory area is created on Shared memory, which is cross-process. This is done in the above code by mmap mapping /dev/s/s properties__ file. The fd member in the pa_workspace variable also holds the handle to the mapping file.
      How do you let other processes know about this Shared memory handle? Android first assigns the file map handle to the variable of s _system_property_area__, which belongs to the output of the bionic_lic library, and then USES the constructor property of GCC, which indicates a function of s _lib_prenit, which automatically calls s _libc_prenit when the bionic_lic library is loaded.

Instead of just talking about principles, let's look directly at the implementation of the function code with successive lib_prenit:


void __attribute__((constructor)) __libc_prenit(void);
void __libc_prenit(void)
{
  // ...
  __libc_init_common(elfdata); //Call this function
  // ...
}


__libc_init_common Function as follows: 

void __libc_init_common(uintptr_t *elfdata)
{
  // ...
  __system_properties_init(); //Initializes the client's property store area
}

 
__system_properties_init The function goes back to what we're used to /bionic/libc/bionic/system_properties.c File: 

static int get_fd_from_env(void)
{
  char *env = getenv("ANDROID_PROPERTY_WORKSPACE");

  if (! env) {
    return -1;
  }

  return atoi(env);
}

static int map_prop_area()
{
  bool formFile = true;
  int result = -1;
  int fd;
  int ret;

  fd = open(property_filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
  if (fd >= 0) {
    
    ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
    if (ret < 0)
      goto cleanup;
  }

  if ((fd < 0) && (error == ENOENT)) {
    fd = get_fd_from_env();
    fromFile = false;
  }

  if (fd < 0) {
    return -1;
  }

  struct stat fd_stat;
  if (fstat(fd, &fd_stat) < 0) {
    goto cleanup;
  }

  if ((fd_stat.st_uid != 0)
      || (fd_stat.st_gid != 0)
      || (fd_stat.st_mode & (S_IWGRP | S_IWOTH) != 0)
      || (fd_stat.st_size < sizeof(prop_area))) {
    goto cleanup;
  }

  pa_size = fd_stat.st_size;
  pa_data_size = pa_size - sizeof(prop_area);

  
  prop_area *pa = mmap(NULL, pa_size, PROT_READ, MAP_SHARED, fd, 0);

  if (pa == MAP_FAILED) {
    goto cleanup;
  }

  if ((pa->magic != PROP_AREA_MAGIC) || (pa->version != PROP_AREA_VERSION && pa->version != PROP_AREA_VERSION_COMPAT)) {
    munmap(pa, pa_size);
    goto cleanup;
  }

  if (pa->version == PROP_AREA_VERSION_COMPAT) {
    compat_mode = true;
  }

  result = 0;

  __system_property_area__ = pa;
cleanup:
  if (fromFile) {
    close(fd);
  }

  return result;
}

int __system_properties_init()
{
  return map_prop_area();
}


Through the reading of the source code, you can find that the client through the mmap map, can read the contents of the property memory, but there is no permission to set the property. So how does the client set the properties? This involves the following property server.
Attribute server analysis

The init process starts a property server, and the client can only set properties by interacting with the property server.
Start the property server

First look at the content of the property server, it is started by the property_service_init_action function, the source code is as follows (/system/core/init/init.c&&property_service.c) :


static int property_service_init_action(int nargs, char **args)
{
  start_property_service();
  return 0;
}

static void load_override_properties()
{
#ifdef ALLOW_LOCAL_PROP_OVERRIDE
  char debuggable[PROP_VALUE_MAX];
  int ret;

  ret = property_get("ro.debuggable", debuggable);
  if (ret && (strcmp(debuggable, "1") == 0)) {
    load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
  }
#endif
}

static void load_properties(char *data)
{
  char *key, *value, *eol, *sol, *tmp;

  sol = data;
  while ((eol = strchr(sol, 'n'))) {
    key = sol;
    //Assign the pointer to the next line to 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);
  }
}

int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid)
{
  struct sockaddr_un addr;
  int fd, ret;
  char *secon;

  fd = socket(PF_UNIX, type, 0);
  if (fd < 0) {
    ERROR("Failed to open socket '%s': %sn", 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) {
    goto out_close;
  }

  ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
  if (ret) {
    goto out_unlink;
  }
  chown(addr.sun_path, uid, gid);
  chmod(addr.sun_path, perm);

  return fd;

out_unlink:
  unlink(addr.sun_path);
out_close:
  close(fd);
  return -1;
}

#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop"
#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"
#define PROP_PATH_FACTORY "/factory/factory.prop"

void start_property_service(void)
{
  int fd;

  load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
  load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
  load_override_properties();
  
  load_persistent_properties();

  fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
  if (fd < 0) return;
  fcntl(fd, F_SETFD, FD_CLOEXEC);
  fcntl(fd, F_SETFL, O_NONBLOCK);

  listen(fd, 8);
  property_set_fd = fd;
}


As you can see from the code above, in addition to prewriting the properties to the specified file (for example, system/build.prop), the init process also creates a UNIX Domain Socket to accept the client's request and build the properties. Where is the socket request processed?
The answer is: the for loop in init has been handled.

The server handles the set property request

The place where the property setting request is received is in the init process. The relevant code is as follows:


int main(int argc, char **argv)
{
  //. Omit irrelevant code

  for (;;) {
    // ...
    for (i = 0; i < fd_count; i ++) {
      if (ufds[i].fd == get_property_set_fd())
        handle_property_set_fd();
    }
  }
}

Attributes can be seen from the above code, when the server receives a client request, the init process will call handle_property_set_fd function, function position is: the system/core/init/property_service. C, let's take a look at the function realization of the source code:


void handle_property_set_fd()
{
  prop_msg msg;
  int s;
  int r;
  int res;
  struct ucred cr;
  struct sockaddr_un addr;
  socklen_t addr_size = sizeof(addr);
  socklen_t cr_size = sizeof(cr);
  char *source_ctx = NULL;

  //Receive TCP connection
  if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
    return;
  }

  //Receive client request data
  r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), 0));
  if (r != sizeof(prop_msg)) {
    ERROR("sys_prop: mis-match msg size received: %d expected : %d errno: %dn", r, sizeof(prop_msg), errno);
    close(s);
    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) {
      close(s);
      if (check_control_perms(msg.value, cr.uid, cr.gid, source_ctx)) {
        handle_control_message((char*) msg.name + 4, (char*) msg.value);
      } else {
        ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%dn", msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
      }
    } else {
      if (check_perms(msg.name, cr.uid, cr.gid, source_ctx)) {
        property_set((char *) msg.name, (char*) msg.value);
      }
      close(s);
    }
    break;
  default:
    close(s);
    break;
  }
}


When the client's permissions meet the requirements, init calls property_set for processing. The property_set source code is implemented as follows:


int property_set(const char *name, const char *value)
{
  prop_info *pi;
  int ret;

  size_t namelen = strlen(name);
  size_t valuelen = strlen(value);

  if (! is_legal_property_name(name, namelen)) return -1;
  if (valuelen >= PROP_VALUE_MAX) return -1;

  //Looks from the property space to see if the property value already exists
  pi = (prop_info*) __system_property_find(name);
  if (pi != 0) {
    //Properties starting with ro are not allowed to be modified after they have been set
    if (! strncmp(name, "ro.", 3)) return -1;

    __system_property_update(pi, value, valuelen);
  } else {
    ret = __system_property_add(name, namelen, value, valuelen);
  }

  //There are special properties that require special handling, such as those at the beginning of.net. And persist
  if (strncmp("net.", name, strlen("net.")) == 0) {
    if (strcmp("net.change", name) == 0) {
      return 0;
    }
    property_set("net.change", name);
  } else if (persistent_properties_loaded && strncmp("persist.", name, strlen("persist.")) == 0) {
    write_persistent_property(name, value);
  }
  property_changed(name, value);
  return 0;
}


So much for the property server side. Finally, let's look at how the client sends socket requests that set properties.
The client sends the request

When the client sets the property, the property_set(" sys.istest ", "true") method is called. From the above analysis shows that this method is different from server-side property_set method, this method must be sent a socket request, this method source location as: / system/core/libcutils/properties. C:


int property_set(const char *key, const char *value)
{
  return __system_property_set(key, value);
}

As you can see, the property_set call __system_property_set method, this method is located in the: / bionic/libc/bionic/system_properties c file:


struct prop_msg
{
  unsigned cmd;
  char name[PROP_NAME_MAX];
  char value[PROP_VALUE_MAX];
};
typedef struct prop_msg prop_msg;

static int send_prop_msg(prop_msg *msg)
{
  struct pollfd pollfds[1];
  struct sockaddr_un addr;
  socklen_t alen;
  size_t namelen;
  int s;
  int r;
  int result = -1;

  s = socket(AF_LOCAL, SOCK_STREAM, 0);
  if (s < 0) {
    return result;
  }

  memset(&addr, 0, sizeof(addr));
  namelen = strlen(property_service_socket);
  strlcpy(addr.sun_path, property_service_socket, sizeof(addr.sun_path));
  addr.sun_family = AF_LOCAL;
  alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;

  if (TEMP_FAILURE_RETRY(connect(s, (struct sockaddr *) &addr, alen)) < 0) {
    close(s);
    return result;
  }

  r = TEMP_FAILURE_RETRY(send(s, msg, sizeof(prop_msg), 0));

  close(s);
  return result;
}

int __system_property_set(const char *key, const char *value)
{
  int err;
  prop_msg msg;

  if (key == 0) return -1;
  if (value == 0) value = "";
  if (strlen(key) >= PROP_NAME_MAX) return -1;
  if (strlen(value) >= PROP_VALUE_MAX) return -1;

  memset(&msg, 0, sizeof(msg));
  msg.cmd = PROP_MSG_SETPROP;
  strlcpy(msg.name, key, sizeof(msg.name));
  strlcpy(msg.value, value, sizeof(msg.value));

  err = send_prop_msg(&msg);
  if (err < 0) {
    return err;
  }
  return 0;
}


Related articles: