static int handle_fuse_request(struct fuse *fuse, struct fuse_handler* handler, const struct fuse_in_header *hdr, const void *data, size_t data_len) { switch (hdr->opcode) { case FUSE_LOOKUP: { /* bytez[] -> entry_out */ const char* name = data; return handle_lookup(fuse, handler, hdr, name); } case FUSE_FORGET: { const struct fuse_forget_in *req = data; return handle_forget(fuse, handler, hdr, req); } case FUSE_GETATTR: { /* getattr_in -> attr_out */ const struct fuse_getattr_in *req = data; return handle_getattr(fuse, handler, hdr, req); } case FUSE_SETATTR: { /* setattr_in -> attr_out */ const struct fuse_setattr_in *req = data; return handle_setattr(fuse, handler, hdr, req); } // case FUSE_READLINK: // case FUSE_SYMLINK: case FUSE_MKNOD: { /* mknod_in, bytez[] -> entry_out */ const struct fuse_mknod_in *req = data; const char *name = ((const char*) data) + sizeof(*req); return handle_mknod(fuse, handler, hdr, req, name); } case FUSE_MKDIR: { /* mkdir_in, bytez[] -> entry_out */ const struct fuse_mkdir_in *req = data; const char *name = ((const char*) data) + sizeof(*req); return handle_mkdir(fuse, handler, hdr, req, name); } case FUSE_UNLINK: { /* bytez[] -> */ const char* name = data; return handle_unlink(fuse, handler, hdr, name); } case FUSE_RMDIR: { /* bytez[] -> */ const char* name = data; return handle_rmdir(fuse, handler, hdr, name); } case FUSE_RENAME: { /* rename_in, oldname, newname -> */ const struct fuse_rename_in *req = data; const char *old_name = ((const char*) data) + sizeof(*req); const char *new_name = old_name + strlen(old_name) + 1; return handle_rename(fuse, handler, hdr, req, old_name, new_name); } // case FUSE_LINK: case FUSE_OPEN: { /* open_in -> open_out */ const struct fuse_open_in *req = data; return handle_open(fuse, handler, hdr, req); } case FUSE_READ: { /* read_in -> byte[] */ const struct fuse_read_in *req = data; return handle_read(fuse, handler, hdr, req); } case FUSE_WRITE: { /* write_in, byte[write_in.size] -> write_out */ const struct fuse_write_in *req = data; const void* buffer = (const __u8*)data + sizeof(*req); return handle_write(fuse, handler, hdr, req, buffer); } case FUSE_STATFS: { /* getattr_in -> attr_out */ return handle_statfs(fuse, handler, hdr); } case FUSE_RELEASE: { /* release_in -> */ const struct fuse_release_in *req = data; return handle_release(fuse, handler, hdr, req); } case FUSE_FSYNC: { const struct fuse_fsync_in *req = data; return handle_fsync(fuse, handler, hdr, req); } // case FUSE_SETXATTR: // case FUSE_GETXATTR: // case FUSE_LISTXATTR: // case FUSE_REMOVEXATTR: case FUSE_FLUSH: { return handle_flush(fuse, handler, hdr); } case FUSE_OPENDIR: { /* open_in -> open_out */ const struct fuse_open_in *req = data; return handle_opendir(fuse, handler, hdr, req); } case FUSE_READDIR: { const struct fuse_read_in *req = data; return handle_readdir(fuse, handler, hdr, req); } case FUSE_RELEASEDIR: { /* release_in -> */ const struct fuse_release_in *req = data; return handle_releasedir(fuse, handler, hdr, req); } // case FUSE_FSYNCDIR: case FUSE_INIT: { /* init_in -> init_out */ const struct fuse_init_in *req = data; return handle_init(fuse, handler, hdr, req); } default: { TRACE("[%d] NOTIMPL op=%d uniq=%llx nid=%llx\n", handler->token, hdr->opcode, hdr->unique, hdr->nodeid); return -ENOSYS; } } }
int run_fuse_sideload(struct provider_vtab* vtab, void* cookie, uint64_t file_size, uint32_t block_size) { int result; uint64_t mem = 0; uint64_t avail = 0; // 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_SIZE); if (fd.hashes == NULL) { fprintf(stderr, "failed to allocate %d bites for hashes\n", fd.file_blocks * SHA256_DIGEST_SIZE); 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.block_cache_max_size = 0; fd.block_cache_size = 0; fd.block_cache = NULL; mem = free_memory(); avail = mem - (INSTALL_REQUIRED_MEMORY + fd.file_blocks * sizeof(uint8_t*)); if (mem > avail) { uint32_t max_size = avail / fd.block_size; if (max_size > fd.file_blocks) { max_size = fd.file_blocks; } // The cache must be at least 1% of the file size or two blocks, // whichever is larger. if (max_size >= fd.file_blocks/100 && max_size >= 2) { fd.block_cache_max_size = max_size; fd.block_cache = (uint8_t**)calloc(fd.file_blocks, sizeof(uint8_t*)); } } signal(SIGTERM, sig_term); 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]; while (!terminated) { fd_set fds; struct timeval tv; FD_ZERO(&fds); FD_SET(fd.ffd, &fds); tv.tv_sec = 1; tv.tv_usec = 0; int rc = select(fd.ffd+1, &fds, NULL, NULL, &tv); if (rc <= 0) { continue; } ssize_t len = TEMP_FAILURE_RETRY(read(fd.ffd, request_buffer, sizeof(request_buffer))); if (len < 0) { if (errno != EINTR) { 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) { 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); if (fd.block_cache) { uint32_t n; for (n = 0; n < fd.file_blocks; ++n) { free(fd.block_cache[n]); } free(fd.block_cache); } free(fd.hashes); free(fd.block_data); free(fd.extra_block); return result; }
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_SIZE); if (fd.hashes == NULL) { fprintf(stderr, "failed to allocate %d bites for hashes\n", fd.file_blocks * SHA256_DIGEST_SIZE); 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=%zu," "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 = read(fd.ffd, request_buffer, sizeof(request_buffer)); if (len < 0) { if (errno != EINTR) { 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; 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; }