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);
     }
 }
 
diff --git a/init/util.c b/init/util.c
index 6f9a12e..a56ba0d 100644
--- a/init/util.c
+++ b/init/util.c
@@ -299,3 +299,81 @@
 
     return ts.tv_sec;
 }
+
+int mkdir_recursive(const char *pathname, mode_t mode)
+{
+    char buf[128];
+    const char *slash;
+    const char *p = pathname;
+    int width;
+    int ret;
+    struct stat info;
+
+    while ((slash = strchr(p, '/')) != NULL) {
+        width = slash - pathname;
+        p = slash + 1;
+        if (width < 0)
+            break;
+        if (width == 0)
+            continue;
+        if ((unsigned int)width > sizeof(buf) - 1) {
+            ERROR("path too long for mkdir_recursive\n");
+            return -1;
+        }
+        memcpy(buf, pathname, width);
+        buf[width] = 0;
+        if (stat(buf, &info) != 0) {
+            ret = mkdir(buf, mode);
+            if (ret && errno != EEXIST)
+                return ret;
+        }
+    }
+    ret = mkdir(pathname, mode);
+    if (ret && errno != EEXIST)
+        return ret;
+    return 0;
+}
+
+void sanitize(char *s)
+{
+    if (!s)
+        return;
+    while (isalnum(*s))
+        s++;
+    *s = 0;
+}
+void make_link(const char *oldpath, const char *newpath)
+{
+    int ret;
+    char buf[256];
+    char *slash;
+    int width;
+
+    slash = strrchr(newpath, '/');
+    if (!slash)
+        return;
+    width = slash - newpath;
+    if (width <= 0 || width > (int)sizeof(buf) - 1)
+        return;
+    memcpy(buf, newpath, width);
+    buf[width] = 0;
+    ret = mkdir_recursive(buf, 0755);
+    if (ret)
+        ERROR("Failed to create directory %s: %s (%d)\n", buf, strerror(errno), errno);
+
+    ret = symlink(oldpath, newpath);
+    if (ret && errno != EEXIST)
+        ERROR("Failed to symlink %s to %s: %s (%d)\n", oldpath, newpath, strerror(errno), errno);
+}
+
+void remove_link(const char *oldpath, const char *newpath)
+{
+    char path[256];
+    ssize_t ret;
+    ret = readlink(newpath, path, sizeof(path) - 1);
+    if (ret <= 0)
+        return;
+    path[ret] = 0;
+    if (!strcmp(path, oldpath))
+        unlink(newpath);
+}
diff --git a/init/util.h b/init/util.h
index 4f473ec..3dadfb9 100644
--- a/init/util.h
+++ b/init/util.h
@@ -24,4 +24,8 @@
 time_t gettime(void);
 unsigned int decode_uid(const char *s);
 
+int mkdir_recursive(const char *pathname, mode_t mode);
+void sanitize(char *p);
+void make_link(const char *oldpath, const char *newpath);
+void remove_link(const char *oldpath, const char *newpath);
 #endif