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