xref: /freebsd/lib/geom/zoned/geom_zoned.c (revision 48efb6c4eb9febc1c8c50d13a794e9165af71930)
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