Remove mkdir() side effect, add .nomedia, utils.

Before this change, FUSE lookup() would have the side effect of
creating the directory on behalf of apps.  This resulted in most
directories being created just by Settings trying to measure disk
space.  Instead, we're switching to have vold do directory creation
when an app doesn't have enough permissions.

Create fs_mkdirs() utility to create all parent directories in a
path as needed.  Allow traversal (+x) into /storage directories.

Fix FUSE derived permissions to be case insensitive.  Mark well-known
directories as .nomedia when created.

Bug: 10577808, 10330221
Change-Id: I53114f2e63ffbe6de4ba6a72d94a232523231cad
diff --git a/include/cutils/fs.h b/include/cutils/fs.h
index fd5296b..d1d4cf2 100644
--- a/include/cutils/fs.h
+++ b/include/cutils/fs.h
@@ -55,6 +55,14 @@
  */
 extern int fs_write_atomic_int(const char* path, int value);
 
+/*
+ * Ensure that all directories along given path exist, creating parent
+ * directories as needed.  Validates that given path is absolute and that
+ * it contains no relative "." or ".." paths or symlinks.  Last path segment
+ * is treated as filename and ignored, unless the path ends with "/".
+ */
+extern int fs_mkdirs(const char* path, mode_t mode);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/libcutils/fs.c b/libcutils/fs.c
index 116526d..8d1da21 100644
--- a/libcutils/fs.c
+++ b/libcutils/fs.c
@@ -16,6 +16,11 @@
 
 #define LOG_TAG "cutils"
 
+/* These defines are only needed because prebuilt headers are out of date */
+#define __USE_XOPEN2K8 1
+#define _ATFILE_SOURCE 1
+#define _GNU_SOURCE 1
+
 #include <cutils/fs.h>
 #include <cutils/log.h>
 
@@ -27,6 +32,7 @@
 #include <string.h>
 #include <limits.h>
 #include <stdlib.h>
+#include <dirent.h>
 
 #define ALL_PERMS (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
 #define BUF_SIZE 64
@@ -141,3 +147,87 @@
     unlink(temp);
     return -1;
 }
+
+int fs_mkdirs(const char* path, mode_t mode) {
+    int res = 0;
+    int fd = 0;
+    struct stat sb;
+    char* buf = strdup(path);
+
+    if (*buf != '/') {
+        ALOGE("Relative paths are not allowed: %s", buf);
+        res = -EINVAL;
+        goto done;
+    }
+
+    if ((fd = open("/", 0)) == -1) {
+        ALOGE("Failed to open(/): %s", strerror(errno));
+        res = -errno;
+        goto done;
+    }
+
+    char* segment = buf + 1;
+    char* p = segment;
+    while (*p != '\0') {
+        if (*p == '/') {
+            *p = '\0';
+
+            if (!strcmp(segment, "..") || !strcmp(segment, ".") || !strcmp(segment, "")) {
+                ALOGE("Invalid path: %s", buf);
+                res = -EINVAL;
+                goto done_close;
+            }
+
+            if (fstatat(fd, segment, &sb, AT_SYMLINK_NOFOLLOW) != 0) {
+                if (errno == ENOENT) {
+                    /* Nothing there yet; let's create it! */
+                    if (mkdirat(fd, segment, mode) != 0) {
+                        if (errno == EEXIST) {
+                            /* We raced with someone; ignore */
+                        } else {
+                            ALOGE("Failed to mkdirat(%s): %s", buf, strerror(errno));
+                            res = -errno;
+                            goto done_close;
+                        }
+                    }
+                } else {
+                    ALOGE("Failed to fstatat(%s): %s", buf, strerror(errno));
+                    res = -errno;
+                    goto done_close;
+                }
+            } else {
+                if (S_ISLNK(sb.st_mode)) {
+                    ALOGE("Symbolic links are not allowed: %s", buf);
+                    res = -ELOOP;
+                    goto done_close;
+                }
+                if (!S_ISDIR(sb.st_mode)) {
+                    ALOGE("Existing segment not a directory: %s", buf);
+                    res = -ENOTDIR;
+                    goto done_close;
+                }
+            }
+
+            /* Yay, segment is ready for us to step into */
+            int next_fd;
+            if ((next_fd = openat(fd, segment, 0)) == -1) {
+                ALOGE("Failed to openat(%s): %s", buf, strerror(errno));
+                res = -errno;
+                goto done_close;
+            }
+
+            close(fd);
+            fd = next_fd;
+
+            *p = '/';
+            segment = p + 1;
+        }
+        p++;
+    }
+
+done_close:
+    close(fd);
+done:
+    free(buf);
+    return res;
+}
diff --git a/rootdir/init.rc b/rootdir/init.rc
index be74f6f..cd99574 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -61,7 +61,7 @@
 
     # See storage config details at http://source.android.com/tech/storage/
     mkdir /mnt/shell 0700 shell shell
-    mkdir /storage 0050 root sdcard_r
+    mkdir /storage 0751 root sdcard_r
 
     # Directory for putting things only root should see.
     mkdir /mnt/secure 0700 root root
diff --git a/sdcard/sdcard.c b/sdcard/sdcard.c
index 9a1dd17..3f1e268 100644
--- a/sdcard/sdcard.c
+++ b/sdcard/sdcard.c
@@ -32,6 +32,7 @@
 #include <sys/resource.h>
 #include <sys/inotify.h>
 
+#include <cutils/fs.h>
 #include <cutils/hashmap.h>
 #include <cutils/multiuser.h>
 
@@ -193,8 +194,9 @@
     return hashmapHash(key, strlen(key));
 }
 
-static bool str_equals(void *keyA, void *keyB) {
-    return strcmp(keyA, keyB) == 0;
+/** Test if two string keys are equal ignoring case */
+static bool str_icase_equals(void *keyA, void *keyB) {
+    return strcasecmp(keyA, keyB) == 0;
 }
 
 static int int_hash(void *key) {
@@ -401,6 +403,20 @@
     attr->mode = (attr->mode & S_IFMT) | filtered_mode;
 }
 
+static int touch(char* path, mode_t mode) {
+    int fd = open(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, mode);
+    if (fd == -1) {
+        if (errno == EEXIST) {
+            return 0;
+        } else {
+            ERROR("Failed to open(%s): %s\n", path, strerror(errno));
+            return -1;
+        }
+    }
+    close(fd);
+    return 0;
+}
+
 static void derive_permissions_locked(struct fuse* fuse, struct node *parent,
         struct node *node) {
     appid_t appid;
@@ -429,37 +445,37 @@
     case PERM_ROOT:
         /* Assume masked off by default. */
         node->mode = 0770;
-        if (!strcmp(node->name, "Android")) {
+        if (!strcasecmp(node->name, "Android")) {
             /* App-specific directories inside; let anyone traverse */
             node->perm = PERM_ANDROID;
             node->mode = 0771;
         } else if (fuse->split_perms) {
-            if (!strcmp(node->name, "DCIM")
-                    || !strcmp(node->name, "Pictures")) {
+            if (!strcasecmp(node->name, "DCIM")
+                    || !strcasecmp(node->name, "Pictures")) {
                 node->gid = AID_SDCARD_PICS;
-            } else if (!strcmp(node->name, "Alarms")
-                    || !strcmp(node->name, "Movies")
-                    || !strcmp(node->name, "Music")
-                    || !strcmp(node->name, "Notifications")
-                    || !strcmp(node->name, "Podcasts")
-                    || !strcmp(node->name, "Ringtones")) {
+            } else if (!strcasecmp(node->name, "Alarms")
+                    || !strcasecmp(node->name, "Movies")
+                    || !strcasecmp(node->name, "Music")
+                    || !strcasecmp(node->name, "Notifications")
+                    || !strcasecmp(node->name, "Podcasts")
+                    || !strcasecmp(node->name, "Ringtones")) {
                 node->gid = AID_SDCARD_AV;
             }
         }
         break;
     case PERM_ANDROID:
-        if (!strcmp(node->name, "data")) {
+        if (!strcasecmp(node->name, "data")) {
             /* App-specific directories inside; let anyone traverse */
             node->perm = PERM_ANDROID_DATA;
             node->mode = 0771;
-        } else if (!strcmp(node->name, "obb")) {
+        } else if (!strcasecmp(node->name, "obb")) {
             /* App-specific directories inside; let anyone traverse */
             node->perm = PERM_ANDROID_OBB;
             node->mode = 0771;
             /* Single OBB directory is always shared */
             node->graft_path = fuse->obbpath;
             node->graft_pathlen = strlen(fuse->obbpath);
-        } else if (!strcmp(node->name, "user")) {
+        } else if (!strcasecmp(node->name, "user")) {
             /* User directories must only be accessible to system, protected
              * by sdcard_all. Zygote will bind mount the appropriate user-
              * specific path. */
@@ -505,9 +521,9 @@
         const char* name, int mode, bool has_rw) {
     /* Always block security-sensitive files at root */
     if (parent_node && parent_node->perm == PERM_ROOT) {
-        if (!strcmp(name, "autorun.inf")
-                || !strcmp(name, ".android_secure")
-                || !strcmp(name, "android_secure")) {
+        if (!strcasecmp(name, "autorun.inf")
+                || !strcasecmp(name, ".android_secure")
+                || !strcasecmp(name, "android_secure")) {
             return false;
         }
     }
@@ -517,8 +533,9 @@
         return true;
     }
 
-    /* Root or shell always have access */
-    if (hdr->uid == 0 || hdr->uid == AID_SHELL) {
+    /* Root always has access; access for any other UIDs should always
+     * be controlled through packages.list. */
+    if (hdr->uid == 0) {
         return true;
     }
 
@@ -696,9 +713,10 @@
         fuse->root.perm = PERM_LEGACY_PRE_ROOT;
         fuse->root.mode = 0771;
         fuse->root.gid = fs_gid;
-        fuse->package_to_appid = hashmapCreate(256, str_hash, str_equals);
+        fuse->package_to_appid = hashmapCreate(256, str_hash, str_icase_equals);
         fuse->appid_with_rw = hashmapCreate(128, int_hash, int_equals);
         snprintf(fuse->obbpath, sizeof(fuse->obbpath), "%s/obb", source_path);
+        fs_prepare_dir(fuse->obbpath, 0775, getuid(), getgid());
         break;
     case DERIVE_UNIFIED:
         /* Unified multiuser layout which places secondary user_id under
@@ -706,7 +724,7 @@
         fuse->root.perm = PERM_ROOT;
         fuse->root.mode = 0771;
         fuse->root.gid = fs_gid;
-        fuse->package_to_appid = hashmapCreate(256, str_hash, str_equals);
+        fuse->package_to_appid = hashmapCreate(256, str_hash, str_icase_equals);
         fuse->appid_with_rw = hashmapCreate(128, int_hash, int_equals);
         snprintf(fuse->obbpath, sizeof(fuse->obbpath), "%s/Android/obb", source_path);
         break;
@@ -752,36 +770,7 @@
     struct stat s;
 
     if (lstat(path, &s) < 0) {
-        /* But wait! We'll automatically create a directory if its
-         * a valid package name under data or obb, since apps may not
-         * have enough permissions to create for themselves. */
-        if (errno == ENOENT && (parent->perm == PERM_ANDROID_DATA
-                || parent->perm == PERM_ANDROID_OBB)) {
-            TRACE("automatically creating %s\n", path);
-
-            pthread_mutex_lock(&fuse->lock);
-            bool validPackage = hashmapContainsKey(fuse->package_to_appid, (char*) name);
-            pthread_mutex_unlock(&fuse->lock);
-
-            if (!validPackage) {
-                return -ENOENT;
-            }
-            if (mkdir(path, 0775) == -1) {
-                /* We might have raced with ourselves and already created */
-                if (errno != EEXIST) {
-                    ERROR("failed to mkdir(%s): %s\n", name, strerror(errno));
-                    return -ENOENT;
-                }
-            }
-
-            /* It should exist this time around! */
-            if (lstat(path, &s) < 0) {
-                ERROR("failed to lstat(%s): %s\n", name, strerror(errno));
-                return -errno;
-            }
-        } else {
-            return -errno;
-        }
+        return -errno;
     }
 
     pthread_mutex_lock(&fuse->lock);
@@ -1006,6 +995,25 @@
     if (mkdir(child_path, mode) < 0) {
         return -errno;
     }
+
+    /* When creating /Android/data and /Android/obb, mark them as .nomedia */
+    if (parent_node->perm == PERM_ANDROID && !strcasecmp(name, "data")) {
+        char nomedia[PATH_MAX];
+        snprintf(nomedia, PATH_MAX, "%s/.nomedia", child_path);
+        if (touch(nomedia, 0664) != 0) {
+            ERROR("Failed to touch(%s): %s\n", nomedia, strerror(errno));
+            return -ENOENT;
+        }
+    }
+    if (parent_node->perm == PERM_ANDROID && !strcasecmp(name, "obb")) {
+        char nomedia[PATH_MAX];
+        snprintf(nomedia, PATH_MAX, "%s/.nomedia", fuse->obbpath);
+        if (touch(nomedia, 0664) != 0) {
+            ERROR("Failed to touch(%s): %s\n", nomedia, strerror(errno));
+            return -ENOENT;
+        }
+    }
+
     return fuse_reply_entry(fuse, hdr->unique, parent_node, name, actual_name, child_path);
 }