init: create symlinks to block device nodes

eMMC block device names may change based on the detection order of
the eMMC device and any other SD bus devices, such as a removable SD
card.

This patch adds support to init for:
  * Symlinks to block devices.  When a block device uevent is
    processed, if it starts with "/devices/platform", the platform
    driver name is parsed out, and symlinks to the block device are
    created in /dev/block/platform/<platform driver>/
  * Symlinks based on partition name and number.  If the uevent for
    a block device contains information on the partition name or
    number, symlinks are created under
    /dev/block/platform/<platform driver>/by-num/p<partition>
    and
    /dev/block/platform/<platform driver>/by-name/<partition name>

init.rc can then use a device path like the following to mount an
eMMC device:
/dev/block/platform/<platform>/by-name/system /system ro

Change-Id: Id11bb7cdf1e2ada7752a5bd671cbf87237b34ae2
diff --git a/init/devices.c b/init/devices.c
index a9ed141..3263e5e 100644
--- a/init/devices.c
+++ b/init/devices.c
@@ -34,6 +34,7 @@
 
 #include "init.h"
 #include "devices.h"
+#include "util.h"
 
 #define CMDLINE_PREFIX  "/dev"
 #define SYSFS_PREFIX    "/sys"
@@ -47,6 +48,8 @@
     const char *path;
     const char *subsystem;
     const char *firmware;
+    const char *partition_name;
+    int partition_num;
     int major;
     int minor;
 };
@@ -346,6 +349,8 @@
     uevent->firmware = "";
     uevent->major = -1;
     uevent->minor = -1;
+    uevent->partition_name = NULL;
+    uevent->partition_num = -1;
 
         /* currently ignoring SEQNUM */
     while(*msg) {
@@ -367,6 +372,12 @@
         } else if(!strncmp(msg, "MINOR=", 6)) {
             msg += 6;
             uevent->minor = atoi(msg);
+        } else if(!strncmp(msg, "PARTN=", 6)) {
+            msg += 6;
+            uevent->partition_num = atoi(msg);
+        } else if(!strncmp(msg, "PARTNAME=", 9)) {
+            msg += 9;
+            uevent->partition_name = msg;
         }
 
             /* advance to after the next \0 */
@@ -379,11 +390,76 @@
                     uevent->firmware, uevent->major, uevent->minor);
 }
 
+static char **parse_platform_block_device(struct uevent *uevent)
+{
+    const char *driver;
+    const char *path;
+    char *slash;
+    int width;
+    char buf[256];
+    char link_path[256];
+    int fd;
+    int link_num = 0;
+    int ret;
+    char *p;
+    unsigned int size;
+    struct stat info;
+
+    char **links = malloc(sizeof(char *) * 4);
+    if (!links)
+        return NULL;
+    memset(links, 0, sizeof(char *) * 4);
+
+    /* Drop "/devices/platform/" */
+    path = uevent->path;
+    driver = path + 18;
+    slash = strchr(driver, '/');
+    if (!slash)
+        goto err;
+    width = slash - driver;
+    if (width <= 0)
+        goto err;
+
+    snprintf(link_path, sizeof(link_path), "/dev/block/platform/%.*s",
+             width, driver);
+
+    if (uevent->partition_name) {
+        p = strdup(uevent->partition_name);
+        sanitize(p);
+        if (asprintf(&links[link_num], "%s/by-name/%s", link_path, p) > 0)
+            link_num++;
+        else
+            links[link_num] = NULL;
+        free(p);
+    }
+
+    if (uevent->partition_num >= 0) {
+        if (asprintf(&links[link_num], "%s/by-num/p%d", link_path, uevent->partition_num) > 0)
+            link_num++;
+        else
+            links[link_num] = NULL;
+    }
+
+    slash = strrchr(path, '/');
+    if (asprintf(&links[link_num], "%s/%s", link_path, slash + 1) > 0)
+        link_num++;
+    else
+        links[link_num] = NULL;
+
+    return links;
+
+err:
+    free(links);
+    return NULL;
+}
+
 static void handle_device_event(struct uevent *uevent)
 {
     char devpath[96];
     char *base, *name;
+    char **links = NULL;
     int block;
+    int i;
 
         /* if it's not a /dev device, nothing to do */
     if((uevent->major < 0) || (uevent->minor < 0))
@@ -404,6 +480,8 @@
         block = 1;
         base = "/dev/block/";
         mkdir(base, 0755);
+        if (!strncmp(uevent->path, "/devices/platform/", 18))
+            links = parse_platform_block_device(uevent);
     } else {
         block = 0;
             /* this should probably be configurable somehow */
@@ -441,12 +519,24 @@
 
     if(!strcmp(uevent->action, "add")) {
         make_device(devpath, block, uevent->major, uevent->minor);
-        return;
+        if (links) {
+            for (i = 0; links[i]; i++)
+                make_link(devpath, links[i]);
+        }
     }
 
     if(!strcmp(uevent->action, "remove")) {
+        if (links) {
+            for (i = 0; links[i]; i++)
+                remove_link(devpath, links[i]);
+        }
         unlink(devpath);
-        return;
+    }
+
+    if (links) {
+        for (i = 0; links[i]; i++)
+            free(links[i]);
+        free(links);
     }
 }