2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 #define _LARGEFILE64_SOURCE
25 #include <base/file.h>
29 #include <openssl/sha.h>
34 #include <sys/ioctl.h>
36 #ifndef IMAGE_NO_SPARSE
37 #include <sparse/sparse.h>
41 #if defined(__linux__)
43 #elif defined(__APPLE__)
45 #define BLKGETSIZE64 DKIOCGETBLOCKCOUNT
49 void image_init(image *ctx)
51 memset(ctx, 0, sizeof(*ctx));
54 static void mmap_image_free(image *ctx)
57 munmap(ctx->input, (size_t)ctx->inp_size);
58 TEMP_FAILURE_RETRY(close(ctx->inp_fd));
61 if (ctx->fec_mmap_addr) {
62 munmap(ctx->fec_mmap_addr, FEC_BLOCKSIZE + ctx->fec_size);
63 TEMP_FAILURE_RETRY(close(ctx->fec_fd));
66 if (!ctx->inplace && ctx->output) {
71 static void file_image_free(image *ctx)
73 assert(ctx->input == ctx->output);
84 void image_free(image *ctx)
95 static uint64_t get_size(int fd)
99 if (fstat(fd, &st) == -1) {
100 FATAL("failed to fstat: %s\n", strerror(errno));
105 if (S_ISBLK(st.st_mode)) {
106 if (ioctl(fd, BLKGETSIZE64, &size) == -1) {
107 FATAL("failed to ioctl(BLKGETSIZE64): %s\n", strerror(errno));
109 } else if (S_ISREG(st.st_mode)) {
112 FATAL("unknown file mode: %d\n", (int)st.st_mode);
118 static void calculate_rounds(uint64_t size, image *ctx)
121 FATAL("empty file?\n");
122 } else if (size % FEC_BLOCKSIZE) {
123 FATAL("file size %" PRIu64 " is not a multiple of %u bytes\n",
124 size, FEC_BLOCKSIZE);
127 ctx->inp_size = size;
128 ctx->blocks = fec_div_round_up(ctx->inp_size, FEC_BLOCKSIZE);
129 ctx->rounds = fec_div_round_up(ctx->blocks, ctx->rs_n);
132 static void mmap_image_load(int fd, image *ctx, bool output_needed)
134 calculate_rounds(get_size(fd), ctx);
136 /* check that we can memory map the file; on 32-bit platforms we are
137 limited to encoding at most 4 GiB files */
138 if (ctx->inp_size > SIZE_MAX) {
139 FATAL("cannot mmap %" PRIu64 " bytes\n", ctx->inp_size);
143 INFO("memory mapping '%s' (size %" PRIu64 ")\n", ctx->fec_filename,
147 int flags = PROT_READ;
153 void *p = mmap(NULL, (size_t)ctx->inp_size, flags, MAP_SHARED, fd, 0);
155 if (p == MAP_FAILED) {
156 FATAL("failed to mmap '%s' (size %" PRIu64 "): %s\n",
157 ctx->fec_filename, ctx->inp_size, strerror(errno));
161 ctx->input = (uint8_t *)p;
164 ctx->output = ctx->input;
165 } else if (output_needed) {
167 INFO("allocating %" PRIu64 " bytes of memory\n", ctx->inp_size);
170 ctx->output = new uint8_t[ctx->inp_size];
173 FATAL("failed to allocate memory\n");
176 memcpy(ctx->output, ctx->input, ctx->inp_size);
179 /* fd is closed in mmap_image_free */
182 #ifndef IMAGE_NO_SPARSE
183 static int process_chunk(void *priv, const void *data, int len)
185 image *ctx = (image *)priv;
186 assert(len % FEC_BLOCKSIZE == 0);
189 memcpy(&ctx->input[ctx->pos], data, len);
197 static void file_image_load(int fd, image *ctx)
201 #ifdef IMAGE_NO_SPARSE
203 FATAL("sparse files not supported\n");
208 struct sparse_file *file;
211 file = sparse_file_import(fd, false, false);
213 file = sparse_file_import_auto(fd, false, ctx->verbose);
217 FATAL("failed to read file %s\n", ctx->fec_filename);
220 len = sparse_file_len(file, false, false);
221 #endif /* IMAGE_NO_SPARSE */
223 calculate_rounds(len, ctx);
226 INFO("allocating %" PRIu64 " bytes of memory\n", ctx->inp_size);
229 ctx->input = new uint8_t[ctx->inp_size];
232 FATAL("failed to allocate memory\n");
235 memset(ctx->input, 0, ctx->inp_size);
236 ctx->output = ctx->input;
238 #ifdef IMAGE_NO_SPARSE
239 if (!android::base::ReadFully(fd, ctx->input, ctx->inp_size)) {
240 FATAL("failed to read: %s\n", strerror(errno));
244 sparse_file_callback(file, false, false, process_chunk, ctx);
245 sparse_file_destroy(file);
248 TEMP_FAILURE_RETRY(close(fd));
251 bool image_load(const char *filename, image *ctx, bool output_needed)
253 assert(ctx->roots > 0 && ctx->roots < FEC_RSM);
254 ctx->rs_n = FEC_RSM - ctx->roots;
256 int flags = O_RDONLY;
262 int fd = TEMP_FAILURE_RETRY(open(filename, flags | O_LARGEFILE));
265 FATAL("failed to open file '%s': %s\n", filename, strerror(errno));
269 mmap_image_load(fd, ctx, output_needed);
271 file_image_load(fd, ctx);
277 bool image_save(const char *filename, image *ctx)
279 if (ctx->inplace && ctx->mmap) {
280 return true; /* nothing to do */
283 /* TODO: support saving as a sparse file */
284 int fd = TEMP_FAILURE_RETRY(open(filename, O_WRONLY | O_CREAT | O_TRUNC,
288 FATAL("failed to open file '%s: %s'\n", filename, strerror(errno));
291 if (!android::base::WriteFully(fd, ctx->output, ctx->inp_size)) {
292 FATAL("failed to write to output: %s\n", strerror(errno));
295 TEMP_FAILURE_RETRY(close(fd));
299 static void mmap_image_ecc_new(image *ctx)
302 INFO("mmaping '%s' (size %u)\n", ctx->fec_filename, ctx->fec_size);
305 int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename,
306 O_RDWR | O_CREAT, 0666));
309 FATAL("failed to open file '%s': %s\n", ctx->fec_filename,
313 assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
314 size_t fec_size = FEC_BLOCKSIZE + ctx->fec_size;
316 if (ftruncate(fd, fec_size) == -1) {
317 FATAL("failed to ftruncate file '%s': %s\n", ctx->fec_filename,
322 INFO("memory mapping '%s' (size %zu)\n", ctx->fec_filename, fec_size);
325 void *p = mmap(NULL, fec_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
327 if (p == MAP_FAILED) {
328 FATAL("failed to mmap '%s' (size %zu): %s\n", ctx->fec_filename,
329 fec_size, strerror(errno));
333 ctx->fec_mmap_addr = (uint8_t *)p;
334 ctx->fec = ctx->fec_mmap_addr;
337 static void file_image_ecc_new(image *ctx)
340 INFO("allocating %u bytes of memory\n", ctx->fec_size);
343 ctx->fec = new uint8_t[ctx->fec_size];
346 FATAL("failed to allocate %u bytes\n", ctx->fec_size);
350 bool image_ecc_new(const char *filename, image *ctx)
352 assert(ctx->rounds > 0); /* image_load should be called first */
354 ctx->fec_filename = filename;
355 ctx->fec_size = ctx->rounds * ctx->roots * FEC_BLOCKSIZE;
358 mmap_image_ecc_new(ctx);
360 file_image_ecc_new(ctx);
366 bool image_ecc_load(const char *filename, image *ctx)
368 int fd = TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
371 FATAL("failed to open file '%s': %s\n", filename, strerror(errno));
374 if (lseek64(fd, -FEC_BLOCKSIZE, SEEK_END) < 0) {
375 FATAL("failed to seek to header in '%s': %s\n", filename,
379 assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
381 uint8_t header[FEC_BLOCKSIZE];
382 fec_header *p = (fec_header *)header;
384 if (!android::base::ReadFully(fd, header, sizeof(header))) {
385 FATAL("failed to read %zd bytes from '%s': %s\n", sizeof(header),
386 filename, strerror(errno));
389 if (p->magic != FEC_MAGIC) {
390 FATAL("invalid magic in '%s': %08x\n", filename, p->magic);
393 if (p->version != FEC_VERSION) {
394 FATAL("unsupported version in '%s': %u\n", filename, p->version);
397 if (p->size != sizeof(fec_header)) {
398 FATAL("unexpected header size in '%s': %u\n", filename, p->size);
401 if (p->roots == 0 || p->roots >= FEC_RSM) {
402 FATAL("invalid roots in '%s': %u\n", filename, p->roots);
405 if (p->fec_size % p->roots || p->fec_size % FEC_BLOCKSIZE) {
406 FATAL("invalid length in '%s': %u\n", filename, p->fec_size);
409 ctx->roots = (int)p->roots;
410 ctx->rs_n = FEC_RSM - ctx->roots;
412 calculate_rounds(p->inp_size, ctx);
414 if (!image_ecc_new(filename, ctx)) {
415 FATAL("failed to allocate ecc\n");
418 if (p->fec_size != ctx->fec_size) {
419 FATAL("inconsistent header in '%s'\n", filename);
422 if (lseek64(fd, 0, SEEK_SET) < 0) {
423 FATAL("failed to rewind '%s': %s", filename, strerror(errno));
426 if (!ctx->mmap && !android::base::ReadFully(fd, ctx->fec, ctx->fec_size)) {
427 FATAL("failed to read %u bytes from '%s': %s\n", ctx->fec_size,
428 filename, strerror(errno));
431 TEMP_FAILURE_RETRY(close(fd));
433 uint8_t hash[SHA256_DIGEST_LENGTH];
434 SHA256(ctx->fec, ctx->fec_size, hash);
436 if (memcmp(hash, p->hash, SHA256_DIGEST_LENGTH) != 0) {
437 FATAL("invalid ecc data\n");
443 bool image_ecc_save(image *ctx)
445 assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
447 uint8_t header[FEC_BLOCKSIZE];
451 p = (uint8_t *)&ctx->fec_mmap_addr[ctx->fec_size];
454 memset(p, 0, FEC_BLOCKSIZE);
456 fec_header *f = (fec_header *)p;
458 f->magic = FEC_MAGIC;
459 f->version = FEC_VERSION;
460 f->size = sizeof(fec_header);
461 f->roots = ctx->roots;
462 f->fec_size = ctx->fec_size;
463 f->inp_size = ctx->inp_size;
465 SHA256(ctx->fec, ctx->fec_size, f->hash);
467 /* store a copy of the fec_header at the end of the header block */
468 memcpy(&p[sizeof(header) - sizeof(fec_header)], p, sizeof(fec_header));
471 assert(ctx->fec_filename);
473 int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename,
474 O_WRONLY | O_CREAT | O_TRUNC, 0666));
477 FATAL("failed to open file '%s': %s\n", ctx->fec_filename,
481 if (!android::base::WriteFully(fd, ctx->fec, ctx->fec_size) ||
482 !android::base::WriteFully(fd, header, sizeof(header))) {
483 FATAL("failed to write to output: %s\n", strerror(errno));
486 TEMP_FAILURE_RETRY(close(fd));
492 static void * process(void *cookie)
494 image_proc_ctx *ctx = (image_proc_ctx *)cookie;
499 bool image_process(image_proc_func func, image *ctx)
501 int threads = ctx->threads;
503 if (threads < IMAGE_MIN_THREADS) {
504 threads = sysconf(_SC_NPROCESSORS_ONLN);
506 if (threads < IMAGE_MIN_THREADS) {
507 threads = IMAGE_MIN_THREADS;
511 assert(ctx->rounds > 0);
513 if ((uint64_t)threads > ctx->rounds) {
514 threads = (int)ctx->rounds;
516 if (threads > IMAGE_MAX_THREADS) {
517 threads = IMAGE_MAX_THREADS;
521 INFO("starting %d threads to compute RS(255, %d)\n", threads,
525 pthread_t pthreads[threads];
526 image_proc_ctx args[threads];
528 uint64_t current = 0;
529 uint64_t end = ctx->rounds * ctx->rs_n * FEC_BLOCKSIZE;
530 uint64_t rs_blocks_per_thread =
531 fec_div_round_up(ctx->rounds * FEC_BLOCKSIZE, threads);
534 INFO("computing %" PRIu64 " codes per thread\n", rs_blocks_per_thread);
537 for (int i = 0; i < threads; ++i) {
542 args[i].fec_pos = current * ctx->roots;
543 args[i].start = current * ctx->rs_n;
544 args[i].end = (current + rs_blocks_per_thread) * ctx->rs_n;
546 args[i].rs = init_rs_char(FEC_PARAMS(ctx->roots));
549 FATAL("failed to initialize encoder for thread %d\n", i);
552 if (args[i].end > end) {
554 } else if (i == threads && args[i].end + rs_blocks_per_thread *
560 INFO("thread %d: [%" PRIu64 ", %" PRIu64 ")\n",
561 i, args[i].start, args[i].end);
564 assert(args[i].start < args[i].end);
565 assert((args[i].end - args[i].start) % ctx->rs_n == 0);
567 if (pthread_create(&pthreads[i], NULL, process, &args[i]) != 0) {
568 FATAL("failed to create thread %d\n", i);
571 current += rs_blocks_per_thread;
576 for (int i = 0; i < threads; ++i) {
577 if (pthread_join(pthreads[i], NULL) != 0) {
578 FATAL("failed to join thread %d: %s\n", i, strerror(errno));
581 ctx->rv += args[i].rv;
584 free_rs_char(args[i].rs);