Home | History | Annotate | Download | only in fec
      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