| /* |
| * QEMU NVMe NGUID functions |
| * |
| * Copyright 2024 Google LLC |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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 "qemu/osdep.h" |
| #include "qapi/visitor.h" |
| #include "qemu/ctype.h" |
| #include "nvme.h" |
| |
| #define NGUID_SEPARATOR '-' |
| |
| #define NGUID_VALUE_AUTO "auto" |
| |
| #define NGUID_FMT \ |
| "%02hhx%02hhx%02hhx%02hhx" \ |
| "%02hhx%02hhx%02hhx%02hhx" \ |
| "%02hhx%02hhx%02hhx%02hhx" \ |
| "%02hhx%02hhx%02hhx%02hhx" |
| |
| #define NGUID_STR_LEN (2 * NGUID_LEN + 1) |
| |
| bool nvme_nguid_is_null(const NvmeNGUID *nguid) |
| { |
| static NvmeNGUID null_nguid; |
| return memcmp(nguid, &null_nguid, sizeof(NvmeNGUID)) == 0; |
| } |
| |
| static void nvme_nguid_generate(NvmeNGUID *out) |
| { |
| int i; |
| uint32_t x; |
| |
| QEMU_BUILD_BUG_ON((NGUID_LEN % sizeof(x)) != 0); |
| |
| for (i = 0; i < NGUID_LEN; i += sizeof(x)) { |
| x = g_random_int(); |
| memcpy(&out->data[i], &x, sizeof(x)); |
| } |
| } |
| |
| /* |
| * The Linux Kernel typically prints the NGUID of an NVMe namespace using the |
| * same format as the UUID. For instance: |
| * |
| * $ cat /sys/class/block/nvme0n1/nguid |
| * e9accd3b-8390-4e13-167c-f0593437f57d |
| * |
| * When there is no UUID but there is NGUID the Kernel will print the NGUID as |
| * wwid and it won't use the UUID format: |
| * |
| * $ cat /sys/class/block/nvme0n1/wwid |
| * eui.e9accd3b83904e13167cf0593437f57d |
| * |
| * The NGUID has different fields compared to the UUID, so the grouping used in |
| * the UUID format has no relation with the 3 fields of the NGUID. |
| * |
| * This implementation won't expect a strict format as the UUID one and instead |
| * it will admit any string of hexadecimal digits. Byte groups could be created |
| * using the '-' separator. The number of bytes needs to be exactly 16 and the |
| * separator '-' has to be exactly in a byte boundary. The following are |
| * examples of accepted formats for the NGUID string: |
| * |
| * nguid="e9accd3b-8390-4e13-167c-f0593437f57d" |
| * nguid="e9accd3b83904e13167cf0593437f57d" |
| * nguid="FEDCBA9876543210-ABCDEF-0123456789" |
| */ |
| static bool nvme_nguid_is_valid(const char *str) |
| { |
| int i; |
| int digit_count = 0; |
| |
| for (i = 0; i < strlen(str); i++) { |
| const char c = str[i]; |
| if (qemu_isxdigit(c)) { |
| digit_count++; |
| continue; |
| } |
| if (c == NGUID_SEPARATOR) { |
| /* |
| * We need to make sure the separator is in a byte boundary, the |
| * string does not start with the separator and they are not back to |
| * back "--". |
| */ |
| if ((i > 0) && (str[i - 1] != NGUID_SEPARATOR) && |
| (digit_count % 2) == 0) { |
| continue; |
| } |
| } |
| return false; |
| } |
| /* |
| * The string should have the correct byte length and not finish with the |
| * separator |
| */ |
| return (digit_count == (2 * NGUID_LEN)) && (str[i - 1] != NGUID_SEPARATOR); |
| } |
| |
| static int nvme_nguid_parse(const char *str, NvmeNGUID *nguid) |
| { |
| uint8_t *id = &nguid->data[0]; |
| int ret = 0; |
| int i; |
| const char *ptr = str; |
| |
| if (!nvme_nguid_is_valid(str)) { |
| return -1; |
| } |
| |
| for (i = 0; i < NGUID_LEN; i++) { |
| ret = sscanf(ptr, "%02hhx", &id[i]); |
| if (ret != 1) { |
| return -1; |
| } |
| ptr += 2; |
| if (*ptr == NGUID_SEPARATOR) { |
| ptr++; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * When converted back to string this implementation will use a raw hex number |
| * with no separators, for instance: |
| * |
| * "e9accd3b83904e13167cf0593437f57d" |
| */ |
| static void nvme_nguid_stringify(const NvmeNGUID *nguid, char *out) |
| { |
| const uint8_t *id = &nguid->data[0]; |
| snprintf(out, NGUID_STR_LEN, NGUID_FMT, |
| id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], |
| id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]); |
| } |
| |
| static void get_nguid(Object *obj, Visitor *v, const char *name, void *opaque, |
| Error **errp) |
| { |
| Property *prop = opaque; |
| NvmeNGUID *nguid = object_field_prop_ptr(obj, prop); |
| char buffer[NGUID_STR_LEN]; |
| char *p = buffer; |
| |
| nvme_nguid_stringify(nguid, buffer); |
| |
| visit_type_str(v, name, &p, errp); |
| } |
| |
| static void set_nguid(Object *obj, Visitor *v, const char *name, void *opaque, |
| Error **errp) |
| { |
| Property *prop = opaque; |
| NvmeNGUID *nguid = object_field_prop_ptr(obj, prop); |
| char *str; |
| |
| if (!visit_type_str(v, name, &str, errp)) { |
| return; |
| } |
| |
| if (!strcmp(str, NGUID_VALUE_AUTO)) { |
| nvme_nguid_generate(nguid); |
| } else if (nvme_nguid_parse(str, nguid) < 0) { |
| error_set_from_qdev_prop_error(errp, EINVAL, obj, name, str); |
| } |
| g_free(str); |
| } |
| |
| const PropertyInfo qdev_prop_nguid = { |
| .name = "str", |
| .description = |
| "NGUID or \"" NGUID_VALUE_AUTO "\" for random value", |
| .get = get_nguid, |
| .set = set_nguid, |
| }; |