diff --git a/include/cutils/fs.h b/include/cutils/fs.h index fd5296bb94f3112d0ddfa6e81fadb476c62c4b3a..d1d4cf28d6d79aae31495b6cf8f445b17d45bb2f 100644 --- a/include/cutils/fs.h +++ b/include/cutils/fs.h @@ -55,6 +55,14 @@ extern int fs_read_atomic_int(const char* path, int* value); */ 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 116526dca5662ca1125a94e077324e1e474ee8ad..8d1da21c83f4455e249e7478fcd39d5f3ad87513 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 @@ fail_closed: 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 be74f6fc2a376d80c896ae5c93c552fa6b27b7af..cd995742f8cc82639df2ffa83738025f25a43182 100644 --- a/rootdir/init.rc +++ b/rootdir/init.rc @@ -61,7 +61,7 @@ loglevel 3 # 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 9a1dd17b7ba22a9b7a00792cd9dd3927841b6520..3f1e268e6f3056431abf5e246a30ca273b22dd84 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 @@ static int str_hash(void *key) { 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 @@ static void attr_from_stat(struct fuse_attr *attr, const struct stat *s, const s 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 @@ static void derive_permissions_locked(struct fuse* fuse, struct node *parent, 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 @@ static bool check_caller_access_to_name(struct fuse* fuse, 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 @@ static bool check_caller_access_to_name(struct fuse* fuse, 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 @@ static void fuse_init(struct fuse *fuse, int fd, const char *source_path, 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 @@ static void fuse_init(struct fuse *fuse, int fd, const char *source_path, 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 @@ static int fuse_reply_entry(struct fuse* fuse, __u64 unique, 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 @@ static int handle_mkdir(struct fuse* fuse, struct fuse_handler* handler, 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); }