init: make chmod/mkdir/chown not follow symlinks

This change brings init's do_chmod, mkdir, and do_chown into line
with open's O_NOFOLLOW semantics, causing them to fail when the
last element of their target path is a symlink.

Change-Id: If00e1a25cfe17ef6f738af4bf0541abd0c1b084b
diff --git a/init/builtins.c b/init/builtins.c
index ad52b19..41fc03e 100644
--- a/init/builtins.c
+++ b/init/builtins.c
@@ -75,6 +75,52 @@
     }
 }
 
+static int _chown(const char *path, unsigned int uid, unsigned int gid)
+{
+    int fd;
+    int ret;
+
+    fd = open(path, O_RDONLY | O_NOFOLLOW);
+    if (fd < 0) {
+        return -1;
+    }
+
+    ret = fchown(fd, uid, gid);
+    if (ret < 0) {
+        int errno_copy = errno;
+        close(fd);
+        errno = errno_copy;
+        return -1;
+    }
+
+    close(fd);
+
+    return 0;
+}
+
+static int _chmod(const char *path, mode_t mode)
+{
+    int fd;
+    int ret;
+
+    fd = open(path, O_RDONLY | O_NOFOLLOW);
+    if (fd < 0) {
+        return -1;
+    }
+
+    ret = fchmod(fd, mode);
+    if (ret < 0) {
+        int errno_copy = errno;
+        close(fd);
+        errno = errno_copy;
+        return -1;
+    }
+
+    close(fd);
+
+    return 0;
+}
+
 static int insmod(const char *filename, char *options)
 {
     void *module;
@@ -246,7 +292,7 @@
     ret = mkdir(args[1], mode);
     /* chmod in case the directory already exists */
     if (ret == -1 && errno == EEXIST) {
-        ret = chmod(args[1], mode);
+        ret = _chmod(args[1], mode);
     }
     if (ret == -1) {
         return -errno;
@@ -260,7 +306,7 @@
             gid = decode_uid(args[4]);
         }
 
-        if (chown(args[1], uid, gid)) {
+        if (_chown(args[1], uid, gid) < 0) {
             return -errno;
         }
     }
@@ -645,10 +691,10 @@
 int do_chown(int nargs, char **args) {
     /* GID is optional. */
     if (nargs == 3) {
-        if (chown(args[2], decode_uid(args[1]), -1) < 0)
+        if (_chown(args[2], decode_uid(args[1]), -1) < 0)
             return -errno;
     } else if (nargs == 4) {
-        if (chown(args[3], decode_uid(args[1]), decode_uid(args[2])))
+        if (_chown(args[3], decode_uid(args[1]), decode_uid(args[2])) < 0)
             return -errno;
     } else {
         return -1;
@@ -671,7 +717,7 @@
 
 int do_chmod(int nargs, char **args) {
     mode_t mode = get_mode(args[1]);
-    if (chmod(args[2], mode) < 0) {
+    if (_chmod(args[2], mode) < 0) {
         return -errno;
     }
     return 0;