OSDN Git Service

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