diff options
Diffstat (limited to 'fuse_sideload.cpp')
-rw-r--r-- | fuse_sideload.cpp | 106 |
1 files changed, 105 insertions, 1 deletions
diff --git a/fuse_sideload.cpp b/fuse_sideload.cpp index ce0ecc3e..dd325569 100644 --- a/fuse_sideload.cpp +++ b/fuse_sideload.cpp @@ -72,6 +72,8 @@ static constexpr int NO_STATUS = 1; using SHA256Digest = std::array<uint8_t, SHA256_DIGEST_LENGTH>; +#define INSTALL_REQUIRED_MEMORY (100 * 1024 * 1024) + struct fuse_data { android::base::unique_fd ffd; // file descriptor for the fuse socket @@ -85,15 +87,84 @@ struct fuse_data { uid_t uid; gid_t gid; - uint32_t curr_block; // cache the block most recently read from the host + uint32_t curr_block; // cache the block most recently used uint8_t* block_data; uint8_t* extra_block; // another block of storage for reads that span two blocks std::vector<SHA256Digest> hashes; // SHA-256 hash of each block (all zeros if block hasn't been read yet) + + // Block cache + uint32_t block_cache_max_size; // Max allowed block cache size + uint32_t block_cache_size; // Current block cache size + uint8_t** block_cache; // Block cache data }; +static uint64_t free_memory() { + uint64_t mem = 0; + FILE* fp = fopen("/proc/meminfo", "r"); + if (fp) { + char buf[256]; + char* linebuf = buf; + size_t buflen = sizeof(buf); + while (getline(&linebuf, &buflen, fp) > 0) { + char* key = buf; + char* val = strchr(buf, ':'); + *val = '\0'; + ++val; + if (strcmp(key, "MemFree") == 0) { + mem += strtoul(val, nullptr, 0) * 1024; + } + if (strcmp(key, "Buffers") == 0) { + mem += strtoul(val, nullptr, 0) * 1024; + } + if (strcmp(key, "Cached") == 0) { + mem += strtoul(val, nullptr, 0) * 1024; + } + } + fclose(fp); + } + return mem; +} + +static int block_cache_fetch(struct fuse_data* fd, uint32_t block) { + if (fd->block_cache == nullptr) { + return -1; + } + if (fd->block_cache[block] == nullptr) { + return -1; + } + memcpy(fd->block_data, fd->block_cache[block], fd->block_size); + return 0; +} + +static void block_cache_enter(struct fuse_data* fd, uint32_t block) { + if (!fd->block_cache) return; + if (fd->block_cache_size == fd->block_cache_max_size) { + // Evict a block from the cache. Since the file is typically read + // sequentially, start looking from the block behind the current + // block and proceed backward. + int n; + for (n = fd->curr_block - 1; n != (int)fd->curr_block; --n) { + if (n < 0) { + n = fd->file_blocks - 1; + } + if (fd->block_cache[n]) { + free(fd->block_cache[n]); + fd->block_cache[n] = nullptr; + fd->block_cache_size--; + break; + } + } + } + + fd->block_cache[block] = (uint8_t*)malloc(fd->block_size); + memcpy(fd->block_cache[block], fd->block_data, fd->block_size); + + fd->block_cache_size++; +} + 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); @@ -228,6 +299,11 @@ static int fetch_block(fuse_data* fd, uint32_t block) { return 0; } + if (block_cache_fetch(fd, block) == 0) { + 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 @@ -264,6 +340,7 @@ static int fetch_block(fuse_data* fd, uint32_t block) { } fd->hashes[block] = hash; + block_cache_enter(fd, block); return 0; } @@ -359,6 +436,9 @@ int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t bl fd.block_size = block_size; fd.file_blocks = (file_size == 0) ? 0 : (((file_size - 1) / block_size) + 1); + uint64_t mem = free_memory(); + uint64_t avail = mem - (INSTALL_REQUIRED_MEMORY + fd.file_blocks * sizeof(uint8_t*)); + int result; if (fd.file_blocks > (1 << 18)) { fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks); @@ -385,6 +465,22 @@ int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t bl goto done; } + fd.block_cache_max_size = 0; + fd.block_cache_size = 0; + fd.block_cache = nullptr; + 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.reset(open("/dev/fuse", O_RDWR)); @@ -489,6 +585,14 @@ done: fprintf(stderr, "fuse_sideload umount failed: %s\n", strerror(errno)); } + 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.block_data); free(fd.extra_block); |