Browse Source

recovery: Provide caching for sideload files

Create a cache of block data received via adb.  The cache size is set
to ensure that there is at least 100mb available for the installer.

When the cache is large enough to hold the entire file, each block is
read via adb at most once.

When the cache is not large enough to hold the entire file, the cache
will need to be pruned.  Because files tend to be read sequentially
during install, the pruning algorithm attempts to discard blocks that
are behind the current file position.

Change-Id: Id8fc7fa5b38f1d80461eb576b1a1b5d53453cfc1
Tom Marshall 6 years ago
parent
commit
629d596ba2
1 changed files with 106 additions and 1 deletions
  1. 106
    1
      fuse_sideload.cpp

+ 106
- 1
fuse_sideload.cpp View File

@@ -71,6 +71,8 @@ static constexpr int NO_STATUS = 1;
71 71
 
72 72
 using SHA256Digest = std::array<uint8_t, SHA256_DIGEST_LENGTH>;
73 73
 
74
+#define INSTALL_REQUIRED_MEMORY (100*1024*1024)
75
+
74 76
 struct fuse_data {
75 77
   android::base::unique_fd ffd;  // file descriptor for the fuse socket
76 78
 
@@ -84,15 +86,85 @@ struct fuse_data {
84 86
   uid_t uid;
85 87
   gid_t gid;
86 88
 
87
-  uint32_t curr_block;  // cache the block most recently read from the host
89
+  uint32_t curr_block;  // cache the block most recently used
88 90
   uint8_t* block_data;
89 91
 
90 92
   uint8_t* extra_block;  // another block of storage for reads that span two blocks
91 93
 
92 94
   std::vector<SHA256Digest>
93 95
       hashes;  // SHA-256 hash of each block (all zeros if block hasn't been read yet)
96
+
97
+  // Block cache
98
+  uint32_t block_cache_max_size;   // Max allowed block cache size
99
+  uint32_t block_cache_size;       // Current block cache size
100
+  uint8_t** block_cache;           // Block cache data
94 101
 };
95 102
 
103
+static uint64_t free_memory() {
104
+  uint64_t mem = 0;
105
+  FILE* fp = fopen("/proc/meminfo", "r");
106
+  if (fp) {
107
+    char buf[256];
108
+    char* linebuf = buf;
109
+    size_t buflen = sizeof(buf);
110
+    while (getline(&linebuf, &buflen, fp) > 0) {
111
+      char* key = buf;
112
+      char* val = strchr(buf, ':');
113
+      *val = '\0';
114
+      ++val;
115
+      if (strcmp(key, "MemFree") == 0) {
116
+        mem += strtoul(val, nullptr, 0) * 1024;
117
+      }
118
+      if (strcmp(key, "Buffers") == 0) {
119
+        mem += strtoul(val, nullptr, 0) * 1024;
120
+      }
121
+      if (strcmp(key, "Cached") == 0) {
122
+        mem += strtoul(val, nullptr, 0) * 1024;
123
+      }
124
+    }
125
+    fclose(fp);
126
+  }
127
+  return mem;
128
+}
129
+
130
+static int block_cache_fetch(struct fuse_data* fd, uint32_t block) {
131
+  if (fd->block_cache == nullptr) {
132
+    return -1;
133
+  }
134
+  if (fd->block_cache[block] == nullptr) {
135
+    return -1;
136
+  }
137
+  memcpy(fd->block_data, fd->block_cache[block], fd->block_size);
138
+  return 0;
139
+}
140
+
141
+static void block_cache_enter(struct fuse_data* fd, uint32_t block) {
142
+  if (!fd->block_cache)
143
+    return;
144
+  if (fd->block_cache_size == fd->block_cache_max_size) {
145
+    // Evict a block from the cache.  Since the file is typically read
146
+    // sequentially, start looking from the block behind the current
147
+    // block and proceed backward.
148
+    int n;
149
+    for (n = fd->curr_block - 1; n != (int)fd->curr_block; --n) {
150
+      if (n < 0) {
151
+        n = fd->file_blocks - 1;
152
+      }
153
+      if (fd->block_cache[n]) {
154
+        free(fd->block_cache[n]);
155
+        fd->block_cache[n] = nullptr;
156
+        fd->block_cache_size--;
157
+        break;
158
+      }
159
+    }
160
+  }
161
+
162
+  fd->block_cache[block] = (uint8_t*)malloc(fd->block_size);
163
+  memcpy(fd->block_cache[block], fd->block_data, fd->block_size);
164
+
165
+  fd->block_cache_size++;
166
+}
167
+
96 168
 static void fuse_reply(const fuse_data* fd, uint64_t unique, const void* data, size_t len) {
97 169
   fuse_out_header hdr;
98 170
   hdr.len = len + sizeof(hdr);
@@ -227,6 +299,11 @@ static int fetch_block(fuse_data* fd, uint32_t block) {
227 299
     return 0;
228 300
   }
229 301
 
302
+  if (block_cache_fetch(fd, block) == 0) {
303
+    fd->curr_block = block;
304
+    return 0;
305
+  }
306
+
230 307
   size_t fetch_size = fd->block_size;
231 308
   if (block * fd->block_size + fetch_size > fd->file_size) {
232 309
     // If we're reading the last (partial) block of the file, expect a shorter response from the
@@ -263,6 +340,7 @@ static int fetch_block(fuse_data* fd, uint32_t block) {
263 340
   }
264 341
 
265 342
   fd->hashes[block] = hash;
343
+  block_cache_enter(fd, block);
266 344
   return 0;
267 345
 }
268 346
 
@@ -359,6 +437,9 @@ int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t bl
359 437
   fd.block_size = block_size;
360 438
   fd.file_blocks = (file_size == 0) ? 0 : (((file_size - 1) / block_size) + 1);
361 439
 
440
+  uint64_t mem = free_memory();
441
+  uint64_t avail = mem - (INSTALL_REQUIRED_MEMORY + fd.file_blocks * sizeof(uint8_t*));
442
+
362 443
   int result;
363 444
   if (fd.file_blocks > (1 << 18)) {
364 445
     fprintf(stderr, "file has too many blocks (%u)\n", fd.file_blocks);
@@ -385,6 +466,22 @@ int run_fuse_sideload(const provider_vtab& vtab, uint64_t file_size, uint32_t bl
385 466
     goto done;
386 467
   }
387 468
 
469
+  fd.block_cache_max_size = 0;
470
+  fd.block_cache_size = 0;
471
+  fd.block_cache = nullptr;
472
+  if (mem > avail) {
473
+    uint32_t max_size = avail / fd.block_size;
474
+    if (max_size > fd.file_blocks) {
475
+      max_size = fd.file_blocks;
476
+    }
477
+    // The cache must be at least 1% of the file size or two blocks,
478
+    // whichever is larger.
479
+    if (max_size >= fd.file_blocks/100 && max_size >= 2) {
480
+      fd.block_cache_max_size = max_size;
481
+      fd.block_cache = (uint8_t**)calloc(fd.file_blocks, sizeof(uint8_t*));
482
+    }
483
+  }
484
+
388 485
   signal(SIGTERM, sig_term);
389 486
 
390 487
   fd.ffd.reset(open("/dev/fuse", O_RDWR));
@@ -489,6 +586,14 @@ done:
489 586
     fprintf(stderr, "fuse_sideload umount failed: %s\n", strerror(errno));
490 587
   }
491 588
 
589
+  if (fd.block_cache) {
590
+    uint32_t n;
591
+    for (n = 0; n < fd.file_blocks; ++n) {
592
+      free(fd.block_cache[n]);
593
+    }
594
+    free(fd.block_cache);
595
+  }
596
+
492 597
   free(fd.block_data);
493 598
   free(fd.extra_block);
494 599