LOCAL_SHARED_LIBRARIES := libcrypto-host libbase
LOCAL_CFLAGS += -Wall -Werror
include $(BUILD_HOST_EXECUTABLE)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
--- /dev/null
+LOCAL_PATH:= $(call my-dir)
+
+ifeq ($(HOST_OS),linux)
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_SANITIZE := integer
+LOCAL_MODULE := fec
+LOCAL_SRC_FILES := main.cpp image.cpp
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_LIBRARIES := \
+ libsparse_host \
+ libz \
+ libcrypto_static \
+ libfec_host \
+ libfec_rs_host \
+ libext4_utils_host \
+ libsquashfs_utils_host
+LOCAL_SHARED_LIBRARIES := libbase
+LOCAL_CFLAGS += -Wall -Werror -O3
+LOCAL_C_INCLUDES += external/fec
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # HOST_OS == linux
+
+include $(CLEAR_VARS)
+LOCAL_CLANG := true
+LOCAL_SANITIZE := integer
+LOCAL_MODULE := fec
+LOCAL_FORCE_STATIC_EXECUTABLE := true
+LOCAL_SRC_FILES := main.cpp image.cpp
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_LIBRARIES := \
+ libcrypto_static \
+ libfec \
+ libfec_rs \
+ libbase \
+ libext4_utils_static \
+ libsquashfs_utils \
+ libcutils
+LOCAL_CFLAGS += -Wall -Werror -O3 -DIMAGE_NO_SPARSE=1
+LOCAL_C_INCLUDES += external/fec
+include $(BUILD_EXECUTABLE)
--- /dev/null
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef NDEBUG
+#define _LARGEFILE64_SOURCE
+
+extern "C" {
+ #include <fec.h>
+}
+
+#include <assert.h>
+#include <base/file.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <linux/fs.h>
+#include <openssl/sha.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#ifndef IMAGE_NO_SPARSE
+#include <sparse/sparse.h>
+#endif
+#include "image.h"
+
+void image_init(image *ctx)
+{
+ memset(ctx, 0, sizeof(*ctx));
+}
+
+static void mmap_image_free(image *ctx)
+{
+ if (ctx->input) {
+ munmap(ctx->input, (size_t)ctx->inp_size);
+ TEMP_FAILURE_RETRY(close(ctx->inp_fd));
+ }
+
+ if (ctx->fec_mmap_addr) {
+ munmap(ctx->fec_mmap_addr, FEC_BLOCKSIZE + ctx->fec_size);
+ TEMP_FAILURE_RETRY(close(ctx->fec_fd));
+ }
+
+ if (!ctx->inplace && ctx->output) {
+ delete[] ctx->output;
+ }
+}
+
+static void file_image_free(image *ctx)
+{
+ assert(ctx->input == ctx->output);
+
+ if (ctx->input) {
+ delete[] ctx->input;
+ }
+
+ if (ctx->fec) {
+ delete[] ctx->fec;
+ }
+}
+
+void image_free(image *ctx)
+{
+ if (ctx->mmap) {
+ mmap_image_free(ctx);
+ } else {
+ file_image_free(ctx);
+ }
+
+ image_init(ctx);
+}
+
+static uint64_t get_size(int fd)
+{
+ struct stat st;
+
+ if (fstat(fd, &st) == -1) {
+ FATAL("failed to fstat: %s\n", strerror(errno));
+ }
+
+ uint64_t size = 0;
+
+ if (S_ISBLK(st.st_mode)) {
+ if (ioctl(fd, BLKGETSIZE64, &size) == -1) {
+ FATAL("failed to ioctl(BLKGETSIZE64): %s\n", strerror(errno));
+ }
+ } else if (S_ISREG(st.st_mode)) {
+ size = st.st_size;
+ } else {
+ FATAL("unknown file mode: %d\n", (int)st.st_mode);
+ }
+
+ return size;
+}
+
+static void calculate_rounds(uint64_t size, image *ctx)
+{
+ if (!size) {
+ FATAL("empty file?\n");
+ } else if (size % FEC_BLOCKSIZE) {
+ FATAL("file size %" PRIu64 " is not a multiple of %u bytes\n",
+ size, FEC_BLOCKSIZE);
+ }
+
+ ctx->inp_size = size;
+ ctx->blocks = fec_div_round_up(ctx->inp_size, FEC_BLOCKSIZE);
+ ctx->rounds = fec_div_round_up(ctx->blocks, ctx->rs_n);
+}
+
+static void mmap_image_load(int fd, image *ctx, bool output_needed)
+{
+ calculate_rounds(get_size(fd), ctx);
+
+ /* check that we can memory map the file; on 32-bit platforms we are
+ limited to encoding at most 4 GiB files */
+ if (ctx->inp_size > SIZE_MAX) {
+ FATAL("cannot mmap %" PRIu64 " bytes\n", ctx->inp_size);
+ }
+
+ if (ctx->verbose) {
+ INFO("memory mapping '%s' (size %" PRIu64 ")\n", ctx->fec_filename,
+ ctx->inp_size);
+ }
+
+ int flags = PROT_READ;
+
+ if (ctx->inplace) {
+ flags |= PROT_WRITE;
+ }
+
+ void *p = mmap(NULL, (size_t)ctx->inp_size, flags, MAP_SHARED, fd, 0);
+
+ if (p == MAP_FAILED) {
+ FATAL("failed to mmap '%s' (size %" PRIu64 "): %s\n",
+ ctx->fec_filename, ctx->inp_size, strerror(errno));
+ }
+
+ ctx->inp_fd = fd;
+ ctx->input = (uint8_t *)p;
+
+ if (ctx->inplace) {
+ ctx->output = ctx->input;
+ } else if (output_needed) {
+ if (ctx->verbose) {
+ INFO("allocating %" PRIu64 " bytes of memory\n", ctx->inp_size);
+ }
+
+ ctx->output = new uint8_t[ctx->inp_size];
+
+ if (!ctx->output) {
+ FATAL("failed to allocate memory\n");
+ }
+
+ memcpy(ctx->output, ctx->input, ctx->inp_size);
+ }
+
+ /* fd is closed in mmap_image_free */
+}
+
+#ifndef IMAGE_NO_SPARSE
+static int process_chunk(void *priv, const void *data, int len)
+{
+ image *ctx = (image *)priv;
+ assert(len % FEC_BLOCKSIZE == 0);
+
+ if (data) {
+ memcpy(&ctx->input[ctx->pos], data, len);
+ }
+
+ ctx->pos += len;
+ return 0;
+}
+#endif
+
+static void file_image_load(int fd, image *ctx)
+{
+ uint64_t len = 0;
+
+#ifdef IMAGE_NO_SPARSE
+ if (ctx->sparse) {
+ FATAL("sparse files not supported\n");
+ }
+
+ len = get_size(fd);
+#else
+ struct sparse_file *file;
+
+ if (ctx->sparse) {
+ file = sparse_file_import(fd, false, false);
+ } else {
+ file = sparse_file_import_auto(fd, false, ctx->verbose);
+ }
+
+ if (!file) {
+ FATAL("failed to read file %s\n", ctx->fec_filename);
+ }
+
+ len = sparse_file_len(file, false, false);
+#endif /* IMAGE_NO_SPARSE */
+
+ calculate_rounds(len, ctx);
+
+ if (ctx->verbose) {
+ INFO("allocating %" PRIu64 " bytes of memory\n", ctx->inp_size);
+ }
+
+ ctx->input = new uint8_t[ctx->inp_size];
+
+ if (!ctx->input) {
+ FATAL("failed to allocate memory\n");
+ }
+
+ memset(ctx->input, 0, ctx->inp_size);
+ ctx->output = ctx->input;
+
+#ifdef IMAGE_NO_SPARSE
+ if (!android::base::ReadFully(fd, ctx->input, ctx->inp_size)) {
+ FATAL("failed to read: %s\n", strerror(errno));
+ }
+#else
+ ctx->pos = 0;
+ sparse_file_callback(file, false, false, process_chunk, ctx);
+ sparse_file_destroy(file);
+#endif
+
+ TEMP_FAILURE_RETRY(close(fd));
+}
+
+bool image_load(const char *filename, image *ctx, bool output_needed)
+{
+ assert(ctx->roots > 0 && ctx->roots < FEC_RSM);
+ ctx->rs_n = FEC_RSM - ctx->roots;
+
+ int flags = O_RDONLY;
+
+ if (ctx->inplace) {
+ flags = O_RDWR;
+ }
+
+ int fd = TEMP_FAILURE_RETRY(open(filename, flags | O_LARGEFILE));
+
+ if (fd < 0) {
+ FATAL("failed to open file '%s': %s\n", filename, strerror(errno));
+ }
+
+ if (ctx->mmap) {
+ mmap_image_load(fd, ctx, output_needed);
+ } else {
+ file_image_load(fd, ctx);
+ }
+
+ return true;
+}
+
+bool image_save(const char *filename, image *ctx)
+{
+ if (ctx->inplace && ctx->mmap) {
+ return true; /* nothing to do */
+ }
+
+ /* TODO: support saving as a sparse file */
+ int fd = TEMP_FAILURE_RETRY(open(filename, O_WRONLY | O_CREAT | O_TRUNC,
+ 0666));
+
+ if (fd < 0) {
+ FATAL("failed to open file '%s: %s'\n", filename, strerror(errno));
+ }
+
+ if (!android::base::WriteFully(fd, ctx->output, ctx->inp_size)) {
+ FATAL("failed to write to output: %s\n", strerror(errno));
+ }
+
+ TEMP_FAILURE_RETRY(close(fd));
+ return true;
+}
+
+static void mmap_image_ecc_new(image *ctx)
+{
+ if (ctx->verbose) {
+ INFO("mmaping '%s' (size %u)\n", ctx->fec_filename, ctx->fec_size);
+ }
+
+ int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename,
+ O_RDWR | O_CREAT, 0666));
+
+ if (fd < 0) {
+ FATAL("failed to open file '%s': %s\n", ctx->fec_filename,
+ strerror(errno));
+ }
+
+ assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
+ size_t fec_size = FEC_BLOCKSIZE + ctx->fec_size;
+
+ if (ftruncate(fd, fec_size) == -1) {
+ FATAL("failed to ftruncate file '%s': %s\n", ctx->fec_filename,
+ strerror(errno));
+ }
+
+ if (ctx->verbose) {
+ INFO("memory mapping '%s' (size %zu)\n", ctx->fec_filename, fec_size);
+ }
+
+ void *p = mmap(NULL, fec_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+ if (p == MAP_FAILED) {
+ FATAL("failed to mmap '%s' (size %zu): %s\n", ctx->fec_filename,
+ fec_size, strerror(errno));
+ }
+
+ ctx->fec_fd = fd;
+ ctx->fec_mmap_addr = (uint8_t *)p;
+ ctx->fec = ctx->fec_mmap_addr;
+}
+
+static void file_image_ecc_new(image *ctx)
+{
+ if (ctx->verbose) {
+ INFO("allocating %u bytes of memory\n", ctx->fec_size);
+ }
+
+ ctx->fec = new uint8_t[ctx->fec_size];
+
+ if (!ctx->fec) {
+ FATAL("failed to allocate %u bytes\n", ctx->fec_size);
+ }
+}
+
+bool image_ecc_new(const char *filename, image *ctx)
+{
+ assert(ctx->rounds > 0); /* image_load should be called first */
+
+ ctx->fec_filename = filename;
+ ctx->fec_size = ctx->rounds * ctx->roots * FEC_BLOCKSIZE;
+
+ if (ctx->mmap) {
+ mmap_image_ecc_new(ctx);
+ } else {
+ file_image_ecc_new(ctx);
+ }
+
+ return true;
+}
+
+bool image_ecc_load(const char *filename, image *ctx)
+{
+ int fd = TEMP_FAILURE_RETRY(open(filename, O_RDONLY));
+
+ if (fd < 0) {
+ FATAL("failed to open file '%s': %s\n", filename, strerror(errno));
+ }
+
+ if (lseek64(fd, -FEC_BLOCKSIZE, SEEK_END) < 0) {
+ FATAL("failed to seek to header in '%s': %s\n", filename,
+ strerror(errno));
+ }
+
+ assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
+
+ uint8_t header[FEC_BLOCKSIZE];
+ fec_header *p = (fec_header *)header;
+
+ if (!android::base::ReadFully(fd, header, sizeof(header))) {
+ FATAL("failed to read %zd bytes from '%s': %s\n", sizeof(header),
+ filename, strerror(errno));
+ }
+
+ if (p->magic != FEC_MAGIC) {
+ FATAL("invalid magic in '%s': %08x\n", filename, p->magic);
+ }
+
+ if (p->version != FEC_VERSION) {
+ FATAL("unsupported version in '%s': %u\n", filename, p->version);
+ }
+
+ if (p->size != sizeof(fec_header)) {
+ FATAL("unexpected header size in '%s': %u\n", filename, p->size);
+ }
+
+ if (p->roots == 0 || p->roots >= FEC_RSM) {
+ FATAL("invalid roots in '%s': %u\n", filename, p->roots);
+ }
+
+ if (p->fec_size % p->roots || p->fec_size % FEC_BLOCKSIZE) {
+ FATAL("invalid length in '%s': %u\n", filename, p->fec_size);
+ }
+
+ ctx->roots = (int)p->roots;
+ ctx->rs_n = FEC_RSM - ctx->roots;
+
+ calculate_rounds(p->inp_size, ctx);
+
+ if (!image_ecc_new(filename, ctx)) {
+ FATAL("failed to allocate ecc\n");
+ }
+
+ if (p->fec_size != ctx->fec_size) {
+ FATAL("inconsistent header in '%s'\n", filename);
+ }
+
+ if (lseek64(fd, 0, SEEK_SET) < 0) {
+ FATAL("failed to rewind '%s': %s", filename, strerror(errno));
+ }
+
+ if (!ctx->mmap && !android::base::ReadFully(fd, ctx->fec, ctx->fec_size)) {
+ FATAL("failed to read %u bytes from '%s': %s\n", ctx->fec_size,
+ filename, strerror(errno));
+ }
+
+ TEMP_FAILURE_RETRY(close(fd));
+
+ uint8_t hash[SHA256_DIGEST_LENGTH];
+ SHA256(ctx->fec, ctx->fec_size, hash);
+
+ if (memcmp(hash, p->hash, SHA256_DIGEST_LENGTH) != 0) {
+ FATAL("invalid ecc data\n");
+ }
+
+ return true;
+}
+
+bool image_ecc_save(image *ctx)
+{
+ assert(sizeof(fec_header) <= FEC_BLOCKSIZE);
+
+ uint8_t header[FEC_BLOCKSIZE];
+ uint8_t *p = header;
+
+ if (ctx->mmap) {
+ p = (uint8_t *)&ctx->fec_mmap_addr[ctx->fec_size];
+ }
+
+ memset(p, 0, FEC_BLOCKSIZE);
+
+ fec_header *f = (fec_header *)p;
+
+ f->magic = FEC_MAGIC;
+ f->version = FEC_VERSION;
+ f->size = sizeof(fec_header);
+ f->roots = ctx->roots;
+ f->fec_size = ctx->fec_size;
+ f->inp_size = ctx->inp_size;
+
+ SHA256(ctx->fec, ctx->fec_size, f->hash);
+
+ /* store a copy of the fec_header at the end of the header block */
+ memcpy(&p[sizeof(header) - sizeof(fec_header)], p, sizeof(fec_header));
+
+ if (!ctx->mmap) {
+ assert(ctx->fec_filename);
+
+ int fd = TEMP_FAILURE_RETRY(open(ctx->fec_filename,
+ O_WRONLY | O_CREAT | O_TRUNC, 0666));
+
+ if (fd < 0) {
+ FATAL("failed to open file '%s': %s\n", ctx->fec_filename,
+ strerror(errno));
+ }
+
+ if (!android::base::WriteFully(fd, ctx->fec, ctx->fec_size) ||
+ !android::base::WriteFully(fd, header, sizeof(header))) {
+ FATAL("failed to write to output: %s\n", strerror(errno));
+ }
+
+ TEMP_FAILURE_RETRY(close(fd));
+ }
+
+ return true;
+}
+
+static void * process(void *cookie)
+{
+ image_proc_ctx *ctx = (image_proc_ctx *)cookie;
+ ctx->func(ctx);
+ return NULL;
+}
+
+bool image_process(image_proc_func func, image *ctx)
+{
+ int threads = ctx->threads;
+
+ if (threads < IMAGE_MIN_THREADS) {
+ threads = sysconf(_SC_NPROCESSORS_ONLN);
+
+ if (threads < IMAGE_MIN_THREADS) {
+ threads = IMAGE_MIN_THREADS;
+ }
+ }
+
+ assert(ctx->rounds > 0);
+
+ if ((uint64_t)threads > ctx->rounds) {
+ threads = (int)ctx->rounds;
+ }
+ if (threads > IMAGE_MAX_THREADS) {
+ threads = IMAGE_MAX_THREADS;
+ }
+
+ if (ctx->verbose) {
+ INFO("starting %d threads to compute RS(255, %d)\n", threads,
+ ctx->rs_n);
+ }
+
+ pthread_t pthreads[threads];
+ image_proc_ctx args[threads];
+
+ uint64_t current = 0;
+ uint64_t end = ctx->rounds * ctx->rs_n * FEC_BLOCKSIZE;
+ uint64_t rs_blocks_per_thread =
+ fec_div_round_up(ctx->rounds * FEC_BLOCKSIZE, threads);
+
+ if (ctx->verbose) {
+ INFO("computing %" PRIu64 " codes per thread\n", rs_blocks_per_thread);
+ }
+
+ for (int i = 0; i < threads; ++i) {
+ args[i].func = func;
+ args[i].id = i;
+ args[i].ctx = ctx;
+ args[i].rv = 0;
+ args[i].fec_pos = current * ctx->roots;
+ args[i].start = current * ctx->rs_n;
+ args[i].end = (current + rs_blocks_per_thread) * ctx->rs_n;
+
+ args[i].rs = init_rs_char(FEC_PARAMS(ctx->roots));
+
+ if (!args[i].rs) {
+ FATAL("failed to initialize encoder for thread %d\n", i);
+ }
+
+ if (args[i].end > end) {
+ args[i].end = end;
+ } else if (i == threads && args[i].end + rs_blocks_per_thread *
+ ctx->rs_n > end) {
+ args[i].end = end;
+ }
+
+ if (ctx->verbose) {
+ INFO("thread %d: [%" PRIu64 ", %" PRIu64 ")\n",
+ i, args[i].start, args[i].end);
+ }
+
+ assert(args[i].start < args[i].end);
+ assert((args[i].end - args[i].start) % ctx->rs_n == 0);
+
+ if (pthread_create(&pthreads[i], NULL, process, &args[i]) != 0) {
+ FATAL("failed to create thread %d\n", i);
+ }
+
+ current += rs_blocks_per_thread;
+ }
+
+ ctx->rv = 0;
+
+ for (int i = 0; i < threads; ++i) {
+ if (pthread_join(pthreads[i], NULL) != 0) {
+ FATAL("failed to join thread %d: %s\n", i, strerror(errno));
+ }
+
+ ctx->rv += args[i].rv;
+
+ if (args[i].rs) {
+ free_rs_char(args[i].rs);
+ args[i].rs = NULL;
+ }
+ }
+
+ return true;
+}
--- /dev/null
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __FEC_H__
+#define __FEC_H__
+
+#include <fec/io.h>
+#include <fec/ecc.h>
+
+#define IMAGE_MIN_THREADS 1
+#define IMAGE_MAX_THREADS 128
+
+#define INFO(x...) \
+ fprintf(stderr, x);
+#define FATAL(x...) { \
+ fprintf(stderr, x); \
+ exit(1); \
+}
+
+#define unlikely(x) __builtin_expect(!!(x), 0)
+
+struct image {
+ /* if true, decode file in place instead of creating a new output file */
+ bool inplace;
+ /* if true, use memory mapping instead of copying all input into memory */
+ bool mmap;
+ /* if true, assume input is a sparse file */
+ bool sparse;
+ /* if true, print more verbose information to stderr */
+ bool verbose;
+ const char *fec_filename;
+ int fec_fd;
+ int inp_fd;
+ /* the number of Reed-Solomon generator polynomial roots, also the number
+ of parity bytes generated for each N bytes in RS(M, N) */
+ int roots;
+ /* for RS(M, N), N = M - roots */
+ int rs_n;
+ int threads;
+ uint32_t fec_size;
+ uint64_t blocks;
+ uint64_t inp_size;
+ uint64_t pos;
+ uint64_t rounds;
+ uint64_t rv;
+ uint8_t *fec;
+ uint8_t *fec_mmap_addr;
+ uint8_t *input;
+ uint8_t *output;
+};
+
+struct image_proc_ctx;
+typedef void (*image_proc_func)(image_proc_ctx *);
+
+struct image_proc_ctx {
+ image_proc_func func;
+ int id;
+ image *ctx;
+ uint64_t rv;
+ uint64_t fec_pos;
+ uint64_t start;
+ uint64_t end;
+ void *rs;
+};
+
+extern bool image_load(const char *filename, image *ctx,
+ bool output_needed);
+extern bool image_save(const char *filename, image *ctx);
+
+extern bool image_ecc_new(const char *filename, image *ctx);
+extern bool image_ecc_load(const char *filename, image *ctx);
+extern bool image_ecc_save(image *ctx);
+
+extern bool image_process(image_proc_func f, image *ctx);
+
+extern void image_init(image *ctx);
+extern void image_free(image *ctx);
+
+inline uint8_t image_get_interleaved_byte(uint64_t i, image *ctx)
+{
+ uint64_t offset = fec_ecc_interleave(i, ctx->rs_n, ctx->rounds);
+
+ if (unlikely(offset >= ctx->inp_size)) {
+ return 0;
+ }
+
+ return ctx->input[offset];
+}
+
+inline void image_set_interleaved_byte(uint64_t i, image *ctx,
+ uint8_t value)
+{
+ uint64_t offset = fec_ecc_interleave(i, ctx->rs_n, ctx->rounds);
+
+ if (unlikely(offset >= ctx->inp_size)) {
+ assert(value == 0);
+ } else if (ctx->output && ctx->output[offset] != value) {
+ ctx->output[offset] = value;
+ }
+}
+
+#endif // __FEC_H__
--- /dev/null
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+extern "C" {
+ #include <fec.h>
+}
+
+#undef NDEBUG
+
+#include <assert.h>
+#include <errno.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <base/file.h>
+#include <fec/io.h>
+#include <fec/ecc.h>
+#include "image.h"
+
+enum {
+ MODE_ENCODE,
+ MODE_DECODE,
+ MODE_PRINTSIZE,
+ MODE_GETECCSTART,
+ MODE_GETVERITYSTART
+};
+
+static void encode_rs(struct image_proc_ctx *ctx)
+{
+ struct image *fcx = ctx->ctx;
+ int j;
+ uint8_t data[fcx->rs_n];
+ uint64_t i;
+
+ for (i = ctx->start; i < ctx->end; i += fcx->rs_n) {
+ for (j = 0; j < fcx->rs_n; ++j) {
+ data[j] = image_get_interleaved_byte(i + j, fcx);
+ }
+
+ encode_rs_char(ctx->rs, data, &fcx->fec[ctx->fec_pos]);
+ ctx->fec_pos += fcx->roots;
+ }
+}
+
+static void decode_rs(struct image_proc_ctx *ctx)
+{
+ struct image *fcx = ctx->ctx;
+ int j, rv;
+ uint8_t data[fcx->rs_n + fcx->roots];
+ uint64_t i;
+
+ assert(sizeof(data) == FEC_RSM);
+
+ for (i = ctx->start; i < ctx->end; i += fcx->rs_n) {
+ for (j = 0; j < fcx->rs_n; ++j) {
+ data[j] = image_get_interleaved_byte(i + j, fcx);
+ }
+
+ memcpy(&data[fcx->rs_n], &fcx->fec[ctx->fec_pos], fcx->roots);
+ rv = decode_rs_char(ctx->rs, data, NULL, 0);
+
+ if (rv < 0) {
+ FATAL("failed to recover [%" PRIu64 ", %" PRIu64 ")\n",
+ i, i + fcx->rs_n);
+ } else if (rv > 0) {
+ /* copy corrected data to output */
+ for (j = 0; j < fcx->rs_n; ++j) {
+ image_set_interleaved_byte(i + j, fcx, data[j]);
+ }
+
+ ctx->rv += rv;
+ }
+
+ ctx->fec_pos += fcx->roots;
+ }
+}
+
+static int usage()
+{
+ printf("fec: a tool for encoding and decoding files using RS(255, N).\n"
+ "\n"
+ "usage: fec <mode> [ <options> ] [ <data> <fec> [ <output> ] ]\n"
+ "mode:\n"
+ " -e --encode encode (default)\n"
+ " -d --decode decode\n"
+ " -s, --print-fec-size=<data size> print FEC size\n"
+ " -E, --get-ecc-start=data print ECC offset in data\n"
+ " -V, --get-verity-start=data print verity offset\n"
+ "options:\n"
+ " -h show this help\n"
+ " -v enable verbose logging\n"
+ " -r, --roots=<bytes> number of parity bytes\n"
+ " -m, --mmap use memory mapping\n"
+ " -j, --threads=<threads> number of threads to use\n"
+ " -S treat data as a sparse file\n"
+ "decoding options:\n"
+ " -i, --inplace correct <data> in place\n"
+ );
+
+ return 1;
+}
+
+static uint64_t parse_arg(const char *arg, const char *name, uint64_t maxval)
+{
+ char* endptr;
+ errno = 0;
+
+ unsigned long long int value = strtoull(arg, &endptr, 0);
+
+ if (arg[0] == '\0' || *endptr != '\0' ||
+ (errno == ERANGE && value == ULLONG_MAX)) {
+ FATAL("invalid value of %s\n", name);
+ }
+ if (value > maxval) {
+ FATAL("value of roots too large (max. %" PRIu64 ")\n", maxval);
+ }
+
+ return (uint64_t)value;
+}
+
+static int print_size(image& ctx)
+{
+ /* output size including header */
+ printf("%" PRIu64 "\n", fec_ecc_get_size(ctx.inp_size, ctx.roots));
+ return 0;
+}
+
+static int get_start(int mode, const char *filename)
+{
+ fec::io fh(filename, O_RDONLY, FEC_VERITY_DISABLE);
+
+ if (!fh) {
+ FATAL("failed to open input\n");
+ }
+
+ if (mode == MODE_GETECCSTART) {
+ fec_ecc_metadata data;
+
+ if (!fh.get_ecc_metadata(data)) {
+ FATAL("no ecc data\n");
+ }
+
+ printf("%" PRIu64 "\n", data.start - FEC_BLOCKSIZE);
+ } else {
+ fec_verity_metadata data;
+
+ if (!fh.get_verity_metadata(data)) {
+ FATAL("no verity data\n");
+ }
+
+ printf("%" PRIu64 "\n", data.data_size);
+ }
+
+ return 0;
+}
+
+static int encode(image& ctx, const char *inp_filename,
+ const char *fec_filename)
+{
+ if (ctx.inplace) {
+ FATAL("invalid parameters: inplace can only used when decoding\n");
+ }
+
+ if (!image_load(inp_filename, &ctx, false)) {
+ FATAL("failed to read input\n");
+ }
+
+ if (!image_ecc_new(fec_filename, &ctx)) {
+ FATAL("failed to allocate ecc\n");
+ }
+
+ INFO("encoding RS(255, %d) for '%s' to '%s'\n", ctx.rs_n, inp_filename,
+ fec_filename);
+
+ if (ctx.verbose) {
+ INFO("\traw fec size: %u\n", ctx.fec_size);
+ INFO("\tblocks: %" PRIu64 "\n", ctx.blocks);
+ INFO("\trounds: %" PRIu64 "\n", ctx.rounds);
+ }
+
+ if (!image_process(encode_rs, &ctx)) {
+ FATAL("failed to process input\n");
+ }
+
+ if (!image_ecc_save(&ctx)) {
+ FATAL("failed to write output\n");
+ }
+
+ image_free(&ctx);
+ return 0;
+}
+
+static int decode(image& ctx, const char *inp_filename,
+ const char *fec_filename, const char *out_filename)
+{
+ if (ctx.inplace && ctx.sparse) {
+ FATAL("invalid parameters: inplace cannot be used with sparse "
+ "files\n");
+ }
+
+ if (!image_ecc_load(fec_filename, &ctx) ||
+ !image_load(inp_filename, &ctx, !!out_filename)) {
+ FATAL("failed to read input\n");
+ }
+
+ if (ctx.inplace) {
+ INFO("correcting '%s' using RS(255, %d) from '%s'\n", inp_filename,
+ ctx.rs_n, fec_filename);
+
+ out_filename = inp_filename;
+ } else {
+ INFO("decoding '%s' to '%s' using RS(255, %d) from '%s'\n",
+ inp_filename, out_filename ? out_filename : "<none>", ctx.rs_n,
+ fec_filename);
+ }
+
+ if (ctx.verbose) {
+ INFO("\traw fec size: %u\n", ctx.fec_size);
+ INFO("\tblocks: %" PRIu64 "\n", ctx.blocks);
+ INFO("\trounds: %" PRIu64 "\n", ctx.rounds);
+ }
+
+ if (!image_process(decode_rs, &ctx)) {
+ FATAL("failed to process input\n");
+ }
+
+ if (ctx.rv) {
+ INFO("corrected %" PRIu64 " errors\n", ctx.rv);
+ } else {
+ INFO("no errors found\n");
+ }
+
+ if (out_filename && !image_save(out_filename, &ctx)) {
+ FATAL("failed to write output\n");
+ }
+
+ image_free(&ctx);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ char *fec_filename = NULL;
+ char *inp_filename = NULL;
+ char *out_filename = NULL;
+ int mode = MODE_ENCODE;
+ image ctx;
+
+ image_init(&ctx);
+ ctx.roots = FEC_DEFAULT_ROOTS;
+
+ while (1) {
+ const static struct option long_options[] = {
+ {"help", no_argument, 0, 'h'},
+ {"encode", no_argument, 0, 'e'},
+ {"decode", no_argument, 0, 'd'},
+ {"sparse", no_argument, 0, 'S'},
+ {"roots", required_argument, 0, 'r'},
+ {"inplace", no_argument, 0, 'i'},
+ {"mmap", no_argument, 0, 'm'},
+ {"threads", required_argument, 0, 'j'},
+ {"print-fec-size", required_argument, 0, 's'},
+ {"get-ecc-start", required_argument, 0, 'E'},
+ {"get-verity-start", required_argument, 0, 'V'},
+ {"verbose", no_argument, 0, 'v'},
+ {NULL, 0, 0, 0}
+ };
+ int c = getopt_long(argc, argv, "hedSr:imj:s:E:V:v", long_options, NULL);
+ if (c < 0) {
+ break;
+ }
+
+ switch (c) {
+ case 'h':
+ return usage();
+ case 'S':
+ ctx.sparse = true;
+ break;
+ case 'e':
+ if (mode != MODE_ENCODE) {
+ return usage();
+ }
+ break;
+ case 'd':
+ if (mode != MODE_ENCODE) {
+ return usage();
+ }
+ mode = MODE_DECODE;
+ break;
+ case 'r':
+ ctx.roots = (int)parse_arg(optarg, "roots", FEC_RSM);
+ break;
+ case 'i':
+ ctx.inplace = true;
+ break;
+ case 'm':
+ ctx.mmap = true;
+ break;
+ case 'j':
+ ctx.threads = (int)parse_arg(optarg, "threads", IMAGE_MAX_THREADS);
+ break;
+ case 's':
+ if (mode != MODE_ENCODE) {
+ return usage();
+ }
+ mode = MODE_PRINTSIZE;
+ ctx.inp_size = parse_arg(optarg, "print-fec-size", UINT64_MAX);
+ break;
+ case 'E':
+ if (mode != MODE_ENCODE) {
+ return usage();
+ }
+ mode = MODE_GETECCSTART;
+ inp_filename = optarg;
+ break;
+ case 'V':
+ if (mode != MODE_ENCODE) {
+ return usage();
+ }
+ mode = MODE_GETVERITYSTART;
+ inp_filename = optarg;
+ break;
+ case 'v':
+ ctx.verbose = true;
+ break;
+ case '?':
+ return usage();
+ default:
+ abort();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ assert(ctx.roots > 0 && ctx.roots < FEC_RSM);
+
+ /* check for input / output parameters */
+ if (mode == MODE_ENCODE || mode == MODE_DECODE) {
+ if (argc < 2 || argc > 3) {
+ return usage();
+ } else if (argc == 3) {
+ if (mode != MODE_DECODE || ctx.inplace) {
+ return usage();
+ }
+ out_filename = argv[2];
+ } else {
+ out_filename = NULL;
+ }
+
+ inp_filename = argv[0];
+ fec_filename = argv[1];
+ }
+
+ switch (mode) {
+ case MODE_PRINTSIZE:
+ return print_size(ctx);
+ case MODE_GETECCSTART:
+ case MODE_GETVERITYSTART:
+ return get_start(mode, inp_filename);
+ case MODE_ENCODE:
+ return encode(ctx, inp_filename, fec_filename);
+ case MODE_DECODE:
+ return decode(ctx, inp_filename, fec_filename, out_filename);
+ default:
+ abort();
+ }
+
+ return 1;
+}
--- /dev/null
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import math
+import os
+import random
+import shutil
+import subprocess
+import sys
+import tempfile
+
+blocksize = 4096
+roots = 2
+
+def corrupt(image, offset, length):
+ print "corrupting %d bytes at offset %d" % (length, offset)
+ f = os.open(image, os.O_WRONLY)
+ os.lseek(f, offset, os.SEEK_SET)
+ os.write(f, os.urandom(length))
+ os.close(f)
+
+def corruptmax(image, roots):
+ size = os.stat(image).st_size
+
+ blocks = int(math.ceil(float(size) / blocksize))
+ rounds = int(math.ceil(float(blocks) / (255 - roots)))
+
+ max_errors = int(math.floor(rounds * roots / 2)) * blocksize
+ offset = random.randrange(0, size - max_errors)
+
+ corrupt(image, offset, max_errors)
+
+def encode(image, fec, roots):
+ if subprocess.call([ "fec", "--roots= " + str(roots), image, fec ]) != 0:
+ raise Exception("encoding failed")
+
+def decode(image, fec, output):
+ return subprocess.call([ "fec", "--decode", image, fec, output ])
+
+def compare(a, b):
+ return subprocess.call([ "cmp", "-s", a, b ])
+
+def simg2img(image, output):
+ print "creating a non-sparse copy of '%s' to '%s'" % (image, output)
+ if subprocess.call([ "simg2img", image, output]) != 0:
+ raise Exception("simg2img failed")
+
+def main(argv):
+ image = argv[0]
+
+ temp_img = tempfile.NamedTemporaryFile()
+ temp_cor = tempfile.NamedTemporaryFile()
+ temp_fec = tempfile.NamedTemporaryFile()
+ temp_out = tempfile.NamedTemporaryFile()
+
+ simg2img(image, temp_img.name)
+ simg2img(image, temp_cor.name)
+
+ encode(image, temp_fec.name, roots)
+ corruptmax(temp_cor.name, roots)
+
+ if decode(temp_cor.name, temp_fec.name, temp_out.name) != 0:
+ raise Exception("FAILED: failed to correct maximum expected errors")
+
+ if compare(temp_img.name, temp_out.name) != 0:
+ raise Exception("FAILED: corrected file not identical")
+ else:
+ print "corrected content matches original"
+
+ corrupt(temp_cor.name, 0, blocksize)
+
+ if decode(temp_cor.name, temp_fec.name, temp_out.name) == 0:
+ raise Exception("FAILED: corrected more than maximum number of errors?")
+
+ print "PASSED"
+
+if __name__ == '__main__':
+ main(sys.argv[1:])