summaryrefslogtreecommitdiffstats
path: root/ffs/src/libffs.c
diff options
context:
space:
mode:
Diffstat (limited to 'ffs/src/libffs.c')
-rw-r--r--ffs/src/libffs.c1520
1 files changed, 1520 insertions, 0 deletions
diff --git a/ffs/src/libffs.c b/ffs/src/libffs.c
new file mode 100644
index 0000000..b912ede
--- /dev/null
+++ b/ffs/src/libffs.c
@@ -0,0 +1,1520 @@
+/*
+ * Copyright (c) International Business Machines Corp., 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+ * the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/*
+ * File: libffs.c
+ * Author: Shaun Wetzstein <shaun@us.ibm.com>
+ * Descr: FFS IO interface
+ * Note:
+ * Date: 05/07/12
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <endian.h>
+#include <libgen.h>
+#include <regex.h>
+
+#include "libffs.h"
+
+#include <clib/assert.h>
+#include <clib/builtin.h>
+#include <clib/checksum.h>
+#include <clib/misc.h>
+#include <clib/err.h>
+#include <clib/raii.h>
+
+#ifndef be32toh
+#include <byteswap.h>
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define be32toh(x) __bswap_32(x)
+#define htobe32(x) __bswap_32(x)
+#else
+#define be32toh(x) (x)
+#define htobe32(x) (x)
+#endif
+#endif
+
+#define FFS_ENTRY_EXTENT 10UL
+
+/* ============================================================ */
+
+static void __hdr_be32toh(ffs_hdr_t * hdr)
+{
+ assert(hdr != NULL);
+
+ hdr->magic = be32toh(hdr->magic);
+ hdr->version = be32toh(hdr->version);
+ hdr->size = be32toh(hdr->size);
+ hdr->entry_size = be32toh(hdr->entry_size);
+ hdr->entry_count = be32toh(hdr->entry_count);
+ hdr->block_size = be32toh(hdr->block_size);
+ hdr->block_count = be32toh(hdr->block_count);
+ hdr->checksum = be32toh(hdr->checksum);
+}
+
+static void __hdr_htobe32(ffs_hdr_t * hdr)
+{
+ assert(hdr != NULL);
+
+ hdr->magic = htobe32(hdr->magic);
+ hdr->version = htobe32(hdr->version);
+ hdr->size = htobe32(hdr->size);
+ hdr->entry_size = htobe32(hdr->entry_size);
+ hdr->entry_count = htobe32(hdr->entry_count);
+ hdr->block_size = htobe32(hdr->block_size);
+ hdr->block_count = htobe32(hdr->block_count);
+ hdr->checksum = htobe32(hdr->checksum);
+}
+
+static void __entry_be32toh(ffs_entry_t * entry)
+{
+ assert(entry != NULL);
+
+ entry->base = be32toh(entry->base);
+ entry->size = be32toh(entry->size);
+ entry->pid = be32toh(entry->pid);
+ entry->id = be32toh(entry->id);
+ entry->type = be32toh(entry->type);
+ entry->flags = be32toh(entry->flags);
+ entry->actual = be32toh(entry->actual);
+ entry->checksum = be32toh(entry->checksum);
+
+ entry->resvd[0] = be32toh(entry->resvd[0]);
+ entry->resvd[1] = be32toh(entry->resvd[1]);
+ entry->resvd[2] = be32toh(entry->resvd[2]);
+ entry->resvd[3] = be32toh(entry->resvd[3]);
+
+ for (int j = 0; j < FFS_USER_WORDS; j++)
+ entry->user.data[j] = be32toh(entry->user.data[j]);
+}
+
+static void __entry_htobe32(ffs_entry_t * entry)
+{
+ assert(entry != NULL);
+
+ entry->base = htobe32(entry->base);
+ entry->size = htobe32(entry->size);
+ entry->pid = htobe32(entry->pid);
+ entry->id = htobe32(entry->id);
+ entry->type = htobe32(entry->type);
+ entry->flags = htobe32(entry->flags);
+ entry->actual = htobe32(entry->actual);
+ entry->checksum = htobe32(entry->checksum);
+
+ entry->resvd[0] = htobe32(entry->resvd[0]);
+ entry->resvd[1] = htobe32(entry->resvd[1]);
+ entry->resvd[2] = htobe32(entry->resvd[2]);
+ entry->resvd[3] = htobe32(entry->resvd[3]);
+
+ for (int j = 0; j < FFS_USER_WORDS; j++)
+ entry->user.data[j] = htobe32(entry->user.data[j]);
+}
+
+static int __hdr_read(ffs_hdr_t * hdr, FILE * file, off_t offset)
+{
+ assert(hdr != NULL);
+
+ if (fseeko(file, offset, SEEK_SET) != 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ size_t rc = fread(hdr, 1, sizeof(*hdr), file);
+ if (rc <= 0 && ferror(file)) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ uint32_t ck = memcpy_checksum(NULL, (void *)hdr,
+ offsetof(ffs_hdr_t, checksum));
+
+ __hdr_be32toh(hdr);
+
+ if (hdr->magic != FFS_MAGIC) {
+ ERROR(ERR_UNEXPECTED, FFS_CHECK_HEADER_MAGIC,
+ "magic number mismatch '%x' != '%x'",
+ hdr->magic, FFS_MAGIC);
+ return -1;
+ }
+
+ if (hdr->checksum != ck) {
+ ERROR(ERR_UNEXPECTED, FFS_CHECK_HEADER_CHECKSUM,
+ "header checksum mismatch '%x' != '%x'",
+ hdr->checksum, ck);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int __hdr_write(ffs_hdr_t * hdr, FILE * file, off_t offset)
+{
+ assert(hdr != NULL);
+ assert(hdr->magic == FFS_MAGIC);
+
+ hdr->checksum = memcpy_checksum(NULL, (void *)hdr,
+ offsetof(ffs_hdr_t, checksum));
+ hdr->checksum = htobe32(hdr->checksum);
+
+ if (fseeko(file, offset, SEEK_SET) != 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ size_t size = sizeof(*hdr);
+
+ if (0 < hdr->entry_count) {
+ size += hdr->entry_count * hdr->entry_size;
+
+ for (size_t i=0; i<hdr->entry_count; i++) {
+ ffs_entry_t *e = hdr->entries + i;
+
+ __entry_htobe32(e);
+
+ e->checksum = memcpy_checksum(NULL, (void *)e,
+ offsetof(ffs_entry_t,
+ checksum));
+ e->checksum = htobe32(e->checksum);
+ }
+ }
+
+ __hdr_htobe32(hdr);
+
+ size_t rc = fwrite(hdr, 1, size, file);
+ if (rc <= 0 && ferror(file)) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ __hdr_be32toh(hdr);
+ if (0 < hdr->entry_count)
+ for (size_t i=0; i<hdr->entry_count; i++)
+ __entry_be32toh(hdr->entries + i);
+
+ return 0;
+}
+
+static int __entries_read(ffs_hdr_t * hdr, FILE * file, off_t offset)
+{
+ assert(hdr != NULL);
+ assert(hdr->magic == FFS_MAGIC);
+
+ if (0 < hdr->entry_count) {
+ if (fseeko(file, offset, SEEK_SET) != 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ size_t size = hdr->entry_count * hdr->entry_size;
+
+ size_t rc = fread(hdr->entries, 1, size, file);
+ if (rc <= 0 && ferror(file)) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ for (size_t i=0; i<hdr->entry_count; i++) {
+ ffs_entry_t *e = hdr->entries + i;
+
+ uint32_t ck = memcpy_checksum(NULL, (void *)e,
+ offsetof(ffs_entry_t,
+ checksum));
+ __entry_be32toh(e);
+
+ if (e->checksum != ck) {
+ ERROR(ERR_UNEXPECTED, FFS_CHECK_ENTRY_CHECKSUM,
+ "'%s' entry checksum mismatch '%x' != "
+ "'%x'", e->name, e->checksum, ck);
+ return -1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+#if 0
+static void __entries_write(ffs_hdr_t * hdr, FILE * file, off_t offset)
+{
+ if (unlikely(hdr == NULL))
+ ffs_throw(UNEX, 10400, "NULL hdr pointer");
+ if (hdr->magic != FFS_MAGIC)
+ ffs_throw(UNEX, 10401, "magic number mismatch '%x' != "
+ "'%x'", hdr->magic, FFS_MAGIC);
+
+ if (0 < hdr->entry_count) {
+ size_t size = hdr->entry_count * hdr->entry_size;
+
+ for (size_t i = 0; i < hdr->entry_count; i++) {
+ ffs_entry_t *e = hdr->entries + i;
+ __entry_htobe32(e);
+ e->checksum = memcpy_checksum(NULL, (void *)e,
+ offsetof(ffs_entry_t,
+ checksum));
+ e->checksum = htobe32(e->checksum);
+ }
+
+ if (fseeko(file, offset, SEEK_SET) != 0)
+ ffs_throw(ERR, 10402, "%s (errno=%d)",
+ strerror(errno), errno);
+
+ size_t rc = fwrite(hdr->entries, 1, size, file);
+ if (rc <= 0 && ferror(file))
+ ffs_throw(ERR, 10403, "%s (errno=%d)",
+ strerror(errno), errno);
+
+ fflush(file);
+
+ for (size_t i = 0; i < hdr->entry_count; i++)
+ __entry_be32toh(hdr->entries + i);
+ }
+}
+#endif
+
+static ffs_entry_t *__iterate_entries(ffs_hdr_t * self,
+ int (*func) (ffs_entry_t *))
+{
+ assert(self != NULL);
+ assert(func != NULL);
+
+ for (uint32_t i = 0; i < self->entry_count; i++) {
+ if (func(self->entries + i) != 0)
+ return self->entries + i;
+ }
+
+ return NULL;
+}
+
+static ffs_entry_t *__find_entry(ffs_hdr_t * self, const char *path)
+{
+ assert(self != NULL);
+
+ if (path == NULL || *path == '\0')
+ return NULL;
+
+ if (*path == '/')
+ path++;
+
+ char __path[strlen(path) + 1];
+ strcpy(__path, path);
+ path = __path;
+
+ ffs_entry_t root = {.id = FFS_PID_TOPLEVEL }, *parent = &root;
+
+ char *name;
+ while (parent != NULL && (name = strtok((char *)path, "/")) != NULL) {
+ path = NULL;
+
+ int find_entry(ffs_entry_t * child) {
+ return (parent->id == child->pid &&
+ strncmp(name, child->name,
+ sizeof(child->name)) == 0);
+ }
+
+ parent = __iterate_entries(self, find_entry);
+ }
+
+ return parent;
+}
+
+/* ============================================================ */
+
+int __ffs_fcheck(FILE *file, off_t offset)
+{
+ assert(file != NULL);
+
+ RAII(ffs_hdr_t*, hdr, malloc(sizeof(*hdr)), free);
+ if (hdr == NULL) {
+ ERRNO(errno);
+ return -1;
+ }
+ memset(hdr, 0, sizeof(*hdr));
+
+ if (fseeko(file, offset, SEEK_SET) != 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ if (fread(hdr, 1, sizeof(*hdr), file) <= 0 && ferror(file)) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ uint32_t ck = memcpy_checksum(NULL, (void *)hdr,
+ offsetof(ffs_hdr_t, checksum));
+ __hdr_be32toh(hdr);
+
+ if (hdr->magic != FFS_MAGIC) {
+ ERROR(ERR_UNEXPECTED, FFS_CHECK_HEADER_MAGIC,
+ "header magic mismatch '%x' != '%x'",
+ hdr->magic, FFS_MAGIC);
+ return FFS_CHECK_HEADER_MAGIC;
+ }
+ if (hdr->checksum != ck) {
+ ERROR(ERR_UNEXPECTED, FFS_CHECK_HEADER_CHECKSUM,
+ "header checksum mismatch '%x' != '%x'",
+ hdr->checksum, ck);
+ return FFS_CHECK_HEADER_CHECKSUM;
+ }
+
+ size_t size = hdr->entry_count * hdr->entry_size;
+
+ hdr = (ffs_hdr_t *)realloc(hdr, sizeof(*hdr) + size);
+ if (hdr == NULL) {
+ ERRNO(errno);
+ return -1;
+ }
+ memset(hdr->entries, 0, size);
+
+ if (0 < hdr->entry_count) {
+ if (fread(hdr->entries, 1, size, file) <= 0 && ferror(file)) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ for (size_t i = 0; i < hdr->entry_count; i++) {
+ ffs_entry_t *e = hdr->entries + i;
+
+ uint32_t ck = memcpy_checksum(NULL, (void *)e,
+ offsetof(ffs_entry_t,
+ checksum));
+
+ __entry_be32toh(e);
+
+ if (e->checksum != ck) {
+ ERROR(ERR_UNEXPECTED, FFS_CHECK_ENTRY_CHECKSUM,
+ "'%s' entry checksum mismatch '%x' != "
+ "'%x'", e->name, e->checksum, ck);
+ return FFS_CHECK_ENTRY_CHECKSUM;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int __ffs_check(const char *path, off_t offset)
+{
+ if (path == NULL || *path == '\0') {
+ UNEXPECTED("invalid path '%s'\n", path);
+ return -1;
+ }
+
+ RAII(FILE*, file, fopen(path, "r"), fclose);
+ if (file == NULL) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ return __ffs_fcheck(file, offset);
+}
+
+ffs_t *__ffs_fcreate(FILE *file, off_t offset, uint32_t block_size,
+ uint32_t block_count)
+{
+ assert(file != NULL);
+
+ if (!is_pow2(block_size)) {
+ UNEXPECTED("'%d' invalid block size (must be non-0 and a "
+ "power of 2)", block_size);
+ return NULL;
+ }
+ if (!is_pow2(block_count)) {
+ UNEXPECTED("'%d' invalid block count (must be non-0 and a "
+ "power of 2)", block_count);
+ return NULL;
+ }
+ if (offset & (block_size - 1)) {
+ UNEXPECTED("'%lld' invalid offset (must be 'block size' "
+ "aligned)", offset);
+ return NULL;
+ }
+
+ ffs_t *self = (ffs_t *) malloc(sizeof(*self));
+ if (self == NULL) {
+ ERRNO(errno);
+ goto error;
+ }
+
+ memset(self, 0, sizeof(*self));
+ self->file = file;
+ self->offset = offset;
+ self->count = FFS_ENTRY_EXTENT;
+ self->dirty = true;
+
+ self->hdr = (ffs_hdr_t *) malloc(sizeof(*self->hdr));
+ if (self->hdr == NULL) {
+ ERRNO(errno);
+ goto error;
+ }
+
+ self->hdr->magic = FFS_MAGIC;
+ self->hdr->version = FFS_VERSION_1;
+ self->hdr->size = 1;
+ self->hdr->entry_size = sizeof(*self->hdr->entries);
+ self->hdr->entry_count = 0;
+ self->hdr->block_size = block_size;
+ self->hdr->block_count = block_count;
+ self->hdr->checksum = 0;
+
+ size_t size = self->count * self->hdr->entry_size;
+
+ self->hdr = (ffs_hdr_t *) realloc(self->hdr, sizeof(*self->hdr) + size);
+ if (self->hdr == NULL) {
+ ERRNO(errno);
+ goto error;
+ }
+ memset(self->hdr->entries, 0, size);
+
+ if (__ffs_entry_add(self, FFS_PARTITION_NAME, offset, block_size,
+ FFS_TYPE_PARTITION, FFS_FLAGS_PROTECTED) < 0)
+ goto error;
+ if (__ffs_entry_truncate(self, FFS_PARTITION_NAME, block_size) < 0)
+ goto error;
+
+ if (false) {
+ error:
+ if (self != NULL) {
+ if (self->file != NULL)
+ fclose(self->file), self->file = NULL;
+ if (self->path != NULL)
+ free(self->path), self->path = NULL;
+ if (self->hdr != NULL)
+ free(self->hdr), self->hdr = NULL;
+ free(self), self = NULL;
+ }
+ }
+
+ return self;
+}
+
+ffs_t *__ffs_create(const char *path, off_t offset, uint32_t block_size,
+ uint32_t block_count)
+{
+ assert(path != NULL);
+
+ FILE * file = fopen(path, "r+");
+ if (file == NULL) {
+ ERRNO(errno);
+ return NULL;
+ }
+
+ ffs_t * self = __ffs_fcreate(file, offset, block_size, block_count);
+ if (self != NULL)
+ self->path = strdup(path);
+
+ return self;
+}
+
+ffs_t *__ffs_fopen(FILE * file, off_t offset)
+{
+ assert(file != NULL);
+
+ ffs_t *self = (ffs_t *) malloc(sizeof(*self));
+ if (self == NULL) {
+ ERRNO(errno);
+ goto error;
+ }
+
+ memset(self, 0, sizeof(*self));
+ self->file = file;
+ self->count = 0;
+ self->offset = offset;
+ self->dirty = false;
+
+ self->hdr = (ffs_hdr_t *) malloc(sizeof(*self->hdr));
+ if (self->hdr == NULL) {
+ ERRNO(errno);
+ goto error;
+ }
+ memset(self->hdr, 0, sizeof(*self->hdr));
+
+ if (__hdr_read(self->hdr, self->file, self->offset) < 0)
+ goto error;
+
+ self->count = max(self->hdr->entry_count, FFS_ENTRY_EXTENT);
+ size_t size = self->count * self->hdr->entry_size;
+
+ self->hdr = (ffs_hdr_t *)realloc(self->hdr, sizeof(*self->hdr) + size);
+ if (self->hdr == NULL) {
+ ERRNO(errno);
+ goto error;
+ }
+ memset(self->hdr->entries, 0, size);
+
+ if (0 < self->hdr->entry_count) {
+ if (__entries_read(self->hdr, self->file,
+ self->offset + sizeof(*self->hdr)) < 0)
+ goto error;
+ }
+
+ self->buf_count = 1; // default to 1
+
+ self->buf = malloc(self->buf_count * self->hdr->block_size);
+ if (self->hdr == NULL) {
+ ERRNO(errno);
+ goto error;
+ }
+
+ if (setvbuf(self->file, self->buf, _IOFBF,
+ self->buf_count * self->hdr->block_size) != 0) {
+ ERRNO(errno);
+ goto error;
+ }
+
+ if (false) {
+ error:
+ if (self != NULL) {
+ if (self->buf != NULL)
+ free(self->buf), self->buf = NULL;
+ if (self->hdr != NULL)
+ free(self->hdr), self->hdr = NULL;
+
+ free(self), self = NULL;
+ }
+ }
+
+ return self;
+}
+
+ffs_t *__ffs_open(const char *path, off_t offset)
+{
+ assert(path != NULL);
+
+ FILE *file = fopen(path, "r+");
+ if (file == NULL) {
+ ERRNO(errno);
+ return NULL;
+ }
+
+ ffs_t *self = __ffs_fopen(file, offset);
+ if (self != NULL)
+ self->path = strdup(path);
+
+ return self;
+}
+
+static int ffs_flush(ffs_t * self)
+{
+ assert(self != NULL);
+
+ if (__hdr_write(self->hdr, self->file, self->offset) < 0)
+ return -1;
+
+ if (fflush(self->file) != 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ if (fileno(self->file) != -1) {
+ if (fsync(fileno(self->file)) < 0) {
+ ERRNO(errno);
+ return -1;
+ }
+ }
+
+
+ self->dirty = false;
+
+ return 0;
+}
+
+int __ffs_info(ffs_t * self, int name, uint32_t *value)
+{
+ assert(self != NULL);
+ assert(value != NULL);
+
+ switch (name) {
+ case FFS_INFO_MAGIC:
+ *value = self->hdr->magic;
+ break;
+ case FFS_INFO_VERSION:
+ *value = self->hdr->version;
+ break;
+ case FFS_INFO_ENTRY_SIZE:
+ *value = self->hdr->entry_size;
+ break;
+ case FFS_INFO_ENTRY_COUNT:
+ *value = self->hdr->entry_count;
+ break;
+ case FFS_INFO_BLOCK_SIZE:
+ *value = self->hdr->block_size;
+ break;
+ case FFS_INFO_BLOCK_COUNT:
+ *value = self->hdr->block_count;
+ break;
+ case FFS_INFO_BUFFER_COUNT:
+ *value = self->buf_count;
+ break;
+ case FFS_INFO_OFFSET:
+ *value = self->offset;
+ break;
+ default:
+ UNEXPECTED("'%d' invalid info field", name);
+ return -1;
+ }
+
+ return 0;
+}
+
+int __ffs_buffer(ffs_t * self, size_t size)
+{
+ assert(self != NULL);
+
+ if (size == 0)
+ size = self->hdr->block_size;
+
+ if (self->buf != NULL) {
+ free(self->buf);
+ self->buf_count = 0;
+ }
+
+ self->buf_count = size / self->hdr->block_size;
+ size = self->buf_count * self->hdr->block_size;
+
+ self->buf = malloc(size);
+ if (self->buf == NULL) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ if (setvbuf(self->file, self->buf, _IOFBF, size) < 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ return 0;
+}
+
+int __ffs_fclose(ffs_t * self)
+{
+ if (self == NULL)
+ return 0;
+
+ if (self->dirty == true)
+ if (ffs_flush(self) < 0)
+ return -1;
+
+ if (self->buf != NULL)
+ free(self->buf), self->buf = NULL;
+ if (self->hdr != NULL)
+ free(self->hdr), self->hdr = NULL;
+
+ memset(self, 0, sizeof(*self));
+ free(self);
+
+ return 0;
+}
+
+int __ffs_close(ffs_t * self)
+{
+ if (unlikely(self == NULL))
+ return 0;
+
+ if (self->dirty == true)
+ if (ffs_flush(self) < 0)
+ return -1;
+
+ if (self->path != NULL)
+ free(self->path), self->path = NULL;
+ if (self->file != NULL)
+ fclose(self->file), self->file = NULL;
+
+ return __ffs_fclose(self);
+}
+
+int __ffs_fsync(ffs_t * self)
+{
+ assert(self != NULL);
+
+ if (fflush(self->file) < 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ if (fileno(self->file) != -1)
+ if (fsync(fileno(self->file)) < 0)
+ return -1;
+
+ return 0;
+}
+
+static ffs_entry_t *__add_entry_check(ffs_hdr_t * self, off_t offset,
+ size_t size)
+{
+ assert(self != NULL);
+
+ int find_overlap(ffs_entry_t * entry) {
+ if (entry->type == FFS_TYPE_LOGICAL)
+ return 0;
+
+ off_t entry_start = entry->base;
+ off_t entry_end = entry_start + entry->size - 1;
+
+ off_t new_start = offset / self->block_size;
+ off_t new_end = new_start + (size / self->block_size) - 1;
+
+ return !(new_start < entry_start && new_end < entry_start) &&
+ !(entry_end < new_start && entry_end < new_end);
+ }
+
+ return __iterate_entries(self, find_overlap);
+}
+
+int __ffs_iterate_entries(ffs_t * self, int (*func) (ffs_entry_t *))
+{
+ return __iterate_entries(self->hdr, func) != NULL;
+}
+
+int __ffs_list_entries(ffs_t * self, const char * name, bool user, FILE * out)
+{
+ assert(self != NULL);
+
+ if (out == NULL)
+ out = stdout;
+
+ char full_name[4096];
+ regex_t rx;
+
+ int print_entry(ffs_entry_t * entry)
+ {
+ size_t offset = entry->base * self->hdr->block_size;
+ size_t size = entry->size * self->hdr->block_size;
+
+ if (__ffs_entry_name(self, entry, full_name,
+ sizeof full_name) < 0)
+ return -1;
+
+ if (regexec(&rx, full_name, 0, NULL, 0) == REG_NOMATCH)
+ return 0;
+
+ fprintf(stdout, "%3d [%08x-%08x:%8x] "
+ "[%c%c%c%c%c%c%c%c%c%c] %s\n",
+ entry->id, offset, offset+size-1, entry->actual,
+ entry->type == FFS_TYPE_LOGICAL ? 'l' : 'd',
+ /* reserved */ '-', '-', '-', '-', '-', '-', '-',
+ entry->flags & FFS_FLAGS_U_BOOT_ENV ? 'b' : '-',
+ entry->flags & FFS_FLAGS_PROTECTED ? 'p' : '-',
+ full_name);
+
+ if (user == true) {
+ for (int i=0; i<FFS_USER_WORDS; i++) {
+ fprintf(stdout, "[%2d] %8x ", i,
+ entry->user.data[i]);
+ if ((i+1) % 4 == 0)
+ fprintf(stdout, "\n");
+ }
+ }
+
+ return 0;
+ }
+
+ if (0 < self->count) {
+ if (regcomp(&rx, name, REG_ICASE | REG_NOSUB) != 0) {
+ ERRNO(errno);
+ return-1;
+ }
+
+ fprintf(out, "========================[ PARTITION TABLE 0x%llx "
+ "]=======================\n", self->offset);
+ fprintf(out, "vers:%04x size:%04x * blk:%06x blk(s):%06x * "
+ "entsz:%06x ent(s):%06x\n",
+ self->hdr->version, self->hdr->size,
+ self->hdr->block_size, self->hdr->block_count,
+ self->hdr->entry_size, self->hdr->entry_count);
+ fprintf(out, "------------------------------------------------"
+ "---------------------------\n");
+
+ (void)__iterate_entries(self->hdr, print_entry);
+
+ fprintf(stdout, "\n");
+
+ regfree(&rx);
+ }
+
+ return 0;
+}
+
+int __ffs_entry_find(ffs_t *self, const char *path, ffs_entry_t *entry)
+{
+ assert(self != NULL);
+ assert(path != NULL);
+
+ ffs_entry_t *__entry = __find_entry(self->hdr, path);
+ if (__entry != NULL && entry != NULL)
+ *entry = *__entry;
+
+ return __entry != NULL;
+}
+
+int __ffs_entry_find_parent(ffs_t *self, const char *path, ffs_entry_t *entry)
+{
+ assert(self != NULL);
+ assert(path != NULL);
+
+ if (*path == '/')
+ path++;
+
+ char __path[strlen(path) + 1];
+ strcpy(__path, path);
+ char *parent_path = dirname(__path);
+
+ int found = 0;
+
+ if (strcmp(parent_path, ".") != 0) {
+ ffs_entry_t parent;
+
+ found = __ffs_entry_find(self, parent_path, &parent);
+
+ if (found && entry != NULL)
+ *entry = parent;
+ }
+
+ return found;
+}
+
+int __ffs_entry_name(ffs_t *self, ffs_entry_t *entry, char *name, size_t size)
+{
+ assert(self != NULL);
+ assert(entry != NULL);
+
+ ffs_hdr_t *hdr = self->hdr;
+
+ int __entry_name(ffs_entry_t *parent, char *name, size_t size) {
+ assert(parent != NULL);
+ assert(name != NULL);
+
+ if (parent->pid != FFS_PID_TOPLEVEL) {
+ for (uint32_t i = 0; i < hdr->entry_count; i++) {
+ if (hdr->entries[i].id == parent->pid) {
+ __entry_name(hdr->entries + i, name,
+ size);
+ break;
+ }
+ }
+ }
+
+ if (strlen(name) + strlen(parent->name) < size)
+ strcat(name, parent->name);
+
+ if (parent->id != entry->id) {
+ if (strlen(name) + strlen("/") < size)
+ strcat(name, "/");
+ }
+
+ return 0;
+ }
+
+ memset(name, 0, size);
+
+ return __entry_name(entry, name, size);
+}
+
+int __ffs_entry_add(ffs_t * self, const char *path, off_t offset, size_t size,
+ ffs_type_t type, uint32_t flags)
+{
+ assert(self != NULL);
+ assert(path != NULL);
+
+ if (__ffs_entry_find(self, path, NULL) == true) {
+ UNEXPECTED("'%s' entry already exists", path);
+ return -1;
+ }
+
+ ffs_entry_t parent = {.id = FFS_PID_TOPLEVEL };
+ (void)__ffs_entry_find_parent(self, path, &parent);
+
+ ffs_hdr_t *hdr = self->hdr;
+
+ if (type != FFS_TYPE_LOGICAL) {
+ ffs_entry_t *overlap = __add_entry_check(hdr, offset, size);
+ if (overlap != NULL) {
+ UNEXPECTED("'%s' at offset %lld and size %d overlaps "
+ "'%s' at offset %d and size %d",
+ path, offset, size, overlap->name,
+ overlap->base * hdr->block_size,
+ overlap->size * hdr->block_size);
+ return -1;
+ }
+ }
+
+ int find_empty(ffs_entry_t * empty) {
+ return empty->type == 0;
+ }
+
+ ffs_entry_t *entry = __iterate_entries(hdr, find_empty);
+ if (entry == NULL) {
+ if (self->count <= hdr->entry_count) {
+ size_t new_size;
+ new_size = hdr->entry_size *
+ (self->count + FFS_ENTRY_EXTENT);
+
+ self->hdr = (ffs_hdr_t *) realloc(self->hdr,
+ sizeof(*self->hdr) +
+ new_size);
+ assert(self->hdr != NULL);
+
+ if (hdr != self->hdr)
+ hdr = self->hdr;
+
+ memset(hdr->entries + self->count, 0,
+ FFS_ENTRY_EXTENT * hdr->entry_size);
+
+ self->count += FFS_ENTRY_EXTENT;
+ }
+
+ entry = hdr->entries + hdr->entry_count;
+ }
+
+ uint32_t max_id = 0;
+
+ int find_max_id(ffs_entry_t * max) {
+ if (max_id < max->id)
+ max_id = max->id;
+ return 0;
+ }
+
+ (void)__iterate_entries(hdr, find_max_id);
+
+ char name[strlen(path) + 1];
+ strcpy(name, path);
+ strncpy(entry->name, basename(name), sizeof(entry->name));
+ entry->id = max_id + 1;
+ entry->pid = parent.id;
+ entry->base = offset / hdr->block_size;
+ entry->size = size / hdr->block_size;
+ entry->type = type;
+ entry->flags = flags;
+ entry->checksum = 0;
+
+ hdr->entry_count++;
+
+ self->dirty = true;
+
+ return 0;
+}
+
+int __ffs_entry_delete(ffs_t * self, const char *path)
+{
+ assert(self != NULL);
+ assert(path != NULL);
+
+ ffs_entry_t entry;
+ if (__ffs_entry_find(self, path, &entry) == false) {
+ UNEXPECTED("entry '%s' not found in table at offset '%llx'",
+ path, self->offset);
+ return -1;
+ }
+
+ if (entry.type == FFS_TYPE_PARTITION) {
+ UNEXPECTED("'%s' cannot --delete partition type entries", path);
+ return -1;
+ }
+
+ uint32_t children = 0;
+
+ int find_children(ffs_entry_t * child) {
+ if (entry.id == child->pid)
+ children++;
+ return 0;
+ }
+
+ ffs_hdr_t *hdr = self->hdr;
+
+ (void)__iterate_entries(hdr, find_children);
+
+ if (0 < children) {
+ UNEXPECTED("'%s' has '%d' children, --delete those first",
+ path, children);
+ return -1;
+ }
+
+ int find_entry_id(ffs_entry_t * __entry) {
+ return entry.id == __entry->id;
+ }
+
+ ffs_entry_t *entry_p = __iterate_entries(hdr, find_entry_id);
+ assert(entry_p != NULL);
+
+ int start = entry_p - hdr->entries;
+ int count = hdr->entry_count - start;
+
+ memmove(entry_p, entry_p + 1, hdr->entry_size * count);
+
+ hdr->entry_count = max(0UL, hdr->entry_count - 1);
+ memset(hdr->entries + hdr->entry_count, 0, hdr->entry_size);
+
+ self->dirty = true;
+
+ return 0;
+}
+
+int __ffs_entry_user_get(ffs_t *self, const char *path, uint32_t word,
+ uint32_t *value)
+{
+ assert(self != NULL);
+ assert(path != NULL);
+ assert(value != NULL);
+
+ if (FFS_USER_WORDS <= word) {
+ UNEXPECTED("word '%d' outside range [0..%d]",
+ word, FFS_USER_WORDS - 1);
+ return -1;
+ }
+
+ ffs_entry_t *entry = __find_entry(self->hdr, path);
+ if (entry == NULL) {
+ UNEXPECTED("entry '%s' not found in partition table at "
+ "offset '%llx'", path, self->offset);
+ return -1;
+ }
+
+ *value = entry->user.data[word];
+
+ return 0;
+}
+
+int __ffs_entry_user_put(ffs_t *self, const char *path, uint32_t word,
+ uint32_t value)
+{
+ assert(self != NULL);
+ assert(path != NULL);
+
+ if (FFS_USER_WORDS <= word) {
+ UNEXPECTED("word '%d' outside range [0..%d]",
+ word, FFS_USER_WORDS - 1);
+ return -1;
+ }
+
+ ffs_entry_t *entry = __find_entry(self->hdr, path);
+ if (entry == NULL) {
+ UNEXPECTED("entry '%s' not found in partition table at "
+ "offset '%llx'", path, self->offset);
+ return -1;
+ }
+
+ entry->user.data[word] = value;
+ self->dirty = true;
+
+ return 0;
+}
+
+ssize_t __ffs_entry_hexdump(ffs_t * self, const char *path, FILE * out)
+{
+ assert(self != NULL);
+ assert(path != NULL);
+
+ ffs_entry_t entry;
+ if (__ffs_entry_find(self, path, &entry) == false) {
+ UNEXPECTED("entry '%s' not found in table at offset '%llx'",
+ path, self->offset);
+ return -1;
+ }
+
+ size_t size = entry.size * self->hdr->block_size;
+ if (entry.actual < size)
+ size = entry.actual;
+
+ off_t offset = entry.base * self->hdr->block_size;
+
+ if (fseeko(self->file, offset, SEEK_SET) != 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ ssize_t total = 0;
+
+ size_t block_size = self->hdr->block_size;
+ char block[block_size];
+ while (0 < size) {
+ size_t rc = fread(block, 1, min(block_size, size),
+ self->file);
+ if (rc <= 0) {
+ if (ferror(self->file)) {
+ ERRNO(errno);
+ return -1;
+ }
+ break;
+ }
+
+ dump_memory(out, offset + total, block, rc);
+
+ total += rc;
+ size -= rc;
+ }
+
+ return total;
+}
+
+ssize_t __ffs_entry_truncate(ffs_t * self, const char *path, size_t size)
+{
+ assert(self != NULL);
+ assert(path != NULL);
+
+ ffs_entry_t * entry = __find_entry(self->hdr, path);
+ if (entry == NULL) {
+ UNEXPECTED("entry '%s' not found in partition table at "
+ "offset '%llx'", path, self->offset);
+ return -1;
+ }
+
+ if ((entry->size * self->hdr->block_size) < size) {
+ errno = EFBIG;
+ ERRNO(errno);
+ return -1;
+ } else {
+ entry->actual = size;
+ self->dirty = true;
+ }
+
+ return 0;
+}
+
+ssize_t __ffs_entry_read(ffs_t * self, const char *path, void *buf,
+ off_t offset, size_t count)
+{
+ assert(self != NULL);
+ assert(path != NULL);
+ assert(buf != NULL);
+
+ if (count == 0)
+ return 0;
+
+ ffs_entry_t entry;
+ if (__ffs_entry_find(self, path, &entry) == false) {
+ UNEXPECTED("entry '%s' not found in partition table at "
+ "offset '%llx'", path, self->offset);
+ return -1;
+ }
+
+ size_t entry_size = entry.size * self->hdr->block_size;
+ if (entry.actual < entry_size)
+ entry_size = entry.actual;
+ off_t entry_offset = entry.base * self->hdr->block_size;
+
+ if (entry_size <= offset)
+ return 0;
+ else
+ count = min(count, (entry_offset + entry_size) - offset);
+
+ ssize_t total = 0;
+
+ if (fseeko(self->file, entry_offset, SEEK_SET) != 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ if (fseeko(self->file, offset, SEEK_CUR) != 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ while (0 < count) {
+ size_t rc = fread(buf + total, 1, count, self->file);
+ if (rc <= 0) {
+ if (ferror(self->file)) {
+ ERRNO(errno);
+ return -1;
+ }
+ break;
+ }
+
+ total += rc;
+ count -= rc;
+ }
+
+ return total;
+}
+
+ssize_t __ffs_entry_write(ffs_t * self, const char *path, const void *buf,
+ off_t offset, size_t count)
+{
+ assert(self != NULL);
+ assert(path != NULL);
+ assert(buf != NULL);
+
+ if (count == 0)
+ return 0;
+
+ ffs_entry_t *entry = __find_entry(self->hdr, path);
+ if (entry == NULL) {
+ UNEXPECTED("entry '%s' not found in partition table at "
+ "offset '%llx'", path, self->offset);
+ return -1;
+ }
+
+ size_t entry_size = entry->size * self->hdr->block_size;
+ off_t entry_offset = entry->base * self->hdr->block_size;
+
+ if (entry_size <= offset)
+ return 0;
+ else
+ count = min(count, (entry_offset + entry_size) - offset);
+
+ ssize_t total = 0;
+
+ if (fseeko(self->file, entry_offset, SEEK_SET) != 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ if (fseeko(self->file, offset, SEEK_CUR) != 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ while (0 < count) {
+ size_t rc = fwrite(buf + total, 1, count, self->file);
+ if (rc <= 0) {
+ if (ferror(self->file)) {
+ ERRNO(errno);
+ return -1;
+ }
+ break;
+ }
+ total += rc;
+ count -= rc;
+ }
+
+ fflush(self->file);
+
+ if (entry->actual < (uint32_t) total) {
+ entry->actual = (uint32_t) total;
+ self->dirty = true;
+ }
+
+ return total;
+}
+
+#if 0
+ssize_t __ffs_entry_copy(ffs_t *self, ffs_t *in, const char *path)
+{
+ assert(self != NULL);
+ assert(in != NULL);
+ assert(path != NULL);
+
+ if (unlikely(*path == '\0'))
+ return 0;
+
+ ffs_entry_t *src = __find_entry(in->hdr, path);
+ if (src == NULL) {
+ UNEXPECTED("entry '%s' not found in table at offset '%llx'",
+ path, in->offset);
+ return -1;
+ }
+
+ ffs_entry_t *dest = __find_entry(self->hdr, path);
+ if (dest == NULL) {
+ UNEXPECTED("entry '%s' not found in table at offset '%llx'",
+ path, self->offset);
+ return -1;
+ }
+
+ if (src->base != dest->base) {
+ UNEXPECTED("partition '%s' offsets differ '%x' != '%x'",
+ path, src->base, dest->base);
+ return -1;
+ }
+
+ if (src->size != dest->size) {
+ UNEXPECTED("partition '%s' sizes differ '%x' != '%x'",
+ path, src->size, dest->size);
+ return -1;
+ }
+
+ size_t block_size = self->hdr->block_size;
+ off_t src_offset = src->base * in->hdr->block_size;
+ size_t src_actual = src->actual;
+ off_t dest_offset = dest->base * self->hdr->block_size;
+
+ if (fseeko(in->file, src_offset, SEEK_SET) != 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ if (fseeko(self->file, dest_offset, SEEK_SET) != 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ ssize_t total = 0;
+ char block[block_size];
+
+ while (0 < src_actual) {
+ size_t count = min(src_actual, block_size);
+
+ size_t rc = fread(block, 1, count, in->file);
+ if (rc <= 0 && ferror(in->file)) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ rc = fwrite(block, 1, rc, self->file);
+ if (rc <= 0 && ferror(self->file)) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ total += rc;
+ src_actual -= rc;
+ }
+
+ if (dest->actual != (uint32_t)total) {
+ dest->actual = (uint32_t) total;
+ self->dirty = true;
+ }
+
+ return total;
+}
+
+ssize_t __ffs_entry_compare(ffs_t * self, ffs_t * in, const char *path)
+{
+ assert(self != NULL);
+ assert(in != NULL);
+ assert(path != NULL);
+
+ if (unlikely(*path == '\0'))
+ return 0;
+
+ ffs_entry_t *src = __find_entry(in->hdr, path);
+ if (src == NULL) {
+ UNEXPECTED("entry '%s' not found in table at offset '%llx'",
+ path, in->offset);
+ return -1;
+ }
+
+ ffs_entry_t *dest = __find_entry(self->hdr, path);
+ if (dest == NULL) {
+ UNEXPECTED("entry '%s' not found in table at offset '%llx'",
+ path, self->offset);
+ return -1;
+ }
+
+ if (src->base != dest->base) {
+ UNEXPECTED("partition '%s' offsets differ '%x' != '%x'",
+ path, src->base, dest->base);
+ return -1;
+ }
+
+ if (src->size != dest->size) {
+ UNEXPECTED("partition '%s' sizes differ '%x' != '%x'",
+ path, src->size, dest->size);
+ return -1;
+ }
+
+ if (src->actual != dest->actual) {
+ UNEXPECTED("partition '%s' actual sizes differ '%x' != '%x'",
+ path, src->actual, dest->actual);
+ return -1;
+ }
+
+ off_t offset = src->base * self->hdr->block_size;
+ size_t actual = src->actual;
+
+ if (fseeko(in->file, offset, SEEK_SET) != 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ if (fseeko(self->file, offset, SEEK_SET) != 0) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ ssize_t total = 0;
+ size_t size = 256;
+ char __src[size], __dest[size];
+
+ while (0 < actual) {
+ size = min(actual, size);
+
+ size_t rc = fread(__src, 1, size, in->file);
+ if (rc <= 0 && ferror(in->file)) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ rc = fread(__dest, 1, size, self->file);
+ if (rc <= 0 && ferror(self->file)) {
+ ERRNO(errno);
+ return -1;
+ }
+
+ if (memcmp(__src, __dest, size) != 0) {
+ printf("==========> '%s'\n", self->path);
+ dump_memory(stdout, offset + total, __dest, size);
+
+ printf("==========> '%s'\n", in->path);
+ dump_memory(stdout, offset + total, __src, size);
+
+ break;
+ }
+
+ actual -= size;
+ total += size;
+ }
+
+ return total;
+}
+#endif
+
+int __ffs_entry_list(ffs_t * self, ffs_entry_t ** list)
+{
+ assert(self != NULL);
+ assert(list != NULL);
+
+ size_t size = 0, count = 0;
+ *list = NULL;
+
+ int name_list(ffs_entry_t * entry) {
+ if (size <= count) {
+ size += 10;
+
+ *list = realloc(*list, size * sizeof(**list));
+ if (*list == NULL) {
+ ERRNO(errno);
+ return -1;
+ }
+ }
+
+ (*list)[count++] = *entry;
+
+ return 0;
+ }
+
+ if (__ffs_iterate_entries(self, name_list) != 0) {
+ if (*list != NULL)
+ free(*list), *list = NULL;
+ count = 0;
+ return -1;
+ }
+
+ return count;
+}
+
+/* ============================================================ */
OpenPOWER on IntegriCloud