blob: 829832bd9f41a8f7bb0e6d635ce2cd184be93cf0 [file] [log] [blame]
Roque Arcudia Hernandezbdc31642024-02-22 17:50:16 +00001/*
2 * QEMU NVMe NGUID functions
3 *
4 * Copyright 2024 Google LLC
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 */
16
17#include "qemu/osdep.h"
18#include "qapi/visitor.h"
19#include "qemu/ctype.h"
20#include "nvme.h"
21
22#define NGUID_SEPARATOR '-'
23
24#define NGUID_VALUE_AUTO "auto"
25
26#define NGUID_FMT \
27 "%02hhx%02hhx%02hhx%02hhx" \
28 "%02hhx%02hhx%02hhx%02hhx" \
29 "%02hhx%02hhx%02hhx%02hhx" \
30 "%02hhx%02hhx%02hhx%02hhx"
31
32#define NGUID_STR_LEN (2 * NGUID_LEN + 1)
33
34bool nvme_nguid_is_null(const NvmeNGUID *nguid)
35{
36 static NvmeNGUID null_nguid;
37 return memcmp(nguid, &null_nguid, sizeof(NvmeNGUID)) == 0;
38}
39
40static void nvme_nguid_generate(NvmeNGUID *out)
41{
42 int i;
43 uint32_t x;
44
45 QEMU_BUILD_BUG_ON((NGUID_LEN % sizeof(x)) != 0);
46
47 for (i = 0; i < NGUID_LEN; i += sizeof(x)) {
48 x = g_random_int();
49 memcpy(&out->data[i], &x, sizeof(x));
50 }
51}
52
53/*
54 * The Linux Kernel typically prints the NGUID of an NVMe namespace using the
55 * same format as the UUID. For instance:
56 *
57 * $ cat /sys/class/block/nvme0n1/nguid
58 * e9accd3b-8390-4e13-167c-f0593437f57d
59 *
60 * When there is no UUID but there is NGUID the Kernel will print the NGUID as
61 * wwid and it won't use the UUID format:
62 *
63 * $ cat /sys/class/block/nvme0n1/wwid
64 * eui.e9accd3b83904e13167cf0593437f57d
65 *
66 * The NGUID has different fields compared to the UUID, so the grouping used in
67 * the UUID format has no relation with the 3 fields of the NGUID.
68 *
69 * This implementation won't expect a strict format as the UUID one and instead
70 * it will admit any string of hexadecimal digits. Byte groups could be created
71 * using the '-' separator. The number of bytes needs to be exactly 16 and the
72 * separator '-' has to be exactly in a byte boundary. The following are
73 * examples of accepted formats for the NGUID string:
74 *
75 * nguid="e9accd3b-8390-4e13-167c-f0593437f57d"
76 * nguid="e9accd3b83904e13167cf0593437f57d"
77 * nguid="FEDCBA9876543210-ABCDEF-0123456789"
78 */
79static bool nvme_nguid_is_valid(const char *str)
80{
81 int i;
82 int digit_count = 0;
83
84 for (i = 0; i < strlen(str); i++) {
85 const char c = str[i];
86 if (qemu_isxdigit(c)) {
87 digit_count++;
88 continue;
89 }
90 if (c == NGUID_SEPARATOR) {
91 /*
92 * We need to make sure the separator is in a byte boundary, the
93 * string does not start with the separator and they are not back to
94 * back "--".
95 */
96 if ((i > 0) && (str[i - 1] != NGUID_SEPARATOR) &&
97 (digit_count % 2) == 0) {
98 continue;
99 }
100 }
101 return false;
102 }
103 /*
104 * The string should have the correct byte length and not finish with the
105 * separator
106 */
107 return (digit_count == (2 * NGUID_LEN)) && (str[i - 1] != NGUID_SEPARATOR);
108}
109
110static int nvme_nguid_parse(const char *str, NvmeNGUID *nguid)
111{
112 uint8_t *id = &nguid->data[0];
113 int ret = 0;
114 int i;
115 const char *ptr = str;
116
117 if (!nvme_nguid_is_valid(str)) {
118 return -1;
119 }
120
121 for (i = 0; i < NGUID_LEN; i++) {
122 ret = sscanf(ptr, "%02hhx", &id[i]);
123 if (ret != 1) {
124 return -1;
125 }
126 ptr += 2;
127 if (*ptr == NGUID_SEPARATOR) {
128 ptr++;
129 }
130 }
131
132 return 0;
133}
134
135/*
136 * When converted back to string this implementation will use a raw hex number
137 * with no separators, for instance:
138 *
139 * "e9accd3b83904e13167cf0593437f57d"
140 */
141static void nvme_nguid_stringify(const NvmeNGUID *nguid, char *out)
142{
143 const uint8_t *id = &nguid->data[0];
144 snprintf(out, NGUID_STR_LEN, NGUID_FMT,
145 id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7],
146 id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]);
147}
148
149static void get_nguid(Object *obj, Visitor *v, const char *name, void *opaque,
150 Error **errp)
151{
152 Property *prop = opaque;
153 NvmeNGUID *nguid = object_field_prop_ptr(obj, prop);
154 char buffer[NGUID_STR_LEN];
155 char *p = buffer;
156
157 nvme_nguid_stringify(nguid, buffer);
158
159 visit_type_str(v, name, &p, errp);
160}
161
162static void set_nguid(Object *obj, Visitor *v, const char *name, void *opaque,
163 Error **errp)
164{
165 Property *prop = opaque;
166 NvmeNGUID *nguid = object_field_prop_ptr(obj, prop);
167 char *str;
168
169 if (!visit_type_str(v, name, &str, errp)) {
170 return;
171 }
172
173 if (!strcmp(str, NGUID_VALUE_AUTO)) {
174 nvme_nguid_generate(nguid);
175 } else if (nvme_nguid_parse(str, nguid) < 0) {
176 error_set_from_qdev_prop_error(errp, EINVAL, obj, name, str);
177 }
178 g_free(str);
179}
180
181const PropertyInfo qdev_prop_nguid = {
182 .name = "str",
183 .description =
184 "NGUID or \"" NGUID_VALUE_AUTO "\" for random value",
185 .get = get_nguid,
186 .set = set_nguid,
187};