{ echo '$(ME): Windows special chars in filename not allowed' 1>&2; echo exit 1; } || :
sc_prohibit_mixed_case_abbreviations:
- @prohibit='Pci|Usb|Scsi' \
+ @prohibit='Pci|Usb|Scsi|Vpd' \
in_vc_files='\.[ch]$$' \
- halt='Use PCI, USB, SCSI, not Pci, Usb, Scsi' \
+ halt='Use PCI, USB, SCSI, VPD, not Pci, Usb, Scsi, Vpd' \
$(_sc_search_regexp)
# Require #include <locale.h> in all files that call setlocale()
@SRCDIR@src/util/virnvme.c
@SRCDIR@src/util/virobject.c
@SRCDIR@src/util/virpci.c
+@SRCDIR@src/util/virpcivpd.c
@SRCDIR@src/util/virperf.c
@SRCDIR@src/util/virpidfile.c
@SRCDIR@src/util/virpolkit.c
virVHBAPathExists;
+# util/virpcivpd.h
+
+virPCIVPDParse;
+virPCIVPDParseVPDLargeResourceFields;
+virPCIVPDParseVPDLargeResourceString;
+virPCIVPDReadVPDBytes;
+virPCIVPDResourceCustomCompareIndex;
+virPCIVPDResourceCustomFree;
+virPCIVPDResourceCustomUpsertValue;
+virPCIVPDResourceFree;
+virPCIVPDResourceGetFieldValueFormat;
+virPCIVPDResourceIsValidTextValue;
+virPCIVPDResourceROFree;
+virPCIVPDResourceRONew;
+virPCIVPDResourceRWFree;
+virPCIVPDResourceRWNew;
+virPCIVPDResourceUpdateKeyword;
+
# util/virvsock.h
virVsockAcquireGuestCid;
virVsockSetGuestCid;
'virutil.c',
'viruuid.c',
'virvhba.c',
+ 'virpcivpd.c',
'virvsock.c',
'virxml.c',
]
--- /dev/null
+/*
+ * virpcivpd.c: helper APIs for working with the PCI/PCIe VPD capability
+ *
+ * Copyright (C) 2021 Canonical Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#ifdef __linux__
+# include <unistd.h>
+#endif
+
+#define LIBVIRT_VIRPCIVPDPRIV_H_ALLOW
+
+#include "virthread.h"
+#include "virpcivpdpriv.h"
+#include "virlog.h"
+#include "virerror.h"
+#include "virfile.h"
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+VIR_LOG_INIT("util.pcivpd");
+
+static bool
+virPCIVPDResourceIsUpperOrNumber(const char c)
+{
+ return g_ascii_isupper(c) || g_ascii_isdigit(c);
+}
+
+static bool
+virPCIVPDResourceIsVendorKeyword(const char *keyword)
+{
+ return g_str_has_prefix(keyword, "V") && virPCIVPDResourceIsUpperOrNumber(keyword[1]);
+}
+
+static bool
+virPCIVPDResourceIsSystemKeyword(const char *keyword)
+{
+ /* Special-case the system-specific keywords since they share the "Y" prefix with "YA". */
+ return (g_str_has_prefix(keyword, "Y") && virPCIVPDResourceIsUpperOrNumber(keyword[1]) &&
+ STRNEQ(keyword, "YA"));
+}
+
+static char *
+virPCIVPDResourceGetKeywordPrefix(const char *keyword)
+{
+ g_autofree char *key = NULL;
+
+ /* Keywords must have a length of 2 bytes. */
+ if (strlen(keyword) != 2) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, _("The keyword length is not 2 bytes: %s"), keyword);
+ return NULL;
+ } else if (!(virPCIVPDResourceIsUpperOrNumber(keyword[0]) &&
+ virPCIVPDResourceIsUpperOrNumber(keyword[1]))) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("The keyword is not comprised only of uppercase ASCII letters or digits"));
+ return NULL;
+ }
+ /* Special-case the system-specific keywords since they share the "Y" prefix with "YA". */
+ if (virPCIVPDResourceIsSystemKeyword(keyword) || virPCIVPDResourceIsVendorKeyword(keyword))
+ key = g_strndup(keyword, 1);
+ else
+ key = g_strndup(keyword, 2);
+
+ return g_steal_pointer(&key);
+}
+
+static GHashTable *fieldValueFormats;
+
+static int
+virPCIVPDResourceOnceInit(void)
+{
+ /* Initialize a hash table once with static format metadata coming from the PCI(e) specs.
+ * The VPD format does not embed format metadata into the resource records so it is not
+ * possible to do format discovery without static information. Legacy PICMIG keywords
+ * are not included. NOTE: string literals are copied as g_hash_table_insert
+ * requires pointers to non-const data. */
+ fieldValueFormats = g_hash_table_new(g_str_hash, g_str_equal);
+ /* Extended capability. Contains binary data per PCI(e) specs. */
+ g_hash_table_insert(fieldValueFormats, g_strdup("CP"),
+ GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_BINARY));
+ /* Engineering Change Level of an Add-in Card. */
+ g_hash_table_insert(fieldValueFormats, g_strdup("EC"),
+ GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
+ /* Manufacture ID */
+ g_hash_table_insert(fieldValueFormats, g_strdup("MN"),
+ GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
+ /* Add-in Card Part Number */
+ g_hash_table_insert(fieldValueFormats, g_strdup("PN"),
+ GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
+ /* Checksum and Reserved */
+ g_hash_table_insert(fieldValueFormats, g_strdup("RV"),
+ GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD));
+ /* Remaining Read/Write Area */
+ g_hash_table_insert(fieldValueFormats, g_strdup("RW"),
+ GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR));
+ /* Serial Number */
+ g_hash_table_insert(fieldValueFormats, g_strdup("SN"),
+ GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
+ /* Asset Tag Identifier */
+ g_hash_table_insert(fieldValueFormats, g_strdup("YA"),
+ GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
+ /* This is a vendor specific item and the characters are alphanumeric. The second
+ * character (x) of the keyword can be 0 through Z so only the first one is stored. */
+ g_hash_table_insert(fieldValueFormats, g_strdup("V"),
+ GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
+ /* This is a system specific item and the characters are alphanumeric.
+ * The second character (x) of the keyword can be 0 through 9 and B through Z. */
+ g_hash_table_insert(fieldValueFormats, g_strdup("Y"),
+ GINT_TO_POINTER(VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT));
+
+ return 0;
+}
+
+VIR_ONCE_GLOBAL_INIT(virPCIVPDResource);
+
+/**
+ * virPCIVPDResourceGetFieldValueFormat:
+ * @keyword: A keyword for which to get a value type
+ *
+ * Returns: a virPCIVPDResourceFieldValueFormat value which specifies the field value type for
+ * a provided keyword based on the static information from PCI(e) specs.
+ */
+virPCIVPDResourceFieldValueFormat
+virPCIVPDResourceGetFieldValueFormat(const char *keyword)
+{
+ g_autofree char *key = NULL;
+ gpointer keyVal = NULL;
+ virPCIVPDResourceFieldValueFormat format = VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST;
+
+ /* Keywords are expected to be 2 bytes in length which is defined in the specs. */
+ if (strlen(keyword) != 2)
+ return VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST;
+
+ if (virPCIVPDResourceInitialize() < 0)
+ return VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST;
+
+ /* The system and vendor-specific keywords have a variable part - lookup
+ * the prefix significant for determining the value format. */
+ key = virPCIVPDResourceGetKeywordPrefix(keyword);
+ if (key) {
+ keyVal = g_hash_table_lookup(fieldValueFormats, key);
+ if (keyVal)
+ format = GPOINTER_TO_INT(keyVal);
+ }
+ return format;
+}
+
+#define ACCEPTED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 -_,.:;="
+
+/**
+ * virPCIVPDResourceIsValidTextValue:
+ * @value: A NULL-terminated string to assess.
+ *
+ * Returns: a boolean indicating whether this value is a valid string resource
+ * value or text field value. The expectations are based on the keywords specified
+ * in relevant sections of PCI(e) specifications
+ * ("I.3. VPD Definitions" in PCI specs, "6.28.1 VPD Format" PCIe 4.0).
+ */
+bool
+virPCIVPDResourceIsValidTextValue(const char *value)
+{
+ /*
+ * The PCI(e) specs mention alphanumeric characters when talking about text fields
+ * and the string resource but also include spaces and dashes in the provided example.
+ * Dots, commas, equal signs have also been observed in values used by major device vendors.
+ * The specs do not specify a full set of allowed code points and for Libvirt it is important
+ * to keep values in the ranges allowed within XML elements (mainly excluding less-than,
+ * greater-than and ampersand).
+ */
+
+ if (value == NULL)
+ return false;
+
+ /* An empty string is a valid value. */
+ if (STREQ(value, ""))
+ return true;
+
+ if (strspn(value, ACCEPTED_CHARS) != strlen(value)) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("The provided value contains invalid characters: %s"), value);
+ return false;
+ }
+ return true;
+}
+
+void
+virPCIVPDResourceFree(virPCIVPDResource *res)
+{
+ if (!res)
+ return;
+
+ g_free(res->name);
+ virPCIVPDResourceROFree(res->ro);
+ virPCIVPDResourceRWFree(res->rw);
+ g_free(res);
+}
+
+virPCIVPDResourceRO *
+virPCIVPDResourceRONew(void)
+{
+ g_autoptr(virPCIVPDResourceRO) ro = g_new0(virPCIVPDResourceRO, 1);
+ ro->vendor_specific = g_ptr_array_new_full(0, (GDestroyNotify)virPCIVPDResourceCustomFree);
+ return g_steal_pointer(&ro);
+}
+
+void
+virPCIVPDResourceROFree(virPCIVPDResourceRO *ro)
+{
+ if (!ro)
+ return;
+
+ g_free(ro->change_level);
+ g_free(ro->manufacture_id);
+ g_free(ro->part_number);
+ g_free(ro->serial_number);
+ g_ptr_array_unref(ro->vendor_specific);
+ g_free(ro);
+}
+
+virPCIVPDResourceRW *
+virPCIVPDResourceRWNew(void)
+{
+ g_autoptr(virPCIVPDResourceRW) rw = g_new0(virPCIVPDResourceRW, 1);
+ rw->vendor_specific = g_ptr_array_new_full(0, (GDestroyNotify)virPCIVPDResourceCustomFree);
+ rw->system_specific = g_ptr_array_new_full(0, (GDestroyNotify)virPCIVPDResourceCustomFree);
+ return g_steal_pointer(&rw);
+}
+
+void
+virPCIVPDResourceRWFree(virPCIVPDResourceRW *rw)
+{
+ if (!rw)
+ return;
+
+ g_free(rw->asset_tag);
+ g_ptr_array_unref(rw->vendor_specific);
+ g_ptr_array_unref(rw->system_specific);
+ g_free(rw);
+}
+
+void
+virPCIVPDResourceCustomFree(virPCIVPDResourceCustom *custom)
+{
+ g_free(custom->value);
+ g_free(custom);
+}
+
+gboolean
+virPCIVPDResourceCustomCompareIndex(virPCIVPDResourceCustom *a, virPCIVPDResourceCustom *b)
+{
+ if (a == b)
+ return TRUE;
+ else if (a == NULL || b == NULL)
+ return FALSE;
+ else
+ return a->idx == b->idx ? TRUE : FALSE;
+}
+
+/**
+ * virPCIVPDResourceCustomUpsertValue:
+ * @arr: A GPtrArray with virPCIVPDResourceCustom entries to update.
+ * @index: An index character for the keyword.
+ * @value: A pointer to the value to be inserted at a given index.
+ *
+ * Returns: true if a value has been updated successfully, false otherwise.
+ */
+bool
+virPCIVPDResourceCustomUpsertValue(GPtrArray *arr, char index, const char *const value)
+{
+ g_autoptr(virPCIVPDResourceCustom) custom = NULL;
+ virPCIVPDResourceCustom *existing = NULL;
+ guint pos = 0;
+ bool found = false;
+
+ if (arr == NULL || value == NULL)
+ return false;
+
+ custom = g_new0(virPCIVPDResourceCustom, 1);
+ custom->idx = index;
+ custom->value = g_strdup(value);
+ found = g_ptr_array_find_with_equal_func(arr, custom,
+ (GEqualFunc)virPCIVPDResourceCustomCompareIndex,
+ &pos);
+ if (found) {
+ existing = g_ptr_array_index(arr, pos);
+ g_free(existing->value);
+ existing->value = g_steal_pointer(&custom->value);
+ } else {
+ g_ptr_array_add(arr, g_steal_pointer(&custom));
+ }
+ return true;
+}
+
+/**
+ * virPCIVPDResourceUpdateKeyword:
+ * @res: A non-NULL pointer to a virPCIVPDResource where a keyword will be updated.
+ * @readOnly: A bool specifying which section to update (in-memory): read-only or read-write.
+ * @keyword: A non-NULL pointer to a name of the keyword that will be updated.
+ * @value: A pointer to the keyword value or NULL. The value is copied on successful update.
+ *
+ * The caller is responsible for initializing the relevant RO or RW sections of the resource,
+ * otherwise, false will be returned.
+ *
+ * Keyword names are either 2-byte keywords from the spec or their human-readable alternatives
+ * used in XML elements. For vendor-specific and system-specific keywords only V%s and Y%s
+ * (except "YA" which is an asset tag) formatted values are accepted.
+ *
+ * Returns: true if a keyword has been updated successfully, false otherwise.
+ */
+bool
+virPCIVPDResourceUpdateKeyword(virPCIVPDResource *res, const bool readOnly,
+ const char *const keyword, const char *const value)
+{
+ if (!res) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Cannot update the resource: a NULL resource pointer has been provided."));
+ return false;
+ } else if (!keyword) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Cannot update the resource: a NULL keyword pointer has been provided."));
+ return false;
+ }
+
+ if (readOnly) {
+ if (!res->ro) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Cannot update the read-only keyword: RO section not initialized."));
+ return false;
+ }
+
+ if (STREQ("EC", keyword) || STREQ("change_level", keyword)) {
+ g_free(res->ro->change_level);
+ res->ro->change_level = g_strdup(value);
+ return true;
+ } else if (STREQ("MN", keyword) || STREQ("manufacture_id", keyword)) {
+ g_free(res->ro->manufacture_id);
+ res->ro->manufacture_id = g_strdup(value);
+ return true;
+ } else if (STREQ("PN", keyword) || STREQ("part_number", keyword)) {
+ g_free(res->ro->part_number);
+ res->ro->part_number = g_strdup(value);
+ return true;
+ } else if (STREQ("SN", keyword) || STREQ("serial_number", keyword)) {
+ g_free(res->ro->serial_number);
+ res->ro->serial_number = g_strdup(value);
+ return true;
+ } else if (virPCIVPDResourceIsVendorKeyword(keyword)) {
+ if (!virPCIVPDResourceCustomUpsertValue(res->ro->vendor_specific, keyword[1], value)) {
+ return false;
+ }
+ return true;
+ } else if (STREQ("FG", keyword) || STREQ("LC", keyword) || STREQ("PG", keyword)) {
+ /* Legacy PICMIG keywords are skipped on purpose. */
+ return true;
+ } else if (STREQ("CP", keyword)) {
+ /* The CP keyword is currently not supported and is skipped. */
+ return true;
+ }
+
+ } else {
+ if (!res->rw) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _
+ ("Cannot update the read-write keyword: read-write section not initialized."));
+ return false;
+ }
+
+ if (STREQ("YA", keyword) || STREQ("asset_tag", keyword)) {
+ g_free(res->rw->asset_tag);
+ res->rw->asset_tag = g_strdup(value);
+ return true;
+ } else if (virPCIVPDResourceIsVendorKeyword(keyword)) {
+ if (!virPCIVPDResourceCustomUpsertValue(res->rw->vendor_specific, keyword[1], value)) {
+ return false;
+ }
+ return true;
+ } else if (virPCIVPDResourceIsSystemKeyword(keyword)) {
+ if (!virPCIVPDResourceCustomUpsertValue(res->rw->system_specific, keyword[1], value)) {
+ return false;
+ }
+ return true;
+ }
+ }
+ VIR_WARN("Tried to update an unsupported keyword %s: skipping.", keyword);
+ return true;
+}
+
+#ifdef __linux__
+
+/**
+ * virPCIVPDReadVPDBytes:
+ * @vpdFileFd: A file descriptor associated with a file containing PCI device VPD.
+ * @buf: An allocated buffer to use for storing VPD bytes read.
+ * @count: The number of bytes to read from the VPD file descriptor.
+ * @offset: The offset at which bytes need to be read.
+ * @csum: A pointer to a byte containing the current checksum value. Mutated by this function.
+ *
+ * Returns: the number of VPD bytes read from the specified file descriptor. The csum value is
+ * also modified as bytes are read. If an error occurs while reading data from the VPD file
+ * descriptor, it is reported and -1 is returned to the caller. If EOF is occurred, 0 is returned
+ * to the caller.
+ */
+size_t
+virPCIVPDReadVPDBytes(int vpdFileFd, uint8_t *buf, size_t count, off_t offset, uint8_t *csum)
+{
+ ssize_t numRead = pread(vpdFileFd, buf, count, offset);
+
+ if (numRead == -1) {
+ VIR_DEBUG("Unable to read %zu bytes at offset %zd from fd: %d",
+ count, (ssize_t)offset, vpdFileFd);
+ } else if (numRead) {
+ /*
+ * Update the checksum for every byte read. Per the PCI(e) specs
+ * the checksum is correct if the sum of all bytes in VPD from
+ * VPD address 0 up to and including the VPD-R RV field's first
+ * data byte is zero.
+ */
+ while (count--) {
+ *csum += *buf;
+ buf++;
+ }
+ }
+ return numRead;
+}
+
+/**
+ * virPCIVPDParseVPDLargeResourceFields:
+ * @vpdFileFd: A file descriptor associated with a file containing PCI device VPD.
+ * @resPos: A position where the resource data bytes begin in a file descriptor.
+ * @resDataLen: A length of the data portion of a resource.
+ * @readOnly: A boolean showing whether the resource type is VPD-R or VPD-W.
+ * @csum: A pointer to a 1-byte checksum.
+ * @res: A pointer to virPCIVPDResource.
+ *
+ * Returns: a pointer to a VPDResource which needs to be freed by the caller or
+ * NULL if getting it failed for some reason.
+ */
+bool
+virPCIVPDParseVPDLargeResourceFields(int vpdFileFd, uint16_t resPos, uint16_t resDataLen,
+ bool readOnly, uint8_t *csum, virPCIVPDResource *res)
+{
+ g_autofree char *fieldKeyword = NULL;
+ g_autofree char *fieldValue = NULL;
+ virPCIVPDResourceFieldValueFormat fieldFormat = VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST;
+
+ /* A buffer of up to one resource record field size (plus a zero byte) is needed. */
+ g_autofree uint8_t *buf = g_malloc0(PCI_VPD_MAX_FIELD_SIZE + 1);
+ uint16_t fieldDataLen = 0, bytesToRead = 0;
+ uint16_t fieldPos = resPos;
+
+ bool hasChecksum = false;
+ bool hasRW = false;
+
+ while (fieldPos + 3 < resPos + resDataLen) {
+ /* Keyword resources consist of keywords (2 ASCII bytes per the spec) and 1-byte length. */
+ if (virPCIVPDReadVPDBytes(vpdFileFd, buf, 3, fieldPos, csum) != 3) {
+ /* Invalid field encountered which means the resource itself is invalid too. Report
+ * That VPD has invalid format and bail. */
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Could not read a resource field header - VPD has invalid format"));
+ return false;
+ }
+ fieldDataLen = buf[2];
+ /* Change the position to the field's data portion skipping the keyword and length bytes. */
+ fieldPos += 3;
+ fieldKeyword = g_strndup((char *)buf, 2);
+ fieldFormat = virPCIVPDResourceGetFieldValueFormat(fieldKeyword);
+
+ /* Handle special cases first */
+ if (!readOnly && fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Unexpected RV keyword in the read-write section."));
+ return false;
+ } else if (readOnly && fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Unexpected RW keyword in the read-only section."));
+ return false;
+ }
+
+ /* Determine how many bytes to read per field value type. */
+ switch (fieldFormat) {
+ case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT:
+ case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR:
+ case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_BINARY:
+ bytesToRead = fieldDataLen;
+ break;
+ case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD:
+ /* Only need one byte to be read and accounted towards
+ * the checksum calculation. */
+ bytesToRead = 1;
+ break;
+ case VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST:
+ /* The VPD format could be extended in future versions with new
+ * keywords - attempt to skip them by reading past them since
+ * their data length would still be specified. */
+ VIR_DEBUG("Could not determine a field value format for keyword: %s", fieldKeyword);
+ bytesToRead = fieldDataLen;
+ break;
+ default:
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Unexpected field value format encountered."));
+ return false;
+ }
+
+ if (virPCIVPDReadVPDBytes(vpdFileFd, buf, bytesToRead, fieldPos, csum) != bytesToRead) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Could not parse a resource field data - VPD has invalid format"));
+ return false;
+ }
+ /* Advance the position to the first byte of the next field. */
+ fieldPos += fieldDataLen;
+
+ if (fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT) {
+ /* Trim whitespace around a retrieved value and set it to be a field's value. Cases
+ * where unnecessary whitespace was present around a field value have been encountered
+ * in the wild.
+ */
+ fieldValue = g_strstrip(g_strndup((char *)buf, fieldDataLen));
+ if (!virPCIVPDResourceIsValidTextValue(fieldValue)) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Field value contains invalid characters"));
+ return false;
+ }
+ } else if (fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD) {
+ if (*csum) {
+ /* All bytes up to and including the checksum byte should add up to 0. */
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Checksum validation has failed"));
+ return false;
+ }
+ hasChecksum = true;
+ g_free(g_steal_pointer(&fieldKeyword));
+ g_free(g_steal_pointer(&fieldValue));
+ continue;
+ } else if (fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR) {
+ /* Skip the read-write space since it is used for indication only. */
+ hasRW = true;
+ g_free(g_steal_pointer(&fieldKeyword));
+ g_free(g_steal_pointer(&fieldValue));
+ } else if (fieldFormat == VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST) {
+ /* Skip unknown fields */
+ g_free(g_steal_pointer(&fieldKeyword));
+ g_free(g_steal_pointer(&fieldValue));
+ continue;
+ } else {
+ fieldValue = g_malloc(fieldDataLen);
+ memcpy(fieldValue, buf, fieldDataLen);
+ }
+
+ if (readOnly) {
+ if (!res->ro)
+ res->ro = virPCIVPDResourceRONew();
+ } else {
+ if (!res->rw)
+ res->rw = virPCIVPDResourceRWNew();
+ }
+ /* The field format, keyword and value are determined. Attempt to update the resource. */
+ if (!virPCIVPDResourceUpdateKeyword(res, readOnly, fieldKeyword, fieldValue)) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Could not update the VPD resource keyword: %s"), fieldKeyword);
+ return false;
+ }
+ /* No longer need those since copies were made during the keyword update. */
+ g_free(g_steal_pointer(&fieldKeyword));
+ g_free(g_steal_pointer(&fieldValue));
+ }
+ if (readOnly && !hasChecksum) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("VPD-R does not contain the mandatory RV field"));
+ return false;
+ } else if (!readOnly && !hasRW) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("VPD-W does not contain the mandatory RW field"));
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * virPCIVPDParseVPDLargeResourceString:
+ * @vpdFileFd: A file descriptor associated with a file containing PCI device VPD.
+ * @resPos: A position where the resource data bytes begin in a file descriptor.
+ * @resDataLen: A length of the data portion of a resource.
+ * @csum: A pointer to a 1-byte checksum.
+ *
+ * Returns: a pointer to a VPDResource which needs to be freed by the caller or
+ * NULL if getting it failed for some reason.
+ */
+bool
+virPCIVPDParseVPDLargeResourceString(int vpdFileFd, uint16_t resPos,
+ uint16_t resDataLen, uint8_t *csum, virPCIVPDResource *res)
+{
+ g_autofree char *resValue = NULL;
+
+ /* The resource value is not NULL-terminated so add one more byte. */
+ g_autofree char *buf = g_malloc0(resDataLen + 1);
+
+ if (virPCIVPDReadVPDBytes(vpdFileFd, (uint8_t *)buf, resDataLen, resPos, csum) != resDataLen) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Could not read a part of a resource - VPD has invalid format"));
+ return false;
+ }
+ resValue = g_strdup(g_strstrip(buf));
+ if (!virPCIVPDResourceIsValidTextValue(resValue)) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("The string resource has invalid characters in its value"));
+ return false;
+ }
+ res->name = g_steal_pointer(&resValue);
+ return true;
+}
+
+/**
+ * virPCIVPDParse:
+ * @vpdFileFd: a file descriptor associated with a file containing PCI device VPD.
+ *
+ * Parse a PCI device's Vital Product Data (VPD) contained in a file descriptor.
+ *
+ * Returns: a pointer to a GList of VPDResource types which needs to be freed by the caller or
+ * NULL if getting it failed for some reason.
+ */
+virPCIVPDResource *
+virPCIVPDParse(int vpdFileFd)
+{
+ /* A checksum which is calculated as a sum of all bytes from VPD byte 0 up to
+ * the checksum byte in the RV field's value. The RV field is only present in the
+ * VPD-R resource and the checksum byte there is the first byte of the field's value.
+ * The checksum byte in RV field is actually a two's complement of the sum of all bytes
+ * of VPD that come before it so adding the two together must produce 0 if data
+ * was not corrupted and VPD storage is intact.
+ */
+ uint8_t csum = 0;
+ uint8_t headerBuf[2];
+
+ bool isWellFormed = false;
+ uint16_t resPos = 0, resDataLen;
+ uint8_t tag = 0;
+ bool endResReached = false, hasReadOnly = false;
+
+ g_autoptr(virPCIVPDResource) res = g_new0(virPCIVPDResource, 1);
+
+ while (resPos <= PCI_VPD_ADDR_MASK) {
+ /* Read the resource data type tag. */
+ if (virPCIVPDReadVPDBytes(vpdFileFd, &tag, 1, resPos, &csum) != 1)
+ break;
+
+ /* 0x80 == 0b10000000 - the large resource data type flag. */
+ if (tag & PCI_VPD_LARGE_RESOURCE_FLAG) {
+ if (resPos > PCI_VPD_ADDR_MASK + 1 - 3) {
+ /* Bail if the large resource starts at the position
+ * where the end tag should be. */
+ break;
+ }
+ /* Read the two length bytes of the large resource record. */
+ if (virPCIVPDReadVPDBytes(vpdFileFd, headerBuf, 2, resPos + 1, &csum) != 2)
+ break;
+
+ resDataLen = headerBuf[0] + (headerBuf[1] << 8);
+ /* Change the position to the byte following the tag and length bytes. */
+ resPos += 3;
+ } else {
+ /* Handle a small resource record.
+ * 0xxxxyyy & 00000111, where xxxx - resource data type bits, yyy - length bits. */
+ resDataLen = tag & 7;
+ /* 0xxxxyyy >> 3 == 0000xxxx */
+ tag >>= 3;
+ /* Change the position to the byte past the byte containing tag and length bits. */
+ resPos += 1;
+ }
+ if (tag == PCI_VPD_RESOURCE_END_TAG) {
+ /* Stop VPD traversal since the end tag was encountered. */
+ endResReached = true;
+ break;
+ }
+ if (resDataLen > PCI_VPD_ADDR_MASK + 1 - resPos) {
+ /* Bail if the resource is too long to fit into the VPD address space. */
+ break;
+ }
+
+ switch (tag) {
+ /* Large resource type which is also a string: 0x80 | 0x02 = 0x82 */
+ case PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_STRING_RESOURCE_FLAG:
+ isWellFormed = virPCIVPDParseVPDLargeResourceString(vpdFileFd, resPos, resDataLen,
+ &csum, res);
+ break;
+ /* Large resource type which is also a VPD-R: 0x80 | 0x10 == 0x90 */
+ case PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG:
+ isWellFormed = virPCIVPDParseVPDLargeResourceFields(vpdFileFd, resPos,
+ resDataLen, true, &csum, res);
+ /* Encountered the VPD-R tag. The resource record parsing also validates
+ * the presence of the required checksum in the RV field. */
+ hasReadOnly = true;
+ break;
+ /* Large resource type which is also a VPD-W: 0x80 | 0x11 == 0x91 */
+ case PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_WRITE_LARGE_RESOURCE_FLAG:
+ isWellFormed = virPCIVPDParseVPDLargeResourceFields(vpdFileFd, resPos, resDataLen,
+ false, &csum, res);
+ break;
+ default:
+ /* While we cannot parse unknown resource types, they can still be skipped
+ * based on the header and data length. */
+ VIR_DEBUG("Encountered an unexpected VPD resource tag: %#x", tag);
+ resPos += resDataLen;
+ continue;
+ }
+
+ if (!isWellFormed) {
+ VIR_DEBUG("Encountered an invalid VPD");
+ return NULL;
+ }
+
+ /* Continue processing other resource records. */
+ resPos += resDataLen;
+ }
+ if (!hasReadOnly) {
+ VIR_DEBUG("Encountered an invalid VPD: does not have a VPD-R record");
+ return NULL;
+ } else if (!endResReached) {
+ /* Does not have an end tag. */
+ VIR_DEBUG("Encountered an invalid VPD");
+ return NULL;
+ }
+ return g_steal_pointer(&res);
+}
+
+#else /* ! __linux__ */
+
+size_t
+virPCIVPDReadVPDBytes(int vpdFileFd G_GNUC_UNUSED,
+ uint8_t *buf G_GNUC_UNUSED,
+ size_t count G_GNUC_UNUSED,
+ off_t offset G_GNUC_UNUSED,
+ uint8_t *csum G_GNUC_UNUSED)
+{
+ virReportError(VIR_ERR_NO_SUPPORT, "%s",
+ _("PCI VPD reporting not available on this platform"));
+ return 0;
+}
+
+bool
+virPCIVPDParseVPDLargeResourceString(int vpdFileFd G_GNUC_UNUSED,
+ uint16_t resPos G_GNUC_UNUSED,
+ uint16_t resDataLen G_GNUC_UNUSED,
+ uint8_t *csum G_GNUC_UNUSED,
+ virPCIVPDResource *res G_GNUC_UNUSED)
+{
+ virReportError(VIR_ERR_NO_SUPPORT, "%s",
+ _("PCI VPD reporting not available on this platform"));
+ return false;
+}
+
+bool
+virPCIVPDParseVPDLargeResourceFields(int vpdFileFd G_GNUC_UNUSED,
+ uint16_t resPos G_GNUC_UNUSED,
+ uint16_t resDataLen G_GNUC_UNUSED,
+ bool readOnly G_GNUC_UNUSED,
+ uint8_t *csum G_GNUC_UNUSED,
+ virPCIVPDResource *res G_GNUC_UNUSED)
+{
+ virReportError(VIR_ERR_NO_SUPPORT, "%s",
+ _("PCI VPD reporting not available on this platform"));
+ return false;
+}
+
+virPCIVPDResource *
+virPCIVPDParse(int vpdFileFd G_GNUC_UNUSED)
+{
+ virReportError(VIR_ERR_NO_SUPPORT, "%s",
+ _("PCI VPD reporting not available on this platform"));
+ return NULL;
+}
+
+#endif /* ! __linux__ */
--- /dev/null
+/*
+ * virpcivpd.h: helper APIs for working with the PCI/PCIe VPD capability
+ *
+ * Copyright (C) 2021 Canonical Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "internal.h"
+
+typedef struct virPCIVPDResourceCustom virPCIVPDResourceCustom;
+struct virPCIVPDResourceCustom {
+ char idx;
+ char *value;
+};
+
+typedef struct virPCIVPDResourceRO virPCIVPDResourceRO;
+struct virPCIVPDResourceRO {
+ char *part_number;
+ char *change_level;
+ char *manufacture_id;
+ char *serial_number;
+ GPtrArray *vendor_specific;
+};
+
+typedef struct virPCIVPDResourceRW virPCIVPDResourceRW;
+struct virPCIVPDResourceRW {
+ char *asset_tag;
+ GPtrArray *vendor_specific;
+ GPtrArray *system_specific;
+};
+
+typedef struct virPCIVPDResource virPCIVPDResource;
+struct virPCIVPDResource {
+ char *name;
+ virPCIVPDResourceRO *ro;
+ virPCIVPDResourceRW *rw;
+};
+
+
+virPCIVPDResource *virPCIVPDParse(int vpdFileFd);
+void virPCIVPDResourceFree(virPCIVPDResource *res);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(virPCIVPDResource, virPCIVPDResourceFree);
+
+virPCIVPDResourceRO *virPCIVPDResourceRONew(void);
+void virPCIVPDResourceROFree(virPCIVPDResourceRO *ro);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(virPCIVPDResourceRO, virPCIVPDResourceROFree);
+
+virPCIVPDResourceRW *virPCIVPDResourceRWNew(void);
+void virPCIVPDResourceRWFree(virPCIVPDResourceRW *rw);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(virPCIVPDResourceRW, virPCIVPDResourceRWFree);
+
+bool
+virPCIVPDResourceUpdateKeyword(virPCIVPDResource *res, const bool readOnly,
+ const char *const keyword, const char *const value);
+
+void virPCIVPDResourceCustomFree(virPCIVPDResourceCustom *custom);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(virPCIVPDResourceCustom, virPCIVPDResourceCustomFree);
--- /dev/null
+/*
+ * virpcivpdpriv.h: helper APIs for working with the PCI/PCIe VPD capability
+ *
+ * Copyright (C) 2021 Canonical Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBVIRT_VIRPCIVPDPRIV_H_ALLOW
+# error "virpcivpdpriv.h may only be included by virpcivpd.c or test suites"
+#endif /* LIBVIRT_VIRPCIVPDPRIV_H_ALLOW */
+
+#pragma once
+
+#include "virpcivpd.h"
+
+/*
+ * PCI Local bus (2.2+, Appendix I) and PCIe 4.0+ (7.9.19 VPD Capability) define
+ * the VPD capability structure (8 bytes in total) and VPD registers that can be used to access
+ * VPD data including:
+ * bit 31 of the first 32-bit DWORD: data transfer completion flag (between the VPD data register
+ * and the VPD data storage hardware);
+ * bits 30:16 of the first 32-bit DWORD: VPD address of the first VPD data byte to be accessed;
+ * bits 31:0 of the second 32-bit DWORD: VPD data bytes with LSB being pointed to by the VPD address.
+ *
+ * Given that only 15 bits (30:16) are allocated for VPD address its mask is 0x7fff.
+*/
+#define PCI_VPD_ADDR_MASK 0x7FFF
+
+/*
+ * VPD data consists of small and large resource data types. Information within a resource type
+ * consists of a 2-byte keyword, 1-byte length and data bytes (up to 255).
+*/
+#define PCI_VPD_MAX_FIELD_SIZE 255
+#define PCI_VPD_LARGE_RESOURCE_FLAG 0x80
+#define PCI_VPD_STRING_RESOURCE_FLAG 0x02
+#define PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG 0x10
+#define PCI_VPD_READ_WRITE_LARGE_RESOURCE_FLAG 0x11
+#define PCI_VPD_RESOURCE_END_TAG 0x0F
+#define PCI_VPD_RESOURCE_END_VAL PCI_VPD_RESOURCE_END_TAG << 3
+
+typedef enum {
+ VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT = 1,
+ VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_BINARY,
+ VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD,
+ VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR,
+ VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST
+} virPCIVPDResourceFieldValueFormat;
+
+virPCIVPDResourceFieldValueFormat virPCIVPDResourceGetFieldValueFormat(const char *value);
+
+bool virPCIVPDResourceIsValidTextValue(const char *value);
+
+gboolean
+virPCIVPDResourceCustomCompareIndex(virPCIVPDResourceCustom *a, virPCIVPDResourceCustom *b);
+
+bool
+virPCIVPDResourceCustomUpsertValue(GPtrArray *arr, char index, const char *const value);
+
+size_t
+virPCIVPDReadVPDBytes(int vpdFileFd, uint8_t *buf, size_t count, off_t offset, uint8_t *csum);
+
+bool virPCIVPDParseVPDLargeResourceFields(int vpdFileFd, uint16_t resPos, uint16_t resDataLen,
+ bool readOnly, uint8_t *csum, virPCIVPDResource *res);
+
+bool virPCIVPDParseVPDLargeResourceString(int vpdFileFd, uint16_t resPos, uint16_t resDataLen,
+ uint8_t *csum, virPCIVPDResource *res);
{ 'name': 'virtimetest' },
{ 'name': 'virtypedparamtest' },
{ 'name': 'viruritest' },
+ { 'name': 'virpcivpdtest' },
{ 'name': 'vshtabletest', 'link_with': [ libvirt_shell_lib ] },
{ 'name': 'virmigtest' },
]
return g_strdup(path);
}
+
+#ifdef __linux__
+/**
+ * virCreateAnonymousFile:
+ * @data: a pointer to data to be written into a new file.
+ * @len: the length of data to be written (in bytes).
+ *
+ * Create a fake fd, write initial data to it.
+ *
+ */
+int
+virCreateAnonymousFile(const uint8_t *data, size_t len)
+{
+ int fd = -1;
+ char path[] = abs_builddir "testutils-memfd-XXXXXX";
+ /* A temp file is used since not all supported distributions support memfd. */
+ if ((fd = g_mkstemp_full(path, O_RDWR | O_CLOEXEC, S_IRUSR | S_IWUSR)) < 0) {
+ return fd;
+ }
+ g_unlink(path);
+
+ if (safewrite(fd, data, len) != len) {
+ VIR_TEST_DEBUG("%s: %s", "failed to write to an anonymous file",
+ g_strerror(errno));
+ goto cleanup;
+ }
+ return fd;
+ cleanup:
+ if (VIR_CLOSE(fd) < 0) {
+ VIR_TEST_DEBUG("%s: %s", "failed to close an anonymous file",
+ g_strerror(errno));
+ }
+ return -1;
+}
+#endif
char *
virTestStablePath(const char *path);
+
+#ifdef __linux__
+int virCreateAnonymousFile(const uint8_t *data, size_t len);
+#endif
--- /dev/null
+/*
+ * Copyright (C) 2021 Canonical Ltd.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "internal.h"
+#include "testutils.h"
+#include "virpcivpd.h"
+
+#define LIBVIRT_VIRPCIVPDPRIV_H_ALLOW
+
+#include "virpcivpdpriv.h"
+#include "virlog.h"
+
+#define VIR_FROM_THIS VIR_FROM_NONE
+
+#ifdef __linux__
+
+VIR_LOG_INIT("tests.vpdtest");
+
+
+typedef struct _TestPCIVPDKeywordValue {
+ const char *keyword;
+ const char *value;
+ char **actual;
+} TestPCIVPDKeywordValue;
+
+static int
+testPCIVPDResourceBasic(const void *data G_GNUC_UNUSED)
+{
+ size_t i = 0;
+ g_autoptr(virPCIVPDResourceRO) ro = virPCIVPDResourceRONew();
+ g_autoptr(virPCIVPDResourceRW) rw = virPCIVPDResourceRWNew();
+ /* Note: when the same keyword is updated multiple times the
+ * virPCIVPDResourceUpdateKeyword function is expected to free the
+ * previous value whether it is a fixed keyword or a custom one.
+ * */
+ const TestPCIVPDKeywordValue readOnlyCases[] = {
+ {.keyword = "EC", .value = "level1", .actual = &ro->change_level},
+ {.keyword = "EC", .value = "level2", .actual = &ro->change_level},
+ {.keyword = "change_level", .value = "level3", .actual = &ro->change_level},
+ {.keyword = "PN", .value = "number1", .actual = &ro->part_number},
+ {.keyword = "PN", .value = "number2", .actual = &ro->part_number},
+ {.keyword = "part_number", .value = "number3", .actual = &ro->part_number},
+ {.keyword = "MN", .value = "id1", .actual = &ro->manufacture_id},
+ {.keyword = "MN", .value = "id2", .actual = &ro->manufacture_id},
+ {.keyword = "manufacture_id", .value = "id3", &ro->manufacture_id},
+ {.keyword = "SN", .value = "serial1", .actual = &ro->serial_number},
+ {.keyword = "SN", .value = "serial2", .actual = &ro->serial_number},
+ {.keyword = "serial_number", .value = "serial3", .actual = &ro->serial_number},
+ };
+ const TestPCIVPDKeywordValue readWriteCases[] = {
+ {.keyword = "YA", .value = "tag1", .actual = &ro->change_level},
+ {.keyword = "YA", .value = "tag2", .actual = &ro->change_level},
+ {.keyword = "asset_tag", .value = "tag3", .actual = &ro->change_level},
+ };
+ const TestPCIVPDKeywordValue unsupportedFieldCases[] = {
+ {.keyword = "FG", .value = "42", .actual = NULL},
+ {.keyword = "LC", .value = "42", .actual = NULL},
+ {.keyword = "PG", .value = "42", .actual = NULL},
+ {.keyword = "CP", .value = "42", .actual = NULL},
+ {.keyword = "EX", .value = "42", .actual = NULL},
+ };
+ size_t numROCases = sizeof(readOnlyCases) / sizeof(TestPCIVPDKeywordValue);
+ size_t numRWCases = sizeof(readWriteCases) / sizeof(TestPCIVPDKeywordValue);
+ size_t numUnsupportedCases = sizeof(unsupportedFieldCases) / sizeof(TestPCIVPDKeywordValue);
+ g_autoptr(virPCIVPDResource) res = g_new0(virPCIVPDResource, 1);
+ virPCIVPDResourceCustom *custom = NULL;
+
+ g_autofree char *val = g_strdup("testval");
+ res->name = g_steal_pointer(&val);
+
+ /* RO has not been initialized - make sure updates fail. */
+ for (i = 0; i < numROCases; ++i) {
+ if (virPCIVPDResourceUpdateKeyword(res, true,
+ readOnlyCases[i].keyword,
+ readOnlyCases[i].value))
+ return -1;
+ }
+ /* RW has not been initialized - make sure updates fail. */
+ for (i = 0; i < numRWCases; ++i) {
+ if (virPCIVPDResourceUpdateKeyword(res, false,
+ readWriteCases[i].keyword,
+ readWriteCases[i].value))
+ return -1;
+ }
+ /* Initialize RO */
+ res->ro = g_steal_pointer(&ro);
+
+ /* Update keywords one by one and compare actual values with the expected ones. */
+ for (i = 0; i < numROCases; ++i) {
+ if (!virPCIVPDResourceUpdateKeyword(res, true,
+ readOnlyCases[i].keyword,
+ readOnlyCases[i].value))
+ return -1;
+ if (STRNEQ(readOnlyCases[i].value, *readOnlyCases[i].actual))
+ return -1;
+ }
+
+ /* Do a basic vendor field check. */
+ if (!virPCIVPDResourceUpdateKeyword(res, true, "V0", "vendor0"))
+ return -1;
+
+ if (res->ro->vendor_specific->len != 1)
+ return -1;
+
+ custom = g_ptr_array_index(res->ro->vendor_specific, 0);
+ if (custom->idx != '0' || STRNEQ(custom->value, "vendor0"))
+ return -1;
+
+ /* Make sure unsupported RO keyword updates are not fatal. */
+ for (i = 0; i < numUnsupportedCases; ++i) {
+ if (!virPCIVPDResourceUpdateKeyword(res, true,
+ unsupportedFieldCases[i].keyword,
+ unsupportedFieldCases[i].value))
+ return -1;
+ }
+
+ /* Check that RW updates fail if RW has not been initialized. */
+ if (virPCIVPDResourceUpdateKeyword(res, false, "YA", "tag1"))
+ return -1;
+
+ if (virPCIVPDResourceUpdateKeyword(res, false, "asset_tag", "tag1"))
+ return -1;
+
+ /* Initialize RW */
+ res->rw = g_steal_pointer(&rw);
+ if (!virPCIVPDResourceUpdateKeyword(res, false, "YA", "tag1")
+ || STRNEQ(res->rw->asset_tag, "tag1"))
+ return -1;
+
+ if (!virPCIVPDResourceUpdateKeyword(res, false, "asset_tag", "tag2")
+ || STRNEQ(res->rw->asset_tag, "tag2"))
+ return -1;
+
+ /* Do a basic system field check. */
+ if (!virPCIVPDResourceUpdateKeyword(res, false, "Y0", "system0"))
+ return -1;
+
+ if (res->rw->system_specific->len != 1)
+ return -1;
+
+ custom = g_ptr_array_index(res->rw->system_specific, 0);
+ if (custom->idx != '0' || STRNEQ(custom->value, "system0"))
+ return -1;
+
+ /* Make sure unsupported RW keyword updates are not fatal. */
+ for (i = 0; i < numUnsupportedCases; ++i) {
+ if (!virPCIVPDResourceUpdateKeyword(res, false,
+ unsupportedFieldCases[i].keyword,
+ unsupportedFieldCases[i].value))
+ return -1;
+ }
+
+
+ /* Just make sure the name has not been changed during keyword updates. */
+ if (!STREQ_NULLABLE(res->name, "testval"))
+ return -1;
+
+ return 0;
+}
+
+static int
+testPCIVPDResourceCustomCompareIndex(const void *data G_GNUC_UNUSED)
+{
+ g_autoptr(virPCIVPDResourceCustom) a = NULL, b = NULL;
+
+ /* Both are NULL */
+ if (!virPCIVPDResourceCustomCompareIndex(a, b))
+ return -1;
+
+ /* a is not NULL */
+ a = g_new0(virPCIVPDResourceCustom, 1);
+ if (virPCIVPDResourceCustomCompareIndex(a, b))
+ return -1;
+
+ /* Reverse */
+ if (virPCIVPDResourceCustomCompareIndex(b, a))
+ return -1;
+
+ /* Same index, different strings */
+ b = g_new0(virPCIVPDResourceCustom, 1);
+ a->idx = 'z';
+ a->value = g_strdup("42");
+ b->idx = 'z';
+ b->value = g_strdup("24");
+ if (!virPCIVPDResourceCustomCompareIndex(b, a))
+ return -1;
+
+ /* Different index, different strings */
+ a->idx = 'a';
+ if (virPCIVPDResourceCustomCompareIndex(b, a))
+ return -1;
+
+ virPCIVPDResourceCustomFree(a);
+ virPCIVPDResourceCustomFree(b);
+ a = g_new0(virPCIVPDResourceCustom, 1);
+ b = g_new0(virPCIVPDResourceCustom, 1);
+
+ /* Same index, same strings */
+ a->idx = 'z';
+ a->value = g_strdup("42");
+ b->idx = 'z';
+ b->value = g_strdup("42");
+ if (!virPCIVPDResourceCustomCompareIndex(b, a))
+ return -1;
+
+ /* Different index, same strings */
+ a->idx = 'a';
+ if (virPCIVPDResourceCustomCompareIndex(b, a))
+ return -1;
+
+ /* Different index, same value pointers */
+ g_free(b->value);
+ b->value = a->value;
+ if (virPCIVPDResourceCustomCompareIndex(b, a))
+ return -1;
+
+ b->value = NULL;
+
+ return 0;
+}
+
+static int
+testPCIVPDResourceCustomUpsertValue(const void *data G_GNUC_UNUSED)
+{
+ g_autoptr(GPtrArray) arr = g_ptr_array_new_full(0, (GDestroyNotify)virPCIVPDResourceCustomFree);
+ virPCIVPDResourceCustom *custom = NULL;
+ if (!virPCIVPDResourceCustomUpsertValue(arr, 'A', "testval"))
+ return -1;
+
+ if (arr->len != 1)
+ return -1;
+
+ custom = g_ptr_array_index(arr, 0);
+ if (custom == NULL || custom->idx != 'A' || STRNEQ_NULLABLE(custom->value, "testval"))
+ return -1;
+
+ /* Idempotency */
+ if (!virPCIVPDResourceCustomUpsertValue(arr, 'A', "testval"))
+ return -1;
+
+ if (arr->len != 1)
+ return -1;
+
+ custom = g_ptr_array_index(arr, 0);
+ if (custom == NULL || custom->idx != 'A' || STRNEQ_NULLABLE(custom->value, "testval"))
+ return -1;
+
+ /* Existing value updates. */
+ if (!virPCIVPDResourceCustomUpsertValue(arr, 'A', "testvalnew"))
+ return -1;
+
+ if (arr->len != 1)
+ return -1;
+
+ custom = g_ptr_array_index(arr, 0);
+ if (custom == NULL || custom->idx != 'A' || STRNEQ_NULLABLE(custom->value, "testvalnew"))
+ return -1;
+
+ /* Inserting multiple values */
+ if (!virPCIVPDResourceCustomUpsertValue(arr, '1', "42"))
+ return -1;
+
+ if (arr->len != 2)
+ return -1;
+
+ custom = g_ptr_array_index(arr, 1);
+ if (custom == NULL || custom->idx != '1' || STRNEQ_NULLABLE(custom->value, "42"))
+ return -1;
+
+ return 0;
+}
+
+
+typedef struct _TestPCIVPDExpectedString {
+ const char *keyword;
+ bool expected;
+} TestPCIVPDExpectedString;
+
+/*
+ * testPCIVPDIsValidTextValue:
+ *
+ * Test expected text value validation. Static metadata about possible values is taken
+ * from the PCI(e) standards and based on some real-world hardware examples.
+ * */
+static int
+testPCIVPDIsValidTextValue(const void *data G_GNUC_UNUSED)
+{
+ size_t i = 0;
+
+ const TestPCIVPDExpectedString textValueCases[] = {
+ /* Numbers */
+ {"42", true},
+ /* Alphanumeric */
+ {"DCM1001008FC52101008FC53201008FC54301008FC5", true},
+ /* Dots */
+ {"DSV1028VPDR.VER1.0", true},
+ /* Whitespace presence */
+ {"NMVIntel Corp", true},
+ /* Comma and spaces */
+ {"BlueField-2 DPU 25GbE Dual-Port SFP56, Tall Bracket", true},
+ /* Equal signs and colons. */
+ {"MLX:MN=MLNX:CSKU=V2:UUID=V3:PCI=V0:MODL=BF2H332A", true},
+ /* Dashes */
+ {"MBF2H332A-AEEOT", true},
+ {"under_score_example", true},
+ {"", true},
+ {";", true},
+ {"\\42", false},
+ {"/42", false},
+ };
+ for (i = 0; i < sizeof(textValueCases) / sizeof(textValueCases[0]); ++i) {
+ if (virPCIVPDResourceIsValidTextValue(textValueCases[i].keyword) !=
+ textValueCases[i].expected)
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * testPCIVPDGetFieldValueFormat:
+ *
+ * A simple test to assess the functionality of the
+ * virPCIVPDResourceGetFieldValueFormat function.
+ * */
+static int
+testPCIVPDGetFieldValueFormat(const void *data G_GNUC_UNUSED)
+{
+ typedef struct _TestPCIVPDExpectedFieldValueFormat {
+ const char *keyword;
+ virPCIVPDResourceFieldValueFormat expected;
+ } TestPCIVPDExpectedFieldValueFormat;
+
+ size_t i = 0;
+
+ const TestPCIVPDExpectedFieldValueFormat valueFormatCases[] = {
+ {"SN", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT},
+ {"EC", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT},
+ {"MN", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT},
+ {"PN", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT},
+ {"RV", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RESVD},
+ {"RW", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_RDWR},
+ {"VA", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT},
+ {"YA", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT},
+ {"YZ", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_TEXT},
+ {"CP", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_BINARY},
+ /* Invalid keywords. */
+ {"", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ {"sn", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ {"ec", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ {"mn", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ {"pn", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ {"4", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ {"42", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ {"Y", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ {"V", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ {"v", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ {"vA", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ {"va", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ {"ya", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ {"Ya", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ /* 2 bytes but not present in the spec. */
+ {"EX", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ /* Many numeric bytes. */
+ {"4242", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ /* Many letters. */
+ {"EXAMPLE", VIR_PCI_VPD_RESOURCE_FIELD_VALUE_FORMAT_LAST},
+ };
+ for (i = 0; i < sizeof(valueFormatCases) / sizeof(valueFormatCases[0]); ++i) {
+ if (virPCIVPDResourceGetFieldValueFormat(valueFormatCases[i].keyword) !=
+ valueFormatCases[i].expected)
+ return -1;
+ }
+ return 0;
+}
+
+# define VPD_STRING_RESOURCE_EXAMPLE_HEADER \
+ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_STRING_RESOURCE_FLAG, 0x08, 0x00
+
+# define VPD_STRING_RESOURCE_EXAMPLE_DATA \
+ 't', 'e', 's', 't', 'n', 'a', 'm', 'e'
+
+# define VPD_R_FIELDS_EXAMPLE_HEADER \
+ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x16, 0x00
+
+# define VPD_R_EXAMPLE_VALID_RV_FIELD \
+ 'R', 'V', 0x02, 0x31, 0x00
+
+# define VPD_R_EXAMPLE_INVALID_RV_FIELD \
+ 'R', 'V', 0x02, 0xFF, 0x00
+
+# define VPD_R_EXAMPLE_FIELDS \
+ 'P', 'N', 0x02, '4', '2', \
+ 'E', 'C', 0x04, '4', '2', '4', '2', \
+ 'V', 'A', 0x02, 'E', 'X'
+
+# define VPD_R_FIELDS_EXAMPLE_DATA \
+ VPD_R_EXAMPLE_FIELDS, \
+ VPD_R_EXAMPLE_VALID_RV_FIELD
+
+# define VPD_W_FIELDS_EXAMPLE_HEADER \
+ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_WRITE_LARGE_RESOURCE_FLAG, 0x19, 0x00
+
+# define VPD_W_EXAMPLE_FIELDS \
+ 'V', 'Z', 0x02, '4', '2', \
+ 'Y', 'A', 0x04, 'I', 'D', '4', '2', \
+ 'Y', 'F', 0x02, 'E', 'X', \
+ 'Y', 'E', 0x00, \
+ 'R', 'W', 0x02, 0x00, 0x00
+
+static int
+testVirPCIVPDReadVPDBytes(const void *opaque G_GNUC_UNUSED)
+{
+ int fd = -1;
+ g_autofree uint8_t *buf = NULL;
+ uint8_t csum = 0;
+ size_t readBytes = 0;
+ size_t dataLen = 0;
+
+ /* An example of a valid VPD record with one VPD-R resource and 2 fields. */
+ uint8_t fullVPDExample[] = {
+ VPD_STRING_RESOURCE_EXAMPLE_HEADER, VPD_STRING_RESOURCE_EXAMPLE_DATA,
+ VPD_R_FIELDS_EXAMPLE_HEADER, VPD_R_FIELDS_EXAMPLE_DATA,
+ PCI_VPD_RESOURCE_END_VAL
+ };
+ dataLen = sizeof(fullVPDExample) / sizeof(uint8_t) - 2;
+ buf = g_malloc0(dataLen);
+
+ fd = virCreateAnonymousFile(fullVPDExample, dataLen);
+
+ readBytes = virPCIVPDReadVPDBytes(fd, buf, dataLen, 0, &csum);
+
+ if (readBytes != dataLen) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ "The number of bytes read %zu is lower than expected %zu ",
+ readBytes, dataLen);
+ return -1;
+ }
+
+ if (csum) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ "The sum of all VPD bytes up to and including the checksum byte"
+ "is equal to zero: 0x%02x", csum);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+testVirPCIVPDParseVPDStringResource(const void *opaque G_GNUC_UNUSED)
+{
+ int fd = -1;
+ uint8_t csum = 0;
+ size_t dataLen = 0;
+ bool result = false;
+
+ g_autoptr(virPCIVPDResource) res = g_new0(virPCIVPDResource, 1);
+ const char *expectedValue = "testname";
+
+ const uint8_t stringResExample[] = {
+ VPD_STRING_RESOURCE_EXAMPLE_DATA
+ };
+
+ dataLen = sizeof(stringResExample) / sizeof(uint8_t);
+ fd = virCreateAnonymousFile(stringResExample, dataLen);
+ result = virPCIVPDParseVPDLargeResourceString(fd, 0, dataLen, &csum, res);
+ VIR_FORCE_CLOSE(fd);
+
+ if (!result) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ "Could not parse the example resource.");
+ return -1;
+ }
+
+ if (STRNEQ(expectedValue, res->name)) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ "Unexpected string resource value: %s, expected: %s",
+ res->name, expectedValue);
+ return -1;
+ }
+ return 0;
+}
+
+static int
+testVirPCIVPDValidateExampleReadOnlyFields(virPCIVPDResource *res)
+{
+ const char *expectedName = "testname";
+ virPCIVPDResourceCustom *custom = NULL;
+ if (STRNEQ(res->name, expectedName)) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ "Unexpected string resource value: %s, expected: %s",
+ res->name, expectedName);
+ return -1;
+ }
+
+ if (!res->ro) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ "Read-only keywords are missing from the VPD resource.");
+ return -1;
+ }
+
+ if (STRNEQ_NULLABLE(res->ro->part_number, "42")) {
+ return -1;
+ } else if (STRNEQ_NULLABLE(res->ro->change_level, "4242")) {
+ return -1;
+ }
+ if (!res->ro->vendor_specific)
+ return -1;
+
+ custom = g_ptr_array_index(res->ro->vendor_specific, 0);
+ if (custom->idx != 'A' || STRNEQ_NULLABLE(custom->value, "EX"))
+ return -1;
+
+ return 0;
+}
+
+static int
+testVirPCIVPDParseFullVPD(const void *opaque G_GNUC_UNUSED)
+{
+ int fd = -1;
+ size_t dataLen = 0;
+ int ret = 0;
+
+ g_autoptr(virPCIVPDResource) res = NULL;
+ /* Note: Custom fields are supposed to be freed by the resource cleanup code. */
+ virPCIVPDResourceCustom *custom = NULL;
+
+ const uint8_t fullVPDExample[] = {
+ VPD_STRING_RESOURCE_EXAMPLE_HEADER, VPD_STRING_RESOURCE_EXAMPLE_DATA,
+ VPD_R_FIELDS_EXAMPLE_HEADER, VPD_R_FIELDS_EXAMPLE_DATA,
+ VPD_W_FIELDS_EXAMPLE_HEADER, VPD_W_EXAMPLE_FIELDS,
+ PCI_VPD_RESOURCE_END_VAL
+ };
+
+ dataLen = sizeof(fullVPDExample) / sizeof(uint8_t);
+ fd = virCreateAnonymousFile(fullVPDExample, dataLen);
+ res = virPCIVPDParse(fd);
+ VIR_FORCE_CLOSE(fd);
+
+ if (!res) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ "The resource pointer is NULL after parsing which is unexpected");
+ return ret;
+ }
+
+ if (!res->ro) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ "Read-only keywords are missing from the VPD resource.");
+ return -1;
+ } else if (!res->rw) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ "Read-write keywords are missing from the VPD resource.");
+ return -1;
+ }
+
+ if (testVirPCIVPDValidateExampleReadOnlyFields(res))
+ return -1;
+
+ if (STRNEQ_NULLABLE(res->rw->asset_tag, "ID42"))
+ return -1;
+
+ if (!res->rw->vendor_specific)
+ return -1;
+
+ custom = g_ptr_array_index(res->rw->vendor_specific, 0);
+ if (custom->idx != 'Z' || STRNEQ_NULLABLE(custom->value, "42"))
+ return -1;
+
+ if (!res->rw->system_specific)
+ return -1;
+
+ custom = g_ptr_array_index(res->rw->system_specific, 0);
+ if (custom->idx != 'F' || STRNEQ_NULLABLE(custom->value, "EX"))
+ return -1;
+
+ custom = g_ptr_array_index(res->rw->system_specific, 1);
+ if (custom->idx != 'E' || STRNEQ_NULLABLE(custom->value, ""))
+ return -1;
+
+ custom = NULL;
+ return ret;
+}
+
+static int
+testVirPCIVPDParseFullVPDSkipInvalidKeywords(const void *opaque G_GNUC_UNUSED)
+{
+ int fd = -1;
+ size_t dataLen = 0;
+
+ g_autoptr(virPCIVPDResource) res = NULL;
+
+ const uint8_t fullVPDExample[] = {
+ VPD_STRING_RESOURCE_EXAMPLE_HEADER,
+ VPD_STRING_RESOURCE_EXAMPLE_DATA,
+ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x25, 0x00,
+ VPD_R_EXAMPLE_FIELDS,
+ /* The keywords below (except for "RV") are invalid but will be skipped by the parser */
+ 0x07, 'A', 0x02, 0x00, 0x00,
+ 'V', 0x07, 0x02, 0x00, 0x00,
+ 'e', 'x', 0x02, 0x00, 0x00,
+ 'R', 'V', 0x02, 0x9A, 0x00,
+ PCI_VPD_RESOURCE_END_VAL
+ };
+
+ dataLen = sizeof(fullVPDExample) / sizeof(uint8_t);
+ fd = virCreateAnonymousFile(fullVPDExample, dataLen);
+ res = virPCIVPDParse(fd);
+ VIR_FORCE_CLOSE(fd);
+
+ if (!res) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ "The resource pointer is NULL after parsing which is unexpected.");
+ return -1;
+ }
+ if (!res->ro) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ "The RO portion of the VPD resource is NULL.");
+ return -1;
+ }
+
+ if (testVirPCIVPDValidateExampleReadOnlyFields(res))
+ return -1;
+
+ return 0;
+}
+
+static int
+testVirPCIVPDParseFullVPDInvalid(const void *opaque G_GNUC_UNUSED)
+{
+ int fd = -1;
+ size_t dataLen = 0;
+
+# define VPD_INVALID_ZERO_BYTE \
+ 0x00
+
+# define VPD_INVALID_STRING_HEADER_DATA_LONG \
+ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_STRING_RESOURCE_FLAG, 0x04, 0x00, \
+ VPD_STRING_RESOURCE_EXAMPLE_DATA, \
+ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x05, 0x00, \
+ 'R', 'V', 0x02, 0xDA, 0x00, \
+ PCI_VPD_RESOURCE_END_VAL
+
+# define VPD_INVALID_STRING_HEADER_DATA_SHORT \
+ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_STRING_RESOURCE_FLAG, 0x0A, 0x00, \
+ VPD_STRING_RESOURCE_EXAMPLE_DATA, \
+ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x05, 0x00, \
+ 'R', 'V', 0x02, 0xD4, 0x00, \
+ PCI_VPD_RESOURCE_END_VAL
+
+# define VPD_NO_VPD_R \
+ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \
+ VPD_STRING_RESOURCE_EXAMPLE_DATA, \
+ PCI_VPD_RESOURCE_END_VAL
+
+# define VPD_R_NO_RV \
+ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \
+ VPD_STRING_RESOURCE_EXAMPLE_DATA, \
+ VPD_R_FIELDS_EXAMPLE_HEADER, \
+ VPD_R_EXAMPLE_FIELDS, \
+ PCI_VPD_RESOURCE_END_VAL
+
+# define VPD_R_INVALID_RV \
+ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \
+ VPD_STRING_RESOURCE_EXAMPLE_DATA, \
+ VPD_R_FIELDS_EXAMPLE_HEADER, \
+ VPD_R_EXAMPLE_FIELDS, \
+ VPD_R_EXAMPLE_INVALID_RV_FIELD, \
+ PCI_VPD_RESOURCE_END_VAL
+
+# define VPD_R_INVALID_RV_ZERO_LENGTH \
+ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \
+ VPD_STRING_RESOURCE_EXAMPLE_DATA, \
+ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x14, 0x00, \
+ VPD_R_EXAMPLE_FIELDS, \
+ 'R', 'V', 0x00, \
+ PCI_VPD_RESOURCE_END_VAL
+
+/* The RW key is not expected in a VPD-R record. */
+# define VPD_R_UNEXPECTED_RW_IN_VPD_R_KEY \
+ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \
+ VPD_STRING_RESOURCE_EXAMPLE_DATA, \
+ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x1B, 0x00, \
+ VPD_R_EXAMPLE_FIELDS, \
+ 'R', 'W', 0x02, 0x00, 0x00, \
+ 'R', 'V', 0x02, 0x81, 0x00, \
+ PCI_VPD_RESOURCE_END_VAL
+
+# define VPD_R_INVALID_FIELD_VALUE \
+ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \
+ VPD_STRING_RESOURCE_EXAMPLE_DATA, \
+ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x0A, 0x00, \
+ 'S', 'N', 0x02, 0x04, 0x02, \
+ 'R', 'V', 0x02, 0x28, 0x00, \
+ PCI_VPD_RESOURCE_END_VAL
+
+# define VPD_INVALID_STRING_RESOURCE_VALUE \
+ VPD_STRING_RESOURCE_EXAMPLE_HEADER, \
+ 't', 0x03, 's', 't', 'n', 'a', 'm', 'e', \
+ PCI_VPD_LARGE_RESOURCE_FLAG | PCI_VPD_READ_ONLY_LARGE_RESOURCE_FLAG, 0x0A, 0x00, \
+ 'S', 'N', 0x02, 0x04, 0x02, \
+ 'R', 'V', 0x02, 0x8A, 0x00, \
+ PCI_VPD_RESOURCE_END_VAL
+
+# define TEST_INVALID_VPD(invalidVPD) \
+ do { \
+ g_autoptr(virPCIVPDResource) res = NULL; \
+ const uint8_t testCase[] = { invalidVPD }; \
+ dataLen = sizeof(testCase) / sizeof(uint8_t); \
+ fd = virCreateAnonymousFile(testCase, dataLen); \
+ if ((res = virPCIVPDParse(fd))) { \
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s", \
+ "Successfully parsed an invalid VPD - this is not expected"); \
+ return -1; \
+ } \
+ VIR_FORCE_CLOSE(fd); \
+ } while (0);
+
+ TEST_INVALID_VPD(VPD_INVALID_ZERO_BYTE);
+ TEST_INVALID_VPD(VPD_INVALID_STRING_HEADER_DATA_SHORT);
+ TEST_INVALID_VPD(VPD_INVALID_STRING_HEADER_DATA_LONG);
+ TEST_INVALID_VPD(VPD_NO_VPD_R);
+ TEST_INVALID_VPD(VPD_R_NO_RV);
+ TEST_INVALID_VPD(VPD_R_INVALID_RV);
+ TEST_INVALID_VPD(VPD_R_INVALID_RV_ZERO_LENGTH);
+ TEST_INVALID_VPD(VPD_R_UNEXPECTED_RW_IN_VPD_R_KEY);
+ TEST_INVALID_VPD(VPD_R_INVALID_FIELD_VALUE);
+ TEST_INVALID_VPD(VPD_INVALID_STRING_RESOURCE_VALUE);
+
+ return 0;
+}
+
+static int
+mymain(void)
+{
+ int ret = 0;
+
+ if (virTestRun("Basic functionality of virPCIVPDResource ", testPCIVPDResourceBasic, NULL) < 0)
+ ret = -1;
+ if (virTestRun("Custom field index comparison",
+ testPCIVPDResourceCustomCompareIndex, NULL) < 0)
+ ret = -1;
+ if (virTestRun("Custom field value insertion and updates ",
+ testPCIVPDResourceCustomUpsertValue, NULL) < 0)
+ ret = -1;
+ if (virTestRun("Valid text values ", testPCIVPDIsValidTextValue, NULL) < 0)
+ ret = -1;
+ if (virTestRun("Determining a field value format by a key ",
+ testPCIVPDGetFieldValueFormat, NULL) < 0)
+ ret = -1;
+ if (virTestRun("Reading VPD bytes ", testVirPCIVPDReadVPDBytes, NULL) < 0)
+ ret = -1;
+ if (virTestRun("Parsing VPD string resources ", testVirPCIVPDParseVPDStringResource, NULL) < 0)
+ ret = -1;
+ if (virTestRun("Parsing a VPD resource with an invalid keyword ",
+ testVirPCIVPDParseFullVPDSkipInvalidKeywords, NULL) < 0)
+ ret = -1;
+ if (virTestRun("Parsing VPD resources from a full VPD ", testVirPCIVPDParseFullVPD, NULL) < 0)
+ ret = -1;
+ if (virTestRun("Parsing invalid VPD records ", testVirPCIVPDParseFullVPDInvalid, NULL) < 0)
+ ret = -1;
+
+ return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+VIR_TEST_MAIN(mymain)
+
+#else /* ! __linux__ */
+int
+main(void)
+{
+ return EXIT_AM_SKIP;
+}
+#endif /* ! __linux__ */