Newer
Older
dt-utils / src / barebox-state / backend_storage.c
@Markus Pargmann Markus Pargmann on 27 May 2016 13 KB barebox-state: Import updated state code
/*
 * 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;

/**
 * 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;

	list_for_each_entry(bucket, &storage->buckets, bucket_list) {
		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->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;

		if (non_circular)
			writesize = meminfo->erasesize;

		ret = state_backend_bucket_circular_create(storage->dev, path,
							   &bucket,
							   eraseblock,
							   writesize,
							   meminfo);
		if (ret) {
			dev_warn(storage->dev, "Failed to create bucket at '%s' eraseblock %u\n",
				 path, eraseblock);
			continue;
		}

		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;
		}

		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;

	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");
}

/**
 * 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);
	}
}