xref: /freebsd/sys/geom/geom_ctl.c (revision c19fb1f963e3dc88a82b20d1b17f94a4cd321e74)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 2002 Poul-Henning Kamp
5  * Copyright (c) 2002 Networks Associates Technology, Inc.
6  * All rights reserved.
7  * Copyright (c) 2022 Alexander Motin <mav@FreeBSD.org>
8  *
9  * This software was developed for the FreeBSD Project by Poul-Henning Kamp
10  * and NAI Labs, the Security Research Division of Network Associates, Inc.
11  * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
12  * DARPA CHATS research program.
13  *
14  * Redistribution and use in source and binary forms, with or without
15  * modification, are permitted provided that the following conditions
16  * are met:
17  * 1. Redistributions of source code must retain the above copyright
18  *    notice, this list of conditions and the following disclaimer.
19  * 2. Redistributions in binary form must reproduce the above copyright
20  *    notice, this list of conditions and the following disclaimer in the
21  *    documentation and/or other materials provided with the distribution.
22  * 3. The names of the authors may not be used to endorse or promote
23  *    products derived from this software without specific prior written
24  *    permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
30  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  */
38 
39 #include <sys/param.h>
40 #include <sys/systm.h>
41 #include <sys/conf.h>
42 #include <sys/malloc.h>
43 #include <sys/sbuf.h>
44 
45 #include <vm/vm.h>
46 #include <vm/vm_extern.h>
47 
48 #include <geom/geom.h>
49 #include <geom/geom_int.h>
50 #define GCTL_TABLE 1
51 #include <geom/geom_ctl.h>
52 
53 #include <machine/stdarg.h>
54 
55 static d_ioctl_t g_ctl_ioctl;
56 
57 static struct cdevsw g_ctl_cdevsw = {
58 	.d_version =	D_VERSION,
59 	.d_flags =	0,
60 	.d_ioctl =	g_ctl_ioctl,
61 	.d_name =	"g_ctl",
62 };
63 
64 CTASSERT(GCTL_PARAM_RD == VM_PROT_READ);
65 CTASSERT(GCTL_PARAM_WR == VM_PROT_WRITE);
66 
67 void
68 g_ctl_init(void)
69 {
70 
71 	make_dev_credf(MAKEDEV_ETERNAL, &g_ctl_cdevsw, 0, NULL,
72 	    UID_ROOT, GID_OPERATOR, 0640, PATH_GEOM_CTL);
73 }
74 
75 /*
76  * Report an error back to the user in ascii format.  Return nerror
77  * or EINVAL if nerror isn't specified.
78  */
79 int
80 gctl_error(struct gctl_req *req, const char *fmt, ...)
81 {
82 	va_list ap;
83 
84 	if (req == NULL)
85 		return (EINVAL);
86 
87 	/* We only record the first error */
88 	if (sbuf_done(req->serror)) {
89 		if (!req->nerror)
90 			req->nerror = EEXIST;
91 #ifdef DIAGNOSTIC
92 		printf("gctl_error: buffer closed, message discarded.\n");
93 #endif
94 		return (req->nerror);
95 	}
96 	if (!req->nerror)
97 		req->nerror = EINVAL;
98 
99 	/* If this is the last of several messages, indent it on a new line */
100 	if (sbuf_len(req->serror) > 0)
101 		sbuf_cat(req->serror, "\n\t");
102 	va_start(ap, fmt);
103 	sbuf_vprintf(req->serror, fmt, ap);
104 	va_end(ap);
105 	gctl_post_messages(req);
106 	return (req->nerror);
107 }
108 
109 /*
110  * The gctl_error() function will only report a single message.
111  * Commands that handle multiple devices may want to report a
112  * message for each of the devices. The gctl_msg() function
113  * can be called multiple times to post messages. When done
114  * the application must either call gctl_post_messages() or
115  * call gctl_error() to cause the messages to be reported to
116  * the calling process.
117  *
118  * The errno argument should be zero if it is an informational
119  * message or an errno value (EINVAL, EBUSY, etc) if it is an error.
120  * If any of the messages has a non-zero errno, the utility will
121  * EXIT_FAILURE. If only informational messages (with zero errno)
122  * are posted, the utility will EXIT_SUCCESS.
123  */
124 void
125 gctl_msg(struct gctl_req *req, int errno, const char *fmt, ...)
126 {
127 	va_list ap;
128 
129 	if (req == NULL)
130 		return;
131 	if (sbuf_done(req->serror)) {
132 #ifdef DIAGNOSTIC
133 		printf("gctl_msg: buffer closed, message discarded.\n");
134 #endif
135 		return;
136 	}
137 	if (req->nerror == 0)
138 		req->nerror = errno;
139 	/* Put second and later messages indented on a new line */
140 	if (sbuf_len(req->serror) > 0)
141 		sbuf_cat(req->serror, "\n\t");
142 	va_start(ap, fmt);
143 	sbuf_vprintf(req->serror, fmt, ap);
144 	va_end(ap);
145 }
146 
147 /*
148  * Post the messages to the user.
149  */
150 void
151 gctl_post_messages(struct gctl_req *req)
152 {
153 
154 	if (sbuf_done(req->serror)) {
155 #ifdef DIAGNOSTIC
156 		printf("gctl_post_messages: message buffer already closed.\n");
157 #endif
158 		return;
159 	}
160 	sbuf_finish(req->serror);
161 	if (g_debugflags & G_F_CTLDUMP)
162 		printf("gctl %p message(s) \"%s\"\n", req,
163 		    sbuf_data(req->serror));
164 }
165 
166 /*
167  * Allocate space and copyin() something.
168  * XXX: this should really be a standard function in the kernel.
169  */
170 static void *
171 geom_alloc_copyin(struct gctl_req *req, void *uaddr, size_t len)
172 {
173 	void *ptr;
174 
175 	ptr = g_malloc(len, M_WAITOK);
176 	req->nerror = copyin(uaddr, ptr, len);
177 	if (!req->nerror)
178 		return (ptr);
179 	g_free(ptr);
180 	return (NULL);
181 }
182 
183 static void
184 gctl_copyin(struct gctl_req *req)
185 {
186 	struct gctl_req_arg *ap;
187 	char *p;
188 	u_int i;
189 
190 	if (req->narg > GEOM_CTL_ARG_MAX) {
191 		gctl_error(req, "too many arguments");
192 		req->arg = NULL;
193 		return;
194 	}
195 
196 	ap = geom_alloc_copyin(req, req->arg, req->narg * sizeof(*ap));
197 	if (ap == NULL) {
198 		gctl_error(req, "bad control request");
199 		req->arg = NULL;
200 		return;
201 	}
202 
203 	/* Nothing have been copyin()'ed yet */
204 	for (i = 0; i < req->narg; i++) {
205 		ap[i].flag &= ~(GCTL_PARAM_NAMEKERNEL|GCTL_PARAM_VALUEKERNEL);
206 		ap[i].flag &= ~GCTL_PARAM_CHANGED;
207 		ap[i].kvalue = NULL;
208 	}
209 
210 	for (i = 0; i < req->narg; i++) {
211 		if (ap[i].nlen < 1 || ap[i].nlen > SPECNAMELEN) {
212 			gctl_error(req,
213 			    "wrong param name length %d: %d", i, ap[i].nlen);
214 			break;
215 		}
216 		p = geom_alloc_copyin(req, ap[i].name, ap[i].nlen);
217 		if (p == NULL)
218 			break;
219 		if (p[ap[i].nlen - 1] != '\0') {
220 			gctl_error(req, "unterminated param name");
221 			g_free(p);
222 			break;
223 		}
224 		ap[i].name = p;
225 		ap[i].flag |= GCTL_PARAM_NAMEKERNEL;
226 		if (ap[i].len <= 0) {
227 			gctl_error(req, "negative param length");
228 			break;
229 		}
230 		if (ap[i].flag & GCTL_PARAM_RD) {
231 			p = geom_alloc_copyin(req, ap[i].value, ap[i].len);
232 			if (p == NULL)
233 				break;
234 			if ((ap[i].flag & GCTL_PARAM_ASCII) &&
235 			    p[ap[i].len - 1] != '\0') {
236 				gctl_error(req, "unterminated param value");
237 				g_free(p);
238 				break;
239 			}
240 		} else {
241 			p = g_malloc(ap[i].len, M_WAITOK | M_ZERO);
242 		}
243 		ap[i].kvalue = p;
244 		ap[i].flag |= GCTL_PARAM_VALUEKERNEL;
245 	}
246 	req->arg = ap;
247 	return;
248 }
249 
250 static void
251 gctl_copyout(struct gctl_req *req)
252 {
253 	int error, i;
254 	struct gctl_req_arg *ap;
255 
256 	if (req->nerror)
257 		return;
258 	error = 0;
259 	ap = req->arg;
260 	for (i = 0; i < req->narg; i++, ap++) {
261 		if (!(ap->flag & GCTL_PARAM_CHANGED))
262 			continue;
263 		error = copyout(ap->kvalue, ap->value, ap->len);
264 		if (!error)
265 			continue;
266 		req->nerror = error;
267 		return;
268 	}
269 	return;
270 }
271 
272 static void
273 gctl_free(struct gctl_req *req)
274 {
275 	u_int i;
276 
277 	sbuf_delete(req->serror);
278 	if (req->arg == NULL)
279 		return;
280 	for (i = 0; i < req->narg; i++) {
281 		if (req->arg[i].flag & GCTL_PARAM_NAMEKERNEL)
282 			g_free(req->arg[i].name);
283 		if ((req->arg[i].flag & GCTL_PARAM_VALUEKERNEL) &&
284 		    req->arg[i].len > 0)
285 			g_free(req->arg[i].kvalue);
286 	}
287 	g_free(req->arg);
288 }
289 
290 static void
291 gctl_dump(struct gctl_req *req, const char *what)
292 {
293 	struct gctl_req_arg *ap;
294 	u_int i;
295 	int j;
296 
297 	printf("Dump of gctl %s at %p:\n", what, req);
298 	if (req->nerror > 0) {
299 		printf("  nerror:\t%d\n", req->nerror);
300 		if (sbuf_len(req->serror) > 0)
301 			printf("  error:\t\"%s\"\n", sbuf_data(req->serror));
302 	}
303 	if (req->arg == NULL)
304 		return;
305 	for (i = 0; i < req->narg; i++) {
306 		ap = &req->arg[i];
307 		if (!(ap->flag & GCTL_PARAM_NAMEKERNEL))
308 			printf("  param:\t%d@%p", ap->nlen, ap->name);
309 		else
310 			printf("  param:\t\"%s\"", ap->name);
311 		printf(" [%s%s%d] = ",
312 		    ap->flag & GCTL_PARAM_RD ? "R" : "",
313 		    ap->flag & GCTL_PARAM_WR ? "W" : "",
314 		    ap->len);
315 		if (!(ap->flag & GCTL_PARAM_VALUEKERNEL)) {
316 			printf(" =@ %p", ap->value);
317 		} else if (ap->flag & GCTL_PARAM_ASCII) {
318 			printf("\"%s\"", (char *)ap->kvalue);
319 		} else if (ap->len > 0) {
320 			for (j = 0; j < ap->len && j < 512; j++)
321 				printf(" %02x", ((u_char *)ap->kvalue)[j]);
322 		} else {
323 			printf(" = %p", ap->kvalue);
324 		}
325 		printf("\n");
326 	}
327 }
328 
329 int
330 gctl_set_param(struct gctl_req *req, const char *param, void const *ptr,
331     int len)
332 {
333 	u_int i;
334 	struct gctl_req_arg *ap;
335 
336 	for (i = 0; i < req->narg; i++) {
337 		ap = &req->arg[i];
338 		if (strcmp(param, ap->name))
339 			continue;
340 		if (!(ap->flag & GCTL_PARAM_WR))
341 			return (EPERM);
342 		ap->flag |= GCTL_PARAM_CHANGED;
343 		if (ap->len < len) {
344 			bcopy(ptr, ap->kvalue, ap->len);
345 			return (ENOSPC);
346 		}
347 		bcopy(ptr, ap->kvalue, len);
348 		return (0);
349 	}
350 	return (EINVAL);
351 }
352 
353 void
354 gctl_set_param_err(struct gctl_req *req, const char *param, void const *ptr,
355     int len)
356 {
357 
358 	switch (gctl_set_param(req, param, ptr, len)) {
359 	case EPERM:
360 		gctl_error(req, "No write access %s argument", param);
361 		break;
362 	case ENOSPC:
363 		gctl_error(req, "Wrong length %s argument", param);
364 		break;
365 	case EINVAL:
366 		gctl_error(req, "Missing %s argument", param);
367 		break;
368 	}
369 }
370 
371 void *
372 gctl_get_param_flags(struct gctl_req *req, const char *param, int flags, int *len)
373 {
374 	u_int i;
375 	void *p;
376 	struct gctl_req_arg *ap;
377 
378 	for (i = 0; i < req->narg; i++) {
379 		ap = &req->arg[i];
380 		if (strcmp(param, ap->name))
381 			continue;
382 		if ((ap->flag & flags) != flags)
383 			continue;
384 		p = ap->kvalue;
385 		if (len != NULL)
386 			*len = ap->len;
387 		return (p);
388 	}
389 	return (NULL);
390 }
391 
392 void *
393 gctl_get_param(struct gctl_req *req, const char *param, int *len)
394 {
395 
396 	return (gctl_get_param_flags(req, param, GCTL_PARAM_RD, len));
397 }
398 
399 char const *
400 gctl_get_asciiparam(struct gctl_req *req, const char *param)
401 {
402 	char const *p;
403 	int len;
404 
405 	p = gctl_get_param_flags(req, param, GCTL_PARAM_RD, &len);
406 	if (p == NULL)
407 		return (NULL);
408 	if (len < 1) {
409 		gctl_error(req, "Argument without length (%s)", param);
410 		return (NULL);
411 	}
412 	if (p[len - 1] != '\0') {
413 		gctl_error(req, "Unterminated argument (%s)", param);
414 		return (NULL);
415 	}
416 	return (p);
417 }
418 
419 void *
420 gctl_get_paraml_opt(struct gctl_req *req, const char *param, int len)
421 {
422 	int i;
423 	void *p;
424 
425 	p = gctl_get_param(req, param, &i);
426 	if (i != len) {
427 		p = NULL;
428 		gctl_error(req, "Wrong length %s argument", param);
429 	}
430 	return (p);
431 }
432 
433 void *
434 gctl_get_paraml(struct gctl_req *req, const char *param, int len)
435 {
436 	void *p;
437 
438 	p = gctl_get_paraml_opt(req, param, len);
439 	if (p == NULL)
440 		gctl_error(req, "Missing %s argument", param);
441 	return (p);
442 }
443 
444 struct g_class *
445 gctl_get_class(struct gctl_req *req, char const *arg)
446 {
447 	char const *p;
448 	struct g_class *cp;
449 
450 	p = gctl_get_asciiparam(req, arg);
451 	if (p == NULL) {
452 		gctl_error(req, "Missing %s argument", arg);
453 		return (NULL);
454 	}
455 	LIST_FOREACH(cp, &g_classes, class) {
456 		if (!strcmp(p, cp->name))
457 			return (cp);
458 	}
459 	gctl_error(req, "Class not found: \"%s\"", p);
460 	return (NULL);
461 }
462 
463 struct g_geom *
464 gctl_get_geom(struct gctl_req *req, struct g_class *mp, char const *arg)
465 {
466 	char const *p;
467 	struct g_geom *gp;
468 
469 	MPASS(mp != NULL);
470 	p = gctl_get_asciiparam(req, arg);
471 	if (p == NULL) {
472 		gctl_error(req, "Missing %s argument", arg);
473 		return (NULL);
474 	}
475 	LIST_FOREACH(gp, &mp->geom, geom)
476 		if (!strcmp(p, gp->name))
477 			return (gp);
478 	gctl_error(req, "Geom not found: \"%s\"", p);
479 	return (NULL);
480 }
481 
482 struct g_provider *
483 gctl_get_provider(struct gctl_req *req, char const *arg)
484 {
485 	char const *p;
486 	struct g_provider *pp;
487 
488 	p = gctl_get_asciiparam(req, arg);
489 	if (p == NULL) {
490 		gctl_error(req, "Missing '%s' argument", arg);
491 		return (NULL);
492 	}
493 	pp = g_provider_by_name(p);
494 	if (pp != NULL)
495 		return (pp);
496 	gctl_error(req, "Provider not found: \"%s\"", p);
497 	return (NULL);
498 }
499 
500 static void
501 g_ctl_getxml(struct gctl_req *req, struct g_class *mp)
502 {
503 	const char *name;
504 	char *buf;
505 	struct sbuf *sb;
506 	int len, i = 0, n = 0, *parents;
507 	struct g_geom *gp, **gps;
508 	struct g_consumer *cp;
509 
510 	parents = gctl_get_paraml(req, "parents", sizeof(*parents));
511 	if (parents == NULL)
512 		return;
513 	name = gctl_get_asciiparam(req, "arg0");
514 	n = 0;
515 	LIST_FOREACH(gp, &mp->geom, geom) {
516 		if (name && strcmp(gp->name, name) != 0)
517 			continue;
518 		n++;
519 		if (*parents) {
520 			LIST_FOREACH(cp, &gp->consumer, consumer)
521 				n++;
522 		}
523 	}
524 	gps = g_malloc((n + 1) * sizeof(*gps), M_WAITOK);
525 	i = 0;
526 	LIST_FOREACH(gp, &mp->geom, geom) {
527 		if (name && strcmp(gp->name, name) != 0)
528 			continue;
529 		gps[i++] = gp;
530 		if (*parents) {
531 			LIST_FOREACH(cp, &gp->consumer, consumer) {
532 				if (cp->provider != NULL)
533 					gps[i++] = cp->provider->geom;
534 			}
535 		}
536 	}
537 	KASSERT(i == n, ("different number of geoms found (%d != %d)",
538 	    i, n));
539 	gps[i] = 0;
540 
541 	buf = gctl_get_param_flags(req, "output", GCTL_PARAM_WR, &len);
542 	if (buf == NULL) {
543 		gctl_error(req, "output parameter missing");
544 		g_free(gps);
545 		return;
546 	}
547 	sb = sbuf_new(NULL, buf, len, SBUF_FIXEDLEN | SBUF_INCLUDENUL);
548 	g_conf_specific(sb, gps);
549 	gctl_set_param(req, "output", buf, 0);
550 	if (sbuf_error(sb))
551 		gctl_error(req, "output buffer overflow");
552 	sbuf_delete(sb);
553 	g_free(gps);
554 }
555 
556 static void
557 g_ctl_req(void *arg, int flag __unused)
558 {
559 	struct g_class *mp;
560 	struct gctl_req *req;
561 	char const *verb;
562 
563 	g_topology_assert();
564 	req = arg;
565 	mp = gctl_get_class(req, "class");
566 	if (mp == NULL)
567 		return;
568 	verb = gctl_get_param(req, "verb", NULL);
569 	if (verb == NULL) {
570 		gctl_error(req, "Verb missing");
571 		return;
572 	}
573 	if (strcmp(verb, "getxml") == 0) {
574 		g_ctl_getxml(req, mp);
575 	} else if (mp->ctlreq == NULL) {
576 		gctl_error(req, "Class takes no requests");
577 	} else {
578 		mp->ctlreq(req, mp, verb);
579 	}
580 	g_topology_assert();
581 }
582 
583 static int
584 g_ctl_ioctl_ctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td)
585 {
586 	struct gctl_req *req;
587 	int nerror;
588 
589 	req = (void *)data;
590 	req->nerror = 0;
591 	/* It is an error if we cannot return an error text */
592 	if (req->lerror < 2)
593 		return (EINVAL);
594 	if (!useracc(req->error, req->lerror, VM_PROT_WRITE))
595 		return (EINVAL);
596 
597 	req->serror = sbuf_new_auto();
598 	/* Check the version */
599 	if (req->version != GCTL_VERSION) {
600 		gctl_error(req, "kernel and libgeom version mismatch.");
601 		req->arg = NULL;
602 	} else {
603 		/* Get things on board */
604 		gctl_copyin(req);
605 
606 		if (g_debugflags & G_F_CTLDUMP)
607 			gctl_dump(req, "request");
608 
609 		if (!req->nerror) {
610 			g_waitfor_event(g_ctl_req, req, M_WAITOK, NULL);
611 
612 			if (g_debugflags & G_F_CTLDUMP)
613 				gctl_dump(req, "result");
614 
615 			gctl_copyout(req);
616 		}
617 	}
618 	if (sbuf_done(req->serror)) {
619 		nerror = copyout(sbuf_data(req->serror), req->error,
620 		    imin(req->lerror, sbuf_len(req->serror) + 1));
621 		if (nerror != 0 && req->nerror == 0)
622 			req->nerror = nerror;
623 	}
624 
625 	nerror = req->nerror;
626 	gctl_free(req);
627 	return (nerror);
628 }
629 
630 static int
631 g_ctl_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td)
632 {
633 	int error;
634 
635 	switch(cmd) {
636 	case GEOM_CTL:
637 		error = g_ctl_ioctl_ctl(dev, cmd, data, fflag, td);
638 		break;
639 	default:
640 		error = ENOIOCTL;
641 		break;
642 	}
643 	return (error);
644 
645 }
646