blob: 302689313d6bb80c15d4c4c2e184a11454a74811 [file] [log] [blame]
Doug Zongkerbc7ffed2014-08-15 14:31:52 -07001/*
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#include <ctype.h>
18#include <errno.h>
19#include <fcntl.h>
20#include <inttypes.h>
21#include <pthread.h>
22#include <stdarg.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <sys/types.h>
27#include <sys/wait.h>
28#include <sys/ioctl.h>
29#include <time.h>
30#include <unistd.h>
31
32#include "applypatch/applypatch.h"
33#include "edify/expr.h"
34#include "mincrypt/sha.h"
35#include "minzip/DirUtil.h"
36#include "updater.h"
37
38#define BLOCKSIZE 4096
39
Doug Zongkerf7bb09d2014-09-04 08:10:32 -070040// Set this to 0 to interpret 'erase' transfers to mean do a
41// BLKDISCARD ioctl (the normal behavior). Set to 1 to interpret
42// erase to mean fill the region with zeroes.
Doug Zongkerbc7ffed2014-08-15 14:31:52 -070043#define DEBUG_ERASE 0
44
45#ifndef BLKDISCARD
46#define BLKDISCARD _IO(0x12,119)
47#endif
48
49char* PrintSha1(const uint8_t* digest);
50
51typedef struct {
52 int count;
53 int size;
54 int pos[0];
55} RangeSet;
56
57static RangeSet* parse_range(char* text) {
58 char* save;
59 int num;
60 num = strtol(strtok_r(text, ",", &save), NULL, 0);
61
62 RangeSet* out = malloc(sizeof(RangeSet) + num * sizeof(int));
63 if (out == NULL) {
Doug Zongkerd83e4f12014-09-08 12:22:09 -070064 fprintf(stderr, "failed to allocate range of %zu bytes\n",
Doug Zongkerbc7ffed2014-08-15 14:31:52 -070065 sizeof(RangeSet) + num * sizeof(int));
66 exit(1);
67 }
68 out->count = num / 2;
69 out->size = 0;
70 int i;
71 for (i = 0; i < num; ++i) {
72 out->pos[i] = strtol(strtok_r(NULL, ",", &save), NULL, 0);
73 if (i%2) {
74 out->size += out->pos[i];
75 } else {
76 out->size -= out->pos[i];
77 }
78 }
79
80 return out;
81}
82
83static void readblock(int fd, uint8_t* data, size_t size) {
84 size_t so_far = 0;
85 while (so_far < size) {
86 ssize_t r = read(fd, data+so_far, size-so_far);
87 if (r < 0 && errno != EINTR) {
88 fprintf(stderr, "read failed: %s\n", strerror(errno));
89 return;
90 } else {
91 so_far += r;
92 }
93 }
94}
95
96static void writeblock(int fd, const uint8_t* data, size_t size) {
97 size_t written = 0;
98 while (written < size) {
99 ssize_t w = write(fd, data+written, size-written);
100 if (w < 0 && errno != EINTR) {
101 fprintf(stderr, "write failed: %s\n", strerror(errno));
102 return;
103 } else {
104 written += w;
105 }
106 }
107}
108
Andrew Boie83289222014-09-03 12:41:06 -0700109static void check_lseek(int fd, off64_t offset, int whence) {
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700110 while (true) {
Andrew Boie83289222014-09-03 12:41:06 -0700111 off64_t ret = lseek64(fd, offset, whence);
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700112 if (ret < 0) {
113 if (errno != EINTR) {
Andrew Boie83289222014-09-03 12:41:06 -0700114 fprintf(stderr, "lseek64 failed: %s\n", strerror(errno));
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700115 exit(1);
116 }
117 } else {
118 break;
119 }
120 }
121}
122
123static void allocate(size_t size, uint8_t** buffer, size_t* buffer_alloc) {
124 // if the buffer's big enough, reuse it.
125 if (size <= *buffer_alloc) return;
126
127 free(*buffer);
128
129 *buffer = (uint8_t*) malloc(size);
130 if (*buffer == NULL) {
Doug Zongker1d5d6092014-08-21 10:47:24 -0700131 fprintf(stderr, "failed to allocate %zu bytes\n", size);
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700132 exit(1);
133 }
134 *buffer_alloc = size;
135}
136
137typedef struct {
138 int fd;
139 RangeSet* tgt;
140 int p_block;
141 size_t p_remain;
142} RangeSinkState;
143
144static ssize_t RangeSinkWrite(const uint8_t* data, ssize_t size, void* token) {
145 RangeSinkState* rss = (RangeSinkState*) token;
146
147 if (rss->p_remain <= 0) {
148 fprintf(stderr, "range sink write overrun");
149 exit(1);
150 }
151
152 ssize_t written = 0;
153 while (size > 0) {
154 size_t write_now = size;
155 if (rss->p_remain < write_now) write_now = rss->p_remain;
156 writeblock(rss->fd, data, write_now);
157 data += write_now;
158 size -= write_now;
159
160 rss->p_remain -= write_now;
161 written += write_now;
162
163 if (rss->p_remain == 0) {
164 // move to the next block
165 ++rss->p_block;
166 if (rss->p_block < rss->tgt->count) {
167 rss->p_remain = (rss->tgt->pos[rss->p_block*2+1] - rss->tgt->pos[rss->p_block*2]) * BLOCKSIZE;
Andrew Boie83289222014-09-03 12:41:06 -0700168 check_lseek(rss->fd, (off64_t)rss->tgt->pos[rss->p_block*2] * BLOCKSIZE, SEEK_SET);
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700169 } else {
170 // we can't write any more; return how many bytes have
171 // been written so far.
172 return written;
173 }
174 }
175 }
176
177 return written;
178}
179
180// All of the data for all the 'new' transfers is contained in one
181// file in the update package, concatenated together in the order in
182// which transfers.list will need it. We want to stream it out of the
183// archive (it's compressed) without writing it to a temp file, but we
184// can't write each section until it's that transfer's turn to go.
185//
186// To achieve this, we expand the new data from the archive in a
187// background thread, and block that threads 'receive uncompressed
188// data' function until the main thread has reached a point where we
189// want some new data to be written. We signal the background thread
190// with the destination for the data and block the main thread,
191// waiting for the background thread to complete writing that section.
192// Then it signals the main thread to wake up and goes back to
193// blocking waiting for a transfer.
194//
195// NewThreadInfo is the struct used to pass information back and forth
196// between the two threads. When the main thread wants some data
197// written, it sets rss to the destination location and signals the
198// condition. When the background thread is done writing, it clears
199// rss and signals the condition again.
200
201typedef struct {
202 ZipArchive* za;
203 const ZipEntry* entry;
204
205 RangeSinkState* rss;
206
207 pthread_mutex_t mu;
208 pthread_cond_t cv;
209} NewThreadInfo;
210
211static bool receive_new_data(const unsigned char* data, int size, void* cookie) {
212 NewThreadInfo* nti = (NewThreadInfo*) cookie;
213
214 while (size > 0) {
215 // Wait for nti->rss to be non-NULL, indicating some of this
216 // data is wanted.
217 pthread_mutex_lock(&nti->mu);
218 while (nti->rss == NULL) {
219 pthread_cond_wait(&nti->cv, &nti->mu);
220 }
221 pthread_mutex_unlock(&nti->mu);
222
223 // At this point nti->rss is set, and we own it. The main
224 // thread is waiting for it to disappear from nti.
225 ssize_t written = RangeSinkWrite(data, size, nti->rss);
226 data += written;
227 size -= written;
228
229 if (nti->rss->p_block == nti->rss->tgt->count) {
230 // we have written all the bytes desired by this rss.
231
232 pthread_mutex_lock(&nti->mu);
233 nti->rss = NULL;
234 pthread_cond_broadcast(&nti->cv);
235 pthread_mutex_unlock(&nti->mu);
236 }
237 }
238
239 return true;
240}
241
242static void* unzip_new_data(void* cookie) {
243 NewThreadInfo* nti = (NewThreadInfo*) cookie;
244 mzProcessZipEntryContents(nti->za, nti->entry, receive_new_data, nti);
245 return NULL;
246}
247
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700248// Do a source/target load for move/bsdiff/imgdiff in version 1.
249// 'wordsave' is the save_ptr of a strtok_r()-in-progress. We expect
250// to parse the remainder of the string as:
251//
252// <src_range> <tgt_range>
253//
254// The source range is loaded into the provided buffer, reallocating
255// it to make it larger if necessary. The target ranges are returned
256// in *tgt, if tgt is non-NULL.
257
258static void LoadSrcTgtVersion1(char* wordsave, RangeSet** tgt, int* src_blocks,
259 uint8_t** buffer, size_t* buffer_alloc, int fd) {
260 char* word;
261
262 word = strtok_r(NULL, " ", &wordsave);
263 RangeSet* src = parse_range(word);
264
265 if (tgt != NULL) {
266 word = strtok_r(NULL, " ", &wordsave);
267 *tgt = parse_range(word);
268 }
269
270 allocate(src->size * BLOCKSIZE, buffer, buffer_alloc);
271 size_t p = 0;
272 int i;
273 for (i = 0; i < src->count; ++i) {
274 check_lseek(fd, (off64_t)src->pos[i*2] * BLOCKSIZE, SEEK_SET);
275 size_t sz = (src->pos[i*2+1] - src->pos[i*2]) * BLOCKSIZE;
276 readblock(fd, *buffer+p, sz);
277 p += sz;
278 }
279
280 *src_blocks = src->size;
281 free(src);
282}
283
284static void MoveRange(uint8_t* dest, RangeSet* locs, const uint8_t* source) {
285 // source contains packed data, which we want to move to the
286 // locations given in *locs in the dest buffer. source and dest
287 // may be the same buffer.
288
289 int start = locs->size;
290 int i;
291 for (i = locs->count-1; i >= 0; --i) {
292 int blocks = locs->pos[i*2+1] - locs->pos[i*2];
293 start -= blocks;
294 memmove(dest + (locs->pos[i*2] * BLOCKSIZE), source + (start * BLOCKSIZE),
295 blocks * BLOCKSIZE);
296 }
297}
298
299// Do a source/target load for move/bsdiff/imgdiff in version 2.
300// 'wordsave' is the save_ptr of a strtok_r()-in-progress. We expect
301// to parse the remainder of the string as one of:
302//
303// <tgt_range> <src_block_count> <src_range>
304// (loads data from source image only)
305//
306// <tgt_range> <src_block_count> - <[stash_id:stash_range] ...>
307// (loads data from stashes only)
308//
309// <tgt_range> <src_block_count> <src_range> <src_loc> <[stash_id:stash_range] ...>
310// (loads data from both source image and stashes)
311//
312// On return, buffer is filled with the loaded source data (rearranged
313// and combined with stashed data as necessary). buffer may be
314// reallocated if needed to accommodate the source data. *tgt is the
315// target RangeSet. Any stashes required are taken from stash_table
316// and free()'d after being used.
317
318static void LoadSrcTgtVersion2(char* wordsave, RangeSet** tgt, int* src_blocks,
319 uint8_t** buffer, size_t* buffer_alloc, int fd,
320 uint8_t** stash_table) {
321 char* word;
322
323 if (tgt != NULL) {
324 word = strtok_r(NULL, " ", &wordsave);
325 *tgt = parse_range(word);
326 }
327
328 word = strtok_r(NULL, " ", &wordsave);
329 *src_blocks = strtol(word, NULL, 0);
330
331 allocate(*src_blocks * BLOCKSIZE, buffer, buffer_alloc);
332
333 word = strtok_r(NULL, " ", &wordsave);
334 if (word[0] == '-' && word[1] == '\0') {
335 // no source ranges, only stashes
336 } else {
337 RangeSet* src = parse_range(word);
338
339 size_t p = 0;
340 int i;
341 for (i = 0; i < src->count; ++i) {
342 check_lseek(fd, (off64_t)src->pos[i*2] * BLOCKSIZE, SEEK_SET);
343 size_t sz = (src->pos[i*2+1] - src->pos[i*2]) * BLOCKSIZE;
344 readblock(fd, *buffer+p, sz);
345 p += sz;
346 }
347 free(src);
348
349 word = strtok_r(NULL, " ", &wordsave);
350 if (word == NULL) {
351 // no stashes, only source range
352 return;
353 }
354
355 RangeSet* locs = parse_range(word);
356 MoveRange(*buffer, locs, *buffer);
357 }
358
359 while ((word = strtok_r(NULL, " ", &wordsave)) != NULL) {
360 // Each word is a an index into the stash table, a colon, and
361 // then a rangeset describing where in the source block that
362 // stashed data should go.
363 char* colonsave = NULL;
364 char* colon = strtok_r(word, ":", &colonsave);
365 int stash_id = strtol(colon, NULL, 0);
366 colon = strtok_r(NULL, ":", &colonsave);
367 RangeSet* locs = parse_range(colon);
368 MoveRange(*buffer, locs, stash_table[stash_id]);
369 free(stash_table[stash_id]);
370 stash_table[stash_id] = NULL;
371 free(locs);
372 }
373}
374
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700375// args:
376// - block device (or file) to modify in-place
377// - transfer list (blob)
378// - new data stream (filename within package.zip)
379// - patch stream (filename within package.zip, must be uncompressed)
380
381Value* BlockImageUpdateFn(const char* name, State* state, int argc, Expr* argv[]) {
382 Value* blockdev_filename;
Doug Zongker1d5d6092014-08-21 10:47:24 -0700383 Value* transfer_list_value;
384 char* transfer_list = NULL;
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700385 Value* new_data_fn;
386 Value* patch_data_fn;
387 bool success = false;
388
Doug Zongker1d5d6092014-08-21 10:47:24 -0700389 if (ReadValueArgs(state, argv, 4, &blockdev_filename, &transfer_list_value,
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700390 &new_data_fn, &patch_data_fn) < 0) {
391 return NULL;
392 }
393
394 if (blockdev_filename->type != VAL_STRING) {
395 ErrorAbort(state, "blockdev_filename argument to %s must be string", name);
396 goto done;
397 }
Doug Zongker1d5d6092014-08-21 10:47:24 -0700398 if (transfer_list_value->type != VAL_BLOB) {
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700399 ErrorAbort(state, "transfer_list argument to %s must be blob", name);
400 goto done;
401 }
402 if (new_data_fn->type != VAL_STRING) {
403 ErrorAbort(state, "new_data_fn argument to %s must be string", name);
404 goto done;
405 }
406 if (patch_data_fn->type != VAL_STRING) {
407 ErrorAbort(state, "patch_data_fn argument to %s must be string", name);
408 goto done;
409 }
410
411 UpdaterInfo* ui = (UpdaterInfo*)(state->cookie);
412 FILE* cmd_pipe = ui->cmd_pipe;
413
414 ZipArchive* za = ((UpdaterInfo*)(state->cookie))->package_zip;
415
416 const ZipEntry* patch_entry = mzFindZipEntry(za, patch_data_fn->data);
417 if (patch_entry == NULL) {
418 ErrorAbort(state, "%s(): no file \"%s\" in package", name, patch_data_fn->data);
419 goto done;
420 }
421
422 uint8_t* patch_start = ((UpdaterInfo*)(state->cookie))->package_zip_addr +
423 mzGetZipEntryOffset(patch_entry);
424
425 const ZipEntry* new_entry = mzFindZipEntry(za, new_data_fn->data);
426 if (new_entry == NULL) {
427 ErrorAbort(state, "%s(): no file \"%s\" in package", name, new_data_fn->data);
428 goto done;
429 }
430
431 // The transfer list is a text file containing commands to
432 // transfer data from one place to another on the target
433 // partition. We parse it and execute the commands in order:
434 //
435 // zero [rangeset]
436 // - fill the indicated blocks with zeros
437 //
438 // new [rangeset]
439 // - fill the blocks with data read from the new_data file
440 //
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700441 // erase [rangeset]
442 // - mark the given blocks as empty
443 //
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700444 // move <...>
445 // bsdiff <patchstart> <patchlen> <...>
446 // imgdiff <patchstart> <patchlen> <...>
447 // - read the source blocks, apply a patch (or not in the
448 // case of move), write result to target blocks. bsdiff or
449 // imgdiff specifies the type of patch; move means no patch
450 // at all.
451 //
452 // The format of <...> differs between versions 1 and 2;
453 // see the LoadSrcTgtVersion{1,2}() functions for a
454 // description of what's expected.
455 //
456 // stash <stash_id> <src_range>
457 // - (version 2 only) load the given source range and stash
458 // the data in the given slot of the stash table.
459 //
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700460 // The creator of the transfer list will guarantee that no block
461 // is read (ie, used as the source for a patch or move) after it
462 // has been written.
463 //
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700464 // In version 2, the creator will guarantee that a given stash is
465 // loaded (with a stash command) before it's used in a
466 // move/bsdiff/imgdiff command.
467 //
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700468 // Within one command the source and target ranges may overlap so
469 // in general we need to read the entire source into memory before
470 // writing anything to the target blocks.
471 //
472 // All the patch data is concatenated into one patch_data file in
473 // the update package. It must be stored uncompressed because we
474 // memory-map it in directly from the archive. (Since patches are
475 // already compressed, we lose very little by not compressing
476 // their concatenation.)
477
478 pthread_t new_data_thread;
479 NewThreadInfo nti;
480 nti.za = za;
481 nti.entry = new_entry;
482 nti.rss = NULL;
483 pthread_mutex_init(&nti.mu, NULL);
484 pthread_cond_init(&nti.cv, NULL);
485
486 pthread_attr_t attr;
487 pthread_attr_init(&attr);
488 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
489 pthread_create(&new_data_thread, &attr, unzip_new_data, &nti);
490
491 int i, j;
492
493 char* linesave;
494 char* wordsave;
495
496 int fd = open(blockdev_filename->data, O_RDWR);
497 if (fd < 0) {
498 ErrorAbort(state, "failed to open %s: %s", blockdev_filename->data, strerror(errno));
499 goto done;
500 }
501
502 char* line;
503 char* word;
504
Doug Zongker1d5d6092014-08-21 10:47:24 -0700505 // The data in transfer_list_value is not necessarily
506 // null-terminated, so we need to copy it to a new buffer and add
507 // the null that strtok_r will need.
508 transfer_list = malloc(transfer_list_value->size+1);
509 if (transfer_list == NULL) {
510 fprintf(stderr, "failed to allocate %zd bytes for transfer list\n",
511 transfer_list_value->size+1);
512 exit(1);
513 }
514 memcpy(transfer_list, transfer_list_value->data, transfer_list_value->size);
515 transfer_list[transfer_list_value->size] = '\0';
516
517 line = strtok_r(transfer_list, "\n", &linesave);
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700518
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700519 int version;
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700520 // first line in transfer list is the version number; currently
521 // there's only version 1.
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700522 if (strcmp(line, "1") == 0) {
523 version = 1;
524 } else if (strcmp(line, "2") == 0) {
525 version = 2;
526 } else {
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700527 ErrorAbort(state, "unexpected transfer list version [%s]\n", line);
528 goto done;
529 }
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700530 printf("blockimg version is %d\n", version);
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700531
532 // second line in transfer list is the total number of blocks we
533 // expect to write.
534 line = strtok_r(NULL, "\n", &linesave);
535 int total_blocks = strtol(line, NULL, 0);
536 // shouldn't happen, but avoid divide by zero.
537 if (total_blocks == 0) ++total_blocks;
538 int blocks_so_far = 0;
539
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700540 uint8_t** stash_table = NULL;
541 if (version >= 2) {
542 // Next line is how many stash entries are needed simultaneously.
543 line = strtok_r(NULL, "\n", &linesave);
544 int stash_entries = strtol(line, NULL, 0);
545
546 stash_table = (uint8_t**) calloc(stash_entries, sizeof(uint8_t*));
547 if (stash_table == NULL) {
548 fprintf(stderr, "failed to allocate %d-entry stash table\n", stash_entries);
549 exit(1);
550 }
551
552 // Next line is the maximum number of blocks that will be
553 // stashed simultaneously. This could be used to verify that
554 // enough memory or scratch disk space is available.
555 line = strtok_r(NULL, "\n", &linesave);
556 int stash_max_blocks = strtol(line, NULL, 0);
557 }
558
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700559 uint8_t* buffer = NULL;
560 size_t buffer_alloc = 0;
561
562 // third and subsequent lines are all individual transfer commands.
563 for (line = strtok_r(NULL, "\n", &linesave); line;
564 line = strtok_r(NULL, "\n", &linesave)) {
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700565
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700566 char* style;
567 style = strtok_r(line, " ", &wordsave);
568
569 if (strcmp("move", style) == 0) {
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700570 RangeSet* tgt;
571 int src_blocks;
572 if (version == 1) {
573 LoadSrcTgtVersion1(wordsave, &tgt, &src_blocks,
574 &buffer, &buffer_alloc, fd);
575 } else if (version == 2) {
576 LoadSrcTgtVersion2(wordsave, &tgt, &src_blocks,
577 &buffer, &buffer_alloc, fd, stash_table);
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700578 }
579
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700580 printf(" moving %d blocks\n", src_blocks);
581
582 size_t p = 0;
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700583 for (i = 0; i < tgt->count; ++i) {
Andrew Boie83289222014-09-03 12:41:06 -0700584 check_lseek(fd, (off64_t)tgt->pos[i*2] * BLOCKSIZE, SEEK_SET);
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700585 size_t sz = (tgt->pos[i*2+1] - tgt->pos[i*2]) * BLOCKSIZE;
586 writeblock(fd, buffer+p, sz);
587 p += sz;
588 }
589
590 blocks_so_far += tgt->size;
591 fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks);
592 fflush(cmd_pipe);
593
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700594 free(tgt);
595
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700596 } else if (strcmp("stash", style) == 0) {
597 word = strtok_r(NULL, " ", &wordsave);
598 int stash_id = strtol(word, NULL, 0);
599 int src_blocks;
600 size_t stash_alloc = 0;
601
602 // Even though the "stash" style only appears in version
603 // 2, the version 1 source loader happens to do exactly
604 // what we want to read data into the stash_table.
605 LoadSrcTgtVersion1(wordsave, NULL, &src_blocks,
606 stash_table + stash_id, &stash_alloc, fd);
607
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700608 } else if (strcmp("zero", style) == 0 ||
609 (DEBUG_ERASE && strcmp("erase", style) == 0)) {
610 word = strtok_r(NULL, " ", &wordsave);
611 RangeSet* tgt = parse_range(word);
612
613 printf(" zeroing %d blocks\n", tgt->size);
614
615 allocate(BLOCKSIZE, &buffer, &buffer_alloc);
616 memset(buffer, 0, BLOCKSIZE);
617 for (i = 0; i < tgt->count; ++i) {
Andrew Boie83289222014-09-03 12:41:06 -0700618 check_lseek(fd, (off64_t)tgt->pos[i*2] * BLOCKSIZE, SEEK_SET);
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700619 for (j = tgt->pos[i*2]; j < tgt->pos[i*2+1]; ++j) {
620 writeblock(fd, buffer, BLOCKSIZE);
621 }
622 }
623
624 if (style[0] == 'z') { // "zero" but not "erase"
625 blocks_so_far += tgt->size;
626 fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks);
627 fflush(cmd_pipe);
628 }
629
630 free(tgt);
631 } else if (strcmp("new", style) == 0) {
632
633 word = strtok_r(NULL, " ", &wordsave);
634 RangeSet* tgt = parse_range(word);
635
636 printf(" writing %d blocks of new data\n", tgt->size);
637
638 RangeSinkState rss;
639 rss.fd = fd;
640 rss.tgt = tgt;
641 rss.p_block = 0;
642 rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE;
Andrew Boie83289222014-09-03 12:41:06 -0700643 check_lseek(fd, (off64_t)tgt->pos[0] * BLOCKSIZE, SEEK_SET);
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700644
645 pthread_mutex_lock(&nti.mu);
646 nti.rss = &rss;
647 pthread_cond_broadcast(&nti.cv);
648 while (nti.rss) {
649 pthread_cond_wait(&nti.cv, &nti.mu);
650 }
651 pthread_mutex_unlock(&nti.mu);
652
653 blocks_so_far += tgt->size;
654 fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks);
655 fflush(cmd_pipe);
656
657 free(tgt);
658
659 } else if (strcmp("bsdiff", style) == 0 ||
660 strcmp("imgdiff", style) == 0) {
661 word = strtok_r(NULL, " ", &wordsave);
662 size_t patch_offset = strtoul(word, NULL, 0);
663 word = strtok_r(NULL, " ", &wordsave);
664 size_t patch_len = strtoul(word, NULL, 0);
665
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700666 RangeSet* tgt;
667 int src_blocks;
668 if (version == 1) {
669 LoadSrcTgtVersion1(wordsave, &tgt, &src_blocks,
670 &buffer, &buffer_alloc, fd);
671 } else if (version == 2) {
672 LoadSrcTgtVersion2(wordsave, &tgt, &src_blocks,
673 &buffer, &buffer_alloc, fd, stash_table);
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700674 }
675
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700676 printf(" patching %d blocks to %d\n", src_blocks, tgt->size);
677
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700678 Value patch_value;
679 patch_value.type = VAL_BLOB;
680 patch_value.size = patch_len;
681 patch_value.data = (char*)(patch_start + patch_offset);
682
683 RangeSinkState rss;
684 rss.fd = fd;
685 rss.tgt = tgt;
686 rss.p_block = 0;
687 rss.p_remain = (tgt->pos[1] - tgt->pos[0]) * BLOCKSIZE;
Andrew Boie83289222014-09-03 12:41:06 -0700688 check_lseek(fd, (off64_t)tgt->pos[0] * BLOCKSIZE, SEEK_SET);
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700689
690 if (style[0] == 'i') { // imgdiff
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700691 ApplyImagePatch(buffer, src_blocks * BLOCKSIZE,
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700692 &patch_value,
693 &RangeSinkWrite, &rss, NULL, NULL);
694 } else {
Doug Zongkerd83e4f12014-09-08 12:22:09 -0700695 ApplyBSDiffPatch(buffer, src_blocks * BLOCKSIZE,
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700696 &patch_value, 0,
697 &RangeSinkWrite, &rss, NULL);
698 }
699
700 // We expect the output of the patcher to fill the tgt ranges exactly.
701 if (rss.p_block != tgt->count || rss.p_remain != 0) {
702 fprintf(stderr, "range sink underrun?\n");
703 }
704
705 blocks_so_far += tgt->size;
706 fprintf(cmd_pipe, "set_progress %.4f\n", (double)blocks_so_far / total_blocks);
707 fflush(cmd_pipe);
708
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700709 free(tgt);
710 } else if (!DEBUG_ERASE && strcmp("erase", style) == 0) {
711 struct stat st;
712 if (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode)) {
713 word = strtok_r(NULL, " ", &wordsave);
714 RangeSet* tgt = parse_range(word);
715
716 printf(" erasing %d blocks\n", tgt->size);
717
718 for (i = 0; i < tgt->count; ++i) {
719 uint64_t range[2];
720 // offset in bytes
Doug Zongker1d5d6092014-08-21 10:47:24 -0700721 range[0] = tgt->pos[i*2] * (uint64_t)BLOCKSIZE;
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700722 // len in bytes
Doug Zongker1d5d6092014-08-21 10:47:24 -0700723 range[1] = (tgt->pos[i*2+1] - tgt->pos[i*2]) * (uint64_t)BLOCKSIZE;
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700724
725 if (ioctl(fd, BLKDISCARD, &range) < 0) {
726 printf(" blkdiscard failed: %s\n", strerror(errno));
727 }
728 }
729
730 free(tgt);
731 } else {
732 printf(" ignoring erase (not block device)\n");
733 }
734 } else {
735 fprintf(stderr, "unknown transfer style \"%s\"\n", style);
736 exit(1);
737 }
738 }
739
740 pthread_join(new_data_thread, NULL);
741 success = true;
742
743 free(buffer);
744 printf("wrote %d blocks; expected %d\n", blocks_so_far, total_blocks);
745 printf("max alloc needed was %zu\n", buffer_alloc);
746
747 done:
Doug Zongker1d5d6092014-08-21 10:47:24 -0700748 free(transfer_list);
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700749 FreeValue(blockdev_filename);
Doug Zongker1d5d6092014-08-21 10:47:24 -0700750 FreeValue(transfer_list_value);
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700751 FreeValue(new_data_fn);
752 FreeValue(patch_data_fn);
753 return StringValue(success ? strdup("t") : strdup(""));
754}
755
756Value* RangeSha1Fn(const char* name, State* state, int argc, Expr* argv[]) {
757 Value* blockdev_filename;
758 Value* ranges;
759 const uint8_t* digest = NULL;
760 if (ReadValueArgs(state, argv, 2, &blockdev_filename, &ranges) < 0) {
761 return NULL;
762 }
763
764 if (blockdev_filename->type != VAL_STRING) {
765 ErrorAbort(state, "blockdev_filename argument to %s must be string", name);
766 goto done;
767 }
768 if (ranges->type != VAL_STRING) {
769 ErrorAbort(state, "ranges argument to %s must be string", name);
770 goto done;
771 }
772
773 int fd = open(blockdev_filename->data, O_RDWR);
774 if (fd < 0) {
775 ErrorAbort(state, "failed to open %s: %s", blockdev_filename->data, strerror(errno));
776 goto done;
777 }
778
779 RangeSet* rs = parse_range(ranges->data);
780 uint8_t buffer[BLOCKSIZE];
781
782 SHA_CTX ctx;
783 SHA_init(&ctx);
784
785 int i, j;
786 for (i = 0; i < rs->count; ++i) {
Andrew Boie83289222014-09-03 12:41:06 -0700787 check_lseek(fd, (off64_t)rs->pos[i*2] * BLOCKSIZE, SEEK_SET);
Doug Zongkerbc7ffed2014-08-15 14:31:52 -0700788 for (j = rs->pos[i*2]; j < rs->pos[i*2+1]; ++j) {
789 readblock(fd, buffer, BLOCKSIZE);
790 SHA_update(&ctx, buffer, BLOCKSIZE);
791 }
792 }
793 digest = SHA_final(&ctx);
794 close(fd);
795
796 done:
797 FreeValue(blockdev_filename);
798 FreeValue(ranges);
799 if (digest == NULL) {
800 return StringValue(strdup(""));
801 } else {
802 return StringValue(PrintSha1(digest));
803 }
804}
805
806void RegisterBlockImageFunctions() {
807 RegisterFunction("block_image_update", BlockImageUpdateFn);
808 RegisterFunction("range_sha1", RangeSha1Fn);
809}