diff options
Diffstat (limited to '')
-rw-r--r-- | fuse/Android.mk | 2 | ||||
-rw-r--r-- | fuse/mount.c | 2 | ||||
-rw-r--r-- | fuse_sdcard_provider.cpp | 88 | ||||
-rw-r--r-- | fuse_sideload.cpp | 550 | ||||
-rw-r--r-- | fuse_sideload.h | 34 | ||||
-rw-r--r-- | fuse_sideload22.cpp | 544 | ||||
-rw-r--r-- | fuse_sideload22.h | 47 |
7 files changed, 922 insertions, 345 deletions
diff --git a/fuse/Android.mk b/fuse/Android.mk index 5260e146b..00763ea7e 100644 --- a/fuse/Android.mk +++ b/fuse/Android.mk @@ -45,6 +45,8 @@ LOCAL_CFLAGS := \ -DFUSE_USE_VERSION=26 \ -fno-strict-aliasing +LOCAL_CFLAGS += -Wno-pointer-arith -Wno-sign-compare -Wno-unused-parameter + LOCAL_MODULE := libfusetwrp LOCAL_MODULE_TAGS := optional diff --git a/fuse/mount.c b/fuse/mount.c index eb0bb17d3..873685634 100644 --- a/fuse/mount.c +++ b/fuse/mount.c @@ -282,7 +282,7 @@ static int receive_fd(int fd) } cmsg = CMSG_FIRSTHDR(&msg); - if (!cmsg->cmsg_type == SCM_RIGHTS) { + if (cmsg->cmsg_type != SCM_RIGHTS) { fprintf(stderr, "got control message of unknown type %d\n", cmsg->cmsg_type); return -1; diff --git a/fuse_sdcard_provider.cpp b/fuse_sdcard_provider.cpp index b0ecf96be..46bdf1774 100644 --- a/fuse_sdcard_provider.cpp +++ b/fuse_sdcard_provider.cpp @@ -14,72 +14,70 @@ * limitations under the License. */ -#include <stdlib.h> +#include "fuse_sdcard_provider.h" + +#include <errno.h> +#include <fcntl.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> -#include <errno.h> #include <sys/mount.h> #include <sys/stat.h> #include <unistd.h> -#include <fcntl.h> + +#include <functional> #include <android-base/file.h> #include "fuse_sideload.h" struct file_data { - int fd; // the underlying sdcard file + int fd; // the underlying sdcard file - uint64_t file_size; - uint32_t block_size; + uint64_t file_size; + uint32_t block_size; }; -static int read_block_file(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size) { - file_data* fd = reinterpret_cast<file_data*>(cookie); - - off64_t offset = ((off64_t) block) * fd->block_size; - if (TEMP_FAILURE_RETRY(lseek64(fd->fd, offset, SEEK_SET)) == -1) { - fprintf(stderr, "seek on sdcard failed: %s\n", strerror(errno)); - return -EIO; - } +static int read_block_file(const file_data& fd, uint32_t block, uint8_t* buffer, + uint32_t fetch_size) { + off64_t offset = static_cast<off64_t>(block) * fd.block_size; + if (TEMP_FAILURE_RETRY(lseek64(fd.fd, offset, SEEK_SET)) == -1) { + fprintf(stderr, "seek on sdcard failed: %s\n", strerror(errno)); + return -EIO; + } - if (!android::base::ReadFully(fd->fd, buffer, fetch_size)) { - fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno)); - return -EIO; - } - - return 0; -} + if (!android::base::ReadFully(fd.fd, buffer, fetch_size)) { + fprintf(stderr, "read on sdcard failed: %s\n", strerror(errno)); + return -EIO; + } -static void close_file(void* cookie) { - file_data* fd = reinterpret_cast<file_data*>(cookie); - close(fd->fd); + return 0; } bool start_sdcard_fuse(const char* path) { - struct stat sb; - if (stat(path, &sb) == -1) { - fprintf(stderr, "failed to stat %s: %s\n", path, strerror(errno)); - return false; - } + struct stat sb; + if (stat(path, &sb) == -1) { + fprintf(stderr, "failed to stat %s: %s\n", path, strerror(errno)); + return false; + } - file_data fd; - fd.fd = open(path, O_RDONLY); - if (fd.fd == -1) { - fprintf(stderr, "failed to open %s: %s\n", path, strerror(errno)); - return false; - } - fd.file_size = sb.st_size; - fd.block_size = 65536; + file_data fd; + fd.fd = open(path, O_RDONLY); + if (fd.fd == -1) { + fprintf(stderr, "failed to open %s: %s\n", path, strerror(errno)); + return false; + } + fd.file_size = sb.st_size; + fd.block_size = 65536; - provider_vtab vtab; - vtab.read_block = read_block_file; - vtab.close = close_file; + provider_vtab vtab; + vtab.read_block = std::bind(&read_block_file, fd, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3); + vtab.close = [&fd]() { close(fd.fd); }; - // The installation process expects to find the sdcard unmounted. - // Unmount it with MNT_DETACH so that our open file continues to - // work but new references see it as unmounted. - umount2("/sdcard", MNT_DETACH); + // The installation process expects to find the sdcard unmounted. Unmount it with MNT_DETACH so + // that our open file continues to work but new references see it as unmounted. + umount2("/sdcard", MNT_DETACH); - return run_fuse_sideload(&vtab, &fd, fd.file_size, fd.block_size) == 0; + return run_fuse_sideload(vtab, fd.file_size, fd.block_size) == 0; } diff --git a/fuse_sideload.cpp b/fuse_sideload.cpp index f667cd4a1..45c79f90b 100644 --- a/fuse_sideload.cpp +++ b/fuse_sideload.cpp @@ -41,23 +41,20 @@ // two files is implemented. In particular, you can't opendir() or // readdir() on the "/sideload" directory; ls on it won't work. -#include <ctype.h> -#include <dirent.h> +#include "fuse_sideload.h" + #include <errno.h> #include <fcntl.h> #include <limits.h> #include "fuse.h" #include <pthread.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sys/inotify.h> #include <sys/mount.h> -#include <sys/param.h> -#include <sys/resource.h> +#include <sys/param.h> // MIN #include <sys/stat.h> -#include <sys/statfs.h> -#include <sys/time.h> #include <sys/uio.h> #include <unistd.h> @@ -68,320 +65,304 @@ #include <openssl/sha.h> #endif -#include "fuse_sideload.h" +#include <array> +#include <string> +#include <vector> -#define PACKAGE_FILE_ID (FUSE_ROOT_ID+1) -#define EXIT_FLAG_ID (FUSE_ROOT_ID+2) +//#include <android-base/stringprintf.h> +//#include <android-base/unique_fd.h> -#define NO_STATUS 1 -#define NO_STATUS_EXIT 2 +static constexpr uint64_t PACKAGE_FILE_ID = FUSE_ROOT_ID + 1; +static constexpr uint64_t EXIT_FLAG_ID = FUSE_ROOT_ID + 2; + +static constexpr int NO_STATUS = 1; +static constexpr int NO_STATUS_EXIT = 2; + +using SHA256Digest = std::array<uint8_t, SHA256_DIGEST_LENGTH>; #ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif struct fuse_data { - int ffd; // file descriptor for the fuse socket + int ffd; // file descriptor for the fuse socket - struct provider_vtab* vtab; - void* cookie; + provider_vtab vtab; - uint64_t file_size; // bytes + uint64_t file_size; // bytes - uint32_t block_size; // block size that the adb host is using to send the file to us - uint32_t file_blocks; // file size in block_size blocks + uint32_t block_size; // block size that the adb host is using to send the file to us + uint32_t file_blocks; // file size in block_size blocks - uid_t uid; - gid_t gid; + uid_t uid; + gid_t gid; - uint32_t curr_block; // cache the block most recently read from the host - uint8_t* block_data; + uint32_t curr_block; // cache the block most recently read from the host + uint8_t* block_data; - uint8_t* extra_block; // another block of storage for reads that - // span two blocks + uint8_t* extra_block; // another block of storage for reads that span two blocks - uint8_t* hashes; // SHA-256 hash of each block (all zeros - // if block hasn't been read yet) + uint8_t* hashes; // SHA-256 hash of each block (all zeros + // if block hasn't been read yet) }; -static void fuse_reply(struct fuse_data* fd, __u64 unique, const void *data, size_t 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 = /* const_cast */(void*)(data); - vec[1].iov_len = len; - - res = writev(fd->ffd, vec, 2); - if (res < 0) { - printf("*** REPLY FAILED *** %s\n", strerror(errno)); - } +static void fuse_reply(const fuse_data* fd, uint64_t unique, const void* data, size_t len) { + fuse_out_header hdr; + hdr.len = len + sizeof(hdr); + hdr.error = 0; + hdr.unique = unique; + + struct iovec vec[2]; + vec[0].iov_base = &hdr; + vec[0].iov_len = sizeof(hdr); + vec[1].iov_base = const_cast<void*>(data); + vec[1].iov_len = len; + + int res = writev(fd->ffd, vec, 2); + if (res == -1) { + printf("*** REPLY FAILED *** %s\n", strerror(errno)); + } } -static int handle_init(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { - const struct fuse_init_in* req = reinterpret_cast<const struct fuse_init_in*>(data); - struct fuse_init_out out; - size_t fuse_struct_size; - - - /* Kernel 2.6.16 is the first stable kernel with struct fuse_init_out - * defined (fuse version 7.6). The structure is the same from 7.6 through - * 7.22. Beginning with 7.23, the structure increased in size and added - * new parameters. - */ - if (req->major != FUSE_KERNEL_VERSION || req->minor < 6) { - printf("Fuse kernel version mismatch: Kernel version %d.%d, Expected at least %d.6", - req->major, req->minor, FUSE_KERNEL_VERSION); - return -1; - } +static int handle_init(void* data, fuse_data* fd, const fuse_in_header* hdr) { + const fuse_init_in* req = static_cast<const fuse_init_in*>(data); - out.minor = MIN(req->minor, FUSE_KERNEL_MINOR_VERSION); - fuse_struct_size = sizeof(out); + // Kernel 2.6.16 is the first stable kernel with struct fuse_init_out defined (fuse version 7.6). + // The structure is the same from 7.6 through 7.22. Beginning with 7.23, the structure increased + // in size and added new parameters. + if (req->major != FUSE_KERNEL_VERSION || req->minor < 6) { + printf("Fuse kernel version mismatch: Kernel version %d.%d, Expected at least %d.6", req->major, + req->minor, FUSE_KERNEL_VERSION); + return -1; + } + + fuse_init_out out; + out.minor = MIN(req->minor, FUSE_KERNEL_MINOR_VERSION); + size_t fuse_struct_size = sizeof(out); #if defined(FUSE_COMPAT_22_INIT_OUT_SIZE) - /* FUSE_KERNEL_VERSION >= 23. */ + /* FUSE_KERNEL_VERSION >= 23. */ - /* If the kernel only works on minor revs older than or equal to 22, - * then use the older structure size since this code only uses the 7.22 - * version of the structure. */ - if (req->minor <= 22) { - fuse_struct_size = FUSE_COMPAT_22_INIT_OUT_SIZE; - } + // If the kernel only works on minor revs older than or equal to 22, then use the older structure + // size since this code only uses the 7.22 version of the structure. + if (req->minor <= 22) { + fuse_struct_size = FUSE_COMPAT_22_INIT_OUT_SIZE; + } #endif - out.major = FUSE_KERNEL_VERSION; - out.max_readahead = req->max_readahead; - out.flags = 0; - out.max_background = 32; - out.congestion_threshold = 32; - out.max_write = 4096; - fuse_reply(fd, hdr->unique, &out, fuse_struct_size); + out.major = FUSE_KERNEL_VERSION; + out.max_readahead = req->max_readahead; + out.flags = 0; + out.max_background = 32; + out.congestion_threshold = 32; + out.max_write = 4096; + fuse_reply(fd, hdr->unique, &out, fuse_struct_size); - return NO_STATUS; + return NO_STATUS; } -static void fill_attr(struct fuse_attr* attr, struct fuse_data* fd, - uint64_t nodeid, uint64_t size, uint32_t mode) { - memset(attr, 0, sizeof(*attr)); - attr->nlink = 1; - attr->uid = fd->uid; - attr->gid = fd->gid; - attr->blksize = 4096; - - attr->ino = nodeid; - attr->size = size; - attr->blocks = (size == 0) ? 0 : (((size-1) / attr->blksize) + 1); - attr->mode = mode; +static void fill_attr(fuse_attr* attr, const fuse_data* fd, uint64_t nodeid, uint64_t size, + uint32_t mode) { + memset(attr, 0, sizeof(*attr)); + attr->nlink = 1; + attr->uid = fd->uid; + attr->gid = fd->gid; + attr->blksize = 4096; + + attr->ino = nodeid; + attr->size = size; + attr->blocks = (size == 0) ? 0 : (((size - 1) / attr->blksize) + 1); + attr->mode = mode; } -static int handle_getattr(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) { - struct fuse_attr_out out; - memset(&out, 0, sizeof(out)); - out.attr_valid = 10; - - if (hdr->nodeid == FUSE_ROOT_ID) { - fill_attr(&(out.attr), fd, hdr->nodeid, 4096, S_IFDIR | 0555); - } else if (hdr->nodeid == PACKAGE_FILE_ID) { - fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); - } else if (hdr->nodeid == EXIT_FLAG_ID) { - fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0); - } else { - return -ENOENT; - } +static int handle_getattr(void* /* data */, const fuse_data* fd, const fuse_in_header* hdr) { + struct fuse_attr_out out; + memset(&out, 0, sizeof(out)); + out.attr_valid = 10; + + if (hdr->nodeid == FUSE_ROOT_ID) { + fill_attr(&(out.attr), fd, hdr->nodeid, 4096, S_IFDIR | 0555); + } else if (hdr->nodeid == PACKAGE_FILE_ID) { + fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); + } else if (hdr->nodeid == EXIT_FLAG_ID) { + fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0); + } else { + return -ENOENT; + } - fuse_reply(fd, hdr->unique, &out, sizeof(out)); - return (hdr->nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; + fuse_reply(fd, hdr->unique, &out, sizeof(out)); + return (hdr->nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; } -static int handle_lookup(void* data, struct fuse_data* fd, - const struct fuse_in_header* hdr) { - struct fuse_entry_out out; - memset(&out, 0, sizeof(out)); - out.entry_valid = 10; - out.attr_valid = 10; - - if (strncmp(FUSE_SIDELOAD_HOST_FILENAME, reinterpret_cast<const char*>(data), - sizeof(FUSE_SIDELOAD_HOST_FILENAME)) == 0) { - out.nodeid = PACKAGE_FILE_ID; - out.generation = PACKAGE_FILE_ID; - fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); - } else if (strncmp(FUSE_SIDELOAD_HOST_EXIT_FLAG, reinterpret_cast<const char*>(data), - sizeof(FUSE_SIDELOAD_HOST_EXIT_FLAG)) == 0) { - out.nodeid = EXIT_FLAG_ID; - out.generation = EXIT_FLAG_ID; - fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0); - } else { - return -ENOENT; - } +static int handle_lookup(void* data, const fuse_data* fd, const fuse_in_header* hdr) { + if (data == nullptr) return -ENOENT; + + struct fuse_entry_out out; + memset(&out, 0, sizeof(out)); + out.entry_valid = 10; + out.attr_valid = 10; + + std::string filename(static_cast<const char*>(data)); + if (filename == FUSE_SIDELOAD_HOST_FILENAME) { + out.nodeid = PACKAGE_FILE_ID; + out.generation = PACKAGE_FILE_ID; + fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); + } else if (filename == FUSE_SIDELOAD_HOST_EXIT_FLAG) { + out.nodeid = EXIT_FLAG_ID; + out.generation = EXIT_FLAG_ID; + fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0); + } else { + return -ENOENT; + } - fuse_reply(fd, hdr->unique, &out, sizeof(out)); - return (out.nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; + fuse_reply(fd, hdr->unique, &out, sizeof(out)); + return (out.nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; } -static int handle_open(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) { - if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM; - if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; +static int handle_open(void* /* data */, const fuse_data* fd, const fuse_in_header* hdr) { + if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM; + if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; - struct fuse_open_out out; - memset(&out, 0, sizeof(out)); - out.fh = 10; // an arbitrary number; we always use the same handle - fuse_reply(fd, hdr->unique, &out, sizeof(out)); - return NO_STATUS; + struct fuse_open_out out; + memset(&out, 0, sizeof(out)); + out.fh = 10; // an arbitrary number; we always use the same handle + fuse_reply(fd, hdr->unique, &out, sizeof(out)); + return NO_STATUS; } -static int handle_flush(void* /* data */, struct fuse_data* /* fd */, - const struct fuse_in_header* /* hdr */) { - return 0; +static int handle_flush(void* /* data */, fuse_data* /* fd */, const fuse_in_header* /* hdr */) { + return 0; } -static int handle_release(void* /* data */, struct fuse_data* /* fd */, - const struct fuse_in_header* /* hdr */) { - return 0; +static int handle_release(void* /* data */, fuse_data* /* fd */, const fuse_in_header* /* hdr */) { + return 0; } // Fetch a block from the host into fd->curr_block and fd->block_data. // Returns 0 on successful fetch, negative otherwise. -static int fetch_block(struct fuse_data* fd, uint32_t block) { - if (block == fd->curr_block) { - return 0; - } +static int fetch_block(fuse_data* fd, uint32_t block) { + if (block == fd->curr_block) { + return 0; + } - if (block >= fd->file_blocks) { - memset(fd->block_data, 0, fd->block_size); - fd->curr_block = block; - return 0; - } + if (block >= fd->file_blocks) { + memset(fd->block_data, 0, fd->block_size); + fd->curr_block = block; + return 0; + } - size_t fetch_size = fd->block_size; - if (block * fd->block_size + fetch_size > fd->file_size) { - // If we're reading the last (partial) block of the file, - // expect a shorter response from the host, and pad the rest - // of the block with zeroes. - fetch_size = fd->file_size - (block * fd->block_size); - memset(fd->block_data + fetch_size, 0, fd->block_size - fetch_size); - } + size_t fetch_size = fd->block_size; + if (block * fd->block_size + fetch_size > fd->file_size) { + // If we're reading the last (partial) block of the file, expect a shorter response from the + // host, and pad the rest of the block with zeroes. + fetch_size = fd->file_size - (block * fd->block_size); + memset(fd->block_data + fetch_size, 0, fd->block_size - fetch_size); + } - int result = fd->vtab->read_block(fd->cookie, block, fd->block_data, fetch_size); - if (result < 0) return result; + int result = fd->vtab.read_block(block, fd->block_data, fetch_size); + if (result < 0) return result; - fd->curr_block = block; + fd->curr_block = block; - // Verify the hash of the block we just got from the host. - // - // - If the hash of the just-received data matches the stored hash - // for the block, accept it. - // - If the stored hash is all zeroes, store the new hash and - // accept the block (this is the first time we've read this - // block). - // - Otherwise, return -EINVAL for the read. + // Verify the hash of the block we just got from the host. + // + // - If the hash of the just-received data matches the stored hash for the block, accept it. + // - If the stored hash is all zeroes, store the new hash and accept the block (this is the first + // time we've read this block). + // - Otherwise, return -EINVAL for the read. - uint8_t hash[SHA256_DIGEST_LENGTH]; + uint8_t hash[SHA256_DIGEST_LENGTH]; #ifdef USE_MINCRYPT - SHA256_hash(fd->block_data, fd->block_size, hash); + SHA256_hash(fd->block_data, fd->block_size, hash); #else - SHA256(fd->block_data, fd->block_size, hash); + SHA256(fd->block_data, fd->block_size, hash); #endif - uint8_t* blockhash = fd->hashes + block * SHA256_DIGEST_LENGTH; - if (memcmp(hash, blockhash, SHA256_DIGEST_LENGTH) == 0) { - return 0; - } + uint8_t* blockhash = fd->hashes + block * SHA256_DIGEST_LENGTH; + if (memcmp(hash, blockhash, SHA256_DIGEST_LENGTH) == 0) { + return 0; + } - int i; - for (i = 0; i < SHA256_DIGEST_LENGTH; ++i) { - if (blockhash[i] != 0) { - fd->curr_block = -1; - return -EIO; - } + int i; + for (i = 0; i < SHA256_DIGEST_LENGTH; ++i) { + if (blockhash[i] != 0) { + fd->curr_block = -1; + return -EIO; } + } - memcpy(blockhash, hash, SHA256_DIGEST_LENGTH); - return 0; + memcpy(blockhash, hash, SHA256_DIGEST_LENGTH); + return 0; } -static int handle_read(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { - const struct fuse_read_in* req = reinterpret_cast<const struct fuse_read_in*>(data); - struct fuse_out_header outhdr; - struct iovec vec[3]; - int vec_used; - int result; - - if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; - - uint64_t offset = req->offset; - uint32_t size = req->size; - - // The docs on the fuse kernel interface are vague about what to - // do when a read request extends past the end of the file. We - // can return a short read -- the return structure does include a - // length field -- but in testing that caused the program using - // the file to segfault. (I speculate that this is due to the - // reading program accessing it via mmap; maybe mmap dislikes when - // you return something short of a whole page?) To fix this we - // zero-pad reads that extend past the end of the file so we're - // always returning exactly as many bytes as were requested. - // (Users of the mapped file have to know its real length anyway.) - - outhdr.len = sizeof(outhdr) + size; - outhdr.error = 0; - outhdr.unique = hdr->unique; - vec[0].iov_base = &outhdr; - vec[0].iov_len = sizeof(outhdr); - - uint32_t block = offset / fd->block_size; - result = fetch_block(fd, block); +static int handle_read(void* data, fuse_data* fd, const fuse_in_header* hdr) { + if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; + + const fuse_read_in* req = static_cast<const fuse_read_in*>(data); + uint64_t offset = req->offset; + uint32_t size = req->size; + + // The docs on the fuse kernel interface are vague about what to do when a read request extends + // past the end of the file. We can return a short read -- the return structure does include a + // length field -- but in testing that caused the program using the file to segfault. (I + // speculate that this is due to the reading program accessing it via mmap; maybe mmap dislikes + // when you return something short of a whole page?) To fix this we zero-pad reads that extend + // past the end of the file so we're always returning exactly as many bytes as were requested. + // (Users of the mapped file have to know its real length anyway.) + + fuse_out_header outhdr; + outhdr.len = sizeof(outhdr) + size; + outhdr.error = 0; + outhdr.unique = hdr->unique; + + struct iovec vec[3]; + vec[0].iov_base = &outhdr; + vec[0].iov_len = sizeof(outhdr); + + uint32_t block = offset / fd->block_size; + int result = fetch_block(fd, block); + if (result != 0) return result; + + // Two cases: + // + // - the read request is entirely within this block. In this case we can reply immediately. + // + // - the read request goes over into the next block. Note that since we mount the filesystem + // with max_read=block_size, a read can never span more than two blocks. In this case we copy + // the block to extra_block and issue a fetch for the following block. + + uint32_t block_offset = offset - (block * fd->block_size); + + int vec_used; + if (size + block_offset <= fd->block_size) { + // First case: the read fits entirely in the first block. + + vec[1].iov_base = fd->block_data + block_offset; + vec[1].iov_len = size; + vec_used = 2; + } else { + // Second case: the read spills over into the next block. + + memcpy(fd->extra_block, fd->block_data + block_offset, fd->block_size - block_offset); + vec[1].iov_base = fd->extra_block; + vec[1].iov_len = fd->block_size - block_offset; + + result = fetch_block(fd, block + 1); if (result != 0) return result; + vec[2].iov_base = fd->block_data; + vec[2].iov_len = size - vec[1].iov_len; + vec_used = 3; + } - // Two cases: - // - // - the read request is entirely within this block. In this - // case we can reply immediately. - // - // - the read request goes over into the next block. Note that - // since we mount the filesystem with max_read=block_size, a - // read can never span more than two blocks. In this case we - // copy the block to extra_block and issue a fetch for the - // following block. - - uint32_t block_offset = offset - (block * fd->block_size); - - if (size + block_offset <= fd->block_size) { - // First case: the read fits entirely in the first block. - - vec[1].iov_base = fd->block_data + block_offset; - vec[1].iov_len = size; - vec_used = 2; - } else { - // Second case: the read spills over into the next block. - - memcpy(fd->extra_block, fd->block_data + block_offset, - fd->block_size - block_offset); - vec[1].iov_base = fd->extra_block; - vec[1].iov_len = fd->block_size - block_offset; - - result = fetch_block(fd, block+1); - if (result != 0) return result; - vec[2].iov_base = fd->block_data; - vec[2].iov_len = size - vec[1].iov_len; - vec_used = 3; - } - - if (writev(fd->ffd, vec, vec_used) < 0) { - printf("*** READ REPLY FAILED: %s ***\n", strerror(errno)); - } - return NO_STATUS; + if (writev(fd->ffd, vec, vec_used) == -1) { + printf("*** READ REPLY FAILED: %s ***\n", strerror(errno)); + } + return NO_STATUS; } -int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_size, - uint32_t block_size) { +int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size, + const char* mount_point) { // If something's already mounted on our mountpoint, try to remove it. (Mostly in case of a // previous abnormal exit.) - umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_FORCE); + umount2(mount_point, MNT_FORCE); // fs/fuse/inode.c in kernel code uses the greater of 4096 and the passed-in max_read. if (block_size < 4096) { @@ -393,9 +374,9 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_si return -1; } - struct fuse_data fd = {}; + fuse_data fd; + memset(&fd, 0, sizeof(fd)); fd.vtab = vtab; - fd.cookie = cookie; fd.file_size = file_size; fd.block_size = block_size; fd.file_blocks = (file_size == 0) ? 0 : (((file_size - 1) / block_size) + 1); @@ -419,42 +400,42 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_si fd.gid = getgid(); fd.curr_block = -1; - fd.block_data = (uint8_t*)malloc(block_size); - if (fd.block_data == NULL) { + fd.block_data = static_cast<uint8_t*>(malloc(block_size)); + if (fd.block_data == nullptr) { fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size); result = -1; goto done; } - fd.extra_block = (uint8_t*)malloc(block_size); - if (fd.extra_block == NULL) { + fd.extra_block = static_cast<uint8_t*>(malloc(block_size)); + if (fd.extra_block == nullptr) { fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size); result = -1; goto done; } fd.ffd = open("/dev/fuse", O_RDWR); - if (fd.ffd < 0) { + if (!fd.ffd) { perror("open /dev/fuse"); result = -1; goto done; } { - char opts[256]; - snprintf(opts, sizeof(opts), - ("fd=%d,user_id=%d,group_id=%d,max_read=%u," - "allow_other,rootmode=040000"), - fd.ffd, fd.uid, fd.gid, block_size); - - result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, "fuse", - MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts); - if (result < 0) { + char opts[256]; + snprintf(opts, sizeof(opts), + ("fd=%d,user_id=%d,group_id=%d,max_read=%u," + "allow_other,rootmode=040000"), + fd.ffd, fd.uid, fd.gid, block_size); + + result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, "fuse", + MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts); + if (result < 0) { perror("mount"); goto done; } } - uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX * 8]; + uint8_t request_buffer[sizeof(fuse_in_header) + PATH_MAX * 8]; for (;;) { ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer))); if (len == -1) { @@ -466,13 +447,13 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_si continue; } - if (static_cast<size_t>(len) < sizeof(struct fuse_in_header)) { + if (static_cast<size_t>(len) < sizeof(fuse_in_header)) { fprintf(stderr, "request too short: len=%zd\n", len); continue; } - struct fuse_in_header* hdr = reinterpret_cast<struct fuse_in_header*>(request_buffer); - void* data = request_buffer + sizeof(struct fuse_in_header); + fuse_in_header* hdr = reinterpret_cast<fuse_in_header*>(request_buffer); + void* data = request_buffer + sizeof(fuse_in_header); result = -ENOSYS; @@ -516,7 +497,7 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_si } if (result != NO_STATUS) { - struct fuse_out_header outhdr; + fuse_out_header outhdr; outhdr.len = sizeof(outhdr); outhdr.error = result; outhdr.unique = hdr->unique; @@ -525,23 +506,20 @@ int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_si } done: - fd.vtab->close(fd.cookie); + fd.vtab.close(); - result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH); - if (result < 0) { - printf("fuse_sideload umount failed: %s\n", strerror(errno)); + if (umount2(mount_point, MNT_DETACH) == -1) { + fprintf(stderr, "fuse_sideload umount failed: %s\n", strerror(errno)); } - if (fd.ffd) close(fd.ffd); - free(fd.hashes); free(fd.block_data); free(fd.extra_block); return result; } -extern "C" int run_old_fuse_sideload(struct provider_vtab* vtab, void* cookie, +extern "C" int run_old_fuse_sideload(const struct provider_vtab& vtab, void* cookie __unused, uint64_t file_size, uint32_t block_size) { - return run_fuse_sideload(vtab, cookie, file_size, block_size); + return run_fuse_sideload(vtab, file_size, block_size, FUSE_SIDELOAD_HOST_MOUNTPOINT); } diff --git a/fuse_sideload.h b/fuse_sideload.h index 2a8ebfc5e..1ea1eb8d8 100644 --- a/fuse_sideload.h +++ b/fuse_sideload.h @@ -17,31 +17,39 @@ #ifndef __FUSE_SIDELOAD_H #define __FUSE_SIDELOAD_H -// define the filenames created by the sideload FUSE filesystem -#define FUSE_SIDELOAD_HOST_MOUNTPOINT "/sideload" -#define FUSE_SIDELOAD_HOST_FILENAME "package.zip" -#define FUSE_SIDELOAD_HOST_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_FILENAME) -#define FUSE_SIDELOAD_HOST_EXIT_FLAG "exit" -#define FUSE_SIDELOAD_HOST_EXIT_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_EXIT_FLAG) +#ifdef USE_FUSE_SIDELOAD22 +#include "fuse_sideload22.h" +#else + +#include <functional> + +// Define the filenames created by the sideload FUSE filesystem. +static constexpr const char* FUSE_SIDELOAD_HOST_MOUNTPOINT = "/sideload"; +static constexpr const char* FUSE_SIDELOAD_HOST_FILENAME = "package.zip"; +static constexpr const char* FUSE_SIDELOAD_HOST_PATHNAME = "/sideload/package.zip"; +static constexpr const char* FUSE_SIDELOAD_HOST_EXIT_FLAG = "exit"; +static constexpr const char* FUSE_SIDELOAD_HOST_EXIT_PATHNAME = "/sideload/exit"; struct provider_vtab { - // read a block - int (*read_block)(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size); + // read a block + std::function<int(uint32_t block, uint8_t* buffer, uint32_t fetch_size)> read_block; - // close down - void (*close)(void* cookie); + // close down + std::function<void(void)> close; }; -int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, - uint64_t file_size, uint32_t block_size); +int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t block_size, + const char* mount_point = FUSE_SIDELOAD_HOST_MOUNTPOINT); #ifdef __cplusplus extern "C" { #endif -int run_old_fuse_sideload(struct provider_vtab* vtab, void* cookie, +int run_old_fuse_sideload(const struct provider_vtab& vtab, void* cookie, uint64_t file_size, uint32_t block_size); #ifdef __cplusplus } #endif #endif + +#endif diff --git a/fuse_sideload22.cpp b/fuse_sideload22.cpp new file mode 100644 index 000000000..f57d479b0 --- /dev/null +++ b/fuse_sideload22.cpp @@ -0,0 +1,544 @@ +/* + * Copyright (C) 2014 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. + */ + +// This module creates a special filesystem containing two files. +// +// "/sideload/package.zip" appears to be a normal file, but reading +// from it causes data to be fetched from the adb host. We can use +// this to sideload packages over an adb connection without having to +// store the entire package in RAM on the device. +// +// Because we may not trust the adb host, this filesystem maintains +// the following invariant: each read of a given position returns the +// same data as the first read at that position. That is, once a +// section of the file is read, future reads of that section return +// the same data. (Otherwise, a malicious adb host process could +// return one set of bits when the package is read for signature +// verification, and then different bits for when the package is +// accessed by the installer.) If the adb host returns something +// different than it did on the first read, the reader of the file +// will see their read fail with EINVAL. +// +// The other file, "/sideload/exit", is used to control the subprocess +// that creates this filesystem. Calling stat() on the exit file +// causes the filesystem to be unmounted and the adb process on the +// device shut down. +// +// Note that only the minimal set of file operations needed for these +// two files is implemented. In particular, you can't opendir() or +// readdir() on the "/sideload" directory; ls on it won't work. + +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include "fuse.h" +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/inotify.h> +#include <sys/mount.h> +#include <sys/param.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <sys/time.h> +#include <sys/uio.h> +#include <unistd.h> + +#ifdef USE_MINCRYPT +#include "mincrypt/sha256.h" +#define SHA256_DIGEST_LENGTH SHA256_DIGEST_SIZE +#else +#include <openssl/sha.h> +#endif + +#include "fuse_sideload.h" + +#define PACKAGE_FILE_ID (FUSE_ROOT_ID+1) +#define EXIT_FLAG_ID (FUSE_ROOT_ID+2) + +#define NO_STATUS 1 +#define NO_STATUS_EXIT 2 + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +struct fuse_data { + int ffd; // file descriptor for the fuse socket + + struct provider_vtab* vtab; + void* cookie; + + uint64_t file_size; // bytes + + uint32_t block_size; // block size that the adb host is using to send the file to us + uint32_t file_blocks; // file size in block_size blocks + + uid_t uid; + gid_t gid; + + uint32_t curr_block; // cache the block most recently read from the host + uint8_t* block_data; + + uint8_t* extra_block; // another block of storage for reads that + // span two blocks + + uint8_t* hashes; // SHA-256 hash of each block (all zeros + // if block hasn't been read yet) +}; + +static void fuse_reply(struct fuse_data* fd, __u64 unique, const void *data, size_t 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 = /* const_cast */(void*)(data); + vec[1].iov_len = len; + + res = writev(fd->ffd, vec, 2); + if (res < 0) { + printf("*** REPLY FAILED *** %s\n", strerror(errno)); + } +} + +static int handle_init(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + const struct fuse_init_in* req = reinterpret_cast<const struct fuse_init_in*>(data); + struct fuse_init_out out; + size_t fuse_struct_size; + + + /* Kernel 2.6.16 is the first stable kernel with struct fuse_init_out + * defined (fuse version 7.6). The structure is the same from 7.6 through + * 7.22. Beginning with 7.23, the structure increased in size and added + * new parameters. + */ + if (req->major != FUSE_KERNEL_VERSION || req->minor < 6) { + printf("Fuse kernel version mismatch: Kernel version %d.%d, Expected at least %d.6", + req->major, req->minor, FUSE_KERNEL_VERSION); + return -1; + } + + out.minor = MIN(req->minor, FUSE_KERNEL_MINOR_VERSION); + fuse_struct_size = sizeof(out); +#if defined(FUSE_COMPAT_22_INIT_OUT_SIZE) + /* FUSE_KERNEL_VERSION >= 23. */ + + /* If the kernel only works on minor revs older than or equal to 22, + * then use the older structure size since this code only uses the 7.22 + * version of the structure. */ + if (req->minor <= 22) { + fuse_struct_size = FUSE_COMPAT_22_INIT_OUT_SIZE; + } +#endif + + out.major = FUSE_KERNEL_VERSION; + out.max_readahead = req->max_readahead; + out.flags = 0; + out.max_background = 32; + out.congestion_threshold = 32; + out.max_write = 4096; + fuse_reply(fd, hdr->unique, &out, fuse_struct_size); + + return NO_STATUS; +} + +static void fill_attr(struct fuse_attr* attr, struct fuse_data* fd, + uint64_t nodeid, uint64_t size, uint32_t mode) { + memset(attr, 0, sizeof(*attr)); + attr->nlink = 1; + attr->uid = fd->uid; + attr->gid = fd->gid; + attr->blksize = 4096; + + attr->ino = nodeid; + attr->size = size; + attr->blocks = (size == 0) ? 0 : (((size-1) / attr->blksize) + 1); + attr->mode = mode; +} + +static int handle_getattr(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) { + struct fuse_attr_out out; + memset(&out, 0, sizeof(out)); + out.attr_valid = 10; + + if (hdr->nodeid == FUSE_ROOT_ID) { + fill_attr(&(out.attr), fd, hdr->nodeid, 4096, S_IFDIR | 0555); + } else if (hdr->nodeid == PACKAGE_FILE_ID) { + fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); + } else if (hdr->nodeid == EXIT_FLAG_ID) { + fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0); + } else { + return -ENOENT; + } + + fuse_reply(fd, hdr->unique, &out, sizeof(out)); + return (hdr->nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; +} + +static int handle_lookup(void* data, struct fuse_data* fd, + const struct fuse_in_header* hdr) { + struct fuse_entry_out out; + memset(&out, 0, sizeof(out)); + out.entry_valid = 10; + out.attr_valid = 10; + + if (strncmp(FUSE_SIDELOAD_HOST_FILENAME, reinterpret_cast<const char*>(data), + sizeof(FUSE_SIDELOAD_HOST_FILENAME)) == 0) { + out.nodeid = PACKAGE_FILE_ID; + out.generation = PACKAGE_FILE_ID; + fill_attr(&(out.attr), fd, PACKAGE_FILE_ID, fd->file_size, S_IFREG | 0444); + } else if (strncmp(FUSE_SIDELOAD_HOST_EXIT_FLAG, reinterpret_cast<const char*>(data), + sizeof(FUSE_SIDELOAD_HOST_EXIT_FLAG)) == 0) { + out.nodeid = EXIT_FLAG_ID; + out.generation = EXIT_FLAG_ID; + fill_attr(&(out.attr), fd, EXIT_FLAG_ID, 0, S_IFREG | 0); + } else { + return -ENOENT; + } + + fuse_reply(fd, hdr->unique, &out, sizeof(out)); + return (out.nodeid == EXIT_FLAG_ID) ? NO_STATUS_EXIT : NO_STATUS; +} + +static int handle_open(void* /* data */, struct fuse_data* fd, const struct fuse_in_header* hdr) { + if (hdr->nodeid == EXIT_FLAG_ID) return -EPERM; + if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; + + struct fuse_open_out out; + memset(&out, 0, sizeof(out)); + out.fh = 10; // an arbitrary number; we always use the same handle + fuse_reply(fd, hdr->unique, &out, sizeof(out)); + return NO_STATUS; +} + +static int handle_flush(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + return 0; +} + +static int handle_release(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + return 0; +} + +// Fetch a block from the host into fd->curr_block and fd->block_data. +// Returns 0 on successful fetch, negative otherwise. +static int fetch_block(struct fuse_data* fd, uint32_t block) { + if (block == fd->curr_block) { + return 0; + } + + if (block >= fd->file_blocks) { + memset(fd->block_data, 0, fd->block_size); + fd->curr_block = block; + return 0; + } + + size_t fetch_size = fd->block_size; + if (block * fd->block_size + fetch_size > fd->file_size) { + // If we're reading the last (partial) block of the file, + // expect a shorter response from the host, and pad the rest + // of the block with zeroes. + fetch_size = fd->file_size - (block * fd->block_size); + memset(fd->block_data + fetch_size, 0, fd->block_size - fetch_size); + } + + int result = fd->vtab->read_block(fd->cookie, block, fd->block_data, fetch_size); + if (result < 0) return result; + + fd->curr_block = block; + + // Verify the hash of the block we just got from the host. + // + // - If the hash of the just-received data matches the stored hash + // for the block, accept it. + // - If the stored hash is all zeroes, store the new hash and + // accept the block (this is the first time we've read this + // block). + // - Otherwise, return -EINVAL for the read. + + uint8_t hash[SHA256_DIGEST_LENGTH]; +#ifdef USE_MINCRYPT + SHA256_hash(fd->block_data, fd->block_size, hash); +#else + SHA256(fd->block_data, fd->block_size, hash); +#endif + uint8_t* blockhash = fd->hashes + block * SHA256_DIGEST_LENGTH; + if (memcmp(hash, blockhash, SHA256_DIGEST_LENGTH) == 0) { + return 0; + } + + int i; + for (i = 0; i < SHA256_DIGEST_LENGTH; ++i) { + if (blockhash[i] != 0) { + fd->curr_block = -1; + return -EIO; + } + } + + memcpy(blockhash, hash, SHA256_DIGEST_LENGTH); + return 0; +} + +static int handle_read(void* data, struct fuse_data* fd, const struct fuse_in_header* hdr) { + const struct fuse_read_in* req = reinterpret_cast<const struct fuse_read_in*>(data); + struct fuse_out_header outhdr; + struct iovec vec[3]; + int vec_used; + int result; + + if (hdr->nodeid != PACKAGE_FILE_ID) return -ENOENT; + + uint64_t offset = req->offset; + uint32_t size = req->size; + + // The docs on the fuse kernel interface are vague about what to + // do when a read request extends past the end of the file. We + // can return a short read -- the return structure does include a + // length field -- but in testing that caused the program using + // the file to segfault. (I speculate that this is due to the + // reading program accessing it via mmap; maybe mmap dislikes when + // you return something short of a whole page?) To fix this we + // zero-pad reads that extend past the end of the file so we're + // always returning exactly as many bytes as were requested. + // (Users of the mapped file have to know its real length anyway.) + + outhdr.len = sizeof(outhdr) + size; + outhdr.error = 0; + outhdr.unique = hdr->unique; + vec[0].iov_base = &outhdr; + vec[0].iov_len = sizeof(outhdr); + + uint32_t block = offset / fd->block_size; + result = fetch_block(fd, block); + if (result != 0) return result; + + // Two cases: + // + // - the read request is entirely within this block. In this + // case we can reply immediately. + // + // - the read request goes over into the next block. Note that + // since we mount the filesystem with max_read=block_size, a + // read can never span more than two blocks. In this case we + // copy the block to extra_block and issue a fetch for the + // following block. + + uint32_t block_offset = offset - (block * fd->block_size); + + if (size + block_offset <= fd->block_size) { + // First case: the read fits entirely in the first block. + + vec[1].iov_base = fd->block_data + block_offset; + vec[1].iov_len = size; + vec_used = 2; + } else { + // Second case: the read spills over into the next block. + + memcpy(fd->extra_block, fd->block_data + block_offset, + fd->block_size - block_offset); + vec[1].iov_base = fd->extra_block; + vec[1].iov_len = fd->block_size - block_offset; + + result = fetch_block(fd, block+1); + if (result != 0) return result; + vec[2].iov_base = fd->block_data; + vec[2].iov_len = size - vec[1].iov_len; + vec_used = 3; + } + + if (writev(fd->ffd, vec, vec_used) < 0) { + printf("*** READ REPLY FAILED: %s ***\n", strerror(errno)); + } + return NO_STATUS; +} + +int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, + uint64_t file_size, uint32_t block_size) +{ + int result; + + // If something's already mounted on our mountpoint, try to remove + // it. (Mostly in case of a previous abnormal exit.) + umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_FORCE); + + if (block_size < 1024) { + fprintf(stderr, "block size (%u) is too small\n", block_size); + return -1; + } + if (block_size > (1<<22)) { // 4 MiB + fprintf(stderr, "block size (%u) is too large\n", block_size); + return -1; + } + + struct fuse_data fd; + memset(&fd, 0, sizeof(fd)); + fd.vtab = vtab; + fd.cookie = cookie; + fd.file_size = file_size; + fd.block_size = block_size; + fd.file_blocks = (file_size == 0) ? 0 : (((file_size-1) / block_size) + 1); + + if (fd.file_blocks > (1<<18)) { + fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks); + result = -1; + goto done; + } + + fd.hashes = (uint8_t*)calloc(fd.file_blocks, SHA256_DIGEST_LENGTH); + if (fd.hashes == NULL) { + fprintf(stderr, "failed to allocate %d bites for hashes\n", + fd.file_blocks * SHA256_DIGEST_LENGTH); + result = -1; + goto done; + } + + fd.uid = getuid(); + fd.gid = getgid(); + + fd.curr_block = -1; + fd.block_data = (uint8_t*)malloc(block_size); + if (fd.block_data == NULL) { + fprintf(stderr, "failed to allocate %d bites for block_data\n", block_size); + result = -1; + goto done; + } + fd.extra_block = (uint8_t*)malloc(block_size); + if (fd.extra_block == NULL) { + fprintf(stderr, "failed to allocate %d bites for extra_block\n", block_size); + result = -1; + goto done; + } + + fd.ffd = open("/dev/fuse", O_RDWR); + if (fd.ffd < 0) { + perror("open /dev/fuse"); + result = -1; + goto done; + } + + char opts[256]; + snprintf(opts, sizeof(opts), + ("fd=%d,user_id=%d,group_id=%d,max_read=%u," + "allow_other,rootmode=040000"), + fd.ffd, fd.uid, fd.gid, block_size); + + result = mount("/dev/fuse", FUSE_SIDELOAD_HOST_MOUNTPOINT, + "fuse", MS_NOSUID | MS_NODEV | MS_RDONLY | MS_NOEXEC, opts); + if (result < 0) { + perror("mount"); + goto done; + } + uint8_t request_buffer[sizeof(struct fuse_in_header) + PATH_MAX*8]; + for (;;) { + ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer))); + if (len == -1) { + perror("read request"); + if (errno == ENODEV) { + result = -1; + break; + } + continue; + } + + if ((size_t)len < sizeof(struct fuse_in_header)) { + fprintf(stderr, "request too short: len=%zu\n", (size_t)len); + continue; + } + + struct fuse_in_header* hdr = (struct fuse_in_header*) request_buffer; + void* data = request_buffer + sizeof(struct fuse_in_header); + + result = -ENOSYS; + + switch (hdr->opcode) { + case FUSE_INIT: + result = handle_init(data, &fd, hdr); + break; + + case FUSE_LOOKUP: + result = handle_lookup(data, &fd, hdr); + break; + + case FUSE_GETATTR: + result = handle_getattr(data, &fd, hdr); + break; + + case FUSE_OPEN: + result = handle_open(data, &fd, hdr); + break; + + case FUSE_READ: + result = handle_read(data, &fd, hdr); + break; + + case FUSE_FLUSH: + result = handle_flush(data, &fd, hdr); + break; + + case FUSE_RELEASE: + result = handle_release(data, &fd, hdr); + break; + + default: + fprintf(stderr, "unknown fuse request opcode %d\n", hdr->opcode); + break; + } + + if (result == NO_STATUS_EXIT) { + result = 0; + break; + } + + if (result != NO_STATUS) { + struct fuse_out_header outhdr; + outhdr.len = sizeof(outhdr); + outhdr.error = result; + outhdr.unique = hdr->unique; + TEMP_FAILURE_RETRY(write(fd.ffd, &outhdr, sizeof(outhdr))); + } + } + + done: + fd.vtab->close(fd.cookie); + + result = umount2(FUSE_SIDELOAD_HOST_MOUNTPOINT, MNT_DETACH); + if (result < 0) { + printf("fuse_sideload umount failed: %s\n", strerror(errno)); + } + + if (fd.ffd) close(fd.ffd); + free(fd.hashes); + free(fd.block_data); + free(fd.extra_block); + + return result; +} + +extern "C" int run_old_fuse_sideload(struct provider_vtab* vtab, void* cookie, + uint64_t file_size, uint32_t block_size) +{ + return run_fuse_sideload(vtab, cookie, file_size, block_size); +} diff --git a/fuse_sideload22.h b/fuse_sideload22.h new file mode 100644 index 000000000..98254d46b --- /dev/null +++ b/fuse_sideload22.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2014 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. + */ + +#ifndef __FUSE_SIDELOAD22_H +#define __FUSE_SIDELOAD22_H + +// define the filenames created by the sideload FUSE filesystem +#define FUSE_SIDELOAD_HOST_MOUNTPOINT "/sideload" +#define FUSE_SIDELOAD_HOST_FILENAME "package.zip" +#define FUSE_SIDELOAD_HOST_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_FILENAME) +#define FUSE_SIDELOAD_HOST_EXIT_FLAG "exit" +#define FUSE_SIDELOAD_HOST_EXIT_PATHNAME (FUSE_SIDELOAD_HOST_MOUNTPOINT "/" FUSE_SIDELOAD_HOST_EXIT_FLAG) + +struct provider_vtab { + // read a block + int (*read_block)(void* cookie, uint32_t block, uint8_t* buffer, uint32_t fetch_size); + + // close down + void (*close)(void* cookie); +}; + +int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, + uint64_t file_size, uint32_t block_size); + +#ifdef __cplusplus +extern "C" { +#endif +int run_old_fuse_sideload(struct provider_vtab* vtab, void* cookie, + uint64_t file_size, uint32_t block_size); +#ifdef __cplusplus +} +#endif + +#endif |