1 /*
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
nvme_nguid_is_null(const NvmeNGUID * nguid)34 bool nvme_nguid_is_null(const NvmeNGUID *nguid)
35 {
36 static NvmeNGUID null_nguid;
37 return memcmp(nguid, &null_nguid, sizeof(NvmeNGUID)) == 0;
38 }
39
nvme_nguid_generate(NvmeNGUID * out)40 static 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 */
nvme_nguid_is_valid(const char * str)79 static 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
nvme_nguid_parse(const char * str,NvmeNGUID * nguid)110 static 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 */
nvme_nguid_stringify(const NvmeNGUID * nguid,char * out)141 static 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
get_nguid(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)149 static 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
set_nguid(Object * obj,Visitor * v,const char * name,void * opaque,Error ** errp)162 static 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
181 const 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 };
188