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