g_concat.c revision 126565
1/*-
2 * Copyright (c) 2003 Pawel Jakub Dawidek <pjd@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/sys/geom/concat/g_concat.c 126565 2004-03-03 22:29:24Z pjd $");
29
30#include <sys/param.h>
31#include <sys/systm.h>
32#include <sys/kernel.h>
33#include <sys/module.h>
34#include <sys/lock.h>
35#include <sys/mutex.h>
36#include <sys/bio.h>
37#include <sys/sysctl.h>
38#include <sys/malloc.h>
39#include <geom/geom.h>
40#include <geom/concat/g_concat.h>
41
42
43static MALLOC_DEFINE(M_CONCAT, "concat data", "GEOM_CONCAT Data");
44
45SYSCTL_DECL(_kern_geom);
46SYSCTL_NODE(_kern_geom, OID_AUTO, concat, CTLFLAG_RW, 0, "GEOM_CONCAT stuff");
47static u_int g_concat_debug = 0;
48SYSCTL_UINT(_kern_geom_concat, OID_AUTO, debug, CTLFLAG_RW, &g_concat_debug, 0,
49    "Debug level");
50
51static int g_concat_destroy(struct g_concat_softc *sc, boolean_t force);
52static int g_concat_destroy_geom(struct gctl_req *req, struct g_class *mp,
53    struct g_geom *gp);
54
55static g_taste_t g_concat_taste;
56static g_ctl_req_t g_concat_config;
57static g_dumpconf_t g_concat_dumpconf;
58
59struct g_class g_concat_class = {
60	.name = G_CONCAT_CLASS_NAME,
61	.ctlreq = g_concat_config,
62	.taste = g_concat_taste,
63	.destroy_geom = g_concat_destroy_geom
64};
65
66
67/*
68 * Return the number of valid disks.
69 */
70static u_int
71g_concat_nvalid(struct g_concat_softc *sc)
72{
73	u_int i, no;
74
75	no = 0;
76	for (i = 0; i < sc->sc_ndisks; i++) {
77		if (sc->sc_disks[i].d_consumer != NULL)
78			no++;
79	}
80
81	return (no);
82}
83
84static void
85g_concat_remove_disk(struct g_concat_disk *disk)
86{
87	struct g_consumer *cp;
88	struct g_concat_softc *sc;
89
90	KASSERT(disk->d_consumer != NULL, ("Non-valid disk in %s.", __func__));
91	sc = disk->d_softc;
92	cp = disk->d_consumer;
93
94	G_CONCAT_DEBUG(1, "Removing disk %s from %s.", cp->provider->name,
95	    sc->sc_provider->name);
96
97	disk->d_consumer = NULL;
98
99	g_error_provider(sc->sc_provider, ENXIO);
100
101	G_CONCAT_DEBUG(0, "Disk %s removed from %s.", cp->provider->name,
102	    sc->sc_provider->name);
103
104	if (cp->acr > 0 || cp->acw > 0 || cp->ace > 0)
105		g_access(cp, -cp->acr, -cp->acw, -cp->ace);
106	g_detach(cp);
107	g_destroy_consumer(cp);
108}
109
110static void
111g_concat_orphan(struct g_consumer *cp)
112{
113	struct g_concat_softc *sc;
114	struct g_concat_disk *disk;
115	struct g_geom *gp;
116
117	g_topology_assert();
118	gp = cp->geom;
119	sc = gp->softc;
120	if (sc == NULL)
121		return;
122
123	disk = cp->private;
124	if (disk == NULL)	/* Possible? */
125		return;
126	g_concat_remove_disk(disk);
127
128	if (sc->sc_type == G_CONCAT_TYPE_MANUAL) {
129		/*
130		 * For manually configured devices, don't remove provider
131		 * even is there are no vaild disks at all.
132		 */
133		return;
134	}
135
136	/* If there are no valid disks anymore, remove device. */
137	if (g_concat_nvalid(sc) == 0)
138		g_concat_destroy(sc, 1);
139}
140
141static int
142g_concat_access(struct g_provider *pp, int dr, int dw, int de)
143{
144	struct g_consumer *cp1, *cp2;
145	struct g_concat_softc *sc;
146	struct g_geom *gp;
147	int error;
148
149	gp = pp->geom;
150	sc = gp->softc;
151
152	if (sc == NULL) {
153		/*
154		 * It looks like geom is being withered.
155		 * In that case we allow only negative requests.
156		 */
157		KASSERT(dr <= 0 && dw <= 0 && de <= 0,
158		    ("Positive access request (device=%s).", pp->name));
159		if ((pp->acr + dr) == 0 && (pp->acw + dw) == 0 &&
160		    (pp->ace + de) == 0) {
161			G_CONCAT_DEBUG(0, "Device %s definitely destroyed.",
162			    gp->name);
163		}
164		return (0);
165	}
166
167	/* On first open, grab an extra "exclusive" bit */
168	if (pp->acr == 0 && pp->acw == 0 && pp->ace == 0)
169		de++;
170	/* ... and let go of it on last close */
171	if ((pp->acr + dr) == 0 && (pp->acw + dw) == 0 && (pp->ace + de) == 1)
172		de--;
173
174	error = ENXIO;
175	LIST_FOREACH(cp1, &gp->consumer, consumer) {
176		error = g_access(cp1, dr, dw, de);
177		if (error == 0)
178			continue;
179		/*
180		 * If we fail here, backout all previous changes.
181		 */
182		LIST_FOREACH(cp2, &gp->consumer, consumer) {
183			if (cp1 == cp2)
184				return (error);
185			g_access(cp2, -dr, -dw, -de);
186		}
187		/* NOTREACHED */
188	}
189
190	return (error);
191}
192
193static void
194g_concat_start(struct bio *bp)
195{
196	struct g_concat_softc *sc;
197	struct g_concat_disk *disk;
198	struct g_provider *pp;
199	off_t offset, end, length, off, len;
200	struct bio *cbp;
201	char *addr;
202	u_int no;
203
204	pp = bp->bio_to;
205	sc = pp->geom->softc;
206	/*
207	 * If sc == NULL, provider's error should be set and g_concat_start()
208	 * should not be called at all.
209	 */
210	KASSERT(sc != NULL,
211	    ("Provider's error should be set (error=%d)(device=%s).",
212	    bp->bio_to->error, bp->bio_to->name));
213
214	G_CONCAT_LOGREQ(bp, "Request received.");
215
216	switch (bp->bio_cmd) {
217	case BIO_READ:
218	case BIO_WRITE:
219	case BIO_DELETE:
220		break;
221	case BIO_GETATTR:
222		/* To which provider it should be delivered? */
223	default:
224		g_io_deliver(bp, EOPNOTSUPP);
225		return;
226	}
227
228	offset = bp->bio_offset;
229	length = bp->bio_length;
230	addr = bp->bio_data;
231	end = offset + length;
232
233	for (no = 0; no < sc->sc_ndisks; no++) {
234		disk = &sc->sc_disks[no];
235		if (disk->d_end <= offset)
236			continue;
237		if (disk->d_start >= end)
238			break;
239
240		off = offset - disk->d_start;
241		len = MIN(length, disk->d_end - offset);
242		length -= len;
243		offset += len;
244
245		cbp = g_clone_bio(bp);
246		if (cbp == NULL) {
247			if (bp->bio_error == 0)
248				bp->bio_error = ENOMEM;
249			return;
250		}
251		/*
252		 * Fill in the component buf structure.
253		 */
254		cbp->bio_done = g_std_done;
255		cbp->bio_offset = off;
256		cbp->bio_data = addr;
257		addr += len;
258		cbp->bio_length = len;
259		cbp->bio_to = disk->d_consumer->provider;
260		G_CONCAT_LOGREQ(cbp, "Sending request.");
261		g_io_request(cbp, disk->d_consumer);
262
263		if (length == 0)
264			break;
265	}
266
267	KASSERT(length == 0,
268	    ("Length is still greater than 0 (class=%s, name=%s).",
269	    bp->bio_to->geom->class->name, bp->bio_to->geom->name));
270}
271
272static void
273g_concat_check_and_run(struct g_concat_softc *sc)
274{
275	struct g_concat_disk *disk;
276	off_t start;
277	u_int no;
278
279	if (g_concat_nvalid(sc) != sc->sc_ndisks)
280		return;
281
282	start = 0;
283	for (no = 0; no < sc->sc_ndisks; no++) {
284		disk = &sc->sc_disks[no];
285		disk->d_start = start;
286		disk->d_end = disk->d_start +
287		    disk->d_consumer->provider->mediasize;
288		if (sc->sc_type == G_CONCAT_TYPE_AUTOMATIC)
289			disk->d_end -= disk->d_consumer->provider->sectorsize;
290		start = disk->d_end;
291	}
292	/* We have sc->sc_disks[sc->sc_ndisks - 1].d_end in 'start'. */
293	sc->sc_provider->mediasize = start;
294	g_error_provider(sc->sc_provider, 0);
295
296	G_CONCAT_DEBUG(0, "Device %s activated.", sc->sc_provider->name);
297}
298
299static int
300g_concat_read_metadata(struct g_consumer *cp, struct g_concat_metadata *md)
301{
302	struct g_provider *pp;
303	u_char *buf;
304	int error;
305
306	g_topology_assert();
307
308	error = g_access(cp, 1, 0, 0);
309	if (error != 0)
310		return (error);
311	pp = cp->provider;
312	g_topology_unlock();
313	buf = g_read_data(cp, pp->mediasize - pp->sectorsize, pp->sectorsize,
314	    &error);
315	g_topology_lock();
316	g_access(cp, -1, 0, 0);
317	if (buf == NULL)
318		return (error);
319
320	/* Decode metadata. */
321	concat_metadata_decode(buf, md);
322	g_free(buf);
323
324	return (0);
325}
326
327/*
328 * Add disk to given device.
329 */
330static int
331g_concat_add_disk(struct g_concat_softc *sc, struct g_provider *pp, u_int no)
332{
333	struct g_concat_disk *disk;
334	struct g_provider *ourpp;
335	struct g_consumer *cp;
336	struct g_geom *gp;
337	int error;
338
339	/* Metadata corrupted? */
340	if (no >= sc->sc_ndisks)
341		return (EINVAL);
342
343	disk = &sc->sc_disks[no];
344	/* Check if disk is not already attached. */
345	if (disk->d_consumer != NULL)
346		return (EEXIST);
347
348	ourpp = sc->sc_provider;
349	gp = ourpp->geom;
350
351	cp = g_new_consumer(gp);
352	error = g_attach(cp, pp);
353	if (error != 0) {
354		g_destroy_consumer(cp);
355		return (error);
356	}
357
358	if (ourpp->acr > 0 || ourpp->acw > 0 || ourpp->ace > 0) {
359		error = g_access(cp, ourpp->acr, ourpp->acw, ourpp->ace);
360		if (error != 0) {
361			g_detach(cp);
362			g_destroy_consumer(cp);
363			return (error);
364		}
365	}
366	if (sc->sc_type == G_CONCAT_TYPE_AUTOMATIC) {
367		struct g_concat_metadata md;
368
369		error = g_concat_read_metadata(cp, &md);
370		if (error != 0)
371			goto fail;
372
373		if (strcmp(md.md_magic, G_CONCAT_MAGIC) != 0 ||
374		    strcmp(md.md_name, sc->sc_name) != 0 ||
375		    md.md_id != sc->sc_id) {
376			G_CONCAT_DEBUG(0, "Metadata on %s changed.", pp->name);
377			goto fail;
378		}
379	}
380
381	cp->private = disk;
382	disk->d_consumer = cp;
383	disk->d_softc = sc;
384	disk->d_start = 0;	/* not yet */
385	disk->d_end = 0;	/* not yet */
386
387	G_CONCAT_DEBUG(0, "Disk %s attached to %s.", pp->name, gp->name);
388
389	g_concat_check_and_run(sc);
390
391	return (0);
392fail:
393	if (ourpp->acr > 0 || ourpp->acw > 0 || ourpp->ace > 0)
394		g_access(cp, -ourpp->acr, -ourpp->acw, -ourpp->ace);
395	g_detach(cp);
396	g_destroy_consumer(cp);
397	return (error);
398}
399
400static struct g_geom *
401g_concat_create(struct g_class *mp, const struct g_concat_metadata *md,
402    u_int type, size_t sectorsize)
403{
404	struct g_provider *pp;
405	struct g_concat_softc *sc;
406	struct g_geom *gp;
407	u_int no;
408
409	G_CONCAT_DEBUG(1, "Creating device %s.concat (id=%u).", md->md_name,
410	    md->md_id);
411
412	/* Two disks is minimum. */
413	if (md->md_all <= 1)
414		return (NULL);
415
416	/* Check for duplicate unit */
417	LIST_FOREACH(gp, &mp->geom, geom) {
418		sc = gp->softc;
419		if (sc != NULL && strcmp(sc->sc_name, md->md_name) == 0) {
420			G_CONCAT_DEBUG(0, "Device %s already cofigured.",
421			    gp->name);
422			return (NULL);
423		}
424	}
425	gp = g_new_geomf(mp, "%s.concat", md->md_name);
426	gp->softc = NULL;	/* for a moment */
427
428	sc = malloc(sizeof(*sc), M_CONCAT, M_NOWAIT | M_ZERO);
429	if (sc == NULL) {
430		G_CONCAT_DEBUG(0, "Can't allocate memory for device %s.",
431		    gp->name);
432		g_destroy_geom(gp);
433		return (NULL);
434	}
435
436	gp->start = g_concat_start;
437	gp->spoiled = g_concat_orphan;
438	gp->orphan = g_concat_orphan;
439	gp->access = g_concat_access;
440	gp->dumpconf = g_concat_dumpconf;
441
442	strlcpy(sc->sc_name, md->md_name, sizeof(sc->sc_name));
443	sc->sc_id = md->md_id;
444	sc->sc_ndisks = md->md_all;
445	sc->sc_disks = malloc(sizeof(struct g_concat_disk) * sc->sc_ndisks,
446	    M_CONCAT, M_WAITOK | M_ZERO);
447	for (no = 0; no < sc->sc_ndisks; no++)
448		sc->sc_disks[no].d_consumer = NULL;
449	sc->sc_type = type;
450
451	gp->softc = sc;
452
453	pp = g_new_providerf(gp, "%s", gp->name);
454	sc->sc_provider = pp;
455	pp->sectorsize = sectorsize;
456	/*
457	 * Don't run provider yet (by setting its error to 0), because we're
458	 * not aware of its mediasize.
459	 */
460
461	G_CONCAT_DEBUG(0, "Device %s created (id=%u).", gp->name, sc->sc_id);
462
463	return (gp);
464}
465
466static int
467g_concat_destroy(struct g_concat_softc *sc, boolean_t force)
468{
469	struct g_provider *pp;
470	struct g_geom *gp;
471	u_int no;
472
473	g_topology_assert();
474
475	if (sc == NULL)
476		return (ENXIO);
477
478	pp = sc->sc_provider;
479	if (pp->acr != 0 || pp->acw != 0 || pp->ace != 0) {
480		if (force) {
481			G_CONCAT_DEBUG(0, "Device %s is still open, so it "
482			    "can't be definitely removed.",
483			    sc->sc_provider->name);
484		} else {
485			G_CONCAT_DEBUG(1,
486			    "Device %s is still open (r%dw%de%d).",
487			    sc->sc_provider->name, pp->acr, pp->acw, pp->ace);
488			return (EBUSY);
489		}
490	}
491
492	g_error_provider(pp, ENXIO);
493
494	for (no = 0; no < sc->sc_ndisks; no++) {
495		if (sc->sc_disks[no].d_consumer != NULL)
496			g_concat_remove_disk(&sc->sc_disks[no]);
497	}
498
499	if (pp->acr == 0 && pp->acw == 0 && pp->ace == 0)
500		G_CONCAT_DEBUG(0, "Device %s removed.", pp->name);
501
502	gp = sc->sc_provider->geom;
503	gp->softc = NULL;
504	free(sc->sc_disks, M_CONCAT);
505	free(sc, M_CONCAT);
506
507	g_wither_geom(gp, ENXIO);
508
509	return (0);
510}
511
512static int
513g_concat_destroy_geom(struct gctl_req *req, struct g_class *mp,
514    struct g_geom *gp)
515{
516	struct g_concat_softc *sc;
517
518	sc = gp->softc;
519	return (g_concat_destroy(sc, 0));
520}
521
522static struct g_geom *
523g_concat_taste(struct g_class *mp, struct g_provider *pp, int flags __unused)
524{
525	struct g_concat_metadata md;
526	struct g_concat_softc *sc;
527	struct g_consumer *cp;
528	struct g_geom *gp;
529	int error;
530
531	g_trace(G_T_TOPOLOGY, "g_concat_taste(%s, %s)", mp->name, pp->name);
532	g_topology_assert();
533
534	G_CONCAT_DEBUG(3, "Tasting %s.", pp->name);
535
536	gp = g_new_geomf(mp, "concat:taste");
537	gp->start = g_concat_start;
538	gp->access = g_concat_access;
539	gp->orphan = g_concat_orphan;
540	cp = g_new_consumer(gp);
541	g_attach(cp, pp);
542
543	error = g_concat_read_metadata(cp, &md);
544	if (error != 0) {
545		g_wither_geom(gp, ENXIO);
546		return (NULL);
547	}
548	g_wither_geom(gp, ENXIO);
549	gp = NULL;
550
551	if (strcmp(md.md_magic, G_CONCAT_MAGIC) != 0)
552		return (NULL);
553	if (md.md_version > G_CONCAT_VERSION) {
554		printf("geom_concat.ko module is too old to handle %s.\n",
555		    pp->name);
556		return (NULL);
557	}
558
559	/*
560	 * Let's check if device already exists.
561	 */
562	LIST_FOREACH(gp, &mp->geom, geom) {
563		sc = gp->softc;
564		if (sc == NULL)
565			continue;
566		if (sc->sc_type != G_CONCAT_TYPE_AUTOMATIC)
567			continue;
568		if (strcmp(md.md_name, sc->sc_name) != 0)
569			continue;
570		if (md.md_id != sc->sc_id)
571			continue;
572		break;
573	}
574	if (gp != NULL) {
575		G_CONCAT_DEBUG(1, "Adding disk %s to %s.", pp->name, gp->name);
576		error = g_concat_add_disk(sc, pp, md.md_no);
577		if (error != 0) {
578			G_CONCAT_DEBUG(0, "Cannot add disk %s to %s.", pp->name,
579			    gp->name);
580			return (NULL);
581		}
582	} else {
583		gp = g_concat_create(mp, &md, G_CONCAT_TYPE_AUTOMATIC,
584		    pp->sectorsize);
585		if (gp == NULL) {
586			G_CONCAT_DEBUG(0, "Cannot create device %s.concat.",
587			    md.md_name);
588			return (NULL);
589		}
590		sc = gp->softc;
591		G_CONCAT_DEBUG(1, "Adding disk %s to %s.", pp->name, gp->name);
592		error = g_concat_add_disk(sc, pp, md.md_no);
593		if (error != 0) {
594			G_CONCAT_DEBUG(0, "Cannot add disk %s to %s.", pp->name,
595			    gp->name);
596			g_concat_destroy(sc, 1);
597			return (NULL);
598		}
599	}
600
601	return (gp);
602}
603
604static void
605g_concat_ctl_create(struct gctl_req *req, struct g_class *mp)
606{
607	u_int attached, no;
608	struct g_concat_metadata *md;
609	struct g_provider *pp;
610	struct g_concat_softc *sc;
611	struct g_geom *gp;
612	struct sbuf *sb;
613	uint32_t sectorsize = 0;
614	char buf[20];
615
616	g_topology_assert();
617	md = gctl_get_paraml(req, "metadata", sizeof(*md));
618	if (md == NULL) {
619		gctl_error(req, "No 'metadata' argument");
620		return;
621	}
622	if (md->md_all <= 1) {
623		G_CONCAT_DEBUG(1, "Invalid 'md_all' value (= %d).", md->md_all);
624		gctl_error(req, "Invalid 'md_all' value (= %d).", md->md_all);
625		return;
626	}
627
628	/* Check all providers are valid */
629	for (no = 0; no < md->md_all; no++) {
630		snprintf(buf, sizeof(buf), "disk%u", no);
631		pp = gctl_get_provider(req, buf);
632		if (pp == NULL) {
633			G_CONCAT_DEBUG(1, "Disk %u is invalid.", no);
634			gctl_error(req, "Disk %u is invalid", no);
635			return;
636		}
637		if (no == 0)
638			sectorsize = pp->sectorsize;
639	}
640
641	gp = g_concat_create(mp, md, G_CONCAT_TYPE_MANUAL, sectorsize);
642	if (gp == NULL) {
643		gctl_error(req, "Can't configure %s.concat", md->md_name);
644		return;
645	}
646
647	sc = gp->softc;
648	sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND);
649	sbuf_printf(sb, "Can't attach disk(s) to %s:", gp->name);
650	for (attached = no = 0; no < md->md_all; no++) {
651		snprintf(buf, sizeof(buf), "disk%u", no);
652		pp = gctl_get_provider(req, buf);
653		if (g_concat_add_disk(sc, pp, no) != 0) {
654			G_CONCAT_DEBUG(1, "Disk %u (%s) not attached to %s.",
655			    no + 1, pp->name, gp->name);
656			sbuf_printf(sb, " %s", pp->name);
657			continue;
658		}
659		attached++;
660	}
661	sbuf_finish(sb);
662	if (md->md_all != attached) {
663		g_concat_destroy(gp->softc, 1);
664		gctl_error(req, "%s", sbuf_data(sb));
665	}
666	sbuf_delete(sb);
667}
668
669static void
670g_concat_ctl_destroy(struct gctl_req *req, struct g_geom *gp)
671{
672	struct g_concat_softc *sc;
673	int *force, error;
674
675	g_topology_assert();
676	force = gctl_get_paraml(req, "force", sizeof(*force));
677	if (force == NULL) {
678		gctl_error(req, "No 'force' argument");
679		return;
680	}
681	sc = gp->softc;
682	if (sc == NULL) {
683		gctl_error(req, "Cannot destroy device %s (not configured).",
684		    gp->name);
685		return;
686	}
687	error = g_concat_destroy(sc, *force);
688	if (error != 0) {
689		gctl_error(req, "Cannot destroy device %s (error=%d).",
690		    gp->name, error);
691		return;
692	}
693}
694
695static void
696g_concat_config(struct gctl_req *req, struct g_class *mp, const char *verb)
697{
698	struct g_geom *gp;
699
700	g_topology_assert();
701
702	if (strcmp(verb, "create device") == 0) {
703		g_concat_ctl_create(req, mp);
704		return;
705	}
706
707	gp = gctl_get_geom(req, mp, "geom");
708	if (gp == NULL || gp->softc == NULL) {
709		gctl_error(req, "unknown device");
710		return;
711	}
712	if (strcmp(verb, "destroy device") == 0) {
713		g_concat_ctl_destroy(req, gp);
714		return;
715	}
716
717	gctl_error(req, "unknown verb");
718}
719
720static void
721g_concat_dumpconf(struct sbuf *sb, const char *indent, struct g_geom *gp,
722    struct g_consumer *cp, struct g_provider *pp)
723{
724        struct g_concat_softc *sc;
725
726        sc = gp->softc;
727        if (sc == NULL)
728                return;
729        if (gp != NULL) {
730                sbuf_printf(sb, "%s<id>%zu</id>\n", indent, sc->sc_id);
731		switch (sc->sc_type) {
732		case G_CONCAT_TYPE_AUTOMATIC:
733                	sbuf_printf(sb, "%s<type>%s</type>\n", indent,
734			    "automatic");
735			break;
736		case G_CONCAT_TYPE_MANUAL:
737                	sbuf_printf(sb, "%s<type>%s</type>\n", indent,
738			    "manual");
739			break;
740		default:
741                	sbuf_printf(sb, "%s<type>%s</type>\n", indent,
742			    "unknown");
743			break;
744		}
745        }
746}
747
748DECLARE_GEOM_CLASS(g_concat_class, g_concat);
749