1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2026 voidanix <voidanix@FreeBSD.org>
5 */
6
7 #include <sys/param.h>
8
9 #include <ctype.h>
10 #include <errno.h>
11 #include <stdint.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <strings.h>
16 #include <libgeom.h>
17 #include <geom/zoned/g_zoned.h>
18
19 #include "core/geom.h"
20 #include "misc/subr.h"
21
22 uint32_t lib_version = G_LIB_VERSION;
23 uint32_t version = G_ZONED_VERSION;
24
25 #define GZONED_ZONESIZE "256M"
26
27 static void zoned_main(struct gctl_req *req, unsigned flags);
28 static void zoned_create(struct gctl_req *req);
29 static void zoned_clear(struct gctl_req *req);
30
31 struct g_command class_commands[] = {
32 { "clear", G_FLAG_VERBOSE, zoned_main, G_NULL_OPTS, "[-v] dev ..." },
33 { "create", G_FLAG_VERBOSE | G_FLAG_LOADKLD, zoned_main,
34 {
35 { 's', "zonesize", GZONED_ZONESIZE, G_TYPE_NUMBER },
36 { 'c', "conventional", "", G_TYPE_STRING },
37 { 'm', "maxopen", "0", G_TYPE_NUMBER },
38 { 'u', "restricted", NULL, G_TYPE_BOOL },
39 G_OPT_SENTINEL
40 },
41 "[-uv] [-s zonesize] [-c zone[-zone][,zone[-zone]]...] "
42 "[-m maxopen] dev"
43 },
44 { "fault", G_FLAG_VERBOSE, NULL,
45 {
46 { 'z', "zone", NULL, G_TYPE_NUMBER },
47 { 's', "state", NULL, G_TYPE_STRING },
48 G_OPT_SENTINEL
49 },
50 "[-v] -z zone -s ro|offline|reset|clear name"
51 },
52 { "destroy", G_FLAG_VERBOSE, NULL,
53 {
54 { 'f', "force", NULL, G_TYPE_BOOL },
55 G_OPT_SENTINEL
56 },
57 "[-fv] name ..." },
58 { "stop", G_FLAG_VERBOSE, NULL,
59 {
60 { 'f', "force", NULL, G_TYPE_BOOL },
61 G_OPT_SENTINEL
62 },
63 "[-fv] name ..." },
64 G_CMD_SENTINEL
65 };
66
67 static int verbose = 0;
68
69 static void
zoned_main(struct gctl_req * req,unsigned flags)70 zoned_main(struct gctl_req *req, unsigned flags)
71 {
72 const char *name;
73
74 if ((flags & G_FLAG_VERBOSE) != 0)
75 verbose = 1;
76
77 name = gctl_get_ascii(req, "verb");
78 if (name == NULL) {
79 gctl_error(req, "No '%s' argument.", "verb");
80 return;
81 }
82 if (strcmp(name, "create") == 0)
83 zoned_create(req);
84 else if (strcmp(name, "clear") == 0)
85 zoned_clear(req);
86 else
87 gctl_error(req, "Unknown command: %s.", name);
88 }
89
90 /*
91 * Parse a conventional-zone specification into md_conv/md_nconv.
92 */
93 static int
zoned_parse_conv(struct gctl_req * req,const char * spec,uint32_t nzones,struct g_zoned_metadata * md)94 zoned_parse_conv(struct gctl_req *req, const char *spec, uint32_t nzones,
95 struct g_zoned_metadata *md)
96 {
97 char *buf, *ep, *sp, *tok;
98 unsigned long first, last;
99 uint32_t i;
100
101 md->md_nconv = 0;
102 if (*spec == '\0')
103 return (0);
104 buf = strdup(spec);
105 if (buf == NULL) {
106 gctl_error(req, "Cannot allocate memory.");
107 return (-1);
108 }
109 for (tok = strtok_r(buf, ",", &sp); tok != NULL;
110 tok = strtok_r(NULL, ",", &sp)) {
111 if (md->md_nconv == G_ZONED_MAXCONV) {
112 gctl_error(req,
113 "Too many conventional zone ranges (max %d).",
114 G_ZONED_MAXCONV);
115 goto fail;
116 }
117 errno = 0;
118 if (!isdigit((unsigned char)*tok))
119 goto syntax;
120 first = strtoul(tok, &ep, 10);
121 if (ep == tok || errno != 0)
122 goto syntax;
123 if (*ep == '-') {
124 tok = ep + 1;
125 if (!isdigit((unsigned char)*tok))
126 goto syntax;
127 last = strtoul(tok, &ep, 10);
128 if (ep == tok || *ep != '\0' || errno != 0)
129 goto syntax;
130 } else if (*ep == '\0') {
131 last = first;
132 } else {
133 goto syntax;
134 }
135 if (last < first) {
136 gctl_error(req, "Backwards zone range %lu-%lu.",
137 first, last);
138 goto fail;
139 }
140 if (last >= nzones) {
141 gctl_error(req,
142 "Zone %lu does not exist (zones 0-%u).", last,
143 nzones - 1);
144 goto fail;
145 }
146 for (i = 0; i < md->md_nconv; i++) {
147 uint32_t ofirst, olast;
148
149 ofirst = md->md_conv[i].cr_first;
150 olast = ofirst + md->md_conv[i].cr_count - 1;
151 if (first <= olast && ofirst <= last) {
152 gctl_error(req, "Zone range %lu-%lu overlaps "
153 "%u-%u.", first, last, ofirst, olast);
154 goto fail;
155 }
156 }
157 md->md_conv[md->md_nconv].cr_first = first;
158 md->md_conv[md->md_nconv].cr_count = last - first + 1;
159 md->md_nconv++;
160 }
161 free(buf);
162 return (0);
163 syntax:
164 gctl_error(req, "Invalid conventional zone specification '%s'.",
165 spec);
166 fail:
167 free(buf);
168 return (-1);
169 }
170
171 static void
zoned_create(struct gctl_req * req)172 zoned_create(struct gctl_req *req)
173 {
174 struct g_zoned_metadata md;
175 u_char sector[512];
176 const char *conv, *dev;
177 off_t msize, zonesize;
178 intmax_t maxopen;
179 unsigned int secsize;
180 uint32_t i, nconv, nzones;
181 int nargs;
182
183 bzero(sector, sizeof(sector));
184 bzero(&md, sizeof(md));
185 nargs = gctl_get_int(req, "nargs");
186 if (nargs != 1) {
187 gctl_error(req,
188 "Usage: create [-u] [-s zonesize] [-c zones] "
189 "[-m maxopen] dev");
190 return;
191 }
192 zonesize = (off_t)gctl_get_intmax(req, "zonesize");
193 conv = gctl_get_ascii(req, "conventional");
194 dev = gctl_get_ascii(req, "arg0");
195
196 msize = g_get_mediasize(dev);
197 secsize = g_get_sectorsize(dev);
198 if (msize == 0 || secsize == 0) {
199 gctl_error(req, "Can't get information about %s: %s.", dev,
200 strerror(errno));
201 return;
202 }
203 if (g_provider_is_host_managed(dev)) {
204 gctl_error(req, "Cannot create a zoned device on %s: the tail "
205 "of a host-managed zoned device cannot hold the metadata "
206 "and zone state table.", dev);
207 return;
208 }
209 if (zonesize <= 0 || (zonesize % secsize) != 0) {
210 gctl_error(req, "Zone size must be a positive multiple of %u.",
211 secsize);
212 return;
213 }
214 if ((uint64_t)zonesize / secsize > G_ZONED_MAXZONESECS) {
215 gctl_error(req, "Zone size %jd exceeds %u sectors.",
216 (intmax_t)zonesize, G_ZONED_MAXZONESECS);
217 return;
218 }
219 nzones = g_zoned_nzones(msize, zonesize, secsize);
220 if (nzones == 0) {
221 gctl_error(req, "Zone size %jd is unusable for %s: it must "
222 "yield at least one zone and at most %u.",
223 (intmax_t)zonesize, dev, G_ZONED_MAXZONES);
224 return;
225 }
226 if (zoned_parse_conv(req, conv, nzones, &md) != 0)
227 return;
228 maxopen = gctl_get_intmax(req, "maxopen");
229 if (maxopen < 0 || maxopen > nzones) {
230 gctl_error(req, "Maximum open zones must be between 0 and "
231 "the number of zones (%u).", nzones);
232 return;
233 }
234
235 memcpy(md.md_magic, G_ZONED_MAGIC, sizeof(G_ZONED_MAGIC));
236 md.md_version = G_ZONED_VERSION;
237 md.md_id = arc4random();
238 md.md_zonesize = zonesize;
239 md.md_sectorsize = secsize;
240 md.md_provsize = msize;
241 md.md_maxopen = (uint32_t)maxopen;
242 if (gctl_get_int(req, "restricted"))
243 md.md_flags |= G_ZONED_MD_RESTRICTED_READS;
244
245 /*
246 * Hand the geometry to the kernel rather than writing it on the media
247 * and waiting for the taste. The metadata should be written only once
248 * the device is up: a request the kernel turns down leaves the provider
249 * untouched.
250 */
251 zoned_metadata_encode(&md, sector);
252 gctl_ro_param(req, "metadata", sizeof(sector), sector);
253 if (gctl_issue(req) != NULL)
254 return;
255 if (verbose) {
256 nconv = 0;
257 for (i = 0; i < md.md_nconv; i++)
258 nconv += md.md_conv[i].cr_count;
259 printf("Device %s zoned: %u zones of %jd bytes"
260 " (%u conventional).\n", dev, nzones, (intmax_t)zonesize,
261 nconv);
262 }
263 }
264
265 static void
zoned_clear(struct gctl_req * req)266 zoned_clear(struct gctl_req *req)
267 {
268 const char *name;
269 int error, i, nargs;
270
271 nargs = gctl_get_int(req, "nargs");
272 if (nargs < 1) {
273 gctl_error(req, "Missing device(s).");
274 return;
275 }
276
277 for (i = 0; i < nargs; i++) {
278 name = gctl_get_ascii(req, "arg%d", i);
279 error = g_metadata_clear(name, G_ZONED_MAGIC);
280 if (error != 0) {
281 fprintf(stderr, "Can't clear metadata on %s: %s.\n",
282 name, strerror(error));
283 gctl_error(req, "Not fully done.");
284 continue;
285 }
286 if (verbose)
287 printf("Metadata cleared on %s.\n", name);
288 }
289 }
290