xref: /qemu/hw/nvme/nguid.c (revision 5837db465053f57414f671448b370a4b29250bae)
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  
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  
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   */
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  
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   */
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  
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  
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