diff options
Diffstat (limited to 'sdcard/sdcard.c')
-rw-r--r-- | sdcard/sdcard.c | 337 |
1 files changed, 287 insertions, 50 deletions
diff --git a/sdcard/sdcard.c b/sdcard/sdcard.c index 82e6354..7112ebf 100644 --- a/sdcard/sdcard.c +++ b/sdcard/sdcard.c @@ -25,6 +25,7 @@ #include <sys/statfs.h> #include <sys/uio.h> #include <dirent.h> +#include <ctype.h> #include <private/android_filesystem_config.h> @@ -55,10 +56,6 @@ * kernel, you must rollback the refcount to reflect the reference the * kernel did not actually acquire * - * - * Bugs: - * - * - need to move/rename node on RENAME */ #define FUSE_TRACE 0 @@ -89,15 +86,21 @@ struct node { __u64 nid; __u64 gen; - struct node *next; - struct node *child; - struct node *all; - struct node *parent; + struct node *next; /* per-dir sibling list */ + struct node *child; /* first contained file by this dir */ + struct node *all; /* global node list */ + struct node *parent; /* containing directory */ __u32 refcount; __u32 namelen; - char name[1]; + 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; }; struct fuse { @@ -112,10 +115,24 @@ struct fuse { char rootpath[1024]; }; +static unsigned uid = -1; +static unsigned gid = -1; + #define PATH_BUFFER_SIZE 1024 -char *node_get_path(struct node *node, char *buf, const char *name) +#define NO_CASE_SENSITIVE_MATCH 0 +#define CASE_SENSITIVE_MATCH 1 + +/* + * Get the real-life absolute path to a node. + * node: start at this node + * buf: storage for returned string + * name: append this string to path if set + */ +char *do_node_get_path(struct node *node, char *buf, const char *name, int match_case_insensitive) { + struct node *in_node = node; + const char *in_name = name; char *out = buf + PATH_BUFFER_SIZE - 1; int len; out[0] = 0; @@ -126,7 +143,7 @@ char *node_get_path(struct node *node, char *buf, const char *name) } while (node) { - name = node->name; + name = (node->actual_name ? node->actual_name : node->name); len = node->namelen; node = node->parent; start: @@ -134,11 +151,45 @@ char *node_get_path(struct node *node, char *buf, const char *name) return 0; out -= len; memcpy(out, name, len); - out --; - out[0] = '/'; + /* avoid double slash at beginning of path */ + if (out[0] != '/') { + out --; + out[0] = '/'; + } } - return out; + /* If we are searching for a file within node (rather than computing node's path) + * and fail, then we need to look for a case insensitive match. + */ + if (in_name && match_case_insensitive && access(out, F_OK) != 0) { + char *path, buffer[PATH_BUFFER_SIZE]; + DIR* dir; + struct dirent* entry; + path = do_node_get_path(in_node, buffer, NULL, NO_CASE_SENSITIVE_MATCH); + dir = opendir(path); + if (!dir) { + ERROR("opendir %s failed: %s", path, strerror(errno)); + return out; + } + + while ((entry = readdir(dir))) { + if (!strcasecmp(entry->d_name, in_name)) { + /* we have a match - replace the name */ + len = strlen(in_name); + memcpy(buf + PATH_BUFFER_SIZE - len - 1, entry->d_name, len); + break; + } + } + closedir(dir); + } + + return out; +} + +char *node_get_path(struct node *node, char *buf, const char *name) +{ + /* We look for case insensitive matches by default */ + return do_node_get_path(node, buf, name, CASE_SENSITIVE_MATCH); } void attr_from_stat(struct fuse_attr *attr, struct stat *s) @@ -189,28 +240,84 @@ int node_get_attr(struct node *node, struct fuse_attr *attr) return 0; } +static void add_node_to_parent(struct node *node, struct node *parent) { + node->parent = parent; + node->next = parent->child; + parent->child = node; + parent->refcount++; +} + +/* Check to see if our parent directory already has a file with a name + * that differs only by case. If we find one, store it in the actual_name + * field so node_get_path will map it to this file in the underlying storage. + */ +static void node_find_actual_name(struct node *node) +{ + char *path, buffer[PATH_BUFFER_SIZE]; + const char *node_name = node->name; + DIR* dir; + struct dirent* entry; + + if (!node->parent) return; + + path = node_get_path(node->parent, buffer, 0); + dir = opendir(path); + if (!dir) { + ERROR("opendir %s failed: %s", path, strerror(errno)); + return; + } + + while ((entry = readdir(dir))) { + const char *test_name = entry->d_name; + if (strcmp(test_name, node_name) && !strcasecmp(test_name, node_name)) { + /* we have a match - differs but only by case */ + node->actual_name = strdup(test_name); + if (!node->actual_name) { + ERROR("strdup failed - out of memory\n"); + exit(1); + } + break; + } + } + closedir(dir); +} + struct node *node_create(struct node *parent, const char *name, __u64 nid, __u64 gen) { struct node *node; int namelen = strlen(name); - node = calloc(1, sizeof(struct node) + namelen); + node = calloc(1, sizeof(struct node)); if (node == 0) { return 0; } + node->name = malloc(namelen + 1); + if (node->name == 0) { + free(node); + return 0; + } node->nid = nid; node->gen = gen; - node->parent = parent; - node->next = parent->child; - parent->child = node; + add_node_to_parent(node, parent); memcpy(node->name, name, namelen + 1); node->namelen = namelen; - parent->refcount++; - + node_find_actual_name(node); return node; } +static char *rename_node(struct node *node, const char *name) +{ + node->namelen = strlen(name); + char *newname = realloc(node->name, node->namelen + 1); + if (newname == 0) + return 0; + node->name = newname; + memcpy(node->name, name, node->namelen + 1); + node_find_actual_name(node); + return node->name; +} + void fuse_init(struct fuse *fuse, int fd, const char *path) { fuse->fd = fd; @@ -227,8 +334,8 @@ void fuse_init(struct fuse *fuse, int fd, const char *path) fuse->root.all = 0; fuse->root.refcount = 2; - strcpy(fuse->root.name, path); - fuse->root.namelen = strlen(fuse->root.name); + fuse->root.name = 0; + rename_node(&fuse->root, path); } static inline void *id_to_ptr(__u64 nid) @@ -271,6 +378,37 @@ struct node *lookup_child_by_inode(struct node *node, __u64 nid) return 0; } +static void dec_refcount(struct node *node) { + if (node->refcount > 0) { + node->refcount--; + TRACE("dec_refcount %p(%s) -> %d\n", node, node->name, node->refcount); + } else { + ERROR("Zero refcnt %p\n", node); + } + } + +static struct node *remove_child(struct node *parent, __u64 nid) +{ + struct node *prev = 0; + struct node *node; + + for (node = parent->child; node; node = node->next) { + if (node->nid == nid) { + if (prev) { + prev->next = node->next; + } else { + parent->child = node->next; + } + node->next = 0; + node->parent = 0; + dec_refcount(parent); + return node; + } + prev = node; + } + return 0; +} + struct node *node_lookup(struct fuse *fuse, struct node *parent, const char *name, struct fuse_attr *attr) { @@ -305,7 +443,7 @@ struct node *node_lookup(struct fuse *fuse, struct node *parent, const char *nam void node_release(struct node *node) { TRACE("RELEASE %p (%s) rc=%d\n", node, node->name, node->refcount); - node->refcount--; + dec_refcount(node); if (node->refcount == 0) { if (node->parent->child == node) { node->parent->child = node->parent->child->next; @@ -326,8 +464,10 @@ void node_release(struct node *node) node->next = 0; /* TODO: remove debugging - poison memory */ - memset(node, 0xef, sizeof(*node) + strlen(node->name)); - + memset(node->name, 0xef, node->namelen); + free(node->name); + free(node->actual_name); + memset(node, 0xfc, sizeof(*node)); free(node); } } @@ -427,7 +567,7 @@ void handle_fuse_request(struct fuse *fuse, struct fuse_in_header *hdr, void *da struct fuse_getattr_in *req = data; struct fuse_attr_out out; - TRACE("GETATTR flags=%x fh=%llx\n",req->getattr_flags, req->fh); + TRACE("GETATTR flags=%x fh=%llx\n", req->getattr_flags, req->fh); memset(&out, 0, sizeof(out)); node_get_attr(node, &out.attr); @@ -439,15 +579,60 @@ void handle_fuse_request(struct fuse *fuse, struct fuse_in_header *hdr, void *da case FUSE_SETATTR: { /* setattr_in -> attr_out */ struct fuse_setattr_in *req = data; struct fuse_attr_out out; + char *path, buffer[PATH_BUFFER_SIZE]; + int res = 0; + struct timespec times[2]; + TRACE("SETATTR fh=%llx id=%llx valid=%x\n", req->fh, hdr->nodeid, req->valid); - /* XXX */ + /* XXX: incomplete implementation on purpose. chmod/chown + * should NEVER be implemented.*/ + + path = node_get_path(node, buffer, 0); + if (req->valid & FATTR_SIZE) + res = truncate(path, req->size); + if (res) + goto getout; + + /* Handle changing atime and mtime. If FATTR_ATIME_and FATTR_ATIME_NOW + * are both set, then set it to the current time. Else, set it to the + * time specified in the request. Same goes for mtime. Use utimensat(2) + * as it allows ATIME and MTIME to be changed independently, and has + * nanosecond resolution which fuse also has. + */ + if (req->valid & (FATTR_ATIME | FATTR_MTIME)) { + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_nsec = UTIME_OMIT; + if (req->valid & FATTR_ATIME) { + if (req->valid & FATTR_ATIME_NOW) { + times[0].tv_nsec = UTIME_NOW; + } else { + times[0].tv_sec = req->atime; + times[0].tv_nsec = req->atimensec; + } + } + if (req->valid & FATTR_MTIME) { + if (req->valid & FATTR_MTIME_NOW) { + times[1].tv_nsec = UTIME_NOW; + } else { + times[1].tv_sec = req->mtime; + times[1].tv_nsec = req->mtimensec; + } + } + TRACE("Calling utimensat on %s with atime %ld, mtime=%ld\n", path, times[0].tv_sec, times[1].tv_sec); + res = utimensat(-1, path, times, 0); + } + getout: memset(&out, 0, sizeof(out)); node_get_attr(node, &out.attr); out.attr_valid = 10; - fuse_reply(fuse, hdr->unique, &out, sizeof(out)); + + if (res) + fuse_status(fuse, hdr->unique, -errno); + else + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); return; } // case FUSE_READLINK: @@ -457,6 +642,7 @@ void handle_fuse_request(struct fuse *fuse, struct fuse_in_header *hdr, void *da char *path, buffer[PATH_BUFFER_SIZE]; char *name = ((char*) data) + sizeof(*req); int res; + TRACE("MKNOD %s @ %llx\n", name, hdr->nodeid); path = node_get_path(node, buffer, name); @@ -475,6 +661,7 @@ void handle_fuse_request(struct fuse *fuse, struct fuse_in_header *hdr, void *da char *path, buffer[PATH_BUFFER_SIZE]; char *name = ((char*) data) + sizeof(*req); int res; + TRACE("MKDIR %s @ %llx 0%o\n", name, hdr->nodeid, req->mode); path = node_get_path(node, buffer, name); @@ -511,19 +698,49 @@ void handle_fuse_request(struct fuse *fuse, struct fuse_in_header *hdr, void *da char *newname = oldname + strlen(oldname) + 1; char *oldpath, oldbuffer[PATH_BUFFER_SIZE]; char *newpath, newbuffer[PATH_BUFFER_SIZE]; - struct node *newnode; + struct node *target; + struct node *newparent; int res; - newnode = lookup_by_inode(fuse, req->newdir); - if (!newnode) { + TRACE("RENAME %s->%s @ %llx\n", oldname, newname, hdr->nodeid); + + target = lookup_child_by_name(node, oldname); + if (!target) { fuse_status(fuse, hdr->unique, -ENOENT); return; } - oldpath = node_get_path(node, oldbuffer, oldname); - newpath = node_get_path(newnode, newbuffer, newname); + + newparent = lookup_by_inode(fuse, req->newdir); + if (!newparent) { + fuse_status(fuse, hdr->unique, -ENOENT); + return; + } + if (newparent == node) { + /* Special case for renaming a file where destination + * is same path differing only by case. + * In this case we don't want to look for a case insensitive match. + * This allows commands like "mv foo FOO" to work as expected. + */ + newpath = do_node_get_path(newparent, newbuffer, newname, NO_CASE_SENSITIVE_MATCH); + } else { + newpath = node_get_path(newparent, newbuffer, newname); + } + + if (!remove_child(node, target->nid)) { + ERROR("RENAME remove_child not found"); + fuse_status(fuse, hdr->unique, -ENOENT); + return; + } + if (!rename_node(target, newname)) { + fuse_status(fuse, hdr->unique, -ENOMEM); + return; + } + add_node_to_parent(target, newparent); res = rename(oldpath, newpath); + TRACE("RENAME result %d\n", res); + fuse_status(fuse, hdr->unique, res ? -errno : 0); return; } @@ -565,7 +782,7 @@ void handle_fuse_request(struct fuse *fuse, struct fuse_in_header *hdr, void *da fuse_status(fuse, hdr->unique, -EINVAL); return; } - res = pread(h->fd, buffer, req->size, req->offset); + res = pread64(h->fd, buffer, req->size, req->offset); if (res < 0) { fuse_status(fuse, hdr->unique, errno); return; @@ -579,7 +796,7 @@ void handle_fuse_request(struct fuse *fuse, struct fuse_in_header *hdr, void *da struct handle *h = id_to_ptr(req->fh); int res; TRACE("WRITE %p(%d) %u@%llu\n", h, h->fd, req->size, req->offset); - res = pwrite(h->fd, ((char*) data) + sizeof(*req), req->size, req->offset); + res = pwrite64(h->fd, ((char*) data) + sizeof(*req), req->size, req->offset); if (res < 0) { fuse_status(fuse, hdr->unique, errno); return; @@ -661,13 +878,19 @@ void handle_fuse_request(struct fuse *fuse, struct fuse_in_header *hdr, void *da struct dirent *de; struct dirhandle *h = id_to_ptr(req->fh); TRACE("READDIR %p\n", h); + if (req->offset == 0) { + /* rewinddir() might have been called above us, so rewind here too */ + TRACE("calling rewinddir()\n"); + rewinddir(h->d); + } de = readdir(h->d); if (!de) { fuse_status(fuse, hdr->unique, 0); return; } fde->ino = FUSE_UNKNOWN_INO; - fde->off = 0; + /* increment the offset so we can detect when rewinddir() seeks back to the beginning */ + fde->off = req->offset + 1; fde->type = de->d_type; fde->namelen = strlen(de->d_name); memcpy(fde->name, de->d_name, fde->namelen + 1); @@ -695,7 +918,7 @@ void handle_fuse_request(struct fuse *fuse, struct fuse_in_header *hdr, void *da out.major = FUSE_KERNEL_VERSION; out.minor = FUSE_KERNEL_MINOR_VERSION; out.max_readahead = req->max_readahead; - out.flags = 0; + out.flags = FUSE_ATOMIC_O_TRUNC; out.max_background = 32; out.congestion_threshold = 32; out.max_write = 256 * 1024; @@ -735,30 +958,44 @@ void handle_fuse_requests(struct fuse *fuse) } } +static int usage() +{ + ERROR("usage: sdcard [-l -f] <path> <uid> <gid>\n\n\t-l force file names to lower case when creating new files\n\t-f fix up file system before starting (repairs bad file name case and group ownership)\n"); + return -1; +} + int main(int argc, char **argv) { struct fuse fuse; char opts[256]; int fd; int res; - unsigned uid; - unsigned gid; - const char *path; - - if (argc != 4) { - ERROR("usage: sdcard <path> <uid> <gid>\n"); - return -1; + const char *path = NULL; + int i; + + for (i = 1; i < argc; i++) { + char* arg = argv[i]; + if (!path) + path = arg; + else if (uid == -1) + uid = strtoul(arg, 0, 10); + else if (gid == -1) + gid = strtoul(arg, 0, 10); + else { + ERROR("too many arguments\n"); + return usage(); + } } - uid = strtoul(argv[2], 0, 10); - gid = strtoul(argv[3], 0, 10); - if (!uid || !gid) { + if (!path) { + ERROR("no path specified\n"); + return usage(); + } + if (uid <= 0 || gid <= 0) { ERROR("uid and gid must be nonzero\n"); - return -1; + return usage(); } - path = argv[1]; - /* cleanup from previous instance, if necessary */ umount2(MOUNT_POINT, 2); |