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
55static d_ioctl_t g_ctl_ioctl;
56
57static 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
64CTASSERT(GCTL_PARAM_RD == VM_PROT_READ);
65CTASSERT(GCTL_PARAM_WR == VM_PROT_WRITE);
66
67void
68g_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 */
79int
80gctl_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 */
124void
125gctl_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 */
150void
151gctl_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 */
170static void *
171geom_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
183static void
184gctl_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
250static void
251gctl_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
272static void
273gctl_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
290static void
291gctl_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
329int
330gctl_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
353void
354gctl_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
371void *
372gctl_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
392void *
393gctl_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
399char const *
400gctl_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
419void *
420gctl_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
433void *
434gctl_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
444struct g_class *
445gctl_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
463struct g_geom *
464gctl_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
482struct g_provider *
483gctl_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
500static void
501g_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
556static void
557g_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
583static int
584g_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
630static int
631g_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