From c255f25ccb700880483c73d9ff823bf9540dd616 Mon Sep 17 00:00:00 2001 From: Jorge Lucangeli Obes Date: Tue, 12 Jul 2016 15:13:05 -0400 Subject: [PATCH] Extract the FUSE implementation from the main sdcard.c file. sdcard.c is a *really* big file. This makes it hard to do things like improving priv dropping or adding more sandboxing. Extract all FUSE-related code to a separate unit, fuse.{h|c}, which exports only two functions. Convert the rest of sdcard.c to C++ as sdcard.cpp. fuse.c is kept as C (at least for now) since interacting with the FUSE API is realistically easier from C. Bug: 30110940 Change-Id: I188bfdc21c184742117e07539adb09090d4d747c --- sdcard/Android.mk | 2 +- sdcard/{sdcard.c => fuse.c} | 566 +----------------------------------- sdcard/fuse.h | 203 +++++++++++++ sdcard/sdcard.cpp | 418 ++++++++++++++++++++++++++ 4 files changed, 625 insertions(+), 564 deletions(-) rename sdcard/{sdcard.c => fuse.c} (73%) create mode 100644 sdcard/fuse.h create mode 100644 sdcard/sdcard.cpp diff --git a/sdcard/Android.mk b/sdcard/Android.mk index ac5faa795..b12f3ee9f 100644 --- a/sdcard/Android.mk +++ b/sdcard/Android.mk @@ -2,7 +2,7 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := sdcard.c +LOCAL_SRC_FILES := sdcard.cpp fuse.c LOCAL_MODULE := sdcard LOCAL_CFLAGS := -Wall -Wno-unused-parameter -Werror LOCAL_SHARED_LIBRARIES := liblog libcutils libpackagelistparser diff --git a/sdcard/sdcard.c b/sdcard/fuse.c similarity index 73% rename from sdcard/sdcard.c rename to sdcard/fuse.c index f08c9d8b3..bbf72846b 100644 --- a/sdcard/sdcard.c +++ b/sdcard/fuse.c @@ -16,238 +16,14 @@ #define LOG_TAG "sdcard" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -/* README - * - * What is this? - * - * sdcard is a program that uses FUSE to emulate FAT-on-sdcard style - * directory permissions (all files are given fixed owner, group, and - * permissions at creation, owner, group, and permissions are not - * changeable, symlinks and hardlinks are not createable, etc. - * - * See usage() for command line options. - * - * It must be run as root, but will drop to requested UID/GID as soon as it - * mounts a filesystem. It will refuse to run if requested UID/GID are zero. - * - * Things I believe to be true: - * - * - ops that return a fuse_entry (LOOKUP, MKNOD, MKDIR, LINK, SYMLINK, - * CREAT) must bump that node's refcount - * - don't forget that FORGET can forget multiple references (req->nlookup) - * - if an op that returns a fuse_entry fails writing the reply to the - * kernel, you must rollback the refcount to reflect the reference the - * kernel did not actually acquire - * - * This daemon can also derive custom filesystem permissions based on directory - * structure when requested. These custom permissions support several features: - * - * - Apps can access their own files in /Android/data/com.example/ without - * requiring any additional GIDs. - * - Separate permissions for protecting directories like Pictures and Music. - * - Multi-user separation on the same physical device. - */ - -#define FUSE_TRACE 0 - -#if FUSE_TRACE -#define TRACE(x...) ALOGD(x) -#else -#define TRACE(x...) do {} while (0) -#endif - -#define ERROR(x...) ALOGE(x) +#include "fuse.h" #define FUSE_UNKNOWN_INO 0xffffffff -/* Maximum number of bytes to write in one request. */ -#define MAX_WRITE (256 * 1024) - -/* Maximum number of bytes to read in one request. */ -#define MAX_READ (128 * 1024) - -/* Largest possible request. - * The request size is bounded by the maximum size of a FUSE_WRITE request because it has - * the largest possible data payload. */ -#define MAX_REQUEST_SIZE (sizeof(struct fuse_in_header) + sizeof(struct fuse_write_in) + MAX_WRITE) - /* Pseudo-error constant used to indicate that no fuse status is needed * or that a reply has already been written. */ #define NO_STATUS 1 -/* Supplementary groups to execute with */ -static const gid_t kGroups[1] = { AID_PACKAGE_INFO }; - -/* Permission mode for a specific node. Controls how file permissions - * are derived for children nodes. */ -typedef enum { - /* Nothing special; this node should just inherit from its parent. */ - PERM_INHERIT, - /* This node is one level above a normal root; used for legacy layouts - * which use the first level to represent user_id. */ - PERM_PRE_ROOT, - /* This node is "/" */ - PERM_ROOT, - /* This node is "/Android" */ - PERM_ANDROID, - /* This node is "/Android/data" */ - PERM_ANDROID_DATA, - /* This node is "/Android/obb" */ - PERM_ANDROID_OBB, - /* This node is "/Android/media" */ - PERM_ANDROID_MEDIA, -} perm_t; - -struct handle { - int fd; -}; - -struct dirhandle { - DIR *d; -}; - -struct node { - __u32 refcount; - __u64 nid; - __u64 gen; - /* - * The inode number for this FUSE node. Note that this isn't stable across - * multiple invocations of the FUSE daemon. - */ - __u32 ino; - - /* State derived based on current position in hierarchy. */ - perm_t perm; - userid_t userid; - uid_t uid; - bool under_android; - - struct node *next; /* per-dir sibling list */ - struct node *child; /* first contained file by this dir */ - struct node *parent; /* containing directory */ - - size_t namelen; - char *name; - /* If non-null, this is the real name of the file in the underlying storage. - * This may differ from the field "name" only by case. - * strlen(actual_name) will always equal strlen(name), so it is safe to use - * namelen for both fields. - */ - char *actual_name; - - /* If non-null, an exact underlying path that should be grafted into this - * position. Used to support things like OBB. */ - char* graft_path; - size_t graft_pathlen; - - bool deleted; -}; - -static int str_hash(void *key) { - return hashmapHash(key, strlen(key)); -} - -/** Test if two string keys are equal ignoring case */ -static bool str_icase_equals(void *keyA, void *keyB) { - return strcasecmp(keyA, keyB) == 0; -} - -/* Global data for all FUSE mounts */ -struct fuse_global { - pthread_mutex_t lock; - - uid_t uid; - gid_t gid; - bool multi_user; - - char source_path[PATH_MAX]; - char obb_path[PATH_MAX]; - - Hashmap* package_to_appid; - - __u64 next_generation; - struct node root; - - /* Used to allocate unique inode numbers for fuse nodes. We use - * a simple counter based scheme where inode numbers from deleted - * nodes aren't reused. Note that inode allocations are not stable - * across multiple invocation of the sdcard daemon, but that shouldn't - * be a huge problem in practice. - * - * Note that we restrict inodes to 32 bit unsigned integers to prevent - * truncation on 32 bit processes when unsigned long long stat.st_ino is - * assigned to an unsigned long ino_t type in an LP32 process. - * - * Also note that fuse_attr and fuse_dirent inode values are 64 bits wide - * on both LP32 and LP64, but the fuse kernel code doesn't squash 64 bit - * inode numbers into 32 bit values on 64 bit kernels (see fuse_squash_ino - * in fs/fuse/inode.c). - * - * Accesses must be guarded by |lock|. - */ - __u32 inode_ctr; - - struct fuse* fuse_default; - struct fuse* fuse_read; - struct fuse* fuse_write; -}; - -/* Single FUSE mount */ -struct fuse { - struct fuse_global* global; - - char dest_path[PATH_MAX]; - - int fd; - - gid_t gid; - mode_t mask; -}; - -/* Private data used by a single FUSE handler */ -struct fuse_handler { - struct fuse* fuse; - int token; - - /* To save memory, we never use the contents of the request buffer and the read - * buffer at the same time. This allows us to share the underlying storage. */ - union { - __u8 request_buffer[MAX_REQUEST_SIZE]; - __u8 read_buffer[MAX_READ + PAGE_SIZE]; - }; -}; - static inline void *id_to_ptr(__u64 nid) { return (void *) (uintptr_t) nid; @@ -507,7 +283,7 @@ static void derive_permissions_locked(struct fuse* fuse, struct node *parent, } } -static void derive_permissions_recursive_locked(struct fuse* fuse, struct node *parent) { +void derive_permissions_recursive_locked(struct fuse* fuse, struct node *parent) { struct node *node; for (node = parent->child; node; node = node->next) { derive_permissions_locked(fuse, parent, node); @@ -1608,7 +1384,7 @@ static int handle_fuse_request(struct fuse *fuse, struct fuse_handler* handler, } } -static void handle_fuse_requests(struct fuse_handler* handler) +void handle_fuse_requests(struct fuse_handler* handler) { struct fuse* fuse = handler->fuse; for (;;) { @@ -1651,339 +1427,3 @@ static void handle_fuse_requests(struct fuse_handler* handler) } } } - -static void* start_handler(void* data) -{ - struct fuse_handler* handler = data; - handle_fuse_requests(handler); - return NULL; -} - -static bool remove_str_to_int(void *key, void *value, void *context) { - Hashmap* map = context; - hashmapRemove(map, key); - free(key); - return true; -} - -static bool package_parse_callback(pkg_info *info, void *userdata) { - struct fuse_global *global = (struct fuse_global *)userdata; - - char* name = strdup(info->name); - hashmapPut(global->package_to_appid, name, (void*) (uintptr_t) info->uid); - packagelist_free(info); - return true; -} - -static bool read_package_list(struct fuse_global* global) { - pthread_mutex_lock(&global->lock); - - hashmapForEach(global->package_to_appid, remove_str_to_int, global->package_to_appid); - - bool rc = packagelist_parse(package_parse_callback, global); - TRACE("read_package_list: found %zu packages\n", - hashmapSize(global->package_to_appid)); - - /* Regenerate ownership details using newly loaded mapping */ - derive_permissions_recursive_locked(global->fuse_default, &global->root); - - pthread_mutex_unlock(&global->lock); - - return rc; -} - -static void watch_package_list(struct fuse_global* global) { - struct inotify_event *event; - char event_buf[512]; - - int nfd = inotify_init(); - if (nfd < 0) { - ERROR("inotify_init failed: %s\n", strerror(errno)); - return; - } - - bool active = false; - while (1) { - if (!active) { - int res = inotify_add_watch(nfd, PACKAGES_LIST_FILE, IN_DELETE_SELF); - if (res == -1) { - if (errno == ENOENT || errno == EACCES) { - /* Framework may not have created yet, sleep and retry */ - ERROR("missing \"%s\"; retrying\n", PACKAGES_LIST_FILE); - sleep(3); - continue; - } else { - ERROR("inotify_add_watch failed: %s\n", strerror(errno)); - return; - } - } - - /* Watch above will tell us about any future changes, so - * read the current state. */ - if (read_package_list(global) == false) { - ERROR("read_package_list failed\n"); - return; - } - active = true; - } - - int event_pos = 0; - int res = read(nfd, event_buf, sizeof(event_buf)); - if (res < (int) sizeof(*event)) { - if (errno == EINTR) - continue; - ERROR("failed to read inotify event: %s\n", strerror(errno)); - return; - } - - while (res >= (int) sizeof(*event)) { - int event_size; - event = (struct inotify_event *) (event_buf + event_pos); - - TRACE("inotify event: %08x\n", event->mask); - if ((event->mask & IN_IGNORED) == IN_IGNORED) { - /* Previously watched file was deleted, probably due to move - * that swapped in new data; re-arm the watch and read. */ - active = false; - } - - event_size = sizeof(*event) + event->len; - res -= event_size; - event_pos += event_size; - } - } -} - -static int usage() { - ERROR("usage: sdcard [OPTIONS]