blob: 1f0f59dbaf4bf1e0576c1927dea44706792ce4bc [file] [log] [blame]
Doug Zongker76adfc52014-01-13 10:04:25 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17// This program takes a file on an ext4 filesystem and produces a list
18// of the blocks that file occupies, which enables the file contents
19// to be read directly from the block device without mounting the
20// filesystem.
21//
22// If the filesystem is using an encrypted block device, it will also
23// read the file and rewrite it to the same blocks of the underlying
24// (unencrypted) block device, so the file contents can be read
25// without the need for the decryption key.
26//
27// The output of this program is a "block map" which looks like this:
28//
29// /dev/block/platform/msm_sdcc.1/by-name/userdata # block device
30// 49652 4096 # file size in bytes, block size
31// 3 # count of block ranges
32// 1000 1008 # block range 0
33// 2100 2102 # ... block range 1
34// 30 33 # ... block range 2
35//
36// Each block range represents a half-open interval; the line "30 33"
37// reprents the blocks [30, 31, 32].
38//
39// Recovery can take this block map file and retrieve the underlying
40// file data to use as an update package.
41
42#include <stdio.h>
43#include <stdlib.h>
44#include <stdarg.h>
45#include <sys/types.h>
46#include <sys/stat.h>
47#include <fcntl.h>
48#include <linux/fs.h>
49#include <sys/mman.h>
50
51#include <cutils/properties.h>
52#include <fs_mgr.h>
53
54#define WINDOW_SIZE 5
55#define RECOVERY_COMMAND_FILE "/cache/recovery/command"
56#define RECOVERY_COMMAND_FILE_TMP "/cache/recovery/command.tmp"
57#define CACHE_BLOCK_MAP "/cache/recovery/block.map"
58
59static int write_at_offset(unsigned char* buffer, size_t size,
60 int wfd, off64_t offset)
61{
62 lseek64(wfd, offset, SEEK_SET);
63 size_t written = 0;
64 while (written < size) {
65 ssize_t wrote = write(wfd, buffer + written, size - written);
66 if (wrote < 0) {
67 fprintf(stderr, "error writing offset %lld: %s\n", offset, strerror(errno));
68 return -1;
69 }
70 written += wrote;
71 }
72 return 0;
73}
74
75void add_block_to_ranges(int** ranges, int* range_alloc, int* range_used, int new_block)
76{
77 // If the current block start is < 0, set the start to the new
78 // block. (This only happens for the very first block of the very
79 // first range.)
80 if ((*ranges)[*range_used*2-2] < 0) {
81 (*ranges)[*range_used*2-2] = new_block;
82 (*ranges)[*range_used*2-1] = new_block;
83 }
84
85 if (new_block == (*ranges)[*range_used*2-1]) {
86 // If the new block comes immediately after the current range,
87 // all we have to do is extend the current range.
88 ++(*ranges)[*range_used*2-1];
89 } else {
90 // We need to start a new range.
91
92 // If there isn't enough room in the array, we need to expand it.
93 if (*range_used >= *range_alloc) {
94 *range_alloc *= 2;
95 *ranges = realloc(*ranges, *range_alloc * 2 * sizeof(int));
96 }
97
98 ++*range_used;
99 (*ranges)[*range_used*2-2] = new_block;
100 (*ranges)[*range_used*2-1] = new_block+1;
101 }
102}
103
104const char* find_block_device(const char* path, int* encryptable, int* encrypted)
105{
106 // The fstab path is always "/fstab.${ro.hardware}".
107 char fstab_path[PATH_MAX+1] = "/fstab.";
108 if (!property_get("ro.hardware", fstab_path+strlen(fstab_path), "")) {
109 fprintf(stderr, "failed to get ro.hardware\n");
110 return NULL;
111 }
112
113 struct fstab* fstab = fs_mgr_read_fstab(fstab_path);
114 if (!fstab) {
115 fprintf(stderr, "failed to read %s\n", fstab_path);
116 return NULL;
117 }
118
119 // Look for a volume whose mount point is the prefix of path and
120 // return its block device. Set encrypted if it's currently
121 // encrypted.
122 int i;
123 for (i = 0; i < fstab->num_entries; ++i) {
124 struct fstab_rec* v = &fstab->recs[i];
125 if (!v->mount_point) continue;
126 int len = strlen(v->mount_point);
127 if (strncmp(path, v->mount_point, len) == 0 &&
128 (path[len] == '/' || path[len] == 0)) {
129 *encrypted = 0;
130 *encryptable = 0;
131 if (fs_mgr_is_encryptable(v)) {
132 *encryptable = 1;
133 char buffer[PROPERTY_VALUE_MAX+1];
134 if (property_get("ro.crypto.state", buffer, "") &&
135 strcmp(buffer, "encrypted") == 0) {
136 *encrypted = 1;
137 }
138 }
139 return v->blk_device;
140 }
141 }
142
143 return NULL;
144}
145
146char* parse_recovery_command_file()
147{
148 char* fn = NULL;
149 int count = 0;
150 char temp[1024];
151
152 FILE* fo = fopen(RECOVERY_COMMAND_FILE_TMP, "w");
153
154 FILE* f = fopen(RECOVERY_COMMAND_FILE, "r");
155 while (fgets(temp, sizeof(temp), f)) {
156 printf("read: %s", temp);
157 if (strncmp(temp, "--update_package=", strlen("--update_package=")) == 0) {
158 fn = strdup(temp + strlen("--update_package="));
159 strcpy(temp, "--update_package=@" CACHE_BLOCK_MAP "\n");
160 }
161 fputs(temp, fo);
162 }
163 fclose(f);
164 fclose(fo);
165
166 if (fn) {
167 char* newline = strchr(fn, '\n');
168 if (newline) *newline = 0;
169 }
170 return fn;
171}
172
173int produce_block_map(const char* path, const char* map_file, const char* blk_dev,
174 int encrypted)
175{
176 struct stat sb;
177 int ret;
178
179 FILE* mapf = fopen(map_file, "w");
180
181 ret = stat(path, &sb);
182 if (ret != 0) {
183 fprintf(stderr, "failed to stat %s\n", path);
184 return -1;
185 }
186
187 printf(" block size: %ld bytes\n", sb.st_blksize);
188
189 int blocks = ((sb.st_size-1) / sb.st_blksize) + 1;
190 printf(" file size: %lld bytes, %d blocks\n", sb.st_size, blocks);
191
192 int* ranges;
193 int range_alloc = 1;
194 int range_used = 1;
195 ranges = malloc(range_alloc * 2 * sizeof(int));
196 ranges[0] = -1;
197 ranges[1] = -1;
198
199 fprintf(mapf, "%s\n%lld %lu\n", blk_dev, sb.st_size, sb.st_blksize);
200
201 unsigned char* buffers[WINDOW_SIZE];
202 int i;
203 if (encrypted) {
204 for (i = 0; i < WINDOW_SIZE; ++i) {
205 buffers[i] = malloc(sb.st_blksize);
206 }
207 }
208 int head_block = 0;
209 int head = 0, tail = 0;
210 size_t pos = 0;
211
212 int fd = open(path, O_RDONLY);
213 if (fd < 0) {
214 fprintf(stderr, "failed to open fd for reading: %s\n", strerror(errno));
215 return -1;
216 }
217 fsync(fd);
218
219 int wfd = -1;
220 if (encrypted) {
221 wfd = open(blk_dev, O_WRONLY);
222 if (wfd < 0) {
223 fprintf(stderr, "failed to open fd for writing: %s\n", strerror(errno));
224 return -1;
225 }
226 }
227
228 while (pos < sb.st_size) {
229 if ((tail+1) % WINDOW_SIZE == head) {
230 // write out head buffer
231 int block = head_block;
232 ret = ioctl(fd, FIBMAP, &block);
233 if (ret != 0) {
234 fprintf(stderr, "failed to find block %d\n", head_block);
235 return -1;
236 }
237 add_block_to_ranges(&ranges, &range_alloc, &range_used, block);
238 if (encrypted) {
239 if (write_at_offset(buffers[head], sb.st_blksize, wfd, (off64_t)sb.st_blksize * block) != 0) {
240 return -1;
241 }
242 }
243 head = (head + 1) % WINDOW_SIZE;
244 ++head_block;
245 }
246
247 // read next block to tail
248 if (encrypted) {
249 size_t so_far = 0;
250 while (so_far < sb.st_blksize && pos < sb.st_size) {
251 ssize_t this_read = read(fd, buffers[tail] + so_far, sb.st_blksize - so_far);
252 if (this_read < 0) {
253 fprintf(stderr, "failed to read: %s\n", strerror(errno));
254 return -1;
255 }
256 so_far += this_read;
257 pos += this_read;
258 }
259 } else {
260 // If we're not encrypting; we don't need to actually read
261 // anything, just skip pos forward as if we'd read a
262 // block.
263 pos += sb.st_blksize;
264 }
265 tail = (tail+1) % WINDOW_SIZE;
266 }
267
268 while (head != tail) {
269 // write out head buffer
270 int block = head_block;
271 ret = ioctl(fd, FIBMAP, &block);
272 if (ret != 0) {
273 fprintf(stderr, "failed to find block %d\n", head_block);
274 return -1;
275 }
276 add_block_to_ranges(&ranges, &range_alloc, &range_used, block);
277 if (encrypted) {
278 if (write_at_offset(buffers[head], sb.st_blksize, wfd, (off64_t)sb.st_blksize * block) != 0) {
279 return -1;
280 }
281 }
282 head = (head + 1) % WINDOW_SIZE;
283 ++head_block;
284 }
285
286 fprintf(mapf, "%d\n", range_used);
287 for (i = 0; i < range_used; ++i) {
288 fprintf(mapf, "%d %d\n", ranges[i*2], ranges[i*2+1]);
289 }
290
291 fclose(mapf);
292 close(fd);
293 if (encrypted) {
294 close(wfd);
295 }
296
297 return 0;
298}
299
300void reboot_to_recovery() {
301 property_set("sys.powerctl", "reboot,recovery");
302 sleep(10);
303}
304
305int main(int argc, char** argv)
306{
307 const char* input_path;
308 const char* map_file;
309 int do_reboot = 1;
310
311 if (argc != 1 && argc != 3) {
312 fprintf(stderr, "usage: %s [<transform_path> <map_file>]\n", argv[0]);
313 return 2;
314 }
315
316 if (argc == 3) {
317 // when command-line args are given this binary is being used
318 // for debugging; don't reboot to recovery at the end.
319 input_path = argv[1];
320 map_file = argv[2];
321 do_reboot = 0;
322 } else {
323 input_path = parse_recovery_command_file();
324 if (input_path == NULL) {
325 // if we're rebooting to recovery without a package (say,
326 // to wipe data), then we don't need to do anything before
327 // going to recovery.
328 fprintf(stderr, "no recovery command file or no update package arg");
329 reboot_to_recovery();
330 return 1;
331 }
332 map_file = CACHE_BLOCK_MAP;
333 }
334
335 // Turn the name of the file we're supposed to convert into an
336 // absolute path, so we can find what filesystem it's on.
337 char path[PATH_MAX+1];
338 if (realpath(input_path, path) == NULL) {
339 fprintf(stderr, "failed to convert %s to absolute path: %s\n", input_path, strerror(errno));
340 return 1;
341 }
342
343 int encryptable;
344 int encrypted;
345 const char* blk_dev = find_block_device(path, &encryptable, &encrypted);
346 if (blk_dev == NULL) {
347 fprintf(stderr, "failed to find block device for %s\n", path);
348 return 1;
349 }
350
351 // If the filesystem it's on isn't encrypted, we only produce the
352 // block map, we don't rewrite the file contents (it would be
353 // pointless to do so).
354 printf("encryptable: %s\n", encryptable ? "yes" : "no");
355 printf(" encrypted: %s\n", encrypted ? "yes" : "no");
356
357 if (!encryptable) {
358 // If the file is on a filesystem that doesn't support
359 // encryption (eg, /cache), then leave it alone.
360 //
361 // TODO: change this to be !encrypted -- if the file is on
362 // /data but /data isn't encrypted, we don't need to use the
363 // block map mechanism. We do for now so as to get more
364 // testing of it (since most dogfood devices aren't
365 // encrypted).
366
367 unlink(RECOVERY_COMMAND_FILE_TMP);
368 } else {
369 if (produce_block_map(path, map_file, blk_dev, encrypted) != 0) {
370 return 1;
371 }
372 }
373
374 rename(RECOVERY_COMMAND_FILE_TMP, RECOVERY_COMMAND_FILE);
375 reboot_to_recovery();
376 return 0;
377}