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