Provides low-level VNVRAM functionality that reads and writes data, such as an entry's binary blob, to a drive image using the block driver.
Signed-off-by: Corey Bryant <cor...@linux.vnet.ibm.com> --- Makefile.objs | 2 + vnvram.c | 487 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vnvram.h | 22 +++ 3 files changed, 511 insertions(+), 0 deletions(-) create mode 100644 vnvram.c create mode 100644 vnvram.h diff --git a/Makefile.objs b/Makefile.objs index 286ce06..4875a94 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -76,6 +76,8 @@ common-obj-$(CONFIG_SECCOMP) += qemu-seccomp.o common-obj-$(CONFIG_SMARTCARD_NSS) += $(libcacard-y) +common-obj-y += vnvram.o + ###################################################################### # qapi diff --git a/vnvram.c b/vnvram.c new file mode 100644 index 0000000..e467198 --- /dev/null +++ b/vnvram.c @@ -0,0 +1,487 @@ +/* + * VNVRAM -- stores persistent data in image files + * + * Copyright (C) 2013 IBM Corporation + * + * Authors: + * Stefan Berger <stef...@us.ibm.com> + * Corey Bryant <cor...@linux.vnet.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "vnvram.h" +#include "block/block.h" + +/* +#define VNVRAM_DEBUG +*/ + +#ifdef VNVRAM_DEBUG +#define DPRINTF(fmt, ...) \ + do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) \ + do { } while (0) +#endif + +#define VNVRAM_ENTRY_DATA \ + VNVRAMEntryName name; /* name of entry */ \ + uint64_t blob_offset; /* start of blob on drive */ \ + uint32_t cur_size; /* current size of blob */ \ + uint32_t max_size; /* max size of blob */ + +/* The following VNVRAM information is stored in-memory */ +typedef struct VNVRAMEntry { + VNVRAM_ENTRY_DATA + QLIST_ENTRY(VNVRAMEntry) next; +} VNVRAMEntry; + +struct VNVRAM { + char *drv_id; /* corresponds to -drive id= on command line */ + BlockDriverState *bds; /* bds for the VNVRAM drive */ + uint64_t end_offset; /* offset on drive where next entry will go */ + QLIST_HEAD(entries_head, VNVRAMEntry) entries_head; /* in-memory entries */ + QLIST_ENTRY(VNVRAM) list; +}; + +/* There can be multiple VNVRAMS */ +static QLIST_HEAD(, VNVRAM) vnvrams = QLIST_HEAD_INITIALIZER(vnvrams); + +#define VNVRAM_VERSION_1 1 +#define VNVRAM_CURRENT_VERSION VNVRAM_VERSION_1 +#define VNVRAM_MAGIC 0x4E56524D /* NVRM */ + +/* VNVRAM drive data consists of a header followed by entries and their blobs. + * For example: + * | header | entry 1 | entry 1's blob | entry 2 | entry 2's blob | ... | + */ +typedef struct VNVRAMDrvHdr { + uint16_t version; + uint32_t magic; + uint32_t num_entries; +} QEMU_PACKED VNVRAMDrvHdr; + +typedef struct VNVRAMDrvEntry { + VNVRAM_ENTRY_DATA +} QEMU_PACKED VNVRAMDrvEntry; + +static int vnvram_drv_entry_create(VNVRAM *, VNVRAMEntry *, uint64_t, uint32_t); +static int vnvram_drv_entry_update(VNVRAM *, VNVRAMEntry *, uint64_t, uint32_t); + +/* + * Macros for finding entries and their drive offsets + */ +#define VNVRAM_FIRST_ENTRY(vnvram) \ + QLIST_FIRST(&(vnvram)->entries_head) + +#define VNVRAM_NEXT_ENTRY(cur_entry) \ + QLIST_NEXT(cur_entry, next) + +#define VNVRAM_FIRST_ENTRY_OFFSET() \ + sizeof(VNVRAMDrvHdr) + +#define VNVRAM_NEXT_ENTRY_OFFSET(entry) \ + ((entry)->blob_offset + (entry)->max_size) + +#define VNVRAM_NEXT_AVAIL_BLOB_OFFSET(vnvram) \ + ((vnvram)->end_offset + sizeof(VNVRAMDrvEntry)) + +#define VNVRAM_ENTRY_OFFSET_FROM_BLOB(blob_offset) \ + (blob_offset - sizeof(VNVRAMDrvEntry)) + +#define VNVRAM_BLOB_OFFSET_FROM_ENTRY(entry_offset) \ + (entry_offset + sizeof(VNVRAMDrvEntry)) + +/************************* VNVRAM drv ********************************/ +/* Low-level VNVRAM functions that work with the drive header and */ +/* entries. */ +/*********************************************************************/ + +/* + * Big-endian conversions + */ +static void vnvram_drv_hdr_cpu_to_be(VNVRAMDrvHdr *hdr) +{ + hdr->version = cpu_to_be16(hdr->version); + hdr->magic = cpu_to_be32(hdr->magic); + hdr->num_entries = cpu_to_be32(hdr->num_entries); +} + +static void vnvram_drv_hdr_be_to_cpu(VNVRAMDrvHdr *hdr) +{ + hdr->version = be16_to_cpu(hdr->version); + hdr->magic = be32_to_cpu(hdr->magic); + hdr->num_entries = be32_to_cpu(hdr->num_entries); +} + +static void vnvram_drv_entry_cpu_to_be(VNVRAMDrvEntry *drv_entry) +{ + drv_entry->blob_offset = cpu_to_be64(drv_entry->blob_offset); + drv_entry->cur_size = cpu_to_be32(drv_entry->cur_size); + drv_entry->max_size = cpu_to_be32(drv_entry->max_size); +} + +static void vnvram_drv_entry_be_to_cpu(VNVRAMDrvEntry *drv_entry) +{ + drv_entry->blob_offset = be64_to_cpu(drv_entry->blob_offset); + drv_entry->cur_size = be32_to_cpu(drv_entry->cur_size); + drv_entry->max_size = be32_to_cpu(drv_entry->max_size); +} + +/* + * Find the VNVRAM that corresponds to the specified drive ID string + */ +static VNVRAM *vnvram_drv_find_by_id(const char *drv_id) +{ + VNVRAM *vnvram; + + QLIST_FOREACH(vnvram, &vnvrams, list) { + if (strcmp(vnvram->drv_id, drv_id) == 0) { + return vnvram; + } + } + + return NULL; +} + +/* + * Increase the drive size if it's too small to fit the VNVRAM data + */ +static int vnvram_drv_adjust_size(VNVRAM *vnvram) +{ + int rc = 0; + int64_t needed_size; + + needed_size = 0; + + if (bdrv_getlength(vnvram->bds) < needed_size) { + rc = bdrv_truncate(vnvram->bds, needed_size); + if (rc != 0) { + DPRINTF("%s: VNVRAM drive too small\n", __func__); + } + } + + return rc; +} + +/* + * Write a header to the drive with entry count of zero + */ +static int vnvram_drv_hdr_create_empty(VNVRAM *vnvram) +{ + VNVRAMDrvHdr hdr; + + hdr.version = VNVRAM_CURRENT_VERSION; + hdr.magic = VNVRAM_MAGIC; + hdr.num_entries = 0; + + vnvram_drv_hdr_cpu_to_be((&hdr)); + + if (bdrv_pwrite(vnvram->bds, 0, (&hdr), sizeof(hdr)) != sizeof(hdr)) { + DPRINTF("%s: Write of header to drive failed\n", __func__); + return -EIO; + } + + vnvram->end_offset = sizeof(VNVRAMDrvHdr); + + return 0; +} + +/* + * Read the header from the drive + */ +static int vnvram_drv_hdr_read(VNVRAM *vnvram, VNVRAMDrvHdr *hdr) +{ + if (bdrv_pread(vnvram->bds, 0, hdr, sizeof(*hdr)) != sizeof(*hdr)) { + DPRINTF("%s: Read of header from drive failed\n", __func__); + return -EIO; + } + + vnvram_drv_hdr_be_to_cpu(hdr); + + return 0; +} + +/* + * Write the header to the drive + */ +static int vnvram_drv_hdr_write(VNVRAM *vnvram, VNVRAMDrvHdr *hdr) +{ + vnvram_drv_hdr_cpu_to_be(hdr); + + if (bdrv_pwrite(vnvram->bds, 0, hdr, sizeof(*hdr)) != sizeof(*hdr)) { + DPRINTF("%s: Write of header to drive failed\n", __func__); + return -EIO; + } + + vnvram_drv_hdr_be_to_cpu(hdr); + + return 0; +} + +/* + * Read an entry from the drive (does not include blob) + */ +static int vnvram_drv_entry_read(VNVRAM *vnvram, uint64_t entry_offset, + VNVRAMDrvEntry *drv_entry) +{ + if (bdrv_pread(vnvram->bds, entry_offset, drv_entry, sizeof(*drv_entry)) + != sizeof(*drv_entry)) { + DPRINTF("%s: VNVRAM error reading entry from drive\n", __func__); + return -EIO; + } + + vnvram_drv_entry_be_to_cpu(drv_entry); + + return 0; +} + +/* + * Write an entry to the drive (does not include blob) + */ +static int vnvram_drv_entry_write(VNVRAM *vnvram, uint64_t entry_offset, + VNVRAMDrvEntry *drv_entry) +{ + vnvram_drv_entry_cpu_to_be(drv_entry); + + if (bdrv_pwrite(vnvram->bds, entry_offset, drv_entry, sizeof(*drv_entry)) + != sizeof(*drv_entry)) { + DPRINTF("%s: VNVRAM error writing entry to drive\n", __func__); + return -EIO; + } + + vnvram_drv_entry_be_to_cpu(drv_entry); + + return 0; +} + +/* + * Read an entry's blob from the drive + */ +static int vnvram_drv_entry_read_blob(VNVRAM *vnvram, const VNVRAMEntry *entry, + char **blob, uint32_t *blob_size) +{ + int rc = 0; + + *blob = NULL; + *blob_size = 0; + + if (entry->cur_size == 0) { + DPRINTF("%s: VNVRAM entry not found\n", __func__); + rc = -ENOENT; + goto err_exit; + } + + *blob = g_malloc(entry->cur_size); + + DPRINTF("%s: VNVRAM read: name=%s, blob_offset=%"PRIu64", size=%"PRIu32"\n", + __func__, (char *)entry->name, entry->blob_offset, entry->cur_size); + + if (bdrv_pread(vnvram->bds, entry->blob_offset, *blob, entry->cur_size) + != entry->cur_size) { + DPRINTF("%s: VNVRAM error reading blob from drive\n", __func__); + rc = -EIO; + goto err_exit; + } + + *blob_size = entry->cur_size; + + return rc; + +err_exit: + g_free(*blob); + *blob = NULL; + + return rc; +} + +/* + * Write an entry's blob to the drive + */ +static int vnvram_drv_entry_write_blob(VNVRAM *vnvram, VNVRAMEntry *entry, + char *blob, uint32_t blob_size) +{ + int rc; + uint64_t blob_offset; + + if (blob_size == 0 || blob_size > entry->max_size) { + DPRINTF("%s: Blob size is not valid for entry\n", __func__); + rc = -EMSGSIZE; + goto err_exit; + } + + rc = vnvram_drv_adjust_size(vnvram); + if (rc != 0) { + goto err_exit; + } + + if (entry->blob_offset == 0) { + /* Entry doesn't exist on the drive yet */ + blob_offset = VNVRAM_NEXT_AVAIL_BLOB_OFFSET(vnvram); + } else { + blob_offset = entry->blob_offset; + } + + DPRINTF("%s: VNVRAM write: name=%s, blob_offset=%"PRIu64", " + "size=%"PRIu32"\n", __func__, (char *)entry->name, + blob_offset, blob_size); + + if (bdrv_pwrite(vnvram->bds, blob_offset, blob, blob_size) != blob_size) { + DPRINTF("%s: VNVRAM error writing blob to drive\n", __func__); + rc = -EIO; + goto err_exit; + } + + if (entry->blob_offset == 0) { + /* Entry doesn't exist on the drive yet */ + rc = vnvram_drv_entry_create(vnvram, entry, + VNVRAM_ENTRY_OFFSET_FROM_BLOB(blob_offset), + blob_size); + if (rc != 0) { + DPRINTF("%s: Unable to create VNVRAM entry\n", __func__); + goto err_exit; + } + } else { + rc = vnvram_drv_entry_update(vnvram, entry, + VNVRAM_ENTRY_OFFSET_FROM_BLOB(blob_offset), + blob_size); + if (rc != 0) { + DPRINTF("%s: Unable to update VNVRAM entry\n", __func__); + goto err_exit; + } + } + + entry->blob_offset = blob_offset; + entry->cur_size = blob_size; + +err_exit: + return rc; +} + +/* + * Create an entry and write it to the drive (does not include blob) + */ +static int vnvram_drv_entry_create(VNVRAM *vnvram, VNVRAMEntry *entry, + uint64_t entry_offset, uint32_t blob_size) +{ + int rc; + VNVRAMDrvHdr hdr; + VNVRAMDrvEntry *drv_entry; + + drv_entry = g_new0(VNVRAMDrvEntry, 1); + + pstrcpy(drv_entry->name, sizeof(drv_entry->name), (char *)entry->name); + drv_entry->blob_offset = VNVRAM_BLOB_OFFSET_FROM_ENTRY(entry_offset); + drv_entry->cur_size = blob_size; + drv_entry->max_size = entry->max_size; + + rc = vnvram_drv_entry_write(vnvram, entry_offset, drv_entry); + if (rc != 0) { + goto err_exit; + } + + rc = vnvram_drv_hdr_read(vnvram, (&hdr)); + if (rc != 0) { + goto err_exit; + } + + hdr.num_entries++; + + rc = vnvram_drv_hdr_write(vnvram, (&hdr)); + if (rc != 0) { + goto err_exit; + } + + vnvram->end_offset = drv_entry->blob_offset + drv_entry->max_size; + +err_exit: + g_free(drv_entry); + + return rc; +} + +/* + * Update an entry on the drive (does not include blob) + */ +static int vnvram_drv_entry_update(VNVRAM *vnvram, VNVRAMEntry *entry, + uint64_t entry_offset, uint32_t blob_size) +{ + int rc; + VNVRAMDrvEntry *drv_entry; + + drv_entry = g_new0(VNVRAMDrvEntry, 1); + + pstrcpy(drv_entry->name, sizeof(drv_entry->name), (char *)entry->name); + drv_entry->blob_offset = VNVRAM_BLOB_OFFSET_FROM_ENTRY(entry_offset); + drv_entry->cur_size = blob_size; + drv_entry->max_size = entry->max_size; + + rc = vnvram_drv_entry_write(vnvram, entry_offset, drv_entry); + + g_free(drv_entry); + + return rc; +} + +/* + * Get all entry data from the drive (does not get blob data) + */ +static int vnvram_drv_entries_get(VNVRAM *vnvram, VNVRAMDrvHdr *hdr, + VNVRAMDrvEntry **drv_entries, + int *num_entries) +{ + int i, rc; + uint64_t entry_offset; + + *drv_entries = NULL; + *num_entries = 0; + + *num_entries = hdr->num_entries; + if (*num_entries == 0) { + return 0; + } + + *drv_entries = g_malloc_n(hdr->num_entries, sizeof(VNVRAMDrvEntry)); + + entry_offset = VNVRAM_FIRST_ENTRY_OFFSET(); + + for (i = 0; i < hdr->num_entries; i++) { + VNVRAMDrvEntry *drv_entry = &(*drv_entries)[i]; + + rc = vnvram_drv_entry_read(vnvram, entry_offset, drv_entry); + if (rc != 0) { + goto err_exit; + } + + entry_offset = VNVRAM_NEXT_ENTRY_OFFSET(drv_entry); + } + + return 0; + +err_exit: + g_free(*drv_entries); + *drv_entries = NULL; + *num_entries = 0; + + return rc; +} + +/* + * Check if the VNVRAM drive header is valid + */ +static bool vnvram_drv_hdr_is_valid(VNVRAM *vnvram, VNVRAMDrvHdr *hdr) +{ + if (hdr->version != VNVRAM_CURRENT_VERSION) { + DPRINTF("%s: VNVRAM drive version not valid\n", __func__); + return false; + } + + if (hdr->magic != VNVRAM_MAGIC) { + DPRINTF("%s: VNVRAM drive magic not valid\n", __func__); + return false; + } + + return true; +} diff --git a/vnvram.h b/vnvram.h new file mode 100644 index 0000000..b6d7cd7 --- /dev/null +++ b/vnvram.h @@ -0,0 +1,22 @@ +/* + * VNVRAM -- stores persistent data in image files + * + * Copyright (C) 2013 IBM Corporation + * + * Authors: + * Stefan Berger <stef...@us.ibm.com> + * Corey Bryant <cor...@linux.vnet.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#ifndef _QEMU_VNVRAM_H_ +#define _QEMU_VNVRAM_H_ + +typedef struct VNVRAM VNVRAM; + +#define VNVRAM_ENTRY_NAME_LENGTH 16 +typedef char VNVRAMEntryName[VNVRAM_ENTRY_NAME_LENGTH]; + +#endif -- 1.7.1