blob: 7c2d994778843ae8f43fb3737beb98b581fae5ea [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
Maxim Siniavinee7b28882014-02-13 15:48:53 -0800152
Doug Zongker76adfc52014-01-13 10:04:25 -0800153
154 FILE* f = fopen(RECOVERY_COMMAND_FILE, "r");
Maxim Siniavinee7b28882014-02-13 15:48:53 -0800155 if (f == NULL) {
156 return NULL;
157 }
158 FILE* fo = fopen(RECOVERY_COMMAND_FILE_TMP, "w");
159
Doug Zongker76adfc52014-01-13 10:04:25 -0800160 while (fgets(temp, sizeof(temp), f)) {
161 printf("read: %s", temp);
162 if (strncmp(temp, "--update_package=", strlen("--update_package=")) == 0) {
163 fn = strdup(temp + strlen("--update_package="));
164 strcpy(temp, "--update_package=@" CACHE_BLOCK_MAP "\n");
165 }
166 fputs(temp, fo);
167 }
168 fclose(f);
169 fclose(fo);
170
171 if (fn) {
172 char* newline = strchr(fn, '\n');
173 if (newline) *newline = 0;
174 }
175 return fn;
176}
177
178int produce_block_map(const char* path, const char* map_file, const char* blk_dev,
179 int encrypted)
180{
181 struct stat sb;
182 int ret;
183
184 FILE* mapf = fopen(map_file, "w");
185
186 ret = stat(path, &sb);
187 if (ret != 0) {
188 fprintf(stderr, "failed to stat %s\n", path);
189 return -1;
190 }
191
192 printf(" block size: %ld bytes\n", sb.st_blksize);
193
194 int blocks = ((sb.st_size-1) / sb.st_blksize) + 1;
195 printf(" file size: %lld bytes, %d blocks\n", sb.st_size, blocks);
196
197 int* ranges;
198 int range_alloc = 1;
199 int range_used = 1;
200 ranges = malloc(range_alloc * 2 * sizeof(int));
201 ranges[0] = -1;
202 ranges[1] = -1;
203
204 fprintf(mapf, "%s\n%lld %lu\n", blk_dev, sb.st_size, sb.st_blksize);
205
206 unsigned char* buffers[WINDOW_SIZE];
207 int i;
208 if (encrypted) {
209 for (i = 0; i < WINDOW_SIZE; ++i) {
210 buffers[i] = malloc(sb.st_blksize);
211 }
212 }
213 int head_block = 0;
214 int head = 0, tail = 0;
215 size_t pos = 0;
216
217 int fd = open(path, O_RDONLY);
218 if (fd < 0) {
219 fprintf(stderr, "failed to open fd for reading: %s\n", strerror(errno));
220 return -1;
221 }
222 fsync(fd);
223
224 int wfd = -1;
225 if (encrypted) {
226 wfd = open(blk_dev, O_WRONLY);
227 if (wfd < 0) {
228 fprintf(stderr, "failed to open fd for writing: %s\n", strerror(errno));
229 return -1;
230 }
231 }
232
233 while (pos < sb.st_size) {
234 if ((tail+1) % WINDOW_SIZE == head) {
235 // write out head buffer
236 int block = head_block;
237 ret = ioctl(fd, FIBMAP, &block);
238 if (ret != 0) {
239 fprintf(stderr, "failed to find block %d\n", head_block);
240 return -1;
241 }
242 add_block_to_ranges(&ranges, &range_alloc, &range_used, block);
243 if (encrypted) {
244 if (write_at_offset(buffers[head], sb.st_blksize, wfd, (off64_t)sb.st_blksize * block) != 0) {
245 return -1;
246 }
247 }
248 head = (head + 1) % WINDOW_SIZE;
249 ++head_block;
250 }
251
252 // read next block to tail
253 if (encrypted) {
254 size_t so_far = 0;
255 while (so_far < sb.st_blksize && pos < sb.st_size) {
256 ssize_t this_read = read(fd, buffers[tail] + so_far, sb.st_blksize - so_far);
257 if (this_read < 0) {
258 fprintf(stderr, "failed to read: %s\n", strerror(errno));
259 return -1;
260 }
261 so_far += this_read;
262 pos += this_read;
263 }
264 } else {
265 // If we're not encrypting; we don't need to actually read
266 // anything, just skip pos forward as if we'd read a
267 // block.
268 pos += sb.st_blksize;
269 }
270 tail = (tail+1) % WINDOW_SIZE;
271 }
272
273 while (head != tail) {
274 // write out head buffer
275 int block = head_block;
276 ret = ioctl(fd, FIBMAP, &block);
277 if (ret != 0) {
278 fprintf(stderr, "failed to find block %d\n", head_block);
279 return -1;
280 }
281 add_block_to_ranges(&ranges, &range_alloc, &range_used, block);
282 if (encrypted) {
283 if (write_at_offset(buffers[head], sb.st_blksize, wfd, (off64_t)sb.st_blksize * block) != 0) {
284 return -1;
285 }
286 }
287 head = (head + 1) % WINDOW_SIZE;
288 ++head_block;
289 }
290
291 fprintf(mapf, "%d\n", range_used);
292 for (i = 0; i < range_used; ++i) {
293 fprintf(mapf, "%d %d\n", ranges[i*2], ranges[i*2+1]);
294 }
295
296 fclose(mapf);
297 close(fd);
298 if (encrypted) {
299 close(wfd);
300 }
301
302 return 0;
303}
304
305void reboot_to_recovery() {
306 property_set("sys.powerctl", "reboot,recovery");
307 sleep(10);
308}
309
310int main(int argc, char** argv)
311{
312 const char* input_path;
313 const char* map_file;
314 int do_reboot = 1;
315
316 if (argc != 1 && argc != 3) {
317 fprintf(stderr, "usage: %s [<transform_path> <map_file>]\n", argv[0]);
318 return 2;
319 }
320
321 if (argc == 3) {
322 // when command-line args are given this binary is being used
323 // for debugging; don't reboot to recovery at the end.
324 input_path = argv[1];
325 map_file = argv[2];
326 do_reboot = 0;
327 } else {
328 input_path = parse_recovery_command_file();
329 if (input_path == NULL) {
330 // if we're rebooting to recovery without a package (say,
331 // to wipe data), then we don't need to do anything before
332 // going to recovery.
333 fprintf(stderr, "no recovery command file or no update package arg");
334 reboot_to_recovery();
335 return 1;
336 }
337 map_file = CACHE_BLOCK_MAP;
338 }
339
340 // Turn the name of the file we're supposed to convert into an
341 // absolute path, so we can find what filesystem it's on.
342 char path[PATH_MAX+1];
343 if (realpath(input_path, path) == NULL) {
344 fprintf(stderr, "failed to convert %s to absolute path: %s\n", input_path, strerror(errno));
345 return 1;
346 }
347
348 int encryptable;
349 int encrypted;
350 const char* blk_dev = find_block_device(path, &encryptable, &encrypted);
351 if (blk_dev == NULL) {
352 fprintf(stderr, "failed to find block device for %s\n", path);
353 return 1;
354 }
355
356 // If the filesystem it's on isn't encrypted, we only produce the
357 // block map, we don't rewrite the file contents (it would be
358 // pointless to do so).
359 printf("encryptable: %s\n", encryptable ? "yes" : "no");
360 printf(" encrypted: %s\n", encrypted ? "yes" : "no");
361
362 if (!encryptable) {
363 // If the file is on a filesystem that doesn't support
364 // encryption (eg, /cache), then leave it alone.
365 //
366 // TODO: change this to be !encrypted -- if the file is on
367 // /data but /data isn't encrypted, we don't need to use the
368 // block map mechanism. We do for now so as to get more
369 // testing of it (since most dogfood devices aren't
370 // encrypted).
371
372 unlink(RECOVERY_COMMAND_FILE_TMP);
373 } else {
374 if (produce_block_map(path, map_file, blk_dev, encrypted) != 0) {
375 return 1;
376 }
377 }
378
379 rename(RECOVERY_COMMAND_FILE_TMP, RECOVERY_COMMAND_FILE);
380 reboot_to_recovery();
381 return 0;
382}