• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
image_init(image * ctx)49 void image_init(image *ctx)
50 {
51     memset(ctx, 0, sizeof(*ctx));
52 }
53 
mmap_image_free(image * ctx)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 
file_image_free(image * ctx)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 
image_free(image * ctx)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 
get_size(int fd)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 
calculate_rounds(uint64_t size,image * ctx)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 
mmap_image_load(const std::vector<int> & fds,image * ctx,bool output_needed)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
process_chunk(void * priv,const void * data,int len)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 
file_image_load(const std::vector<int> & fds,image * ctx)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 
image_load(const std::vector<std::string> & filenames,image * ctx,bool output_needed)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 
image_save(const std::string & filename,image * ctx)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 
mmap_image_ecc_new(image * ctx)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 
file_image_ecc_new(image * ctx)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 
image_ecc_new(const std::string & filename,image * ctx)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 
image_ecc_load(const std::string & filename,image * ctx)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 
image_ecc_save(image * ctx)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 
process(void * cookie)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 
image_process(image_proc_func func,image * ctx)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