1 /* 2 * Copyright (C) 2015 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 #undef NDEBUG 18 #define _LARGEFILE64_SOURCE 19 20 extern "C" { 21 #include <fec.h> 22 } 23 24 #include <assert.h> 25 #include <android-base/file.h> 26 #include <errno.h> 27 #include <fcntl.h> 28 #include <getopt.h> 29 #include <openssl/sha.h> 30 #include <pthread.h> 31 #include <stdbool.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <sys/ioctl.h> 35 #include <sys/mman.h> 36 #ifndef IMAGE_NO_SPARSE 37 #include <sparse/sparse.h> 38 #endif 39 #include "image.h" 40 41 #if defined(__linux__) 42 #include <linux/fs.h> 43 #elif defined(__APPLE__) 44 #include <sys/disk.h> 45 #define BLKGETSIZE64 DKIOCGETBLOCKCOUNT 46 #define O_LARGEFILE 0 47 #endif 48 49 void image_init(image *ctx) 50 { 51 memset(ctx, 0, sizeof(*ctx)); 52 } 53 54 static void mmap_image_free(image *ctx) 55 { 56 if (ctx->input) { 57 munmap(ctx->input, (size_t)ctx->inp_size); 58 close(ctx->inp_fd); 59 } 60 61 if (ctx->fec_mmap_addr) { 62 munmap(ctx->fec_mmap_addr, FEC_BLOCKSIZE + ctx->fec_size); 63 close(ctx->fec_fd); 64 } 65 66 if (!ctx->inplace && ctx->output) { 67 delete[] ctx->output; 68 } 69 } 70 71 static void file_image_free(image *ctx) 72 { 73 assert(ctx->input == ctx->output); 74 75 if (ctx->input) { 76 delete[] ctx->input; 77 } 78 79 if (ctx->fec) { 80 delete[] ctx->fec; 81 } 82 } 83 84 void image_free(image *ctx) 85 { 86 if (ctx->mmap) { 87 mmap_image_free(ctx); 88 } else { 89 file_image_free(ctx); 90 } 91 92 image_init(ctx); 93 } 94 95 static uint64_t get_size(int fd) 96 { 97 struct stat st; 98 99 if (fstat(fd, &st) == -1) { 100 FATAL("failed to fstat: %s\n", strerror(errno)); 101 } 102 103 uint64_t size = 0; 104 105 if (S_ISBLK(st.st_mode)) { 106 if (ioctl(fd, BLKGETSIZE64, &size) == -1) { 107 FATAL("failed to ioctl(BLKGETSIZE64): %s\n", strerror(errno)); 108 } 109 } else if (S_ISREG(st.st_mode)) { 110 size = st.st_size; 111 } else { 112 FATAL("unknown file mode: %d\n", (int)st.st_mode); 113 } 114 115 return size; 116 } 117 118 static void calculate_rounds(uint64_t size, image *ctx) 119 { 120 if (!size) { 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); 125 } 126 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); 130 } 131 132 static void mmap_image_load(const std::vector<int>& fds, image *ctx, 133 bool output_needed) 134 { 135 if (fds.size() != 1) { 136 FATAL("multiple input files not supported with mmap\n"); 137 } 138 139 int fd = fds.front(); 140 141 calculate_rounds(get_size(fd), ctx); 142 143 /* check that we can memory map the file; on 32-bit platforms we are 144 limited to encoding at most 4 GiB files */ 145 if (ctx->inp_size > SIZE_MAX) { 146 FATAL("cannot mmap %" PRIu64 " bytes\n", ctx->inp_size); 147 } 148 149 if (ctx->verbose) { 150 INFO("memory mapping '%s' (size %" PRIu64 ")\n", ctx->fec_filename, 151 ctx->inp_size); 152 } 153 154 int flags = PROT_READ; 155 156 if (ctx->inplace) { 157 flags |= PROT_WRITE; 158 } 159 160 void *p = mmap(NULL, (size_t)ctx->inp_size, flags, MAP_SHARED, fd, 0); 161 162 if (p == MAP_FAILED) { 163 FATAL("failed to mmap '%s' (size %" PRIu64 "): %s\n", 164 ctx->fec_filename, ctx->inp_size, strerror(errno)); 165 } 166 167 ctx->inp_fd = fd; 168 ctx->input = (uint8_t *)p; 169 170 if (ctx->inplace) { 171 ctx->output = ctx->input; 172 } else if (output_needed) { 173 if (ctx->verbose) { 174 INFO("allocating %" PRIu64 " bytes of memory\n", ctx->inp_size); 175 } 176 177 ctx->output = new uint8_t[ctx->inp_size]; 178 179 if (!ctx->output) { 180 FATAL("failed to allocate memory\n"); 181 } 182 183 memcpy(ctx->output, ctx->input, ctx->inp_size); 184 } 185 186 /* fd is closed in mmap_image_free */ 187 } 188 189 #ifndef IMAGE_NO_SPARSE 190 static int process_chunk(void *priv, const void *data, int len) 191 { 192 image *ctx = (image *)priv; 193 assert(len % FEC_BLOCKSIZE == 0); 194 195 if (data) { 196 memcpy(&ctx->input[ctx->pos], data, len); 197 } 198 199 ctx->pos += len; 200 return 0; 201 } 202 #endif 203 204 static void file_image_load(const std::vector<int>& fds, image *ctx) 205 { 206 uint64_t size = 0; 207 #ifndef IMAGE_NO_SPARSE 208 std::vector<struct sparse_file *> files; 209 #endif 210 211 for (auto fd : fds) { 212 uint64_t len = 0; 213 214 #ifdef IMAGE_NO_SPARSE 215 if (ctx->sparse) { 216 FATAL("sparse files not supported\n"); 217 } 218 219 len = get_size(fd); 220 #else 221 struct sparse_file *file; 222 223 if (ctx->sparse) { 224 file = sparse_file_import(fd, false, false); 225 } else { 226 file = sparse_file_import_auto(fd, false, ctx->verbose); 227 } 228 229 if (!file) { 230 FATAL("failed to read file %s\n", ctx->fec_filename); 231 } 232 233 len = sparse_file_len(file, false, false); 234 files.push_back(file); 235 #endif /* IMAGE_NO_SPARSE */ 236 237 size += len; 238 } 239 240 calculate_rounds(size, ctx); 241 242 if (ctx->verbose) { 243 INFO("allocating %" PRIu64 " bytes of memory\n", ctx->inp_size); 244 } 245 246 ctx->input = new uint8_t[ctx->inp_size]; 247 248 if (!ctx->input) { 249 FATAL("failed to allocate memory\n"); 250 } 251 252 memset(ctx->input, 0, ctx->inp_size); 253 ctx->output = ctx->input; 254 ctx->pos = 0; 255 256 #ifdef IMAGE_NO_SPARSE 257 for (auto fd : fds) { 258 uint64_t len = get_size(fd); 259 260 if (!android::base::ReadFully(fd, &ctx->input[ctx->pos], len)) { 261 FATAL("failed to read: %s\n", strerror(errno)); 262 } 263 264 ctx->pos += len; 265 close(fd); 266 } 267 #else 268 for (auto file : files) { 269 sparse_file_callback(file, false, false, process_chunk, ctx); 270 sparse_file_destroy(file); 271 } 272 273 for (auto fd : fds) { 274 close(fd); 275 } 276 #endif 277 } 278 279 bool image_load(const std::vector<std::string>& filenames, image *ctx, 280 bool output_needed) 281 { 282 assert(ctx->roots > 0 && ctx->roots < FEC_RSM); 283 ctx->rs_n = FEC_RSM - ctx->roots; 284 285 int flags = O_RDONLY; 286 287 if (ctx->inplace) { 288 flags = O_RDWR; 289 } 290 291 std::vector<int> fds; 292 293 for (auto fn : filenames) { 294 int fd = TEMP_FAILURE_RETRY(open(fn.c_str(), flags | O_LARGEFILE)); 295 296 if (fd < 0) { 297 FATAL("failed to open file '%s': %s\n", fn.c_str(), strerror(errno)); 298 } 299 300 fds.push_back(fd); 301 } 302 303 if (ctx->mmap) { 304 mmap_image_load(fds, ctx, output_needed); 305 } else { 306 file_image_load(fds, ctx); 307 } 308 309 return true; 310 } 311 312 bool image_save(const std::string& filename, image *ctx) 313 { 314 if (ctx->inplace && ctx->mmap) { 315 return true; /* nothing to do */ 316 } 317 318 /* TODO: support saving as a sparse file */ 319 int fd = TEMP_FAILURE_RETRY(open(filename.c_str(), 320 O_WRONLY | O_CREAT | O_TRUNC, 0666)); 321 322 if (fd < 0) { 323 FATAL("failed to open file '%s: %s'\n", filename.c_str(), 324 strerror(errno)); 325 } 326 327 if (!android::base::WriteFully(fd, ctx->output, ctx->inp_size)) { 328 FATAL("failed to write to output: %s\n", strerror(errno)); 329 } 330 331 close(fd); 332 return true; 333 } 334 335 static void mmap_image_ecc_new(image *ctx) 336 { 337 if (ctx->verbose) { 338 INFO("mmaping '%s' (size %u)\n", ctx->fec_filename, ctx->fec_size); 339 } 340 341 int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename, 342 O_RDWR | O_CREAT, 0666)); 343 344 if (fd < 0) { 345 FATAL("failed to open file '%s': %s\n", ctx->fec_filename, 346 strerror(errno)); 347 } 348 349 assert(sizeof(fec_header) <= FEC_BLOCKSIZE); 350 size_t fec_size = FEC_BLOCKSIZE + ctx->fec_size; 351 352 if (ftruncate(fd, fec_size) == -1) { 353 FATAL("failed to ftruncate file '%s': %s\n", ctx->fec_filename, 354 strerror(errno)); 355 } 356 357 if (ctx->verbose) { 358 INFO("memory mapping '%s' (size %zu)\n", ctx->fec_filename, fec_size); 359 } 360 361 void *p = mmap(NULL, fec_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 362 363 if (p == MAP_FAILED) { 364 FATAL("failed to mmap '%s' (size %zu): %s\n", ctx->fec_filename, 365 fec_size, strerror(errno)); 366 } 367 368 ctx->fec_fd = fd; 369 ctx->fec_mmap_addr = (uint8_t *)p; 370 ctx->fec = ctx->fec_mmap_addr; 371 } 372 373 static void file_image_ecc_new(image *ctx) 374 { 375 if (ctx->verbose) { 376 INFO("allocating %u bytes of memory\n", ctx->fec_size); 377 } 378 379 ctx->fec = new uint8_t[ctx->fec_size]; 380 381 if (!ctx->fec) { 382 FATAL("failed to allocate %u bytes\n", ctx->fec_size); 383 } 384 } 385 386 bool image_ecc_new(const std::string& filename, image *ctx) 387 { 388 assert(ctx->rounds > 0); /* image_load should be called first */ 389 390 ctx->fec_filename = filename.c_str(); 391 ctx->fec_size = ctx->rounds * ctx->roots * FEC_BLOCKSIZE; 392 393 if (ctx->mmap) { 394 mmap_image_ecc_new(ctx); 395 } else { 396 file_image_ecc_new(ctx); 397 } 398 399 return true; 400 } 401 402 bool image_ecc_load(const std::string& filename, image *ctx) 403 { 404 int fd = TEMP_FAILURE_RETRY(open(filename.c_str(), O_RDONLY)); 405 406 if (fd < 0) { 407 FATAL("failed to open file '%s': %s\n", filename.c_str(), 408 strerror(errno)); 409 } 410 411 if (lseek64(fd, -FEC_BLOCKSIZE, SEEK_END) < 0) { 412 FATAL("failed to seek to header in '%s': %s\n", filename.c_str(), 413 strerror(errno)); 414 } 415 416 assert(sizeof(fec_header) <= FEC_BLOCKSIZE); 417 418 uint8_t header[FEC_BLOCKSIZE]; 419 fec_header *p = (fec_header *)header; 420 421 if (!android::base::ReadFully(fd, header, sizeof(header))) { 422 FATAL("failed to read %zd bytes from '%s': %s\n", sizeof(header), 423 filename.c_str(), strerror(errno)); 424 } 425 426 if (p->magic != FEC_MAGIC) { 427 FATAL("invalid magic in '%s': %08x\n", filename.c_str(), p->magic); 428 } 429 430 if (p->version != FEC_VERSION) { 431 FATAL("unsupported version in '%s': %u\n", filename.c_str(), 432 p->version); 433 } 434 435 if (p->size != sizeof(fec_header)) { 436 FATAL("unexpected header size in '%s': %u\n", filename.c_str(), 437 p->size); 438 } 439 440 if (p->roots == 0 || p->roots >= FEC_RSM) { 441 FATAL("invalid roots in '%s': %u\n", filename.c_str(), p->roots); 442 } 443 444 if (p->fec_size % p->roots || p->fec_size % FEC_BLOCKSIZE) { 445 FATAL("invalid length in '%s': %u\n", filename.c_str(), p->fec_size); 446 } 447 448 ctx->roots = (int)p->roots; 449 ctx->rs_n = FEC_RSM - ctx->roots; 450 451 calculate_rounds(p->inp_size, ctx); 452 453 if (!image_ecc_new(filename, ctx)) { 454 FATAL("failed to allocate ecc\n"); 455 } 456 457 if (p->fec_size != ctx->fec_size) { 458 FATAL("inconsistent header in '%s'\n", filename.c_str()); 459 } 460 461 if (lseek64(fd, 0, SEEK_SET) < 0) { 462 FATAL("failed to rewind '%s': %s", filename.c_str(), strerror(errno)); 463 } 464 465 if (!ctx->mmap && !android::base::ReadFully(fd, ctx->fec, ctx->fec_size)) { 466 FATAL("failed to read %u bytes from '%s': %s\n", ctx->fec_size, 467 filename.c_str(), strerror(errno)); 468 } 469 470 close(fd); 471 472 uint8_t hash[SHA256_DIGEST_LENGTH]; 473 SHA256(ctx->fec, ctx->fec_size, hash); 474 475 if (memcmp(hash, p->hash, SHA256_DIGEST_LENGTH) != 0) { 476 FATAL("invalid ecc data\n"); 477 } 478 479 return true; 480 } 481 482 bool image_ecc_save(image *ctx) 483 { 484 assert(sizeof(fec_header) <= FEC_BLOCKSIZE); 485 486 uint8_t header[FEC_BLOCKSIZE]; 487 uint8_t *p = header; 488 489 if (ctx->mmap) { 490 p = (uint8_t *)&ctx->fec_mmap_addr[ctx->fec_size]; 491 } 492 493 memset(p, 0, FEC_BLOCKSIZE); 494 495 fec_header *f = (fec_header *)p; 496 497 f->magic = FEC_MAGIC; 498 f->version = FEC_VERSION; 499 f->size = sizeof(fec_header); 500 f->roots = ctx->roots; 501 f->fec_size = ctx->fec_size; 502 f->inp_size = ctx->inp_size; 503 504 SHA256(ctx->fec, ctx->fec_size, f->hash); 505 506 /* store a copy of the fec_header at the end of the header block */ 507 memcpy(&p[sizeof(header) - sizeof(fec_header)], p, sizeof(fec_header)); 508 509 if (!ctx->mmap) { 510 assert(ctx->fec_filename); 511 512 int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename, 513 O_WRONLY | O_CREAT | O_TRUNC, 0666)); 514 515 if (fd < 0) { 516 FATAL("failed to open file '%s': %s\n", ctx->fec_filename, 517 strerror(errno)); 518 } 519 520 if (!android::base::WriteFully(fd, ctx->fec, ctx->fec_size) || 521 !android::base::WriteFully(fd, header, sizeof(header))) { 522 FATAL("failed to write to output: %s\n", strerror(errno)); 523 } 524 525 close(fd); 526 } 527 528 return true; 529 } 530 531 static void * process(void *cookie) 532 { 533 image_proc_ctx *ctx = (image_proc_ctx *)cookie; 534 ctx->func(ctx); 535 return NULL; 536 } 537 538 bool image_process(image_proc_func func, image *ctx) 539 { 540 int threads = ctx->threads; 541 542 if (threads < IMAGE_MIN_THREADS) { 543 threads = sysconf(_SC_NPROCESSORS_ONLN); 544 545 if (threads < IMAGE_MIN_THREADS) { 546 threads = IMAGE_MIN_THREADS; 547 } 548 } 549 550 assert(ctx->rounds > 0); 551 552 if ((uint64_t)threads > ctx->rounds) { 553 threads = (int)ctx->rounds; 554 } 555 if (threads > IMAGE_MAX_THREADS) { 556 threads = IMAGE_MAX_THREADS; 557 } 558 559 if (ctx->verbose) { 560 INFO("starting %d threads to compute RS(255, %d)\n", threads, 561 ctx->rs_n); 562 } 563 564 pthread_t pthreads[threads]; 565 image_proc_ctx args[threads]; 566 567 uint64_t current = 0; 568 uint64_t end = ctx->rounds * ctx->rs_n * FEC_BLOCKSIZE; 569 uint64_t rs_blocks_per_thread = 570 fec_div_round_up(ctx->rounds * FEC_BLOCKSIZE, threads); 571 572 if (ctx->verbose) { 573 INFO("computing %" PRIu64 " codes per thread\n", rs_blocks_per_thread); 574 } 575 576 for (int i = 0; i < threads; ++i) { 577 args[i].func = func; 578 args[i].id = i; 579 args[i].ctx = ctx; 580 args[i].rv = 0; 581 args[i].fec_pos = current * ctx->roots; 582 args[i].start = current * ctx->rs_n; 583 args[i].end = (current + rs_blocks_per_thread) * ctx->rs_n; 584 585 args[i].rs = init_rs_char(FEC_PARAMS(ctx->roots)); 586 587 if (!args[i].rs) { 588 FATAL("failed to initialize encoder for thread %d\n", i); 589 } 590 591 if (args[i].end > end) { 592 args[i].end = end; 593 } else if (i == threads && args[i].end + rs_blocks_per_thread * 594 ctx->rs_n > end) { 595 args[i].end = end; 596 } 597 598 if (ctx->verbose) { 599 INFO("thread %d: [%" PRIu64 ", %" PRIu64 ")\n", 600 i, args[i].start, args[i].end); 601 } 602 603 assert(args[i].start < args[i].end); 604 assert((args[i].end - args[i].start) % ctx->rs_n == 0); 605 606 if (pthread_create(&pthreads[i], NULL, process, &args[i]) != 0) { 607 FATAL("failed to create thread %d\n", i); 608 } 609 610 current += rs_blocks_per_thread; 611 } 612 613 ctx->rv = 0; 614 615 for (int i = 0; i < threads; ++i) { 616 if (pthread_join(pthreads[i], NULL) != 0) { 617 FATAL("failed to join thread %d: %s\n", i, strerror(errno)); 618 } 619 620 ctx->rv += args[i].rv; 621 622 if (args[i].rs) { 623 free_rs_char(args[i].rs); 624 args[i].rs = NULL; 625 } 626 } 627 628 return true; 629 } 630