summaryrefslogtreecommitdiffstats
path: root/common/state/backend_storage.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/state/backend_storage.c')
-rw-r--r--common/state/backend_storage.c525
1 files changed, 525 insertions, 0 deletions
diff --git a/common/state/backend_storage.c b/common/state/backend_storage.c
new file mode 100644
index 0000000000..c4508a8aba
--- /dev/null
+++ b/common/state/backend_storage.c
@@ -0,0 +1,525 @@
+/*
+ * Copyright (C) 2016 Pengutronix, Markus Pargmann <mpa@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2, as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+#include <asm-generic/ioctl.h>
+#include <fcntl.h>
+#include <fs.h>
+#include <libfile.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mtd/mtd-abi.h>
+#include <linux/stat.h>
+#include <malloc.h>
+#include <printk.h>
+
+#include "state.h"
+
+const unsigned int min_copies_written = 1;
+
+static int bucket_lazy_init(struct state_backend_storage_bucket *bucket)
+{
+ int ret;
+
+ if (bucket->initialized)
+ return 0;
+
+ if (bucket->init) {
+ ret = bucket->init(bucket);
+ if (ret)
+ return ret;
+ }
+ bucket->initialized = true;
+
+ return 0;
+}
+
+/**
+ * state_storage_write - Writes the given data to the storage
+ * @param storage Storage object
+ * @param buf Buffer with the data
+ * @param len Length of the buffer
+ * @return 0 on success, -errno otherwise
+ *
+ * This function iterates over all registered buckets and executes a write
+ * operation on all of them. Writes are always in the same sequence. This
+ * ensures, that reading in the same sequence will always return the latest
+ * written valid data first.
+ * We try to at least write min_copies_written. If this fails we return with an
+ * error.
+ */
+int state_storage_write(struct state_backend_storage *storage,
+ const uint8_t * buf, ssize_t len)
+{
+ struct state_backend_storage_bucket *bucket;
+ int ret;
+ int copies_written = 0;
+
+ if (storage->readonly)
+ return 0;
+
+ list_for_each_entry(bucket, &storage->buckets, bucket_list) {
+ ret = bucket_lazy_init(bucket);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to init bucket/write state backend bucket, %d\n",
+ ret);
+ continue;
+ }
+
+ ret = bucket->write(bucket, buf, len);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to write state backend bucket, %d\n",
+ ret);
+ } else {
+ ++copies_written;
+ }
+ }
+
+ if (copies_written >= min_copies_written)
+ return 0;
+
+ dev_err(storage->dev, "Failed to write state to at least %d buckets. Successfully written to %d buckets\n",
+ min_copies_written, copies_written);
+ return -EIO;
+}
+
+/**
+ * state_storage_restore_consistency - Restore consistency on all storage backends
+ * @param storage Storage object
+ * @param buf Buffer with valid data that should be on all buckets after this operation
+ * @param len Length of the buffer
+ * @return 0 on success, -errno otherwise
+ *
+ * This function brings valid data onto all buckets we have to ensure that all
+ * data copies are in sync. In the current implementation we just write the data
+ * to all buckets. Bucket implementations that need to keep the number of writes
+ * low, can read their own copy first and compare it.
+ */
+int state_storage_restore_consistency(struct state_backend_storage *storage,
+ const uint8_t * buf, ssize_t len)
+{
+ return state_storage_write(storage, buf, len);
+}
+
+/**
+ * state_storage_read - Reads valid data from the backend storage
+ * @param storage Storage object
+ * @param format Format of the data that is stored
+ * @param magic state magic value
+ * @param buf The newly allocated data area will be stored in this pointer
+ * @param len The resulting length of the buffer
+ * @param len_hint Hint of how big the data may be.
+ * @return 0 on success, -errno otherwise. buf and len will be set to valid
+ * values on success.
+ *
+ * This function goes through all buckets and tries to read valid data from
+ * them. The first bucket which returns data that is successfully verified
+ * against the data format is used. To ensure the validity of all bucket copies,
+ * we restore the consistency at the end.
+ */
+int state_storage_read(struct state_backend_storage *storage,
+ struct state_backend_format *format,
+ uint32_t magic, uint8_t ** buf, ssize_t * len,
+ ssize_t len_hint)
+{
+ struct state_backend_storage_bucket *bucket;
+ int ret;
+
+ list_for_each_entry(bucket, &storage->buckets, bucket_list) {
+ *len = len_hint;
+ ret = bucket_lazy_init(bucket);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to init bucket/read state backend bucket, %d\n",
+ ret);
+ continue;
+ }
+
+ ret = bucket->read(bucket, buf, len);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to read from state backend bucket, trying next, %d\n",
+ ret);
+ continue;
+ }
+ ret = format->verify(format, magic, *buf, *len);
+ if (!ret) {
+ goto found;
+ }
+ free(*buf);
+ dev_warn(storage->dev, "Failed to verify read copy, trying next bucket, %d\n",
+ ret);
+ }
+
+ dev_err(storage->dev, "Failed to find any valid state copy in any bucket\n");
+
+ return -ENOENT;
+
+found:
+ /* A failed restore consistency is not a failure of reading the state */
+ state_storage_restore_consistency(storage, *buf, *len);
+
+ return 0;
+}
+
+static int mtd_get_meminfo(const char *path, struct mtd_info_user *meminfo)
+{
+ int fd, ret;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ pr_err("Failed to open '%s', %d\n", path, ret);
+ return fd;
+ }
+
+ ret = ioctl(fd, MEMGETINFO, meminfo);
+
+ close(fd);
+
+ return ret;
+}
+
+#ifdef __BAREBOX__
+#define STAT_GIVES_SIZE(s) (S_ISREG(s.st_mode) || S_ISCHR(s.st_mode))
+#define BLKGET_GIVES_SIZE(s) 0
+#else
+#define STAT_GIVES_SIZE(s) (S_ISREG(s.st_mode))
+#define BLKGET_GIVES_SIZE(s) (S_ISBLK(s.st_mode))
+#endif
+#ifndef BLKGETSIZE64
+#define BLKGETSIZE64 -1
+#endif
+
+static int state_backend_storage_get_size(const char *path, size_t * out_size)
+{
+ struct mtd_info_user meminfo;
+ struct stat s;
+ int ret;
+
+ ret = stat(path, &s);
+ if (ret)
+ return -errno;
+
+ /*
+ * under Linux, stat() gives the size only on regular files
+ * under barebox, it works on char dev, too
+ */
+ if (STAT_GIVES_SIZE(s)) {
+ *out_size = s.st_size;
+ return 0;
+ }
+
+ /* this works under Linux on block devs */
+ if (BLKGET_GIVES_SIZE(s)) {
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return -errno;
+
+ ret = ioctl(fd, BLKGETSIZE64, out_size);
+ close(fd);
+ if (!ret)
+ return 0;
+ }
+
+ /* try mtd next */
+ ret = mtd_get_meminfo(path, &meminfo);
+ if (!ret) {
+ *out_size = meminfo.size;
+ return 0;
+ }
+
+ return ret;
+}
+
+/* Number of copies that should be allocated */
+const int desired_copies = 3;
+
+/**
+ * state_storage_mtd_buckets_init - Creates storage buckets for mtd devices
+ * @param storage Storage object
+ * @param meminfo Info about the mtd device
+ * @param path Path to the device
+ * @param non_circular Use non-circular mode to write data that is compatible with the old on-flash format
+ * @param dev_offset Offset to start at in the device.
+ * @param max_size Maximum size to use for data. May be 0 for infinite.
+ * @return 0 on success, -errno otherwise
+ *
+ * Starting from offset 0 this function tries to create circular buckets on
+ * different offsets in the device. Different copies of the data are located in
+ * different eraseblocks.
+ * For MTD devices we use circular buckets to minimize the number of erases.
+ * Circular buckets write new data always in the next free space.
+ */
+static int state_storage_mtd_buckets_init(struct state_backend_storage *storage,
+ struct mtd_info_user *meminfo,
+ const char *path, bool non_circular,
+ off_t dev_offset, size_t max_size)
+{
+ struct state_backend_storage_bucket *bucket;
+ ssize_t end = dev_offset + max_size;
+ int nr_copies = 0;
+ off_t offset;
+
+ if (!end || end > meminfo->size)
+ end = meminfo->size;
+
+ if (!IS_ALIGNED(dev_offset, meminfo->erasesize)) {
+ dev_err(storage->dev, "Offset within the device is not aligned to eraseblocks. Offset is %ld, erasesize %zu\n",
+ dev_offset, meminfo->erasesize);
+ return -EINVAL;
+ }
+
+ for (offset = dev_offset; offset < end; offset += meminfo->erasesize) {
+ int ret;
+ ssize_t writesize = meminfo->writesize;
+ unsigned int eraseblock = offset / meminfo->erasesize;
+ bool lazy_init = true;
+
+ if (non_circular)
+ writesize = meminfo->erasesize;
+
+ ret = state_backend_bucket_circular_create(storage->dev, path,
+ &bucket,
+ eraseblock,
+ writesize,
+ meminfo,
+ lazy_init);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to create bucket at '%s' eraseblock %u\n",
+ path, eraseblock);
+ continue;
+ }
+
+ ret = state_backend_bucket_cached_create(storage->dev, bucket,
+ &bucket);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to setup cache bucket, continuing without cache, %d\n",
+ ret);
+ }
+
+ list_add_tail(&bucket->bucket_list, &storage->buckets);
+ ++nr_copies;
+ if (nr_copies >= desired_copies)
+ return 0;
+ }
+
+ if (!nr_copies) {
+ dev_err(storage->dev, "Failed to initialize any state storage bucket\n");
+ return -EIO;
+ }
+
+ dev_warn(storage->dev, "Failed to initialize desired amount of buckets, only %d of %d succeeded\n",
+ nr_copies, desired_copies);
+ return 0;
+}
+
+static int state_storage_file_create(struct device_d *dev, const char *path,
+ size_t fd_size)
+{
+ int fd;
+ uint8_t *buf;
+ int ret;
+
+ fd = open(path, O_RDWR | O_CREAT, 0600);
+ if (fd < 0) {
+ dev_err(dev, "Failed to open/create file '%s', %d\n", path,
+ -errno);
+ return -errno;
+ }
+
+ buf = xzalloc(fd_size);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto out_close;
+ }
+
+ ret = write_full(fd, buf, fd_size);
+ if (ret < 0) {
+ dev_err(dev, "Failed to initialize empty file '%s', %d\n", path,
+ ret);
+ goto out_free;
+ }
+ ret = 0;
+
+out_free:
+ free(buf);
+out_close:
+ close(fd);
+ return ret;
+}
+
+/**
+ * state_storage_file_buckets_init - Create buckets for a conventional file descriptor
+ * @param storage Storage object
+ * @param path Path to file/device
+ * @param dev_offset Offset in the device to start writing at.
+ * @param max_size Maximum size of the data. May be 0 for infinite.
+ * @param stridesize How far apart the different data copies are placed. If
+ * stridesize is 0, only one copy can be created.
+ * @return 0 on success, -errno otherwise
+ *
+ * For blockdevices and other regular files we create direct buckets beginning
+ * at offset 0. Direct buckets are simple and write data always to offset 0.
+ */
+static int state_storage_file_buckets_init(struct state_backend_storage *storage,
+ const char *path, off_t dev_offset,
+ size_t max_size, uint32_t stridesize)
+{
+ struct state_backend_storage_bucket *bucket;
+ size_t fd_size = 0;
+ int ret;
+ off_t offset;
+ int nr_copies = 0;
+
+ ret = state_backend_storage_get_size(path, &fd_size);
+ if (ret) {
+ if (ret != -ENOENT) {
+ dev_err(storage->dev, "Failed to get the filesize of '%s', %d\n",
+ path, ret);
+ return ret;
+ }
+ if (!stridesize) {
+ dev_err(storage->dev, "File '%s' does not exist and no information about the needed size. Please specify stridesize\n",
+ path);
+ return ret;
+ }
+
+ if (max_size)
+ fd_size = min(dev_offset + stridesize * desired_copies,
+ dev_offset + max_size);
+ else
+ fd_size = dev_offset + stridesize * desired_copies;
+ dev_info(storage->dev, "File '%s' does not exist, creating file of size %zd\n",
+ path, fd_size);
+ ret = state_storage_file_create(storage->dev, path, fd_size);
+ if (ret) {
+ dev_info(storage->dev, "Failed to create file '%s', %d\n",
+ path, ret);
+ return ret;
+ }
+ } else if (max_size) {
+ fd_size = min(fd_size, (size_t)dev_offset + max_size);
+ }
+
+ if (!stridesize) {
+ dev_warn(storage->dev, "WARNING, no stridesize given although we use a direct file write. Starting in degraded mode\n");
+ stridesize = fd_size;
+ }
+
+ for (offset = dev_offset; offset < fd_size; offset += stridesize) {
+ size_t maxsize = min((size_t)stridesize,
+ (size_t)(fd_size - offset));
+
+ ret = state_backend_bucket_direct_create(storage->dev, path,
+ &bucket, offset,
+ maxsize);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to create direct bucket at '%s' offset %ld\n",
+ path, offset);
+ continue;
+ }
+
+ ret = state_backend_bucket_cached_create(storage->dev, bucket,
+ &bucket);
+ if (ret) {
+ dev_warn(storage->dev, "Failed to setup cache bucket, continuing without cache, %d\n",
+ ret);
+ }
+
+ list_add_tail(&bucket->bucket_list, &storage->buckets);
+ ++nr_copies;
+ if (nr_copies >= desired_copies)
+ return 0;
+ }
+
+ if (!nr_copies) {
+ dev_err(storage->dev, "Failed to initialize any state direct storage bucket\n");
+ return -EIO;
+ }
+ dev_warn(storage->dev, "Failed to initialize desired amount of direct buckets, only %d of %d succeeded\n",
+ nr_copies, desired_copies);
+
+ return 0;
+}
+
+
+/**
+ * state_storage_init - Init backend storage
+ * @param storage Storage object
+ * @param path Path to the backend storage file
+ * @param dev_offset Offset in the device to start writing at.
+ * @param max_size Maximum size of the data. May be 0 for infinite.
+ * @param stridesize Distance between two copies of the data. Not relevant for MTD
+ * @param storagetype Type of the storage backend. This may be NULL where we
+ * autoselect some backwardscompatible backend options
+ * @return 0 on success, -errno otherwise
+ *
+ * Depending on the filetype, we create mtd buckets or normal file buckets.
+ */
+int state_storage_init(struct state_backend_storage *storage,
+ struct device_d *dev, const char *path,
+ off_t offset, size_t max_size, uint32_t stridesize,
+ const char *storagetype)
+{
+ int ret;
+ struct mtd_info_user meminfo;
+
+ INIT_LIST_HEAD(&storage->buckets);
+ storage->dev = dev;
+ storage->name = storagetype;
+ storage->stridesize = stridesize;
+
+ ret = mtd_get_meminfo(path, &meminfo);
+ if (!ret && !(meminfo.flags & MTD_NO_ERASE)) {
+ bool non_circular = false;
+ if (!storagetype) {
+ non_circular = true;
+ } else if (strcmp(storagetype, "circular")) {
+ dev_warn(storage->dev, "Unknown storagetype '%s', falling back to old format circular storage type.\n",
+ storagetype);
+ non_circular = true;
+ }
+ return state_storage_mtd_buckets_init(storage, &meminfo, path,
+ non_circular, offset,
+ max_size);
+ } else {
+ return state_storage_file_buckets_init(storage, path, offset,
+ max_size, stridesize);
+ }
+
+ dev_err(storage->dev, "storage init done\n");
+}
+
+void state_storage_set_readonly(struct state_backend_storage *storage)
+{
+ storage->readonly = true;
+}
+
+/**
+ * state_storage_free - Free backend storage
+ * @param storage Storage object
+ */
+void state_storage_free(struct state_backend_storage *storage)
+{
+ struct state_backend_storage_bucket *bucket;
+ struct state_backend_storage_bucket *bucket_tmp;
+
+ if (!storage->buckets.next)
+ return;
+
+ list_for_each_entry_safe(bucket, bucket_tmp, &storage->buckets,
+ bucket_list) {
+ list_del(&bucket->bucket_list);
+ bucket->free(bucket);
+ }
+}