• 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 <stdlib.h>
32 #include <string.h>
33 #include <sys/ioctl.h>
34 #include <sys/mman.h>
35 #include <sparse/sparse.h>
36 #include "image.h"
37 
38 #if defined(__linux__)
39     #include <linux/fs.h>
40 #elif defined(__APPLE__)
41     #include <sys/disk.h>
42     #define BLKGETSIZE64 DKIOCGETBLOCKCOUNT
43     #define O_LARGEFILE 0
44 #endif
45 
image_init(image * ctx)46 void image_init(image *ctx)
47 {
48     memset(ctx, 0, sizeof(*ctx));
49 }
50 
image_free(image * ctx)51 void image_free(image *ctx)
52 {
53     assert(ctx->input == ctx->output);
54 
55     if (ctx->input) {
56         delete[] ctx->input;
57     }
58 
59     if (ctx->fec) {
60         delete[] ctx->fec;
61     }
62 
63     image_init(ctx);
64 }
65 
calculate_rounds(uint64_t size,image * ctx)66 static void calculate_rounds(uint64_t size, image *ctx)
67 {
68     if (!size) {
69         FATAL("empty file?\n");
70     } else if (size % FEC_BLOCKSIZE) {
71         FATAL("file size %" PRIu64 " is not a multiple of %u bytes\n",
72             size, FEC_BLOCKSIZE);
73     }
74 
75     ctx->inp_size = size;
76     ctx->blocks = fec_div_round_up(ctx->inp_size, FEC_BLOCKSIZE);
77     ctx->rounds = fec_div_round_up(ctx->blocks, ctx->rs_n);
78 }
79 
process_chunk(void * priv,const void * data,int len)80 static int process_chunk(void *priv, const void *data, int len)
81 {
82     image *ctx = (image *)priv;
83     assert(len % FEC_BLOCKSIZE == 0);
84 
85     if (data) {
86         memcpy(&ctx->input[ctx->pos], data, len);
87     }
88 
89     ctx->pos += len;
90     return 0;
91 }
92 
file_image_load(const std::vector<int> & fds,image * ctx)93 static void file_image_load(const std::vector<int>& fds, image *ctx)
94 {
95     uint64_t size = 0;
96     std::vector<struct sparse_file *> files;
97 
98     for (auto fd : fds) {
99         uint64_t len = 0;
100         struct sparse_file *file;
101 
102         if (ctx->sparse) {
103             file = sparse_file_import(fd, false, false);
104         } else {
105             file = sparse_file_import_auto(fd, false, ctx->verbose);
106         }
107 
108         if (!file) {
109             FATAL("failed to read file %s\n", ctx->fec_filename);
110         }
111 
112         len = sparse_file_len(file, false, false);
113         files.push_back(file);
114 
115         size += len;
116     }
117 
118     calculate_rounds(size, ctx);
119 
120     if (ctx->verbose) {
121         INFO("allocating %" PRIu64 " bytes of memory\n", ctx->inp_size);
122     }
123 
124     ctx->input = new uint8_t[ctx->inp_size];
125 
126     if (!ctx->input) {
127         FATAL("failed to allocate memory\n");
128     }
129 
130     memset(ctx->input, 0, ctx->inp_size);
131     ctx->output = ctx->input;
132     ctx->pos = 0;
133 
134     for (auto file : files) {
135         sparse_file_callback(file, false, false, process_chunk, ctx);
136         sparse_file_destroy(file);
137     }
138 
139     for (auto fd : fds) {
140         close(fd);
141     }
142 }
143 
image_load(const std::vector<std::string> & filenames,image * ctx)144 bool image_load(const std::vector<std::string>& filenames, image *ctx)
145 {
146     assert(ctx->roots > 0 && ctx->roots < FEC_RSM);
147     ctx->rs_n = FEC_RSM - ctx->roots;
148 
149     int flags = O_RDONLY;
150 
151     if (ctx->inplace) {
152         flags = O_RDWR;
153     }
154 
155     std::vector<int> fds;
156 
157     for (const auto& fn : filenames) {
158         int fd = TEMP_FAILURE_RETRY(open(fn.c_str(), flags | O_LARGEFILE));
159 
160         if (fd < 0) {
161             FATAL("failed to open file '%s': %s\n", fn.c_str(), strerror(errno));
162         }
163 
164         fds.push_back(fd);
165     }
166 
167     file_image_load(fds, ctx);
168 
169     return true;
170 }
171 
image_save(const std::string & filename,image * ctx)172 bool image_save(const std::string& filename, image *ctx)
173 {
174     /* TODO: support saving as a sparse file */
175     int fd = TEMP_FAILURE_RETRY(open(filename.c_str(),
176                 O_WRONLY | O_CREAT | O_TRUNC, 0666));
177 
178     if (fd < 0) {
179         FATAL("failed to open file '%s: %s'\n", filename.c_str(),
180             strerror(errno));
181     }
182 
183     if (!android::base::WriteFully(fd, ctx->output, ctx->inp_size)) {
184         FATAL("failed to write to output: %s\n", strerror(errno));
185     }
186 
187     close(fd);
188     return true;
189 }
190 
image_ecc_new(const std::string & filename,image * ctx)191 bool image_ecc_new(const std::string& filename, image *ctx)
192 {
193     assert(ctx->rounds > 0); /* image_load should be called first */
194 
195     ctx->fec_filename = filename.c_str();
196     ctx->fec_size = ctx->rounds * ctx->roots * FEC_BLOCKSIZE;
197 
198     if (ctx->verbose) {
199         INFO("allocating %u bytes of memory\n", ctx->fec_size);
200     }
201 
202     ctx->fec = new uint8_t[ctx->fec_size];
203 
204     if (!ctx->fec) {
205         FATAL("failed to allocate %u bytes\n", ctx->fec_size);
206     }
207 
208     return true;
209 }
210 
image_ecc_load(const std::string & filename,image * ctx)211 bool image_ecc_load(const std::string& filename, image *ctx)
212 {
213     int fd = TEMP_FAILURE_RETRY(open(filename.c_str(), O_RDONLY));
214 
215     if (fd < 0) {
216         FATAL("failed to open file '%s': %s\n", filename.c_str(),
217             strerror(errno));
218     }
219 
220     if (lseek64(fd, -FEC_BLOCKSIZE, SEEK_END) < 0) {
221         FATAL("failed to seek to header in '%s': %s\n", filename.c_str(),
222             strerror(errno));
223     }
224 
225     assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
226 
227     uint8_t header[FEC_BLOCKSIZE];
228     fec_header *p = (fec_header *)header;
229 
230     if (!android::base::ReadFully(fd, header, sizeof(header))) {
231         FATAL("failed to read %zd bytes from '%s': %s\n", sizeof(header),
232             filename.c_str(), strerror(errno));
233     }
234 
235     if (p->magic != FEC_MAGIC) {
236         FATAL("invalid magic in '%s': %08x\n", filename.c_str(), p->magic);
237     }
238 
239     if (p->version != FEC_VERSION) {
240         FATAL("unsupported version in '%s': %u\n", filename.c_str(),
241             p->version);
242     }
243 
244     if (p->size != sizeof(fec_header)) {
245         FATAL("unexpected header size in '%s': %u\n", filename.c_str(),
246             p->size);
247     }
248 
249     if (p->roots == 0 || p->roots >= FEC_RSM) {
250         FATAL("invalid roots in '%s': %u\n", filename.c_str(), p->roots);
251     }
252 
253     if (p->fec_size % p->roots || p->fec_size % FEC_BLOCKSIZE) {
254         FATAL("invalid length in '%s': %u\n", filename.c_str(), p->fec_size);
255     }
256 
257     ctx->roots = (int)p->roots;
258     ctx->rs_n = FEC_RSM - ctx->roots;
259 
260     calculate_rounds(p->inp_size, ctx);
261 
262     if (!image_ecc_new(filename, ctx)) {
263         FATAL("failed to allocate ecc\n");
264     }
265 
266     if (p->fec_size != ctx->fec_size) {
267         FATAL("inconsistent header in '%s'\n", filename.c_str());
268     }
269 
270     if (lseek64(fd, 0, SEEK_SET) < 0) {
271         FATAL("failed to rewind '%s': %s", filename.c_str(), strerror(errno));
272     }
273 
274     if (!android::base::ReadFully(fd, ctx->fec, ctx->fec_size)) {
275         FATAL("failed to read %u bytes from '%s': %s\n", ctx->fec_size,
276             filename.c_str(), strerror(errno));
277     }
278 
279     close(fd);
280 
281     uint8_t hash[SHA256_DIGEST_LENGTH];
282     SHA256(ctx->fec, ctx->fec_size, hash);
283 
284     if (memcmp(hash, p->hash, SHA256_DIGEST_LENGTH) != 0) {
285         FATAL("invalid ecc data\n");
286     }
287 
288     return true;
289 }
290 
image_ecc_save(image * ctx)291 bool image_ecc_save(image *ctx)
292 {
293     assert(2 * sizeof(fec_header) <= FEC_BLOCKSIZE);
294 
295     uint8_t header[FEC_BLOCKSIZE] = {0};
296 
297     fec_header *f = (fec_header *)header;
298 
299     f->magic = FEC_MAGIC;
300     f->version = FEC_VERSION;
301     f->size = sizeof(fec_header);
302     f->roots = ctx->roots;
303     f->fec_size = ctx->fec_size;
304     f->inp_size = ctx->inp_size;
305 
306     SHA256(ctx->fec, ctx->fec_size, f->hash);
307 
308     /* store a copy of the fec_header at the end of the header block */
309     memcpy(&header[sizeof(header) - sizeof(fec_header)], header,
310         sizeof(fec_header));
311 
312     assert(ctx->fec_filename);
313 
314     int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename,
315                 O_WRONLY | O_CREAT | O_TRUNC, 0666));
316 
317     if (fd < 0) {
318         FATAL("failed to open file '%s': %s\n", ctx->fec_filename,
319             strerror(errno));
320     }
321 
322     if (!android::base::WriteFully(fd, ctx->fec, ctx->fec_size)) {
323         FATAL("failed to write to output: %s\n", strerror(errno));
324     }
325 
326     if (ctx->padding > 0) {
327         uint8_t padding[FEC_BLOCKSIZE] = {0};
328 
329         for (uint32_t i = 0; i < ctx->padding; i += FEC_BLOCKSIZE) {
330             if (!android::base::WriteFully(fd, padding, FEC_BLOCKSIZE)) {
331                 FATAL("failed to write padding: %s\n", strerror(errno));
332             }
333         }
334     }
335 
336     if (!android::base::WriteFully(fd, header, sizeof(header))) {
337         FATAL("failed to write to header: %s\n", strerror(errno));
338     }
339 
340     close(fd);
341 
342     return true;
343 }
344 
process(void * cookie)345 static void * process(void *cookie)
346 {
347     image_proc_ctx *ctx = (image_proc_ctx *)cookie;
348     ctx->func(ctx);
349     return NULL;
350 }
351 
image_process(image_proc_func func,image * ctx)352 bool image_process(image_proc_func func, image *ctx)
353 {
354     int threads = ctx->threads;
355 
356     if (threads < IMAGE_MIN_THREADS) {
357         threads = sysconf(_SC_NPROCESSORS_ONLN);
358 
359         if (threads < IMAGE_MIN_THREADS) {
360             threads = IMAGE_MIN_THREADS;
361         }
362     }
363 
364     assert(ctx->rounds > 0);
365 
366     if ((uint64_t)threads > ctx->rounds) {
367         threads = (int)ctx->rounds;
368     }
369     if (threads > IMAGE_MAX_THREADS) {
370         threads = IMAGE_MAX_THREADS;
371     }
372 
373     if (ctx->verbose) {
374         INFO("starting %d threads to compute RS(255, %d)\n", threads,
375             ctx->rs_n);
376     }
377 
378     pthread_t pthreads[threads];
379     image_proc_ctx args[threads];
380 
381     uint64_t current = 0;
382     uint64_t end = ctx->rounds * ctx->rs_n * FEC_BLOCKSIZE;
383     uint64_t rs_blocks_per_thread =
384         fec_div_round_up(ctx->rounds * FEC_BLOCKSIZE, threads);
385 
386     if (ctx->verbose) {
387         INFO("computing %" PRIu64 " codes per thread\n", rs_blocks_per_thread);
388     }
389 
390     for (int i = 0; i < threads; ++i) {
391         args[i].func = func;
392         args[i].id = i;
393         args[i].ctx = ctx;
394         args[i].rv = 0;
395         args[i].fec_pos = current * ctx->roots;
396         args[i].start = current * ctx->rs_n;
397         args[i].end = (current + rs_blocks_per_thread) * ctx->rs_n;
398 
399         args[i].rs = init_rs_char(FEC_PARAMS(ctx->roots));
400 
401         if (!args[i].rs) {
402             FATAL("failed to initialize encoder for thread %d\n", i);
403         }
404 
405         if (args[i].end > end) {
406             args[i].end = end;
407         } else if (i == threads && args[i].end + rs_blocks_per_thread *
408                                         ctx->rs_n > end) {
409             args[i].end = end;
410         }
411 
412         if (ctx->verbose) {
413             INFO("thread %d: [%" PRIu64 ", %" PRIu64 ")\n",
414                 i, args[i].start, args[i].end);
415         }
416 
417         assert(args[i].start < args[i].end);
418         assert((args[i].end - args[i].start) % ctx->rs_n == 0);
419 
420         if (pthread_create(&pthreads[i], NULL, process, &args[i]) != 0) {
421             FATAL("failed to create thread %d\n", i);
422         }
423 
424         current += rs_blocks_per_thread;
425     }
426 
427     ctx->rv = 0;
428 
429     for (int i = 0; i < threads; ++i) {
430         if (pthread_join(pthreads[i], NULL) != 0) {
431             FATAL("failed to join thread %d: %s\n", i, strerror(errno));
432         }
433 
434         ctx->rv += args[i].rv;
435 
436         if (args[i].rs) {
437             free_rs_char(args[i].rs);
438             args[i].rs = NULL;
439         }
440     }
441 
442     return true;
443 }
444