git: 7f16b501e25b - main - GEOM: Introduce partial confxml API

From: Alexander Motin <mav_at_FreeBSD.org>
Date: Sat, 12 Mar 2022 16:55:56 UTC
The branch main has been updated by mav:

URL: https://cgit.FreeBSD.org/src/commit/?id=7f16b501e25b6c792fefc4535e0d1b8363392fe0

commit 7f16b501e25b6c792fefc4535e0d1b8363392fe0
Author:     Alexander Motin <mav@FreeBSD.org>
AuthorDate: 2022-03-12 16:49:37 +0000
Commit:     Alexander Motin <mav@FreeBSD.org>
CommitDate: 2022-03-12 16:55:52 +0000

    GEOM: Introduce partial confxml API
    
    Traditionally the GEOM's primary channel of information from kernel to
    user-space was confxml, fetched by libgeom through kern.geom.confxml
    sysctl.  It is convenient and informative, representing full state of
    GEOM in a single XML document.  But problems start to arise on systems
    with hundreds of disks, where the full confxml size reaches many
    megabytes, taking significant time to first write it and then parse.
    
    This patch introduces alternative solution, allowing to fetch much
    smaller XML document, subset of the full confxml, limited to 64KB and
    representing only one specified geom and optionally its parents.  It
    uses existing GEOM control interface, extended with new "getxml" verb.
    In case of any error, such as the buffer overflow, it just transparently
    falls back to traditional full confxml.  This patch uses the new API in
    user-space GEOM tools where it is possible.
    
    Reviewed by:    imp
    MFC after:      2 month
    Sponsored by:   iXsystems, Inc.
    Differential Revision:  https://reviews.freebsd.org/D34529
---
 lib/geom/mirror/geom_mirror.c |  26 +++++-----
 lib/geom/part/geom_part.c     |  90 ++++++++++++++++++----------------
 lib/libgeom/geom_getxml.c     |  41 ++++++++++++++++
 lib/libgeom/geom_xml2tree.c   |  50 +++++++++++++------
 lib/libgeom/libgeom.h         |   2 +
 sbin/geom/core/geom.c         |  10 ++--
 sys/geom/geom.h               |   1 +
 sys/geom/geom_ctl.c           | 111 ++++++++++++++++++++++++++++++++----------
 sys/geom/geom_dump.c          |  55 ++++++++++++---------
 sys/geom/geom_int.h           |   2 +-
 10 files changed, 264 insertions(+), 124 deletions(-)

diff --git a/lib/geom/mirror/geom_mirror.c b/lib/geom/mirror/geom_mirror.c
index a1b399338814..2b1860eb7548 100644
--- a/lib/geom/mirror/geom_mirror.c
+++ b/lib/geom/mirror/geom_mirror.c
@@ -440,32 +440,30 @@ mirror_resize(struct gctl_req *req, unsigned flags __unused)
 	struct gconsumer *cp;
 	off_t size;
 	int error, nargs;
-	const char *name;
+	const char *name, *g;
 	char ssize[30];
 
 	nargs = gctl_get_int(req, "nargs");
-	if (nargs < 1) {
-		gctl_error(req, "Too few arguments.");
-		return;
-	}
-	error = geom_gettree(&mesh);
-	if (error)
-		errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
+	if (nargs != 1)
+		errx(EXIT_FAILURE, "Invalid number of arguments.");
 	name = gctl_get_ascii(req, "class");
 	if (name == NULL)
 		abort();
+	g = gctl_get_ascii(req, "arg0");
+	if (g == NULL)
+		abort();
+	error = geom_gettree_geom(&mesh, name, g, 1);
+	if (error)
+		errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
 	classp = find_class(&mesh, name);
 	if (classp == NULL)
 		errx(EXIT_FAILURE, "Class %s not found.", name);
-	name = gctl_get_ascii(req, "arg0");
-	if (name == NULL)
-		abort();
-	gp = find_geom(classp, name);
+	gp = find_geom(classp, g);
 	if (gp == NULL)
-		errx(EXIT_FAILURE, "No such geom: %s.", name);
+		errx(EXIT_FAILURE, "No such geom: %s.", g);
 	pp = LIST_FIRST(&gp->lg_provider);
 	if (pp == NULL)
-		errx(EXIT_FAILURE, "Provider of geom %s not found.", name);
+		errx(EXIT_FAILURE, "Provider of geom %s not found.", g);
 	size = pp->lg_mediasize;
 	name = gctl_get_ascii(req, "size");
 	if (name == NULL)
diff --git a/lib/geom/part/geom_part.c b/lib/geom/part/geom_part.c
index 330a4708251d..2d8f02053a69 100644
--- a/lib/geom/part/geom_part.c
+++ b/lib/geom/part/geom_part.c
@@ -329,31 +329,31 @@ gpart_autofill_resize(struct gctl_req *req)
 	struct gprovider *pp;
 	off_t last, size, start, new_size;
 	off_t lba, new_lba, alignment, offset;
-	const char *s;
+	const char *g, *s;
 	int error, idx, has_alignment;
 
 	idx = (int)gctl_get_intmax(req, GPART_PARAM_INDEX);
 	if (idx < 1)
 		errx(EXIT_FAILURE, "invalid partition index");
 
-	error = geom_gettree(&mesh);
-	if (error)
-		return (error);
 	s = gctl_get_ascii(req, "class");
 	if (s == NULL)
 		abort();
+	g = gctl_get_ascii(req, "arg0");
+	if (g == NULL)
+		abort();
+	error = geom_gettree_geom(&mesh, s, g, 1);
+	if (error)
+		return (error);
 	cp = find_class(&mesh, s);
 	if (cp == NULL)
 		errx(EXIT_FAILURE, "Class %s not found.", s);
-	s = gctl_get_ascii(req, "arg0");
-	if (s == NULL)
-		abort();
-	gp = find_geom(cp, s);
+	gp = find_geom(cp, g);
 	if (gp == NULL)
-		errx(EXIT_FAILURE, "No such geom: %s.", s);
+		errx(EXIT_FAILURE, "No such geom: %s.", g);
 	pp = LIST_FIRST(&gp->lg_consumer)->lg_provider;
 	if (pp == NULL)
-		errx(EXIT_FAILURE, "Provider for geom %s not found.", s);
+		errx(EXIT_FAILURE, "Provider for geom %s not found.", g);
 
 	s = gctl_get_ascii(req, "alignment");
 	has_alignment = (*s == '*') ? 0 : 1;
@@ -454,7 +454,7 @@ gpart_autofill(struct gctl_req *req)
 	off_t size, start, a_lba;
 	off_t lba, len, alignment, offset;
 	uintmax_t grade;
-	const char *s;
+	const char *g, *s;
 	int error, has_size, has_start, has_alignment;
 
 	s = gctl_get_ascii(req, "verb");
@@ -463,22 +463,22 @@ gpart_autofill(struct gctl_req *req)
 	if (strcmp(s, "add") != 0)
 		return (0);
 
-	error = geom_gettree(&mesh);
-	if (error)
-		return (error);
 	s = gctl_get_ascii(req, "class");
 	if (s == NULL)
 		abort();
+	g = gctl_get_ascii(req, "arg0");
+	if (g == NULL)
+		abort();
+	error = geom_gettree_geom(&mesh, s, g, 1);
+	if (error)
+		return (error);
 	cp = find_class(&mesh, s);
 	if (cp == NULL)
 		errx(EXIT_FAILURE, "Class %s not found.", s);
-	s = gctl_get_ascii(req, "arg0");
-	if (s == NULL)
-		abort();
-	gp = find_geom(cp, s);
+	gp = find_geom(cp, g);
 	if (gp == NULL) {
-		if (g_device_path(s) == NULL) {
-			errx(EXIT_FAILURE, "No such geom %s.", s);
+		if (g_device_path(g) == NULL) {
+			errx(EXIT_FAILURE, "No such geom %s.", g);
 		} else {
 			/*
 			 * We don't free memory allocated by g_device_path() as
@@ -486,12 +486,12 @@ gpart_autofill(struct gctl_req *req)
 			 */
 			errx(EXIT_FAILURE,
 			    "No partitioning scheme found on geom %s. Create one first using 'gpart create'.",
-			    s);
+			    g);
 		}
 	}
 	pp = LIST_FIRST(&gp->lg_consumer)->lg_provider;
 	if (pp == NULL)
-		errx(EXIT_FAILURE, "Provider for geom %s not found.", s);
+		errx(EXIT_FAILURE, "Provider for geom %s not found.", g);
 
 	s = gctl_get_ascii(req, "alignment");
 	has_alignment = (*s == '*') ? 0 : 1;
@@ -742,7 +742,12 @@ gpart_show(struct gctl_req *req, unsigned int fl __unused)
 	name = gctl_get_ascii(req, "class");
 	if (name == NULL)
 		abort();
-	error = geom_gettree(&mesh);
+	nargs = gctl_get_int(req, "nargs");
+	if (nargs == 1) {
+		error = geom_gettree_geom(&mesh, name,
+		    gctl_get_ascii(req, "arg0"), 1);
+	} else
+		error = geom_gettree(&mesh);
 	if (error != 0)
 		errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
 	classp = find_class(&mesh, name);
@@ -751,7 +756,6 @@ gpart_show(struct gctl_req *req, unsigned int fl __unused)
 		errx(EXIT_FAILURE, "Class %s not found.", name);
 	}
 	show_providers = gctl_get_int(req, "show_providers");
-	nargs = gctl_get_int(req, "nargs");
 	if (nargs > 0) {
 		for (i = 0; i < nargs; i++) {
 			name = gctl_get_ascii(req, "arg%d", i);
@@ -776,34 +780,33 @@ gpart_backup(struct gctl_req *req, unsigned int fl __unused)
 	struct gclass *classp;
 	struct gprovider *pp;
 	struct ggeom *gp;
-	const char *s, *scheme;
+	const char *g, *s, *scheme;
 	off_t sector, end;
 	off_t length;
 	int error, i, windex, wblocks, wtype;
 
 	if (gctl_get_int(req, "nargs") != 1)
 		errx(EXIT_FAILURE, "Invalid number of arguments.");
-	error = geom_gettree(&mesh);
-	if (error != 0)
-		errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
 	s = gctl_get_ascii(req, "class");
 	if (s == NULL)
 		abort();
+	g = gctl_get_ascii(req, "arg0");
+	if (g == NULL)
+		abort();
+	error = geom_gettree_geom(&mesh, s, g, 0);
+	if (error != 0)
+		errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
 	classp = find_class(&mesh, s);
 	if (classp == NULL) {
 		geom_deletetree(&mesh);
 		errx(EXIT_FAILURE, "Class %s not found.", s);
 	}
-	s = gctl_get_ascii(req, "arg0");
-	if (s == NULL)
-		abort();
-	gp = find_geom(classp, s);
+	gp = find_geom(classp, g);
 	if (gp == NULL)
-		errx(EXIT_FAILURE, "No such geom: %s.", s);
+		errx(EXIT_FAILURE, "No such geom: %s.", g);
 	scheme = find_geomcfg(gp, "scheme");
 	if (scheme == NULL)
 		abort();
-	pp = LIST_FIRST(&gp->lg_consumer)->lg_provider;
 	s = find_geomcfg(gp, "last");
 	if (s == NULL)
 		abort();
@@ -1201,11 +1204,14 @@ gpart_bootcode(struct gctl_req *req, unsigned int fl)
 	struct gmesh mesh;
 	struct gclass *classp;
 	struct ggeom *gp;
-	const char *s;
+	const char *g, *s;
 	void *bootcode, *partcode;
 	size_t bootsize, partsize;
 	int error, idx, vtoc8;
 
+	if (gctl_get_int(req, "nargs") != 1)
+		errx(EXIT_FAILURE, "Invalid number of arguments.");
+
 	if (gctl_has_param(req, GPART_PARAM_BOOTCODE)) {
 		s = gctl_get_ascii(req, GPART_PARAM_BOOTCODE);
 		bootsize = 800 * 1024;		/* Arbitrary limit. */
@@ -1228,7 +1234,10 @@ gpart_bootcode(struct gctl_req *req, unsigned int fl)
 	s = gctl_get_ascii(req, "class");
 	if (s == NULL)
 		abort();
-	error = geom_gettree(&mesh);
+	g = gctl_get_ascii(req, "arg0");
+	if (g == NULL)
+		abort();
+	error = geom_gettree_geom(&mesh, s, g, 0);
 	if (error != 0)
 		errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
 	classp = find_class(&mesh, s);
@@ -1236,14 +1245,9 @@ gpart_bootcode(struct gctl_req *req, unsigned int fl)
 		geom_deletetree(&mesh);
 		errx(EXIT_FAILURE, "Class %s not found.", s);
 	}
-	if (gctl_get_int(req, "nargs") != 1)
-		errx(EXIT_FAILURE, "Invalid number of arguments.");
-	s = gctl_get_ascii(req, "arg0");
-	if (s == NULL)
-		abort();
-	gp = find_geom(classp, s);
+	gp = find_geom(classp, g);
 	if (gp == NULL)
-		errx(EXIT_FAILURE, "No such geom: %s.", s);
+		errx(EXIT_FAILURE, "No such geom: %s.", g);
 	s = find_geomcfg(gp, "scheme");
 	if (s == NULL)
 		errx(EXIT_FAILURE, "Scheme not found for geom %s", gp->lg_name);
diff --git a/lib/libgeom/geom_getxml.c b/lib/libgeom/geom_getxml.c
index 48565f707b03..a6bb130fd5a7 100644
--- a/lib/libgeom/geom_getxml.c
+++ b/lib/libgeom/geom_getxml.c
@@ -3,6 +3,7 @@
  *
  * Copyright (c) 2003 Poul-Henning Kamp
  * All rights reserved.
+ * Copyright (c) 2022 Alexander Motin <mav@FreeBSD.org>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -50,6 +51,13 @@
  */
 #define	GEOM_GETXML_RETRIES	4
 
+/*
+ * Size of confxml buffer to request via getxml control request.  It is
+ * expected to be sufficient for single geom and its parents.  In case of
+ * overflow fall back to requesting full confxml via sysctl interface.
+ */
+#define	GEOM_GETXML_BUFFER	65536
+
 char *
 geom_getxml(void)
 {
@@ -87,3 +95,36 @@ geom_getxml(void)
 
 	return (NULL);
 }
+
+char *
+geom_getxml_geom(const char *class, const char *geom, int parents)
+{
+	struct gctl_req *r;
+	char *p;
+	const char *errstr;
+	int nargs = 0;
+
+	p = malloc(GEOM_GETXML_BUFFER);
+	if (p == NULL)
+		return (NULL);
+	r = gctl_get_handle();
+	gctl_ro_param(r, "class", -1, class);
+	gctl_ro_param(r, "verb", -1, "getxml");
+	gctl_ro_param(r, "parents", sizeof(parents), &parents);
+	if (geom) {
+		gctl_ro_param(r, "arg0", -1, geom);
+		nargs = 1;
+	}
+	gctl_ro_param(r, "nargs", sizeof(nargs), &nargs);
+	p[0] = '\0';
+	gctl_add_param(r, "output", GEOM_GETXML_BUFFER, p,
+	    GCTL_PARAM_WR | GCTL_PARAM_ASCII);
+	errstr = gctl_issue(r);
+	if (errstr != NULL && errstr[0] != '\0') {
+		gctl_free(r);
+		free(p);
+		return (geom_getxml());
+	}
+	gctl_free(r);
+	return (p);
+}
diff --git a/lib/libgeom/geom_xml2tree.c b/lib/libgeom/geom_xml2tree.c
index 824800070933..a6da0e6d163f 100644
--- a/lib/libgeom/geom_xml2tree.c
+++ b/lib/libgeom/geom_xml2tree.c
@@ -358,6 +358,17 @@ geom_lookupid(struct gmesh *gmp, const void *id)
 	return (NULL);
 }
 
+static void *
+geom_lookupidptr(struct gmesh *gmp, const void *id)
+{
+	struct gident *gip;
+
+	gip = geom_lookupid(gmp, id);
+	if (gip)
+		return (gip->lg_ptr);
+	return (NULL);
+}
+
 int
 geom_xml2tree(struct gmesh *gmp, char *p)
 {
@@ -430,22 +441,19 @@ geom_xml2tree(struct gmesh *gmp, char *p)
 	/* Substitute all identifiers */
 	LIST_FOREACH(cl, &gmp->lg_class, lg_class) {
 		LIST_FOREACH(ge, &cl->lg_geom, lg_geom) {
-			ge->lg_class =
-			    geom_lookupid(gmp, ge->lg_class)->lg_ptr;
-			LIST_FOREACH(pr, &ge->lg_provider, lg_provider) {
-				pr->lg_geom =
-				    geom_lookupid(gmp, pr->lg_geom)->lg_ptr;
-			}
+			ge->lg_class = geom_lookupidptr(gmp, ge->lg_class);
+			LIST_FOREACH(pr, &ge->lg_provider, lg_provider)
+				pr->lg_geom = geom_lookupidptr(gmp, pr->lg_geom);
 			LIST_FOREACH(co, &ge->lg_consumer, lg_consumer) {
-				co->lg_geom =
-				    geom_lookupid(gmp, co->lg_geom)->lg_ptr;
+				co->lg_geom = geom_lookupidptr(gmp, co->lg_geom);
 				if (co->lg_provider != NULL) {
-					co->lg_provider = 
-					    geom_lookupid(gmp,
-						co->lg_provider)->lg_ptr;
-					LIST_INSERT_HEAD(
-					    &co->lg_provider->lg_consumers,
-					    co, lg_consumers);
+					co->lg_provider = geom_lookupidptr(gmp,
+						co->lg_provider);
+					if (co->lg_provider != NULL) {
+						LIST_INSERT_HEAD(
+						    &co->lg_provider->lg_consumers,
+						    co, lg_consumers);
+					}
 				}
 			}
 		}
@@ -467,6 +475,20 @@ geom_gettree(struct gmesh *gmp)
 	return (error);
 }
 
+int
+geom_gettree_geom(struct gmesh *gmp, const char *c, const char *g, int parents)
+{
+	char *p;
+	int error;
+
+	p = geom_getxml_geom(c, g, parents);
+	if (p == NULL)
+		return (errno);
+	error = geom_xml2tree(gmp, p);
+	free(p);
+	return (error);
+}
+
 static void 
 delete_config(struct gconf *gp)
 {
diff --git a/lib/libgeom/libgeom.h b/lib/libgeom/libgeom.h
index 9be27208a98c..339a8d34729f 100644
--- a/lib/libgeom/libgeom.h
+++ b/lib/libgeom/libgeom.h
@@ -56,6 +56,7 @@ void geom_stats_snapshot_reset(void *);
 struct devstat *geom_stats_snapshot_next(void *);
 
 char *geom_getxml(void);
+char *geom_getxml_geom(const char *, const char *, int);
 
 /* geom_xml2tree.c */
 
@@ -137,6 +138,7 @@ struct gprovider {
 struct gident * geom_lookupid(struct gmesh *, const void *);
 int geom_xml2tree(struct gmesh *, char *);
 int geom_gettree(struct gmesh *);
+int geom_gettree_geom(struct gmesh *, const char *, const char *, int);
 void geom_deletetree(struct gmesh *);
 
 /* geom_ctl.c */
diff --git a/sbin/geom/core/geom.c b/sbin/geom/core/geom.c
index b29d73327df8..9b43910b88f9 100644
--- a/sbin/geom/core/geom.c
+++ b/sbin/geom/core/geom.c
@@ -996,7 +996,7 @@ std_list_available(void)
 	struct gclass *classp;
 	int error;
 
-	error = geom_gettree(&mesh);
+	error = geom_gettree_geom(&mesh, gclass_name, "", 0);
 	if (error != 0)
 		errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
 	classp = find_class(&mesh, gclass_name);
@@ -1015,7 +1015,12 @@ std_list(struct gctl_req *req, unsigned flags __unused)
 	const char *name;
 	int all, error, i, nargs;
 
-	error = geom_gettree(&mesh);
+	nargs = gctl_get_int(req, "nargs");
+	if (nargs == 1) {
+		error = geom_gettree_geom(&mesh, gclass_name,
+		    gctl_get_ascii(req, "arg0"), 1);
+	} else
+		error = geom_gettree(&mesh);
 	if (error != 0)
 		errc(EXIT_FAILURE, error, "Cannot get GEOM tree");
 	classp = find_class(&mesh, gclass_name);
@@ -1023,7 +1028,6 @@ std_list(struct gctl_req *req, unsigned flags __unused)
 		geom_deletetree(&mesh);
 		errx(EXIT_FAILURE, "Class '%s' not found.", gclass_name);
 	}
-	nargs = gctl_get_int(req, "nargs");
 	all = gctl_get_int(req, "all");
 	if (nargs > 0) {
 		for (i = 0; i < nargs; i++) {
diff --git a/sys/geom/geom.h b/sys/geom/geom.h
index fbd9336b2e50..70d21e346c9b 100644
--- a/sys/geom/geom.h
+++ b/sys/geom/geom.h
@@ -431,6 +431,7 @@ int g_is_geom_thread(struct thread *td);
 int gctl_set_param(struct gctl_req *req, const char *param, void const *ptr, int len);
 void gctl_set_param_err(struct gctl_req *req, const char *param, void const *ptr, int len);
 void *gctl_get_param(struct gctl_req *req, const char *param, int *len);
+void *gctl_get_param_flags(struct gctl_req *req, const char *param, int flags, int *len);
 char const *gctl_get_asciiparam(struct gctl_req *req, const char *param);
 void *gctl_get_paraml(struct gctl_req *req, const char *param, int len);
 void *gctl_get_paraml_opt(struct gctl_req *req, const char *param, int len);
diff --git a/sys/geom/geom_ctl.c b/sys/geom/geom_ctl.c
index 307ccd989ca0..0f1dce934b63 100644
--- a/sys/geom/geom_ctl.c
+++ b/sys/geom/geom_ctl.c
@@ -4,6 +4,7 @@
  * Copyright (c) 2002 Poul-Henning Kamp
  * Copyright (c) 2002 Networks Associates Technology, Inc.
  * All rights reserved.
+ * Copyright (c) 2022 Alexander Motin <mav@FreeBSD.org>
  *
  * This software was developed for the FreeBSD Project by Poul-Henning Kamp
  * and NAI Labs, the Security Research Division of Network Associates, Inc.
@@ -363,7 +364,7 @@ gctl_set_param_err(struct gctl_req *req, const char *param, void const *ptr,
 }
 
 void *
-gctl_get_param(struct gctl_req *req, const char *param, int *len)
+gctl_get_param_flags(struct gctl_req *req, const char *param, int flags, int *len)
 {
 	u_int i;
 	void *p;
@@ -373,7 +374,7 @@ gctl_get_param(struct gctl_req *req, const char *param, int *len)
 		ap = &req->arg[i];
 		if (strcmp(param, ap->name))
 			continue;
-		if (!(ap->flag & GCTL_PARAM_RD))
+		if ((ap->flag & flags) != flags)
 			continue;
 		p = ap->kvalue;
 		if (len != NULL)
@@ -383,31 +384,31 @@ gctl_get_param(struct gctl_req *req, const char *param, int *len)
 	return (NULL);
 }
 
+void *
+gctl_get_param(struct gctl_req *req, const char *param, int *len)
+{
+
+	return (gctl_get_param_flags(req, param, GCTL_PARAM_RD, len));
+}
+
 char const *
 gctl_get_asciiparam(struct gctl_req *req, const char *param)
 {
-	u_int i;
 	char const *p;
-	struct gctl_req_arg *ap;
+	int len;
 
-	for (i = 0; i < req->narg; i++) {
-		ap = &req->arg[i];
-		if (strcmp(param, ap->name))
-			continue;
-		if (!(ap->flag & GCTL_PARAM_RD))
-			continue;
-		p = ap->kvalue;
-		if (ap->len < 1) {
-			gctl_error(req, "No length argument (%s)", param);
-			return (NULL);
-		}
-		if (p[ap->len - 1] != '\0') {
-			gctl_error(req, "Unterminated argument (%s)", param);
-			return (NULL);
-		}
-		return (p);
+	p = gctl_get_param_flags(req, param, GCTL_PARAM_RD, &len);
+	if (p == NULL)
+		return (NULL);
+	if (len < 1) {
+		gctl_error(req, "Argument without length (%s)", param);
+		return (NULL);
 	}
-	return (NULL);
+	if (p[len - 1] != '\0') {
+		gctl_error(req, "Unterminated argument (%s)", param);
+		return (NULL);
+	}
+	return (p);
 }
 
 void *
@@ -491,6 +492,62 @@ gctl_get_provider(struct gctl_req *req, char const *arg)
 	return (NULL);
 }
 
+static void
+g_ctl_getxml(struct gctl_req *req, struct g_class *mp)
+{
+	const char *name;
+	char *buf;
+	struct sbuf *sb;
+	int len, i = 0, n = 0, *parents;
+	struct g_geom *gp, **gps;
+	struct g_consumer *cp;
+
+	parents = gctl_get_paraml(req, "parents", sizeof(*parents));
+	if (parents == NULL)
+		return;
+	name = gctl_get_asciiparam(req, "arg0");
+	n = 0;
+	LIST_FOREACH(gp, &mp->geom, geom) {
+		if (name && strcmp(gp->name, name) != 0)
+			continue;
+		n++;
+		if (*parents) {
+			LIST_FOREACH(cp, &gp->consumer, consumer)
+				n++;
+		}
+	}
+	gps = g_malloc((n + 1) * sizeof(*gps), M_WAITOK);
+	i = 0;
+	LIST_FOREACH(gp, &mp->geom, geom) {
+		if (name && strcmp(gp->name, name) != 0)
+			continue;
+		gps[i++] = gp;
+		if (*parents) {
+			LIST_FOREACH(cp, &gp->consumer, consumer) {
+				if (cp->provider != NULL)
+					gps[i++] = cp->provider->geom;
+			}
+		}
+	}
+	KASSERT(i == n, ("different number of geoms found (%d != %d)",
+	    i, n));
+	gps[i] = 0;
+
+	buf = gctl_get_param_flags(req, "output", GCTL_PARAM_WR, &len);
+	if (buf == NULL) {
+		gctl_error(req, "output parameter missing");
+		g_free(gps);
+		return;
+	}
+	sb = sbuf_new(NULL, buf, len, SBUF_FIXEDLEN | SBUF_INCLUDENUL);
+	g_conf_specific(sb, gps);
+	gctl_set_param(req, "output", buf, 0);
+	if (sbuf_error(sb))
+		gctl_error(req, "output buffer overflow");
+	sbuf_delete(sb);
+	g_free(gps);
+}
+
 static void
 g_ctl_req(void *arg, int flag __unused)
 {
@@ -503,16 +560,18 @@ g_ctl_req(void *arg, int flag __unused)
 	mp = gctl_get_class(req, "class");
 	if (mp == NULL)
 		return;
-	if (mp->ctlreq == NULL) {
-		gctl_error(req, "Class takes no requests");
-		return;
-	}
 	verb = gctl_get_param(req, "verb", NULL);
 	if (verb == NULL) {
 		gctl_error(req, "Verb missing");
 		return;
 	}
-	mp->ctlreq(req, mp, verb);
+	if (strcmp(verb, "getxml") == 0) {
+		g_ctl_getxml(req, mp);
+	} else if (mp->ctlreq == NULL) {
+		gctl_error(req, "Class takes no requests");
+	} else {
+		mp->ctlreq(req, mp, verb);
+	}
 	g_topology_assert();
 }
 
diff --git a/sys/geom/geom_dump.c b/sys/geom/geom_dump.c
index 067ab43f7d05..58077147ff8d 100644
--- a/sys/geom/geom_dump.c
+++ b/sys/geom/geom_dump.c
@@ -4,6 +4,7 @@
  * Copyright (c) 2002 Poul-Henning Kamp
  * Copyright (c) 2002 Networks Associates Technology, Inc.
  * All rights reserved.
+ * Copyright (c) 2013-2022 Alexander Motin <mav@FreeBSD.org>
  *
  * This software was developed for the FreeBSD Project by Poul-Henning Kamp
  * and NAI Labs, the Security Research Division of Network Associates, Inc.
@@ -242,10 +243,10 @@ g_conf_provider(struct sbuf *sb, struct g_provider *pp)
 }
 
 static void
-g_conf_geom(struct sbuf *sb, struct g_geom *gp, struct g_provider *pp, struct g_consumer *cp)
+g_conf_geom(struct sbuf *sb, struct g_geom *gp)
 {
-	struct g_consumer *cp2;
-	struct g_provider *pp2;
+	struct g_consumer *cp;
+	struct g_provider *pp;
 
 	sbuf_printf(sb, "    <geom id=\"%p\">\n", gp);
 	sbuf_printf(sb, "      <class ref=\"%p\"/>\n", gp->class);
@@ -260,48 +261,56 @@ g_conf_geom(struct sbuf *sb, struct g_geom *gp, struct g_provider *pp, struct g_
 		gp->dumpconf(sb, "\t", gp, NULL, NULL);
 		sbuf_cat(sb, "      </config>\n");
 	}
-	LIST_FOREACH(cp2, &gp->consumer, consumer) {
-		if (cp != NULL && cp != cp2)
-			continue;
-		g_conf_consumer(sb, cp2);
-	}
+	LIST_FOREACH(cp, &gp->consumer, consumer)
+		g_conf_consumer(sb, cp);
+	LIST_FOREACH(pp, &gp->provider, provider)
+		g_conf_provider(sb, pp);
+	sbuf_cat(sb, "    </geom>\n");
+}
 
-	LIST_FOREACH(pp2, &gp->provider, provider) {
-		if (pp != NULL && pp != pp2)
-			continue;
-		g_conf_provider(sb, pp2);
+static bool
+g_conf_matchgp(struct g_geom *gp, struct g_geom **gps)
+{
+
+	if (gps == NULL)
+		return (true);
+	for (; *gps != NULL; gps++) {
+		if (*gps == gp)
+			return (true);
 	}
-	sbuf_cat(sb, "    </geom>\n");
+	return (false);
 }
 
 static void
-g_conf_class(struct sbuf *sb, struct g_class *mp, struct g_geom *gp, struct g_provider *pp, struct g_consumer *cp)
+g_conf_class(struct sbuf *sb, struct g_class *mp, struct g_geom **gps)
 {
-	struct g_geom *gp2;
+	struct g_geom *gp;
 
 	sbuf_printf(sb, "  <class id=\"%p\">\n", mp);
 	sbuf_cat(sb, "    <name>");
 	g_conf_cat_escaped(sb, mp->name);
 	sbuf_cat(sb, "</name>\n");
-	LIST_FOREACH(gp2, &mp->geom, geom) {
-		if (gp != NULL && gp != gp2)
+	LIST_FOREACH(gp, &mp->geom, geom) {
+		if (!g_conf_matchgp(gp, gps))
 			continue;
-		g_conf_geom(sb, gp2, pp, cp);
+		g_conf_geom(sb, gp);
+		if (sbuf_error(sb))
+			break;
 	}
 	sbuf_cat(sb, "  </class>\n");
 }
 
 void
-g_conf_specific(struct sbuf *sb, struct g_class *mp, struct g_geom *gp, struct g_provider *pp, struct g_consumer *cp)
+g_conf_specific(struct sbuf *sb, struct g_geom **gps)
 {
 	struct g_class *mp2;
 
 	g_topology_assert();
 	sbuf_cat(sb, "<mesh>\n");
 	LIST_FOREACH(mp2, &g_classes, class) {
-		if (mp != NULL && mp != mp2)
-			continue;
-		g_conf_class(sb, mp2, gp, pp, cp);
+		g_conf_class(sb, mp2, gps);
+		if (sbuf_error(sb))
+			break;
 	}
 	sbuf_cat(sb, "</mesh>\n");
 	sbuf_finish(sb);
@@ -313,7 +322,7 @@ g_confxml(void *p, int flag)
 
 	KASSERT(flag != EV_CANCEL, ("g_confxml was cancelled"));
 	g_topology_assert();
-	g_conf_specific(p, NULL, NULL, NULL, NULL);
+	g_conf_specific(p, NULL);
 }
 
 void
diff --git a/sys/geom/geom_int.h b/sys/geom/geom_int.h
index bef5374e6f38..b4d139d76218 100644
--- a/sys/geom/geom_int.h
+++ b/sys/geom/geom_int.h
@@ -46,7 +46,7 @@ extern int g_collectstats;
 
 /* geom_dump.c */
 void g_confxml(void *, int flag);
-void g_conf_specific(struct sbuf *sb, struct g_class *mp, struct g_geom *gp, struct g_provider *pp, struct g_consumer *cp);
+void g_conf_specific(struct sbuf *sb, struct g_geom **gps);
 void g_conf_cat_escaped(struct sbuf *sb, const char *buf);
 void g_conf_printf_escaped(struct sbuf *sb, const char *fmt, ...);
 void g_confdot(void *, int flag);