OSDN Git Service

Error correction: Add a tool for en/decoding files
authorSami Tolvanen <samitolvanen@google.com>
Sat, 16 May 2015 14:14:14 +0000 (15:14 +0100)
committerSami Tolvanen <samitolvanen@google.com>
Fri, 25 Sep 2015 12:06:17 +0000 (13:06 +0100)
Add fec, a tool for generating error-correcting codes for files
and recovering them.

Bug: 21893453
Change-Id: I389c92e5cc7f825d632759b9f96045767e74120a

verity/Android.mk
verity/fec/Android.mk [new file with mode: 0644]
verity/fec/image.cpp [new file with mode: 0644]
verity/fec/image.h [new file with mode: 0644]
verity/fec/main.cpp [new file with mode: 0644]
verity/fec/tests/fec.py [new file with mode: 0644]

index 5db543b..7b3d13f 100644 (file)
@@ -105,3 +105,5 @@ LOCAL_STATIC_LIBRARIES := libsparse_host libz
 LOCAL_SHARED_LIBRARIES := libcrypto-host libbase
 LOCAL_CFLAGS += -Wall -Werror
 include $(BUILD_HOST_EXECUTABLE)
+
+include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/verity/fec/Android.mk b/verity/fec/Android.mk
new file mode 100644 (file)
index 0000000..774878d
--- /dev/null
@@ -0,0 +1,43 @@
+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)
diff --git a/verity/fec/image.cpp b/verity/fec/image.cpp
new file mode 100644 (file)
index 0000000..7acf4c8
--- /dev/null
@@ -0,0 +1,583 @@
+/*
+ * 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;
+}
diff --git a/verity/fec/image.h b/verity/fec/image.h
new file mode 100644 (file)
index 0000000..a7f5553
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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__
diff --git a/verity/fec/main.cpp b/verity/fec/main.cpp
new file mode 100644 (file)
index 0000000..6fa38f9
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+ * 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;
+}
diff --git a/verity/fec/tests/fec.py b/verity/fec/tests/fec.py
new file mode 100644 (file)
index 0000000..71a1834
--- /dev/null
@@ -0,0 +1,89 @@
+# 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:])