/ src / commonlib / bsd / cbfs_mcache.c
cbfs_mcache.c
  1  /* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later */
  2  
  3  #include <assert.h>
  4  #include <commonlib/bsd/cbfs_private.h>
  5  #include <commonlib/bsd/helpers.h>
  6  
  7  /*
  8   * A CBFS metadata cache is an in memory data structure storing CBFS file headers (= metadata).
  9   * It is defined by its start pointer and size. It contains a sequence of variable-length
 10   * union mcache_entry entries. There is no overall header structure for the cache.
 11   *
 12   * Each mcache_entry is the raw metadata for a CBFS file (including attributes) in the same form
 13   * as stored on flash (i.e. values in big-endian), except that the CBFS magic signature in the
 14   * first 8 bytes ('LARCHIVE') is overwritten with mcache-internal bookkeeping data. The first 4
 15   * bytes are a magic number (MCACHE_MAGIC_FILE) and the next 4 bytes are the absolute offset in
 16   * bytes on the cbfs_dev_t that this metadata blob was found at. (Note that depending on the
 17   * implementation of cbfs_dev_t, this offset may still be relative to the start of a subregion
 18   * of the underlying storage device.)
 19   *
 20   * The length of an mcache_entry (i.e. length of the underlying metadata blob) is encoded in the
 21   * metadata (entry->file.h.offset). The next mcache_entry begins at the next
 22   * CBFS_MCACHE_ALIGNMENT boundary after that. The cache is terminated by a special 4-byte
 23   * mcache_entry that consists only of a magic number (MCACHE_MAGIC_END or MCACHE_MAGIC_FULL).
 24   */
 25  
 26  #define MCACHE_MAGIC_FILE	0x454c4946	/* 'FILE' */
 27  #define MCACHE_MAGIC_FULL	0x4c4c5546	/* 'FULL' */
 28  #define MCACHE_MAGIC_END	0x444e4524	/* '$END' */
 29  
 30  union mcache_entry {
 31  	union cbfs_mdata file;
 32  	struct {	/* These fields exactly overlap file.h.magic */
 33  		uint32_t magic;
 34  		uint32_t offset;
 35  	};
 36  };
 37  
 38  struct cbfs_mcache_build_args {
 39  	void *mcache;
 40  	void *end;
 41  	int count;
 42  };
 43  
 44  static enum cb_err build_walker(cbfs_dev_t dev, size_t offset, const union cbfs_mdata *mdata,
 45  				size_t already_read, void *arg)
 46  {
 47  	struct cbfs_mcache_build_args *args = arg;
 48  	union mcache_entry *entry = args->mcache;
 49  	const uint32_t data_offset = be32toh(mdata->h.offset);
 50  
 51  	if (args->end - args->mcache < data_offset)
 52  		return CB_CBFS_CACHE_FULL;
 53  
 54  	if (cbfs_copy_fill_metadata(args->mcache, mdata, already_read, dev, offset))
 55  		return CB_CBFS_IO;
 56  
 57  	entry->magic = MCACHE_MAGIC_FILE;
 58  	entry->offset = offset;
 59  
 60  	args->mcache += ALIGN_UP(data_offset, CBFS_MCACHE_ALIGNMENT);
 61  	args->count++;
 62  
 63  	return CB_CBFS_NOT_FOUND;
 64  }
 65  
 66  enum cb_err cbfs_mcache_build(cbfs_dev_t dev, void *mcache, size_t size,
 67  			      struct vb2_hash *metadata_hash)
 68  {
 69  	struct cbfs_mcache_build_args args = {
 70  		.mcache = mcache,
 71  		.end = mcache + ALIGN_DOWN(size, CBFS_MCACHE_ALIGNMENT)
 72  		       - sizeof(uint32_t), /* leave space for terminating magic */
 73  		.count = 0,
 74  	};
 75  
 76  	assert(size > sizeof(uint32_t) && IS_ALIGNED((uintptr_t)mcache, CBFS_MCACHE_ALIGNMENT));
 77  	enum cb_err ret = cbfs_walk(dev, build_walker, &args, metadata_hash, 0);
 78  	union mcache_entry *entry = args.mcache;
 79  	if (ret == CB_CBFS_NOT_FOUND) {
 80  		ret = CB_SUCCESS;
 81  		entry->magic = MCACHE_MAGIC_END;
 82  	} else if (ret == CB_CBFS_CACHE_FULL) {
 83  		ERROR("mcache overflow, should increase CBFS_MCACHE size!\n");
 84  		entry->magic = MCACHE_MAGIC_FULL;
 85  	}
 86  
 87  	LOG("mcache @%p built for %d files, used %#zx of %#zx bytes\n", mcache,
 88  	    args.count, args.mcache + sizeof(entry->magic) - mcache, size);
 89  	return ret;
 90  }
 91  
 92  enum cb_err cbfs_mcache_lookup(const void *mcache, size_t mcache_size, const char *name,
 93  			       union cbfs_mdata *mdata_out, size_t *data_offset_out)
 94  {
 95  	const size_t namesize = strlen(name) + 1; /* Count trailing \0 so we can memcmp() it. */
 96  	const void *end = mcache + mcache_size;
 97  	const void *current = mcache;
 98  
 99  	while (current + sizeof(uint32_t) <= end) {
100  		const union mcache_entry *entry = current;
101  
102  		if (entry->magic == MCACHE_MAGIC_END)
103  			return CB_CBFS_NOT_FOUND;
104  		if (entry->magic == MCACHE_MAGIC_FULL)
105  			return CB_CBFS_CACHE_FULL;
106  
107  		assert(entry->magic == MCACHE_MAGIC_FILE);
108  		const uint32_t data_offset = be32toh(entry->file.h.offset);
109  		const uint32_t data_length = be32toh(entry->file.h.len);
110  		if (namesize <= data_offset - offsetof(union cbfs_mdata, h.filename) &&
111  		    memcmp(name, entry->file.h.filename, namesize) == 0) {
112  			LOG("Found '%s' @%#x size %#x in mcache @%p\n",
113  			    name, entry->offset, data_length, current);
114  			*data_offset_out = entry->offset + data_offset;
115  			memcpy(mdata_out, &entry->file, data_offset);
116  			return CB_SUCCESS;
117  		}
118  
119  		current += ALIGN_UP(data_offset, CBFS_MCACHE_ALIGNMENT);
120  	}
121  
122  	ERROR("CBFS mcache is not terminated!\n");	/* should never happen */
123  	return CB_ERR;
124  }
125  
126  size_t cbfs_mcache_real_size(const void *mcache, size_t mcache_size)
127  {
128  	const void *end = mcache + mcache_size;
129  	const void *current = mcache;
130  
131  	while (current + sizeof(uint32_t) <= end) {
132  		const union mcache_entry *entry = current;
133  
134  		if (entry->magic == MCACHE_MAGIC_FULL || entry->magic == MCACHE_MAGIC_END) {
135  			current += sizeof(entry->magic);
136  			break;
137  		}
138  
139  		assert(entry->magic == MCACHE_MAGIC_FILE);
140  		current += ALIGN_UP(be32toh(entry->file.h.offset), CBFS_MCACHE_ALIGNMENT);
141  	}
142  
143  	return current - mcache;
144  }