From 03ee9479a4ed67689b9bbccda20c60800a38b178 Mon Sep 17 00:00:00 2001 From: Brian Swetland Date: Thu, 12 Aug 2010 18:01:08 -0700 Subject: [PATCH] sdcard: a program to create a "virtual" /sdcard pointed at a path 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. usage: sdcard It must be run as root, but will change to uid/gid as soon as it mounts a filesystem on /sdcard. It will refuse to run if uid or gid are zero. Change-Id: I9a5d2e5daaebeee632f8470172cbb77b7fa689f8 Signed-off-by: Brian Swetland --- sdcard/Android.mk | 10 + sdcard/fuse.h | 578 +++++++++++++++++++++++++++++++++++ sdcard/sdcard.c | 752 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1340 insertions(+) create mode 100644 sdcard/Android.mk create mode 100644 sdcard/fuse.h create mode 100644 sdcard/sdcard.c diff --git a/sdcard/Android.mk b/sdcard/Android.mk new file mode 100644 index 000000000..c430ac8ee --- /dev/null +++ b/sdcard/Android.mk @@ -0,0 +1,10 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= sdcard.c +LOCAL_MODULE:= sdcard + +LOCAL_SHARED_LIBRARIES := libc + +include $(BUILD_EXECUTABLE) diff --git a/sdcard/fuse.h b/sdcard/fuse.h new file mode 100644 index 000000000..3138da904 --- /dev/null +++ b/sdcard/fuse.h @@ -0,0 +1,578 @@ +/* + FUSE: Filesystem in Userspace + Copyright (C) 2001-2008 Miklos Szeredi + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. +*/ + +/* + * from the libfuse FAQ (and consistent with the Linux Kernel license): + * + * Under what conditions may I distribute a filesystem that uses the + * raw kernel interface of FUSE? + * + * There are no restrictions whatsoever for using the raw kernel interface. + * + */ + +/* + * This file defines the kernel interface of FUSE + * + * Protocol changelog: + * + * 7.9: + * - new fuse_getattr_in input argument of GETATTR + * - add lk_flags in fuse_lk_in + * - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in + * - add blksize field to fuse_attr + * - add file flags field to fuse_read_in and fuse_write_in + * + * 7.10 + * - add nonseekable open flag + * + * 7.11 + * - add IOCTL message + * - add unsolicited notification support + * - add POLL message and NOTIFY_POLL notification + * + * 7.12 + * - add umask flag to input argument of open, mknod and mkdir + * - add notification messages for invalidation of inodes and + * directory entries + * + * 7.13 + * - make max number of background requests and congestion threshold + * tunables + */ + +#ifndef _LINUX_FUSE_H +#define _LINUX_FUSE_H + +#include + +/* + * Version negotiation: + * + * Both the kernel and userspace send the version they support in the + * INIT request and reply respectively. + * + * If the major versions match then both shall use the smallest + * of the two minor versions for communication. + * + * If the kernel supports a larger major version, then userspace shall + * reply with the major version it supports, ignore the rest of the + * INIT message and expect a new INIT message from the kernel with a + * matching major version. + * + * If the library supports a larger major version, then it shall fall + * back to the major protocol version sent by the kernel for + * communication and reply with that major version (and an arbitrary + * supported minor version). + */ + +/** Version number of this interface */ +#define FUSE_KERNEL_VERSION 7 + +/** Minor version number of this interface */ +#define FUSE_KERNEL_MINOR_VERSION 13 + +/** The node ID of the root inode */ +#define FUSE_ROOT_ID 1 + +/* Make sure all structures are padded to 64bit boundary, so 32bit + userspace works under 64bit kernels */ + +struct fuse_attr { + __u64 ino; + __u64 size; + __u64 blocks; + __u64 atime; + __u64 mtime; + __u64 ctime; + __u32 atimensec; + __u32 mtimensec; + __u32 ctimensec; + __u32 mode; + __u32 nlink; + __u32 uid; + __u32 gid; + __u32 rdev; + __u32 blksize; + __u32 padding; +}; + +struct fuse_kstatfs { + __u64 blocks; + __u64 bfree; + __u64 bavail; + __u64 files; + __u64 ffree; + __u32 bsize; + __u32 namelen; + __u32 frsize; + __u32 padding; + __u32 spare[6]; +}; + +struct fuse_file_lock { + __u64 start; + __u64 end; + __u32 type; + __u32 pid; /* tgid */ +}; + +/** + * Bitmasks for fuse_setattr_in.valid + */ +#define FATTR_MODE (1 << 0) +#define FATTR_UID (1 << 1) +#define FATTR_GID (1 << 2) +#define FATTR_SIZE (1 << 3) +#define FATTR_ATIME (1 << 4) +#define FATTR_MTIME (1 << 5) +#define FATTR_FH (1 << 6) +#define FATTR_ATIME_NOW (1 << 7) +#define FATTR_MTIME_NOW (1 << 8) +#define FATTR_LOCKOWNER (1 << 9) + +/** + * Flags returned by the OPEN request + * + * FOPEN_DIRECT_IO: bypass page cache for this open file + * FOPEN_KEEP_CACHE: don't invalidate the data cache on open + * FOPEN_NONSEEKABLE: the file is not seekable + */ +#define FOPEN_DIRECT_IO (1 << 0) +#define FOPEN_KEEP_CACHE (1 << 1) +#define FOPEN_NONSEEKABLE (1 << 2) + +/** + * INIT request/reply flags + * + * FUSE_EXPORT_SUPPORT: filesystem handles lookups of "." and ".." + * FUSE_DONT_MASK: don't apply umask to file mode on create operations + */ +#define FUSE_ASYNC_READ (1 << 0) +#define FUSE_POSIX_LOCKS (1 << 1) +#define FUSE_FILE_OPS (1 << 2) +#define FUSE_ATOMIC_O_TRUNC (1 << 3) +#define FUSE_EXPORT_SUPPORT (1 << 4) +#define FUSE_BIG_WRITES (1 << 5) +#define FUSE_DONT_MASK (1 << 6) + +/** + * CUSE INIT request/reply flags + * + * CUSE_UNRESTRICTED_IOCTL: use unrestricted ioctl + */ +#define CUSE_UNRESTRICTED_IOCTL (1 << 0) + +/** + * Release flags + */ +#define FUSE_RELEASE_FLUSH (1 << 0) + +/** + * Getattr flags + */ +#define FUSE_GETATTR_FH (1 << 0) + +/** + * Lock flags + */ +#define FUSE_LK_FLOCK (1 << 0) + +/** + * WRITE flags + * + * FUSE_WRITE_CACHE: delayed write from page cache, file handle is guessed + * FUSE_WRITE_LOCKOWNER: lock_owner field is valid + */ +#define FUSE_WRITE_CACHE (1 << 0) +#define FUSE_WRITE_LOCKOWNER (1 << 1) + +/** + * Read flags + */ +#define FUSE_READ_LOCKOWNER (1 << 1) + +/** + * Ioctl flags + * + * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine + * FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed + * FUSE_IOCTL_RETRY: retry with new iovecs + * + * FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs + */ +#define FUSE_IOCTL_COMPAT (1 << 0) +#define FUSE_IOCTL_UNRESTRICTED (1 << 1) +#define FUSE_IOCTL_RETRY (1 << 2) + +#define FUSE_IOCTL_MAX_IOV 256 + +/** + * Poll flags + * + * FUSE_POLL_SCHEDULE_NOTIFY: request poll notify + */ +#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0) + +enum fuse_opcode { + FUSE_LOOKUP = 1, + FUSE_FORGET = 2, /* no reply */ + FUSE_GETATTR = 3, + FUSE_SETATTR = 4, + FUSE_READLINK = 5, + FUSE_SYMLINK = 6, + FUSE_MKNOD = 8, + FUSE_MKDIR = 9, + FUSE_UNLINK = 10, + FUSE_RMDIR = 11, + FUSE_RENAME = 12, + FUSE_LINK = 13, + FUSE_OPEN = 14, + FUSE_READ = 15, + FUSE_WRITE = 16, + FUSE_STATFS = 17, + FUSE_RELEASE = 18, + FUSE_FSYNC = 20, + FUSE_SETXATTR = 21, + FUSE_GETXATTR = 22, + FUSE_LISTXATTR = 23, + FUSE_REMOVEXATTR = 24, + FUSE_FLUSH = 25, + FUSE_INIT = 26, + FUSE_OPENDIR = 27, + FUSE_READDIR = 28, + FUSE_RELEASEDIR = 29, + FUSE_FSYNCDIR = 30, + FUSE_GETLK = 31, + FUSE_SETLK = 32, + FUSE_SETLKW = 33, + FUSE_ACCESS = 34, + FUSE_CREATE = 35, + FUSE_INTERRUPT = 36, + FUSE_BMAP = 37, + FUSE_DESTROY = 38, + FUSE_IOCTL = 39, + FUSE_POLL = 40, + + /* CUSE specific operations */ + CUSE_INIT = 4096, +}; + +enum fuse_notify_code { + FUSE_NOTIFY_POLL = 1, + FUSE_NOTIFY_INVAL_INODE = 2, + FUSE_NOTIFY_INVAL_ENTRY = 3, + FUSE_NOTIFY_CODE_MAX, +}; + +/* The read buffer is required to be at least 8k, but may be much larger */ +#define FUSE_MIN_READ_BUFFER 8192 + +#define FUSE_COMPAT_ENTRY_OUT_SIZE 120 + +struct fuse_entry_out { + __u64 nodeid; /* Inode ID */ + __u64 generation; /* Inode generation: nodeid:gen must + be unique for the fs's lifetime */ + __u64 entry_valid; /* Cache timeout for the name */ + __u64 attr_valid; /* Cache timeout for the attributes */ + __u32 entry_valid_nsec; + __u32 attr_valid_nsec; + struct fuse_attr attr; +}; + +struct fuse_forget_in { + __u64 nlookup; +}; + +struct fuse_getattr_in { + __u32 getattr_flags; + __u32 dummy; + __u64 fh; +}; + +#define FUSE_COMPAT_ATTR_OUT_SIZE 96 + +struct fuse_attr_out { + __u64 attr_valid; /* Cache timeout for the attributes */ + __u32 attr_valid_nsec; + __u32 dummy; + struct fuse_attr attr; +}; + +#define FUSE_COMPAT_MKNOD_IN_SIZE 8 + +struct fuse_mknod_in { + __u32 mode; + __u32 rdev; + __u32 umask; + __u32 padding; +}; + +struct fuse_mkdir_in { + __u32 mode; + __u32 umask; +}; + +struct fuse_rename_in { + __u64 newdir; +}; + +struct fuse_link_in { + __u64 oldnodeid; +}; + +struct fuse_setattr_in { + __u32 valid; + __u32 padding; + __u64 fh; + __u64 size; + __u64 lock_owner; + __u64 atime; + __u64 mtime; + __u64 unused2; + __u32 atimensec; + __u32 mtimensec; + __u32 unused3; + __u32 mode; + __u32 unused4; + __u32 uid; + __u32 gid; + __u32 unused5; +}; + +struct fuse_open_in { + __u32 flags; + __u32 unused; +}; + +struct fuse_create_in { + __u32 flags; + __u32 mode; + __u32 umask; + __u32 padding; +}; + +struct fuse_open_out { + __u64 fh; + __u32 open_flags; + __u32 padding; +}; + +struct fuse_release_in { + __u64 fh; + __u32 flags; + __u32 release_flags; + __u64 lock_owner; +}; + +struct fuse_flush_in { + __u64 fh; + __u32 unused; + __u32 padding; + __u64 lock_owner; +}; + +struct fuse_read_in { + __u64 fh; + __u64 offset; + __u32 size; + __u32 read_flags; + __u64 lock_owner; + __u32 flags; + __u32 padding; +}; + +#define FUSE_COMPAT_WRITE_IN_SIZE 24 + +struct fuse_write_in { + __u64 fh; + __u64 offset; + __u32 size; + __u32 write_flags; + __u64 lock_owner; + __u32 flags; + __u32 padding; +}; + +struct fuse_write_out { + __u32 size; + __u32 padding; +}; + +#define FUSE_COMPAT_STATFS_SIZE 48 + +struct fuse_statfs_out { + struct fuse_kstatfs st; +}; + +struct fuse_fsync_in { + __u64 fh; + __u32 fsync_flags; + __u32 padding; +}; + +struct fuse_setxattr_in { + __u32 size; + __u32 flags; +}; + +struct fuse_getxattr_in { + __u32 size; + __u32 padding; +}; + +struct fuse_getxattr_out { + __u32 size; + __u32 padding; +}; + +struct fuse_lk_in { + __u64 fh; + __u64 owner; + struct fuse_file_lock lk; + __u32 lk_flags; + __u32 padding; +}; + +struct fuse_lk_out { + struct fuse_file_lock lk; +}; + +struct fuse_access_in { + __u32 mask; + __u32 padding; +}; + +struct fuse_init_in { + __u32 major; + __u32 minor; + __u32 max_readahead; + __u32 flags; +}; + +struct fuse_init_out { + __u32 major; + __u32 minor; + __u32 max_readahead; + __u32 flags; + __u16 max_background; + __u16 congestion_threshold; + __u32 max_write; +}; + +#define CUSE_INIT_INFO_MAX 4096 + +struct cuse_init_in { + __u32 major; + __u32 minor; + __u32 unused; + __u32 flags; +}; + +struct cuse_init_out { + __u32 major; + __u32 minor; + __u32 unused; + __u32 flags; + __u32 max_read; + __u32 max_write; + __u32 dev_major; /* chardev major */ + __u32 dev_minor; /* chardev minor */ + __u32 spare[10]; +}; + +struct fuse_interrupt_in { + __u64 unique; +}; + +struct fuse_bmap_in { + __u64 block; + __u32 blocksize; + __u32 padding; +}; + +struct fuse_bmap_out { + __u64 block; +}; + +struct fuse_ioctl_in { + __u64 fh; + __u32 flags; + __u32 cmd; + __u64 arg; + __u32 in_size; + __u32 out_size; +}; + +struct fuse_ioctl_out { + __s32 result; + __u32 flags; + __u32 in_iovs; + __u32 out_iovs; +}; + +struct fuse_poll_in { + __u64 fh; + __u64 kh; + __u32 flags; + __u32 padding; +}; + +struct fuse_poll_out { + __u32 revents; + __u32 padding; +}; + +struct fuse_notify_poll_wakeup_out { + __u64 kh; +}; + +struct fuse_in_header { + __u32 len; + __u32 opcode; + __u64 unique; + __u64 nodeid; + __u32 uid; + __u32 gid; + __u32 pid; + __u32 padding; +}; + +struct fuse_out_header { + __u32 len; + __s32 error; + __u64 unique; +}; + +struct fuse_dirent { + __u64 ino; + __u64 off; + __u32 namelen; + __u32 type; + char name[0]; +}; + +#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name) +#define FUSE_DIRENT_ALIGN(x) (((x) + sizeof(__u64) - 1) & ~(sizeof(__u64) - 1)) +#define FUSE_DIRENT_SIZE(d) \ + FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen) + +struct fuse_notify_inval_inode_out { + __u64 ino; + __s64 off; + __s64 len; +}; + +struct fuse_notify_inval_entry_out { + __u64 parent; + __u32 namelen; + __u32 padding; +}; + +#endif /* _LINUX_FUSE_H */ diff --git a/sdcard/sdcard.c b/sdcard/sdcard.c new file mode 100644 index 000000000..5d1960451 --- /dev/null +++ b/sdcard/sdcard.c @@ -0,0 +1,752 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fuse.h" + +/* 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. + * + * usage: sdcard + * + * It must be run as root, but will change to uid/gid as soon as it + * mounts a filesystem on /sdcard. It will refuse to run if uid or + * 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 + * + * + * Bugs: + * + * - need to move/rename node on RENAME + */ + +#define FUSE_TRACE 0 + +#if FUSE_TRACE +#define TRACE(x...) fprintf(stderr,x) +#else +#define TRACE(x...) do {} while (0) +#endif + +#define ERROR(x...) fprintf(stderr,x) + +#define FUSE_UNKNOWN_INO 0xffffffff + +struct handle { + struct node *node; + int fd; +}; + +struct dirhandle { + struct node *node; + DIR *d; +}; + +struct node { + __u64 nid; + __u64 gen; + + struct node *next; + struct node *child; + struct node *all; + struct node *parent; + + __u32 refcount; + __u32 namelen; + + char name[1]; +}; + +struct fuse { + __u64 next_generation; + __u64 next_node_id; + + int fd; + + struct node *all; + + struct node root; + char rootpath[1024]; +}; + +#define PATH_BUFFER_SIZE 1024 + +char *node_get_path(struct node *node, char *buf, const char *name) +{ + char *out = buf + PATH_BUFFER_SIZE - 1; + int len; + out[0] = 0; + + if (name) { + len = strlen(name); + goto start; + } + + while (node) { + name = node->name; + len = node->namelen; + node = node->parent; + start: + if ((len + 1) > (out - buf)) + return 0; + out -= len; + memcpy(out, name, len); + out --; + out[0] = '/'; + } + + return out; +} + +void attr_from_stat(struct fuse_attr *attr, struct stat *s) +{ + attr->ino = s->st_ino; + attr->size = s->st_size; + attr->blocks = s->st_blocks; + /* TODO: time */ + attr->mode = s->st_mode; + attr->nlink = s->st_nlink; + /* TODO: uid/gid */ + + attr->uid = 1000; + attr->gid = 1000; +} + +int node_get_attr(struct node *node, struct fuse_attr *attr) +{ + int res; + struct stat s; + char *path, buffer[PATH_BUFFER_SIZE]; + + path = node_get_path(node, buffer, 0); + res = lstat(path, &s); + if (res < 0) { + ERROR("lstat('%s') errno %d\n", path, errno); + return -1; + } + + attr_from_stat(attr, &s); + attr->ino = node->nid; + + return 0; +} + +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); + if (node == 0) { + return 0; + } + + node->nid = nid; + node->gen = gen; + node->parent = parent; + node->next = parent->child; + parent->child = node; + memcpy(node->name, name, namelen + 1); + node->namelen = namelen; + parent->refcount++; + + return node; +} + +void fuse_init(struct fuse *fuse, int fd, const char *path) +{ + fuse->fd = fd; + fuse->next_node_id = 2; + fuse->next_generation = 0; + + fuse->all = &fuse->root; + + fuse->root.nid = FUSE_ROOT_ID; /* 1 */ + fuse->root.next = 0; + fuse->root.child = 0; + fuse->root.parent = 0; + + fuse->root.all = 0; + fuse->root.refcount = 2; + + strcpy(fuse->root.name, path); + fuse->root.namelen = strlen(fuse->root.name); +} + +static inline void *id_to_ptr(__u64 nid) +{ + return (void *) nid; +} + +static inline __u64 ptr_to_id(void *ptr) +{ + return (__u64) ptr; +} + + +struct node *lookup_by_inode(struct fuse *fuse, __u64 nid) +{ + if (nid == FUSE_ROOT_ID) { + return &fuse->root; + } else { + return id_to_ptr(nid); + } +} + +struct node *lookup_child_by_name(struct node *node, const char *name) +{ + for (node = node->child; node; node = node->next) { + if (!strcmp(name, node->name)) { + return node; + } + } + return 0; +} + +struct node *lookup_child_by_inode(struct node *node, __u64 nid) +{ + for (node = node->child; node; node = node->next) { + if (node->nid == nid) { + return node; + } + } + return 0; +} + +struct node *node_lookup(struct fuse *fuse, struct node *parent, const char *name, + struct fuse_attr *attr) +{ + int res; + struct stat s; + char *path, buffer[PATH_BUFFER_SIZE]; + struct node *node; + + path = node_get_path(parent, buffer, name); + /* XXX error? */ + + res = lstat(path, &s); + if (res < 0) + return 0; + + node = lookup_child_by_name(parent, name); + if (!node) { + node = node_create(parent, name, fuse->next_node_id++, fuse->next_generation++); + if (!node) + return 0; + node->nid = ptr_to_id(node); + node->all = fuse->all; + fuse->all = node; + } + + attr_from_stat(attr, &s); + attr->ino = node->nid; + + return node; +} + +void node_release(struct node *node) +{ + TRACE("RELEASE %p (%s) rc=%d\n", node, node->name, node->refcount); + node->refcount--; + if (node->refcount == 0) { + if (node->parent->child == node) { + node->parent->child = node->parent->child->next; + } else { + struct node *node2; + + node2 = node->parent->child; + while (node2->next != node) + node2 = node2->next; + node2->next = node->next; + } + + TRACE("DESTROY %p (%s)\n", node, node->name); + + node_release(node->parent); + + node->parent = 0; + node->next = 0; + + /* TODO: remove debugging - poison memory */ + memset(node, 0xef, sizeof(*node) + strlen(node->name)); + + free(node); + } +} + +void fuse_status(struct fuse *fuse, __u64 unique, int err) +{ + struct fuse_out_header hdr; + hdr.len = sizeof(hdr); + hdr.error = err; + hdr.unique = unique; + if (err) { +// ERROR("*** %d ***\n", err); + } + write(fuse->fd, &hdr, sizeof(hdr)); +} + +void fuse_reply(struct fuse *fuse, __u64 unique, void *data, int len) +{ + struct fuse_out_header hdr; + struct iovec vec[2]; + int res; + + hdr.len = len + sizeof(hdr); + hdr.error = 0; + hdr.unique = unique; + + vec[0].iov_base = &hdr; + vec[0].iov_len = sizeof(hdr); + vec[1].iov_base = data; + vec[1].iov_len = len; + + res = writev(fuse->fd, vec, 2); + if (res < 0) { + ERROR("*** REPLY FAILED *** %d\n", errno); + } +} + +void lookup_entry(struct fuse *fuse, struct node *node, + const char *name, __u64 unique) +{ + struct fuse_entry_out out; + + memset(&out, 0, sizeof(out)); + + node = node_lookup(fuse, node, name, &out.attr); + if (!node) { + fuse_status(fuse, unique, -ENOENT); + return; + } + + node->refcount++; +// fprintf(stderr,"ACQUIRE %p (%s) rc=%d\n", node, node->name, node->refcount); + out.nodeid = node->nid; + out.generation = node->gen; + out.entry_valid = 10; + out.attr_valid = 10; + + fuse_reply(fuse, unique, &out, sizeof(out)); +} + +void handle_fuse_request(struct fuse *fuse, struct fuse_in_header *hdr, void *data, unsigned len) +{ + struct node *node; + + if ((len < sizeof(*hdr)) || (hdr->len != len)) { + ERROR("malformed header\n"); + return; + } + + len -= hdr->len; + + if (hdr->nodeid) { + node = lookup_by_inode(fuse, hdr->nodeid); + if (!node) { + fuse_status(fuse, hdr->unique, -ENOENT); + return; + } + } else { + node = 0; + } + + switch (hdr->opcode) { + case FUSE_LOOKUP: { /* bytez[] -> entry_out */ + TRACE("LOOKUP %llx %s\n", hdr->nodeid, (char*) data); + lookup_entry(fuse, node, (char*) data, hdr->unique); + return; + } + case FUSE_FORGET: { + struct fuse_forget_in *req = data; + TRACE("FORGET %llx (%s) #%lld\n", hdr->nodeid, node->name, req->nlookup); + /* no reply */ + while (req->nlookup--) + node_release(node); + return; + } + case FUSE_GETATTR: { /* getattr_in -> attr_out */ + struct fuse_getattr_in *req = data; + struct fuse_attr_out out; + + TRACE("GETATTR flags=%x fh=%llx\n",req->getattr_flags, req->fh); + + memset(&out, 0, sizeof(out)); + node_get_attr(node, &out.attr); + out.attr_valid = 10; + + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); + return; + } + case FUSE_SETATTR: { /* setattr_in -> attr_out */ + struct fuse_setattr_in *req = data; + struct fuse_attr_out out; + TRACE("SETATTR fh=%llx id=%llx valid=%x\n", + req->fh, hdr->nodeid, req->valid); + + /* XXX */ + + memset(&out, 0, sizeof(out)); + node_get_attr(node, &out.attr); + out.attr_valid = 10; + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); + return; + } +// case FUSE_READLINK: +// case FUSE_SYMLINK: + case FUSE_MKNOD: { /* mknod_in, bytez[] -> entry_out */ + struct fuse_mknod_in *req = data; + 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); + + req->mode = (req->mode & (~0777)) | 0664; + res = mknod(path, req->mode, req->rdev); /* XXX perm?*/ + if (res < 0) { + fuse_status(fuse, hdr->unique, -errno); + } else { + lookup_entry(fuse, node, name, hdr->unique); + } + return; + } + case FUSE_MKDIR: { /* mkdir_in, bytez[] -> entry_out */ + struct fuse_mkdir_in *req = data; + struct fuse_entry_out out; + 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); + + req->mode = (req->mode & (~0777)) | 0775; + res = mkdir(path, req->mode); + if (res < 0) { + fuse_status(fuse, hdr->unique, -errno); + } else { + lookup_entry(fuse, node, name, hdr->unique); + } + return; + } + case FUSE_UNLINK: { /* bytez[] -> */ + char *path, buffer[PATH_BUFFER_SIZE]; + int res; + TRACE("UNLINK %s @ %llx\n", (char*) data, hdr->nodeid); + path = node_get_path(node, buffer, (char*) data); + res = unlink(path); + fuse_status(fuse, hdr->unique, res ? -errno : 0); + return; + } + case FUSE_RMDIR: { /* bytez[] -> */ + char *path, buffer[PATH_BUFFER_SIZE]; + int res; + TRACE("RMDIR %s @ %llx\n", (char*) data, hdr->nodeid); + path = node_get_path(node, buffer, (char*) data); + res = rmdir(path); + fuse_status(fuse, hdr->unique, res ? -errno : 0); + return; + } + case FUSE_RENAME: { /* rename_in, oldname, newname -> */ + struct fuse_rename_in *req = data; + char *oldname = ((char*) data) + sizeof(*req); + char *newname = oldname + strlen(oldname) + 1; + char *oldpath, oldbuffer[PATH_BUFFER_SIZE]; + char *newpath, newbuffer[PATH_BUFFER_SIZE]; + struct node *newnode; + int res; + + newnode = lookup_by_inode(fuse, req->newdir); + if (!newnode) { + fuse_status(fuse, hdr->unique, -ENOENT); + return; + } + + oldpath = node_get_path(node, oldbuffer, oldname); + newpath = node_get_path(newnode, newbuffer, newname); + + res = rename(oldpath, newpath); + fuse_status(fuse, hdr->unique, res ? -errno : 0); + return; + } +// case FUSE_LINK: + case FUSE_OPEN: { /* open_in -> open_out */ + struct fuse_open_in *req = data; + struct fuse_open_out out; + char *path, buffer[PATH_BUFFER_SIZE]; + struct handle *h; + + h = malloc(sizeof(*h)); + if (!h) { + fuse_status(fuse, hdr->unique, -ENOMEM); + return; + } + + path = node_get_path(node, buffer, 0); + TRACE("OPEN %llx '%s' 0%o fh=%p\n", hdr->nodeid, path, req->flags, h); + h->fd = open(path, req->flags); + if (h->fd < 0) { + ERROR("ERROR\n"); + fuse_status(fuse, hdr->unique, errno); + free(h); + return; + } + out.fh = ptr_to_id(h); + out.open_flags = 0; + out.padding = 0; + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); + return; + } + case FUSE_READ: { /* read_in -> byte[] */ + char buffer[128 * 1024]; + struct fuse_read_in *req = data; + struct handle *h = id_to_ptr(req->fh); + int res; + TRACE("READ %p(%d) %u@%llu\n", h, h->fd, req->size, req->offset); + if (req->size > sizeof(buffer)) { + fuse_status(fuse, hdr->unique, -EINVAL); + return; + } + res = pread(h->fd, buffer, req->size, req->offset); + if (res < 0) { + fuse_status(fuse, hdr->unique, errno); + return; + } + fuse_reply(fuse, hdr->unique, buffer, res); + return; + } + case FUSE_WRITE: { /* write_in, byte[write_in.size] -> write_out */ + struct fuse_write_in *req = data; + struct fuse_write_out out; + 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); + if (res < 0) { + fuse_status(fuse, hdr->unique, errno); + return; + } + out.size = res; + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); + goto oops; + } +// case FUSE_STATFS: + case FUSE_RELEASE: { /* release_in -> */ + struct fuse_release_in *req = data; + struct handle *h = id_to_ptr(req->fh); + TRACE("RELEASE %p(%d)\n", h, h->fd); + close(h->fd); + free(h); + fuse_status(fuse, hdr->unique, 0); + return; + } +// case FUSE_FSYNC: +// case FUSE_SETXATTR: +// case FUSE_GETXATTR: +// case FUSE_LISTXATTR: +// case FUSE_REMOVEXATTR: + case FUSE_FLUSH: + fuse_status(fuse, hdr->unique, 0); + return; + case FUSE_OPENDIR: { /* open_in -> open_out */ + struct fuse_open_in *req = data; + struct fuse_open_out out; + char *path, buffer[PATH_BUFFER_SIZE]; + struct dirhandle *h; + + h = malloc(sizeof(*h)); + if (!h) { + fuse_status(fuse, hdr->unique, -ENOMEM); + return; + } + + path = node_get_path(node, buffer, 0); + TRACE("OPENDIR %llx '%s'\n", hdr->nodeid, path); + h->d = opendir(path); + if (h->d == 0) { + ERROR("ERROR\n"); + fuse_status(fuse, hdr->unique, -errno); + free(h); + return; + } + out.fh = ptr_to_id(h); + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); + return; + } + case FUSE_READDIR: { + struct fuse_read_in *req = data; + char buffer[8192]; + struct fuse_dirent *fde = (struct fuse_dirent*) buffer; + struct dirent *de; + struct dirhandle *h = id_to_ptr(req->fh); + TRACE("READDIR %p\n", h); + de = readdir(h->d); + if (!de) { + fuse_status(fuse, hdr->unique, 0); + return; + } + fde->ino = FUSE_UNKNOWN_INO; + fde->off = 0; + fde->type = de->d_type; + fde->namelen = strlen(de->d_name); + memcpy(fde->name, de->d_name, fde->namelen + 1); + fuse_reply(fuse, hdr->unique, fde, + FUSE_DIRENT_ALIGN(sizeof(struct fuse_dirent) + fde->namelen)); + return; + } + case FUSE_RELEASEDIR: { /* release_in -> */ + struct fuse_release_in *req = data; + struct dirhandle *h = id_to_ptr(req->fh); + TRACE("RELEASEDIR %p\n",h); + closedir(h->d); + free(h); + fuse_status(fuse, hdr->unique, 0); + return; + } +// case FUSE_FSYNCDIR: + case FUSE_INIT: { /* init_in -> init_out */ + struct fuse_init_in *req = data; + struct fuse_init_out out; + + TRACE("INIT ver=%d.%d maxread=%d flags=%x\n", + req->major, req->minor, req->max_readahead, req->flags); + + out.major = FUSE_KERNEL_VERSION; + out.minor = FUSE_KERNEL_MINOR_VERSION; + out.max_readahead = req->max_readahead; + out.flags = 0; + out.max_background = 32; + out.congestion_threshold = 32; + out.max_write = 256 * 1024; + + fuse_reply(fuse, hdr->unique, &out, sizeof(out)); + return; + } + default: { + struct fuse_out_header h; + ERROR("NOTIMPL op=%d uniq=%llx nid=%llx\n", + hdr->opcode, hdr->unique, hdr->nodeid); + + oops: + h.len = sizeof(h); + h.error = -ENOSYS; + h.unique = hdr->unique; + write(fuse->fd, &h, sizeof(h)); + break; + } + } +} + +void handle_fuse_requests(struct fuse *fuse) +{ + unsigned char req[256 * 1024 + 128]; + int len; + + for (;;) { + len = read(fuse->fd, req, 8192); + if (len < 0) { + if (errno == EINTR) + continue; + ERROR("handle_fuse_requests: errno=%d\n", errno); + return; + } + handle_fuse_request(fuse, (void*) req, (void*) (req + sizeof(struct fuse_in_header)), len); + } +} + +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 \n"); + return -1; + } + + uid = strtoul(argv[2], 0, 10); + gid = strtoul(argv[3], 0, 10); + if (!uid || !gid) { + ERROR("uid and gid must be nonzero\n"); + return -1; + } + + path = argv[1]; + + /* cleanup from previous instance, if necessary */ + umount2("/sdcard", 2); + + fd = open("/dev/fuse", O_RDWR); + if (fd < 0){ + ERROR("cannot open fuse device (%d)\n", errno); + return -1; + } + + sprintf(opts, "fd=%i,rootmode=40000,default_permissions,allow_other," + "user_id=%d,group_id=%d", fd, uid, gid); + + res = mount("/dev/fuse", "/sdcard", "fuse", MS_NOSUID | MS_NODEV, opts); + if (res < 0) { + ERROR("cannot mount fuse filesystem (%d)\n", errno); + return -1; + } + + if (setgid(gid) < 0) { + ERROR("cannot setgid!\n"); + return -1; + } + if (setuid(uid) < 0) { + ERROR("cannot setuid!\n"); + return -1; + } + + fuse_init(&fuse, fd, path); + + umask(0); + handle_fuse_requests(&fuse); + + return 0; +}