1176417Sthompsa/*-
2176417Sthompsa * Copyright (c) 2008 Andrew Thompson <thompsa@FreeBSD.org>
3176417Sthompsa * All rights reserved.
4176417Sthompsa *
5176417Sthompsa * Redistribution and use in source and binary forms, with or without
6176417Sthompsa * modification, are permitted provided that the following conditions
7176417Sthompsa * are met:
8176417Sthompsa * 1. Redistributions of source code must retain the above copyright
9176417Sthompsa *    notice, this list of conditions and the following disclaimer.
10176417Sthompsa * 2. Redistributions in binary form must reproduce the above copyright
11176417Sthompsa *    notice, this list of conditions and the following disclaimer in the
12176417Sthompsa *    documentation and/or other materials provided with the distribution.
13176417Sthompsa *
14176417Sthompsa * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
15176417Sthompsa * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16176417Sthompsa * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17176417Sthompsa * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
18176417Sthompsa * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19176417Sthompsa * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20176417Sthompsa * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21176417Sthompsa * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22176417Sthompsa * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23176417Sthompsa * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24176417Sthompsa * SUCH DAMAGE.
25176417Sthompsa */
26176417Sthompsa
27176417Sthompsa#include <sys/cdefs.h>
28176417Sthompsa__FBSDID("$FreeBSD$");
29176417Sthompsa
30176417Sthompsa#include <sys/ctype.h>
31176417Sthompsa#include <sys/param.h>
32176417Sthompsa#include <sys/bio.h>
33176417Sthompsa#include <sys/kernel.h>
34176417Sthompsa#include <sys/limits.h>
35176417Sthompsa#include <sys/malloc.h>
36176417Sthompsa#include <sys/queue.h>
37176417Sthompsa#include <sys/sysctl.h>
38176417Sthompsa#include <sys/systm.h>
39176417Sthompsa
40176417Sthompsa#include <geom/geom.h>
41176417Sthompsa#include <sys/endian.h>
42176417Sthompsa
43176417Sthompsa#include <geom/linux_lvm/g_linux_lvm.h>
44176417Sthompsa
45219029SnetchildFEATURE(geom_linux_lvm, "GEOM Linux LVM partitioning support");
46219029Snetchild
47176417Sthompsa/* Declare malloc(9) label */
48176417Sthompsastatic MALLOC_DEFINE(M_GLLVM, "gllvm", "GEOM_LINUX_LVM Data");
49176417Sthompsa
50176417Sthompsa/* GEOM class methods */
51176417Sthompsastatic g_access_t g_llvm_access;
52176417Sthompsastatic g_init_t g_llvm_init;
53176417Sthompsastatic g_orphan_t g_llvm_orphan;
54176417Sthompsastatic g_orphan_t g_llvm_taste_orphan;
55176417Sthompsastatic g_start_t g_llvm_start;
56176417Sthompsastatic g_taste_t g_llvm_taste;
57176417Sthompsastatic g_ctl_destroy_geom_t g_llvm_destroy_geom;
58176417Sthompsa
59176417Sthompsastatic void	g_llvm_done(struct bio *);
60176417Sthompsastatic void	g_llvm_remove_disk(struct g_llvm_vg *, struct g_consumer *);
61176417Sthompsastatic int	g_llvm_activate_lv(struct g_llvm_vg *, struct g_llvm_lv *);
62176417Sthompsastatic int	g_llvm_add_disk(struct g_llvm_vg *, struct g_provider *, char *);
63176417Sthompsastatic void	g_llvm_free_vg(struct g_llvm_vg *);
64176417Sthompsastatic int	g_llvm_destroy(struct g_llvm_vg *, int);
65176417Sthompsastatic int	g_llvm_read_label(struct g_consumer *, struct g_llvm_label *);
66176417Sthompsastatic int	g_llvm_read_md(struct g_consumer *, struct g_llvm_metadata *,
67176417Sthompsa		    struct g_llvm_label *);
68176417Sthompsa
69176417Sthompsastatic int	llvm_label_decode(const u_char *, struct g_llvm_label *, int);
70176417Sthompsastatic int	llvm_md_decode(const u_char *, struct g_llvm_metadata *,
71176417Sthompsa		    struct g_llvm_label *);
72176417Sthompsastatic int	llvm_textconf_decode(u_char *, int,
73176417Sthompsa		    struct g_llvm_metadata *);
74176417Sthompsastatic int	llvm_textconf_decode_pv(char **, char *, struct g_llvm_vg *);
75176417Sthompsastatic int	llvm_textconf_decode_lv(char **, char *, struct g_llvm_vg *);
76176417Sthompsastatic int	llvm_textconf_decode_sg(char **, char *, struct g_llvm_lv *);
77176417Sthompsa
78176417SthompsaSYSCTL_DECL(_kern_geom);
79176417SthompsaSYSCTL_NODE(_kern_geom, OID_AUTO, linux_lvm, CTLFLAG_RW, 0,
80176417Sthompsa    "GEOM_LINUX_LVM stuff");
81176417Sthompsastatic u_int g_llvm_debug = 0;
82176417SthompsaTUNABLE_INT("kern.geom.linux_lvm.debug", &g_llvm_debug);
83176417SthompsaSYSCTL_UINT(_kern_geom_linux_lvm, OID_AUTO, debug, CTLFLAG_RW, &g_llvm_debug, 0,
84176417Sthompsa    "Debug level");
85176417Sthompsa
86176417SthompsaLIST_HEAD(, g_llvm_vg) vg_list;
87176417Sthompsa
88176417Sthompsa/*
89176417Sthompsa * Called to notify geom when it's been opened, and for what intent
90176417Sthompsa */
91176417Sthompsastatic int
92176417Sthompsag_llvm_access(struct g_provider *pp, int dr, int dw, int de)
93176417Sthompsa{
94176417Sthompsa	struct g_consumer *c;
95176417Sthompsa	struct g_llvm_vg *vg;
96176417Sthompsa	struct g_geom *gp;
97176417Sthompsa	int error;
98176417Sthompsa
99176417Sthompsa	KASSERT(pp != NULL, ("%s: NULL provider", __func__));
100176417Sthompsa	gp = pp->geom;
101176417Sthompsa	KASSERT(gp != NULL, ("%s: NULL geom", __func__));
102176417Sthompsa	vg = gp->softc;
103176417Sthompsa
104176417Sthompsa	if (vg == NULL) {
105176417Sthompsa		/* It seems that .access can be called with negative dr,dw,dx
106176417Sthompsa		 * in this case but I want to check for myself */
107176417Sthompsa		G_LLVM_DEBUG(0, "access(%d, %d, %d) for %s",
108176417Sthompsa		    dr, dw, de, pp->name);
109176417Sthompsa
110176417Sthompsa		/* This should only happen when geom is withered so
111176417Sthompsa		 * allow only negative requests */
112176417Sthompsa		KASSERT(dr <= 0 && dw <= 0 && de <= 0,
113176417Sthompsa		    ("%s: Positive access for %s", __func__, pp->name));
114176417Sthompsa		if (pp->acr + dr == 0 && pp->acw + dw == 0 && pp->ace + de == 0)
115176417Sthompsa			G_LLVM_DEBUG(0,
116176417Sthompsa			    "Device %s definitely destroyed", pp->name);
117176417Sthompsa		return (0);
118176417Sthompsa	}
119176417Sthompsa
120176417Sthompsa	/* Grab an exclusive bit to propagate on our consumers on first open */
121176417Sthompsa	if (pp->acr == 0 && pp->acw == 0 && pp->ace == 0)
122176417Sthompsa		de++;
123176417Sthompsa	/* ... drop it on close */
124176417Sthompsa	if (pp->acr + dr == 0 && pp->acw + dw == 0 && pp->ace + de == 0)
125176417Sthompsa		de--;
126176417Sthompsa
127176417Sthompsa	error = ENXIO;
128176417Sthompsa	LIST_FOREACH(c, &gp->consumer, consumer) {
129176417Sthompsa		KASSERT(c != NULL, ("%s: consumer is NULL", __func__));
130176417Sthompsa		error = g_access(c, dr, dw, de);
131176417Sthompsa		if (error != 0) {
132176417Sthompsa			struct g_consumer *c2;
133176417Sthompsa
134176417Sthompsa			/* Backout earlier changes */
135176417Sthompsa			LIST_FOREACH(c2, &gp->consumer, consumer) {
136176417Sthompsa				if (c2 == c) /* all eariler components fixed */
137176417Sthompsa					return (error);
138176417Sthompsa				g_access(c2, -dr, -dw, -de);
139176417Sthompsa			}
140176417Sthompsa		}
141176417Sthompsa	}
142176417Sthompsa
143176417Sthompsa	return (error);
144176417Sthompsa}
145176417Sthompsa
146176417Sthompsa/*
147176417Sthompsa * Dismantle bio_queue and destroy its components
148176417Sthompsa */
149176417Sthompsastatic void
150176417Sthompsabioq_dismantle(struct bio_queue_head *bq)
151176417Sthompsa{
152176417Sthompsa	struct bio *b;
153176417Sthompsa
154176417Sthompsa	for (b = bioq_first(bq); b != NULL; b = bioq_first(bq)) {
155176417Sthompsa		bioq_remove(bq, b);
156176417Sthompsa		g_destroy_bio(b);
157176417Sthompsa	}
158176417Sthompsa}
159176417Sthompsa
160176417Sthompsa/*
161176417Sthompsa * GEOM .done handler
162176417Sthompsa * Can't use standard handler because one requested IO may
163176417Sthompsa * fork into additional data IOs
164176417Sthompsa */
165176417Sthompsastatic void
166176417Sthompsag_llvm_done(struct bio *b)
167176417Sthompsa{
168176417Sthompsa	struct bio *parent_b;
169176417Sthompsa
170176417Sthompsa	parent_b = b->bio_parent;
171176417Sthompsa
172176417Sthompsa	if (b->bio_error != 0) {
173176417Sthompsa		G_LLVM_DEBUG(0, "Error %d for offset=%ju, length=%ju on %s",
174176417Sthompsa		    b->bio_error, b->bio_offset, b->bio_length,
175176417Sthompsa		    b->bio_to->name);
176176417Sthompsa		if (parent_b->bio_error == 0)
177176417Sthompsa			parent_b->bio_error = b->bio_error;
178176417Sthompsa	}
179176417Sthompsa
180176417Sthompsa	parent_b->bio_inbed++;
181176417Sthompsa	parent_b->bio_completed += b->bio_completed;
182176417Sthompsa
183176417Sthompsa	if (parent_b->bio_children == parent_b->bio_inbed) {
184176417Sthompsa		parent_b->bio_completed = parent_b->bio_length;
185176417Sthompsa		g_io_deliver(parent_b, parent_b->bio_error);
186176417Sthompsa	}
187176417Sthompsa	g_destroy_bio(b);
188176417Sthompsa}
189176417Sthompsa
190176417Sthompsastatic void
191176417Sthompsag_llvm_start(struct bio *bp)
192176417Sthompsa{
193176417Sthompsa	struct g_provider *pp;
194176417Sthompsa	struct g_llvm_vg *vg;
195176417Sthompsa	struct g_llvm_pv *pv;
196176417Sthompsa	struct g_llvm_lv *lv;
197176417Sthompsa	struct g_llvm_segment *sg;
198176417Sthompsa	struct bio *cb;
199176417Sthompsa	struct bio_queue_head bq;
200176417Sthompsa	size_t chunk_size;
201176417Sthompsa	off_t offset, length;
202176417Sthompsa	char *addr;
203176417Sthompsa	u_int count;
204176417Sthompsa
205176417Sthompsa	pp = bp->bio_to;
206176417Sthompsa	lv = pp->private;
207176417Sthompsa	vg = pp->geom->softc;
208176417Sthompsa
209176417Sthompsa	switch (bp->bio_cmd) {
210176417Sthompsa	case BIO_READ:
211176417Sthompsa	case BIO_WRITE:
212176417Sthompsa	case BIO_DELETE:
213176417Sthompsa	/* XXX BIO_GETATTR allowed? */
214176417Sthompsa		break;
215176417Sthompsa	default:
216176417Sthompsa		g_io_deliver(bp, EOPNOTSUPP);
217176417Sthompsa		return;
218176417Sthompsa	}
219176417Sthompsa
220176417Sthompsa	bioq_init(&bq);
221176417Sthompsa
222176417Sthompsa	chunk_size = vg->vg_extentsize;
223176417Sthompsa	addr = bp->bio_data;
224176417Sthompsa	offset = bp->bio_offset;	/* virtual offset and length */
225176417Sthompsa	length = bp->bio_length;
226176417Sthompsa
227176417Sthompsa	while (length > 0) {
228176417Sthompsa		size_t chunk_index, in_chunk_offset, in_chunk_length;
229176417Sthompsa
230176417Sthompsa		pv = NULL;
231176417Sthompsa		cb = g_clone_bio(bp);
232176417Sthompsa		if (cb == NULL) {
233176417Sthompsa			bioq_dismantle(&bq);
234176417Sthompsa			if (bp->bio_error == 0)
235176417Sthompsa				bp->bio_error = ENOMEM;
236176417Sthompsa			g_io_deliver(bp, bp->bio_error);
237176417Sthompsa			return;
238176417Sthompsa		}
239176417Sthompsa
240176417Sthompsa		/* get the segment and the pv */
241176417Sthompsa		if (lv->lv_sgcount == 1) {
242176417Sthompsa			/* skip much of the calculations for a single sg */
243176417Sthompsa			chunk_index = 0;
244176417Sthompsa			in_chunk_offset = 0;
245176417Sthompsa			in_chunk_length = length;
246176417Sthompsa			sg = lv->lv_firstsg;
247176417Sthompsa			pv = sg->sg_pv;
248176417Sthompsa			cb->bio_offset = offset + sg->sg_pvoffset;
249176417Sthompsa		} else {
250176417Sthompsa			chunk_index = offset / chunk_size; /* round downwards */
251176417Sthompsa			in_chunk_offset = offset % chunk_size;
252176417Sthompsa			in_chunk_length =
253176417Sthompsa			    min(length, chunk_size - in_chunk_offset);
254176417Sthompsa
255176417Sthompsa			/* XXX could be faster */
256176417Sthompsa			LIST_FOREACH(sg, &lv->lv_segs, sg_next) {
257176417Sthompsa				if (chunk_index >= sg->sg_start &&
258176417Sthompsa				    chunk_index <= sg->sg_end) {
259176417Sthompsa					/* adjust chunk index for sg start */
260176417Sthompsa					chunk_index -= sg->sg_start;
261176417Sthompsa					pv = sg->sg_pv;
262176417Sthompsa					break;
263176417Sthompsa				}
264176417Sthompsa			}
265176417Sthompsa			cb->bio_offset =
266176417Sthompsa			    (off_t)chunk_index * (off_t)chunk_size
267176417Sthompsa			    + in_chunk_offset + sg->sg_pvoffset;
268176417Sthompsa		}
269176417Sthompsa
270176417Sthompsa		KASSERT(pv != NULL, ("Can't find PV for chunk %zu",
271176417Sthompsa		    chunk_index));
272176417Sthompsa
273176417Sthompsa		cb->bio_to = pv->pv_gprov;
274176417Sthompsa		cb->bio_done = g_llvm_done;
275176417Sthompsa		cb->bio_length = in_chunk_length;
276176417Sthompsa		cb->bio_data = addr;
277176417Sthompsa		cb->bio_caller1 = pv;
278176417Sthompsa		bioq_disksort(&bq, cb);
279176417Sthompsa
280176417Sthompsa		G_LLVM_DEBUG(5,
281176417Sthompsa		    "Mapped %s(%ju, %ju) on %s to %zu(%zu,%zu) @ %s:%ju",
282176417Sthompsa		    bp->bio_cmd == BIO_READ ? "R" : "W",
283176417Sthompsa		    offset, length, lv->lv_name,
284176417Sthompsa		    chunk_index, in_chunk_offset, in_chunk_length,
285176417Sthompsa		    pv->pv_name, cb->bio_offset);
286176417Sthompsa
287176417Sthompsa		addr += in_chunk_length;
288176417Sthompsa		length -= in_chunk_length;
289176417Sthompsa		offset += in_chunk_length;
290176417Sthompsa	}
291176417Sthompsa
292176417Sthompsa	/* Fire off bio's here */
293176417Sthompsa	count = 0;
294176417Sthompsa	for (cb = bioq_first(&bq); cb != NULL; cb = bioq_first(&bq)) {
295176417Sthompsa		bioq_remove(&bq, cb);
296176417Sthompsa		pv = cb->bio_caller1;
297176417Sthompsa		cb->bio_caller1 = NULL;
298176417Sthompsa		G_LLVM_DEBUG(6, "firing bio to %s, offset=%ju, length=%ju",
299176417Sthompsa		    cb->bio_to->name, cb->bio_offset, cb->bio_length);
300176417Sthompsa		g_io_request(cb, pv->pv_gcons);
301176417Sthompsa		count++;
302176417Sthompsa	}
303176417Sthompsa	if (count == 0) { /* We handled everything locally */
304176417Sthompsa		bp->bio_completed = bp->bio_length;
305176417Sthompsa		g_io_deliver(bp, 0);
306176417Sthompsa	}
307176417Sthompsa}
308176417Sthompsa
309176417Sthompsastatic void
310176417Sthompsag_llvm_remove_disk(struct g_llvm_vg *vg, struct g_consumer *cp)
311176417Sthompsa{
312176417Sthompsa	struct g_llvm_pv *pv;
313176417Sthompsa	struct g_llvm_lv *lv;
314176417Sthompsa	struct g_llvm_segment *sg;
315176417Sthompsa	int found;
316176417Sthompsa
317176417Sthompsa	KASSERT(cp != NULL, ("Non-valid disk in %s.", __func__));
318176417Sthompsa	pv = (struct g_llvm_pv *)cp->private;
319176417Sthompsa
320176417Sthompsa	G_LLVM_DEBUG(0, "Disk %s removed from %s.", cp->provider->name,
321176417Sthompsa	    pv->pv_name);
322176417Sthompsa
323176417Sthompsa	LIST_FOREACH(lv, &vg->vg_lvs, lv_next) {
324176417Sthompsa		/* Find segments that map to this disk */
325176417Sthompsa		found = 0;
326176417Sthompsa		LIST_FOREACH(sg, &lv->lv_segs, sg_next) {
327176417Sthompsa			if (sg->sg_pv == pv) {
328176417Sthompsa				sg->sg_pv = NULL;
329176417Sthompsa				lv->lv_sgactive--;
330176417Sthompsa				found = 1;
331176417Sthompsa				break;
332176417Sthompsa			}
333176417Sthompsa		}
334176417Sthompsa		if (found) {
335176417Sthompsa			G_LLVM_DEBUG(0, "Device %s removed.",
336176417Sthompsa			    lv->lv_gprov->name);
337176417Sthompsa			g_orphan_provider(lv->lv_gprov, ENXIO);
338176417Sthompsa			lv->lv_gprov = NULL;
339176417Sthompsa		}
340176417Sthompsa	}
341176417Sthompsa
342176417Sthompsa	if (cp->acr > 0 || cp->acw > 0 || cp->ace > 0)
343176417Sthompsa		g_access(cp, -cp->acr, -cp->acw, -cp->ace);
344176417Sthompsa	g_detach(cp);
345176417Sthompsa	g_destroy_consumer(cp);
346176417Sthompsa}
347176417Sthompsa
348176417Sthompsastatic void
349176417Sthompsag_llvm_orphan(struct g_consumer *cp)
350176417Sthompsa{
351176417Sthompsa	struct g_llvm_vg *vg;
352176417Sthompsa	struct g_geom *gp;
353176417Sthompsa
354176417Sthompsa	g_topology_assert();
355176417Sthompsa	gp = cp->geom;
356176417Sthompsa	vg = gp->softc;
357176417Sthompsa	if (vg == NULL)
358176417Sthompsa		return;
359176417Sthompsa
360176417Sthompsa	g_llvm_remove_disk(vg, cp);
361176417Sthompsa	g_llvm_destroy(vg, 1);
362176417Sthompsa}
363176417Sthompsa
364176417Sthompsastatic int
365176417Sthompsag_llvm_activate_lv(struct g_llvm_vg *vg, struct g_llvm_lv *lv)
366176417Sthompsa{
367176417Sthompsa	struct g_geom *gp;
368176417Sthompsa	struct g_provider *pp;
369176417Sthompsa
370176417Sthompsa	g_topology_assert();
371176417Sthompsa
372176417Sthompsa	KASSERT(lv->lv_sgactive == lv->lv_sgcount, ("segment missing"));
373176417Sthompsa
374176417Sthompsa	gp = vg->vg_geom;
375176417Sthompsa	pp = g_new_providerf(gp, "linux_lvm/%s-%s", vg->vg_name, lv->lv_name);
376176417Sthompsa	pp->mediasize = vg->vg_extentsize * (off_t)lv->lv_extentcount;
377176417Sthompsa	pp->sectorsize = vg->vg_sectorsize;
378176417Sthompsa	g_error_provider(pp, 0);
379176417Sthompsa	lv->lv_gprov = pp;
380176417Sthompsa	pp->private = lv;
381176417Sthompsa
382176417Sthompsa	G_LLVM_DEBUG(1, "Created %s, %juM", pp->name,
383176417Sthompsa	    pp->mediasize / (1024*1024));
384176417Sthompsa
385176417Sthompsa	return (0);
386176417Sthompsa}
387176417Sthompsa
388176417Sthompsastatic int
389176417Sthompsag_llvm_add_disk(struct g_llvm_vg *vg, struct g_provider *pp, char *uuid)
390176417Sthompsa{
391176417Sthompsa	struct g_geom *gp;
392176417Sthompsa	struct g_consumer *cp, *fcp;
393176417Sthompsa	struct g_llvm_pv *pv;
394176417Sthompsa	struct g_llvm_lv *lv;
395176417Sthompsa	struct g_llvm_segment *sg;
396176417Sthompsa	int error;
397176417Sthompsa
398176417Sthompsa	g_topology_assert();
399176417Sthompsa
400176417Sthompsa	LIST_FOREACH(pv, &vg->vg_pvs, pv_next) {
401176417Sthompsa		if (strcmp(pv->pv_uuid, uuid) == 0)
402176417Sthompsa			break;	/* found it */
403176417Sthompsa	}
404176417Sthompsa	if (pv == NULL) {
405176417Sthompsa		G_LLVM_DEBUG(3, "uuid %s not found in pv list", uuid);
406176417Sthompsa		return (ENOENT);
407176417Sthompsa	}
408176417Sthompsa	if (pv->pv_gprov != NULL) {
409176417Sthompsa		G_LLVM_DEBUG(0, "disk %s already initialised in %s",
410176417Sthompsa		    pv->pv_name, vg->vg_name);
411176417Sthompsa		return (EEXIST);
412176417Sthompsa	}
413176417Sthompsa
414176417Sthompsa	pv->pv_start *= vg->vg_sectorsize;
415176417Sthompsa	gp = vg->vg_geom;
416176417Sthompsa	fcp = LIST_FIRST(&gp->consumer);
417176417Sthompsa
418176417Sthompsa	cp = g_new_consumer(gp);
419176417Sthompsa	error = g_attach(cp, pp);
420176417Sthompsa	G_LLVM_DEBUG(1, "Attached %s to %s at offset %ju",
421176417Sthompsa	    pp->name, pv->pv_name, pv->pv_start);
422176417Sthompsa
423176417Sthompsa	if (error != 0) {
424176417Sthompsa		G_LLVM_DEBUG(0, "cannot attach %s to %s",
425176417Sthompsa		    pp->name, vg->vg_name);
426176417Sthompsa		g_destroy_consumer(cp);
427176417Sthompsa		return (error);
428176417Sthompsa	}
429176417Sthompsa
430176417Sthompsa	if (fcp != NULL) {
431176417Sthompsa		if (fcp->provider->sectorsize != pp->sectorsize) {
432176417Sthompsa			G_LLVM_DEBUG(0, "Provider %s of %s has invalid "
433176417Sthompsa			    "sector size (%d)", pp->name, vg->vg_name,
434176417Sthompsa			    pp->sectorsize);
435176417Sthompsa			return (EINVAL);
436176417Sthompsa		}
437176417Sthompsa		if (fcp->acr > 0 || fcp->acw || fcp->ace > 0) {
438176417Sthompsa			/* Replicate access permissions from first "live"
439176417Sthompsa			 * consumer to the new one */
440176417Sthompsa			error = g_access(cp, fcp->acr, fcp->acw, fcp->ace);
441176417Sthompsa			if (error != 0) {
442176417Sthompsa				g_detach(cp);
443176417Sthompsa				g_destroy_consumer(cp);
444176417Sthompsa				return (error);
445176417Sthompsa			}
446176417Sthompsa		}
447176417Sthompsa	}
448176417Sthompsa
449176417Sthompsa	cp->private = pv;
450176417Sthompsa	pv->pv_gcons = cp;
451176417Sthompsa	pv->pv_gprov = pp;
452176417Sthompsa
453176417Sthompsa	LIST_FOREACH(lv, &vg->vg_lvs, lv_next) {
454176417Sthompsa		/* Find segments that map to this disk */
455176417Sthompsa		LIST_FOREACH(sg, &lv->lv_segs, sg_next) {
456176417Sthompsa			if (strcmp(sg->sg_pvname, pv->pv_name) == 0) {
457176417Sthompsa				/* avtivate the segment */
458176417Sthompsa				KASSERT(sg->sg_pv == NULL,
459176417Sthompsa				    ("segment already mapped"));
460176417Sthompsa				sg->sg_pvoffset =
461176417Sthompsa				    (off_t)sg->sg_pvstart * vg->vg_extentsize
462176417Sthompsa				    + pv->pv_start;
463176417Sthompsa				sg->sg_pv = pv;
464176417Sthompsa				lv->lv_sgactive++;
465176417Sthompsa
466176417Sthompsa				G_LLVM_DEBUG(2, "%s: %d to %d @ %s:%d"
467176417Sthompsa				    " offset %ju sector %ju",
468176417Sthompsa				    lv->lv_name, sg->sg_start, sg->sg_end,
469176417Sthompsa				    sg->sg_pvname, sg->sg_pvstart,
470176417Sthompsa				    sg->sg_pvoffset,
471176417Sthompsa				    sg->sg_pvoffset / vg->vg_sectorsize);
472176417Sthompsa			}
473176417Sthompsa		}
474176417Sthompsa		/* Activate any lvs waiting on this disk */
475176417Sthompsa		if (lv->lv_gprov == NULL && lv->lv_sgactive == lv->lv_sgcount) {
476176417Sthompsa			error = g_llvm_activate_lv(vg, lv);
477176417Sthompsa			if (error)
478176417Sthompsa				break;
479176417Sthompsa		}
480176417Sthompsa	}
481176417Sthompsa	return (error);
482176417Sthompsa}
483176417Sthompsa
484176417Sthompsastatic void
485176417Sthompsag_llvm_init(struct g_class *mp)
486176417Sthompsa{
487176417Sthompsa	LIST_INIT(&vg_list);
488176417Sthompsa}
489176417Sthompsa
490176417Sthompsastatic void
491176417Sthompsag_llvm_free_vg(struct g_llvm_vg *vg)
492176417Sthompsa{
493176417Sthompsa	struct g_llvm_pv *pv;
494176417Sthompsa	struct g_llvm_lv *lv;
495176417Sthompsa	struct g_llvm_segment *sg;
496176417Sthompsa
497176417Sthompsa	/* Free all the structures */
498176417Sthompsa	while ((pv = LIST_FIRST(&vg->vg_pvs)) != NULL) {
499176417Sthompsa		LIST_REMOVE(pv, pv_next);
500176417Sthompsa		free(pv, M_GLLVM);
501176417Sthompsa	}
502176417Sthompsa	while ((lv = LIST_FIRST(&vg->vg_lvs)) != NULL) {
503176417Sthompsa		while ((sg = LIST_FIRST(&lv->lv_segs)) != NULL) {
504176417Sthompsa			LIST_REMOVE(sg, sg_next);
505176417Sthompsa			free(sg, M_GLLVM);
506176417Sthompsa		}
507176417Sthompsa		LIST_REMOVE(lv, lv_next);
508176417Sthompsa		free(lv, M_GLLVM);
509176417Sthompsa	}
510176417Sthompsa	LIST_REMOVE(vg, vg_next);
511176417Sthompsa	free(vg, M_GLLVM);
512176417Sthompsa}
513176417Sthompsa
514176417Sthompsastatic void
515176417Sthompsag_llvm_taste_orphan(struct g_consumer *cp)
516176417Sthompsa{
517176417Sthompsa
518176417Sthompsa	KASSERT(1 == 0, ("%s called while tasting %s.", __func__,
519176417Sthompsa	    cp->provider->name));
520176417Sthompsa}
521176417Sthompsa
522176417Sthompsastatic struct g_geom *
523176417Sthompsag_llvm_taste(struct g_class *mp, struct g_provider *pp, int flags __unused)
524176417Sthompsa{
525176417Sthompsa	struct g_consumer *cp;
526176417Sthompsa	struct g_geom *gp;
527176417Sthompsa	struct g_llvm_label ll;
528176417Sthompsa	struct g_llvm_metadata md;
529176417Sthompsa	struct g_llvm_vg *vg;
530176417Sthompsa	int error;
531176417Sthompsa
532176417Sthompsa	bzero(&md, sizeof(md));
533176417Sthompsa
534176417Sthompsa	g_topology_assert();
535176417Sthompsa	g_trace(G_T_TOPOLOGY, "%s(%s, %s)", __func__, mp->name, pp->name);
536176417Sthompsa	gp = g_new_geomf(mp, "linux_lvm:taste");
537176417Sthompsa	/* This orphan function should be never called. */
538176417Sthompsa	gp->orphan = g_llvm_taste_orphan;
539176417Sthompsa	cp = g_new_consumer(gp);
540176417Sthompsa	g_attach(cp, pp);
541176417Sthompsa	error = g_llvm_read_label(cp, &ll);
542176417Sthompsa	if (!error)
543176417Sthompsa		error = g_llvm_read_md(cp, &md, &ll);
544176417Sthompsa	g_detach(cp);
545176417Sthompsa	g_destroy_consumer(cp);
546176417Sthompsa	g_destroy_geom(gp);
547176417Sthompsa	if (error != 0)
548176417Sthompsa		return (NULL);
549176417Sthompsa
550176417Sthompsa	vg = md.md_vg;
551176417Sthompsa	if (vg->vg_geom == NULL) {
552176417Sthompsa		/* new volume group */
553176417Sthompsa		gp = g_new_geomf(mp, "%s", vg->vg_name);
554176417Sthompsa		gp->start = g_llvm_start;
555176417Sthompsa		gp->spoiled = g_llvm_orphan;
556176417Sthompsa		gp->orphan = g_llvm_orphan;
557176417Sthompsa		gp->access = g_llvm_access;
558176417Sthompsa		vg->vg_sectorsize = pp->sectorsize;
559176417Sthompsa		vg->vg_extentsize *= vg->vg_sectorsize;
560176417Sthompsa		vg->vg_geom = gp;
561176417Sthompsa		gp->softc = vg;
562176417Sthompsa		G_LLVM_DEBUG(1, "Created volume %s, extent size %zuK",
563176417Sthompsa		    vg->vg_name, vg->vg_extentsize / 1024);
564176417Sthompsa	}
565176417Sthompsa
566176417Sthompsa	/* initialise this disk in the volume group */
567176417Sthompsa	g_llvm_add_disk(vg, pp, ll.ll_uuid);
568176417Sthompsa	return (vg->vg_geom);
569176417Sthompsa}
570176417Sthompsa
571176417Sthompsastatic int
572176417Sthompsag_llvm_destroy(struct g_llvm_vg *vg, int force)
573176417Sthompsa{
574176417Sthompsa	struct g_provider *pp;
575176417Sthompsa	struct g_geom *gp;
576176417Sthompsa
577176417Sthompsa	g_topology_assert();
578176417Sthompsa	if (vg == NULL)
579176417Sthompsa		return (ENXIO);
580176417Sthompsa	gp = vg->vg_geom;
581176417Sthompsa
582176417Sthompsa	LIST_FOREACH(pp, &gp->provider, provider) {
583176417Sthompsa		if (pp->acr != 0 || pp->acw != 0 || pp->ace != 0) {
584176417Sthompsa			G_LLVM_DEBUG(1, "Device %s is still open (r%dw%de%d)",
585176417Sthompsa			    pp->name, pp->acr, pp->acw, pp->ace);
586176417Sthompsa			if (!force)
587176417Sthompsa				return (EBUSY);
588176417Sthompsa		}
589176417Sthompsa	}
590176417Sthompsa
591176417Sthompsa	g_llvm_free_vg(gp->softc);
592176417Sthompsa	gp->softc = NULL;
593176417Sthompsa	g_wither_geom(gp, ENXIO);
594176417Sthompsa	return (0);
595176417Sthompsa}
596176417Sthompsa
597176417Sthompsastatic int
598176417Sthompsag_llvm_destroy_geom(struct gctl_req *req __unused, struct g_class *mp __unused,
599176417Sthompsa    struct g_geom *gp)
600176417Sthompsa{
601176417Sthompsa	struct g_llvm_vg *vg;
602176417Sthompsa
603176417Sthompsa	vg = gp->softc;
604176417Sthompsa	return (g_llvm_destroy(vg, 0));
605176417Sthompsa}
606176417Sthompsa
607176417Sthompsaint
608176417Sthompsag_llvm_read_label(struct g_consumer *cp, struct g_llvm_label *ll)
609176417Sthompsa{
610176417Sthompsa	struct g_provider *pp;
611176417Sthompsa	u_char *buf;
612176417Sthompsa	int i, error = 0;
613176417Sthompsa
614176417Sthompsa	g_topology_assert();
615176417Sthompsa
616176417Sthompsa	/* The LVM label is stored on the first four sectors */
617176417Sthompsa	error = g_access(cp, 1, 0, 0);
618176417Sthompsa	if (error != 0)
619176417Sthompsa		return (error);
620176417Sthompsa	pp = cp->provider;
621176417Sthompsa	g_topology_unlock();
622176417Sthompsa	buf = g_read_data(cp, 0, pp->sectorsize * 4, &error);
623176417Sthompsa	g_topology_lock();
624176417Sthompsa	g_access(cp, -1, 0, 0);
625176417Sthompsa	if (buf == NULL) {
626176417Sthompsa		G_LLVM_DEBUG(1, "Cannot read metadata from %s (error=%d)",
627176417Sthompsa		    pp->name, error);
628176417Sthompsa		return (error);
629176417Sthompsa	}
630176417Sthompsa
631176417Sthompsa	/* Search the four sectors for the LVM label. */
632176417Sthompsa	for (i = 0; i < 4; i++) {
633176417Sthompsa		error = llvm_label_decode(&buf[i * pp->sectorsize], ll, i);
634176417Sthompsa		if (error == 0)
635176417Sthompsa			break;	/* found it */
636176417Sthompsa	}
637176417Sthompsa	g_free(buf);
638176417Sthompsa	return (error);
639176417Sthompsa}
640176417Sthompsa
641176417Sthompsaint
642176417Sthompsag_llvm_read_md(struct g_consumer *cp, struct g_llvm_metadata *md,
643176417Sthompsa    struct g_llvm_label *ll)
644176417Sthompsa{
645176417Sthompsa	struct g_provider *pp;
646176417Sthompsa	u_char *buf;
647176417Sthompsa	int error;
648176417Sthompsa	int size;
649176417Sthompsa
650176417Sthompsa	g_topology_assert();
651176417Sthompsa
652176417Sthompsa	error = g_access(cp, 1, 0, 0);
653176417Sthompsa	if (error != 0)
654176417Sthompsa		return (error);
655176417Sthompsa	pp = cp->provider;
656176417Sthompsa	g_topology_unlock();
657176417Sthompsa	buf = g_read_data(cp, ll->ll_md_offset, pp->sectorsize, &error);
658176417Sthompsa	g_topology_lock();
659176417Sthompsa	g_access(cp, -1, 0, 0);
660176417Sthompsa	if (buf == NULL) {
661176417Sthompsa		G_LLVM_DEBUG(0, "Cannot read metadata from %s (error=%d)",
662176417Sthompsa		    cp->provider->name, error);
663176417Sthompsa		return (error);
664176417Sthompsa	}
665176417Sthompsa
666176417Sthompsa	error = llvm_md_decode(buf, md, ll);
667176417Sthompsa	g_free(buf);
668176417Sthompsa	if (error != 0) {
669176417Sthompsa		return (error);
670176417Sthompsa	}
671176417Sthompsa
672176417Sthompsa	G_LLVM_DEBUG(1, "reading LVM2 config @ %s:%ju", pp->name,
673176417Sthompsa		    ll->ll_md_offset + md->md_reloffset);
674176417Sthompsa	error = g_access(cp, 1, 0, 0);
675176417Sthompsa	if (error != 0)
676176417Sthompsa		return (error);
677176417Sthompsa	pp = cp->provider;
678176417Sthompsa	g_topology_unlock();
679176417Sthompsa	/* round up to the nearest sector */
680176417Sthompsa	size = md->md_relsize +
681176417Sthompsa	    (pp->sectorsize - md->md_relsize % pp->sectorsize);
682176417Sthompsa	buf = g_read_data(cp, ll->ll_md_offset + md->md_reloffset, size, &error);
683176417Sthompsa	g_topology_lock();
684176417Sthompsa	g_access(cp, -1, 0, 0);
685176417Sthompsa	if (buf == NULL) {
686176417Sthompsa		G_LLVM_DEBUG(0, "Cannot read LVM2 config from %s (error=%d)",
687176417Sthompsa		    pp->name, error);
688176417Sthompsa		return (error);
689176417Sthompsa	}
690176417Sthompsa	buf[md->md_relsize] = '\0';
691176417Sthompsa	G_LLVM_DEBUG(10, "LVM config:\n%s\n", buf);
692176417Sthompsa	error = llvm_textconf_decode(buf, md->md_relsize, md);
693176417Sthompsa	g_free(buf);
694176417Sthompsa
695176417Sthompsa	return (error);
696176417Sthompsa}
697176417Sthompsa
698176417Sthompsastatic int
699176417Sthompsallvm_label_decode(const u_char *data, struct g_llvm_label *ll, int sector)
700176417Sthompsa{
701176417Sthompsa	uint64_t off;
702176417Sthompsa	char *uuid;
703176417Sthompsa
704176417Sthompsa	/* Magic string */
705176417Sthompsa	if (bcmp("LABELONE", data , 8) != 0)
706176417Sthompsa		return (EINVAL);
707176417Sthompsa
708176417Sthompsa	/* We only support LVM2 text format */
709176417Sthompsa	if (bcmp("LVM2 001", data + 24, 8) != 0) {
710176417Sthompsa		G_LLVM_DEBUG(0, "Unsupported LVM format");
711176417Sthompsa		return (EINVAL);
712176417Sthompsa	}
713176417Sthompsa
714176417Sthompsa	ll->ll_sector = le64dec(data + 8);
715176417Sthompsa	ll->ll_crc = le32dec(data + 16);
716176417Sthompsa	ll->ll_offset = le32dec(data + 20);
717176417Sthompsa
718176417Sthompsa	if (ll->ll_sector != sector) {
719176417Sthompsa		G_LLVM_DEBUG(0, "Expected sector %ju, found at %d",
720176417Sthompsa		    ll->ll_sector, sector);
721176417Sthompsa		return (EINVAL);
722176417Sthompsa	}
723176417Sthompsa
724176417Sthompsa	off = ll->ll_offset;
725176417Sthompsa	/*
726176417Sthompsa	 * convert the binary uuid to string format, the format is
727176417Sthompsa	 * xxxxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxxxx (6-4-4-4-4-4-6)
728176417Sthompsa	 */
729176417Sthompsa	uuid = ll->ll_uuid;
730176417Sthompsa	bcopy(data + off, uuid, 6);
731176417Sthompsa	off += 6;
732176417Sthompsa	uuid += 6;
733176417Sthompsa	*uuid++ = '-';
734176417Sthompsa	for (int i = 0; i < 5; i++) {
735176417Sthompsa		bcopy(data + off, uuid, 4);
736176417Sthompsa		off += 4;
737176417Sthompsa		uuid += 4;
738176417Sthompsa		*uuid++ = '-';
739176417Sthompsa	}
740176417Sthompsa	bcopy(data + off, uuid, 6);
741176417Sthompsa	off += 6;
742176417Sthompsa	uuid += 6;
743176417Sthompsa	*uuid++ = '\0';
744176417Sthompsa
745176417Sthompsa	ll->ll_size = le64dec(data + off);
746176417Sthompsa	off += 8;
747176417Sthompsa	ll->ll_pestart = le64dec(data + off);
748176417Sthompsa	off += 16;
749176417Sthompsa
750176417Sthompsa	/* Only one data section is supported */
751176417Sthompsa	if (le64dec(data + off) != 0) {
752176417Sthompsa		G_LLVM_DEBUG(0, "Only one data section supported");
753176417Sthompsa		return (EINVAL);
754176417Sthompsa	}
755176417Sthompsa
756176417Sthompsa	off += 16;
757176417Sthompsa	ll->ll_md_offset = le64dec(data + off);
758176417Sthompsa	off += 8;
759176417Sthompsa	ll->ll_md_size = le64dec(data + off);
760176417Sthompsa	off += 8;
761176417Sthompsa
762176417Sthompsa	G_LLVM_DEBUG(1, "LVM metadata: offset=%ju, size=%ju", ll->ll_md_offset,
763176417Sthompsa	    ll->ll_md_size);
764176417Sthompsa
765176417Sthompsa	/* Only one data section is supported */
766176417Sthompsa	if (le64dec(data + off) != 0) {
767176417Sthompsa		G_LLVM_DEBUG(0, "Only one metadata section supported");
768176417Sthompsa		return (EINVAL);
769176417Sthompsa	}
770176417Sthompsa
771176417Sthompsa	G_LLVM_DEBUG(2, "label uuid=%s", ll->ll_uuid);
772176417Sthompsa	G_LLVM_DEBUG(2, "sector=%ju, crc=%u, offset=%u, size=%ju, pestart=%ju",
773176417Sthompsa	    ll->ll_sector, ll->ll_crc, ll->ll_offset, ll->ll_size,
774176417Sthompsa	    ll->ll_pestart);
775176417Sthompsa
776176417Sthompsa	return (0);
777176417Sthompsa}
778176417Sthompsa
779176417Sthompsastatic int
780176417Sthompsallvm_md_decode(const u_char *data, struct g_llvm_metadata *md,
781176417Sthompsa    struct g_llvm_label *ll)
782176417Sthompsa{
783176417Sthompsa	uint64_t off;
784176417Sthompsa	char magic[16];
785176417Sthompsa
786176417Sthompsa	off = 0;
787176417Sthompsa	md->md_csum = le32dec(data + off);
788176417Sthompsa	off += 4;
789176417Sthompsa	bcopy(data + off, magic, 16);
790176417Sthompsa	off += 16;
791176417Sthompsa	md->md_version = le32dec(data + off);
792176417Sthompsa	off += 4;
793176417Sthompsa	md->md_start = le64dec(data + off);
794176417Sthompsa	off += 8;
795176417Sthompsa	md->md_size = le64dec(data + off);
796176417Sthompsa	off += 8;
797176417Sthompsa
798176417Sthompsa	if (bcmp(G_LLVM_MAGIC, magic, 16) != 0) {
799176417Sthompsa		G_LLVM_DEBUG(0, "Incorrect md magic number");
800176417Sthompsa		return (EINVAL);
801176417Sthompsa	}
802176417Sthompsa	if (md->md_version != 1) {
803176417Sthompsa		G_LLVM_DEBUG(0, "Incorrect md version number (%u)",
804176417Sthompsa		    md->md_version);
805176417Sthompsa		return (EINVAL);
806176417Sthompsa	}
807176417Sthompsa	if (md->md_start != ll->ll_md_offset) {
808176417Sthompsa		G_LLVM_DEBUG(0, "Incorrect md offset (%ju)", md->md_start);
809176417Sthompsa		return (EINVAL);
810176417Sthompsa	}
811176417Sthompsa
812176417Sthompsa	/* Aparently only one is ever returned */
813176417Sthompsa	md->md_reloffset = le64dec(data + off);
814176417Sthompsa	off += 8;
815176417Sthompsa	md->md_relsize = le64dec(data + off);
816176417Sthompsa	off += 16;	/* XXX skipped checksum */
817176417Sthompsa
818176417Sthompsa	if (le64dec(data + off) != 0) {
819176417Sthompsa		G_LLVM_DEBUG(0, "Only one reloc supported");
820176417Sthompsa		return (EINVAL);
821176417Sthompsa	}
822176417Sthompsa
823176417Sthompsa	G_LLVM_DEBUG(3, "reloc: offset=%ju, size=%ju",
824176417Sthompsa	    md->md_reloffset, md->md_relsize);
825176417Sthompsa	G_LLVM_DEBUG(3, "md: version=%u, start=%ju, size=%ju",
826176417Sthompsa	    md->md_version, md->md_start, md->md_size);
827176417Sthompsa
828176417Sthompsa	return (0);
829176417Sthompsa}
830176417Sthompsa
831176417Sthompsa#define	GRAB_INT(key, tok1, tok2, v)					\
832176417Sthompsa	if (tok1 && tok2 && strncmp(tok1, key, sizeof(key)) == 0) {	\
833176417Sthompsa		v = strtol(tok2, &tok1, 10);				\
834176417Sthompsa		if (tok1 == tok2)					\
835176417Sthompsa			/* strtol did not eat any of the buffer */	\
836176417Sthompsa			goto bad;					\
837176417Sthompsa		continue;						\
838176417Sthompsa	}
839176417Sthompsa
840176417Sthompsa#define	GRAB_STR(key, tok1, tok2, v, len)				\
841176417Sthompsa	if (tok1 && tok2 && strncmp(tok1, key, sizeof(key)) == 0) {	\
842176417Sthompsa		strsep(&tok2, "\"");					\
843176417Sthompsa		if (tok2 == NULL)					\
844176417Sthompsa			continue;					\
845176417Sthompsa		tok1 = strsep(&tok2, "\"");				\
846176417Sthompsa		if (tok2 == NULL)					\
847176417Sthompsa			continue;					\
848176417Sthompsa		strncpy(v, tok1, len);					\
849176417Sthompsa		continue;						\
850176417Sthompsa	}
851176417Sthompsa
852176417Sthompsa#define	SPLIT(key, value, str)						\
853176417Sthompsa	key = strsep(&value, str);					\
854176417Sthompsa	/* strip trailing whitespace on the key */			\
855176417Sthompsa	for (char *t = key; *t != '\0'; t++)				\
856176417Sthompsa		if (isspace(*t)) {					\
857176417Sthompsa			*t = '\0';					\
858176417Sthompsa			break;						\
859176417Sthompsa		}
860176417Sthompsa
861194924Slulfstatic size_t
862194924Slulfllvm_grab_name(char *name, const char *tok)
863194924Slulf{
864194924Slulf	size_t len;
865194924Slulf
866194924Slulf	len = 0;
867194924Slulf	if (tok == NULL)
868194924Slulf		return (0);
869194924Slulf	if (tok[0] == '-')
870194924Slulf		return (0);
871194924Slulf	if (strcmp(tok, ".") == 0 || strcmp(tok, "..") == 0)
872194924Slulf		return (0);
873194924Slulf	while (tok[len] && (isalpha(tok[len]) || isdigit(tok[len]) ||
874194924Slulf	    tok[len] == '.' || tok[len] == '_' || tok[len] == '-' ||
875194924Slulf	    tok[len] == '+') && len < G_LLVM_NAMELEN - 1)
876194924Slulf		len++;
877194924Slulf	bcopy(tok, name, len);
878194924Slulf	name[len] = '\0';
879194924Slulf	return (len);
880194924Slulf}
881194924Slulf
882176417Sthompsastatic int
883176417Sthompsallvm_textconf_decode(u_char *data, int buflen, struct g_llvm_metadata *md)
884176417Sthompsa{
885176417Sthompsa	struct g_llvm_vg	*vg;
886176417Sthompsa	char *buf = data;
887176417Sthompsa	char *tok, *v;
888176417Sthompsa	char name[G_LLVM_NAMELEN];
889176417Sthompsa	char uuid[G_LLVM_UUIDLEN];
890194924Slulf	size_t len;
891176417Sthompsa
892176417Sthompsa	if (buf == NULL || *buf == '\0')
893176417Sthompsa		return (EINVAL);
894176417Sthompsa
895176417Sthompsa	tok = strsep(&buf, "\n");
896176417Sthompsa	if (tok == NULL)
897176417Sthompsa		return (EINVAL);
898194924Slulf	len = llvm_grab_name(name, tok);
899176417Sthompsa	if (len == 0)
900176417Sthompsa		return (EINVAL);
901176417Sthompsa
902176417Sthompsa	/* check too see if the vg has already been loaded off another disk */
903176417Sthompsa	LIST_FOREACH(vg, &vg_list, vg_next) {
904176417Sthompsa		if (strcmp(vg->vg_name, name) == 0) {
905176417Sthompsa			uuid[0] = '\0';
906176417Sthompsa			/* grab the volume group uuid */
907176417Sthompsa			while ((tok = strsep(&buf, "\n")) != NULL) {
908176417Sthompsa				if (strstr(tok, "{"))
909176417Sthompsa					break;
910176417Sthompsa				if (strstr(tok, "=")) {
911176417Sthompsa					SPLIT(v, tok, "=");
912176417Sthompsa					GRAB_STR("id", v, tok, uuid,
913176417Sthompsa					    sizeof(uuid));
914176417Sthompsa				}
915176417Sthompsa			}
916176417Sthompsa			if (strcmp(vg->vg_uuid, uuid) == 0) {
917176417Sthompsa				/* existing vg */
918176417Sthompsa				md->md_vg = vg;
919176417Sthompsa				return (0);
920176417Sthompsa			}
921176417Sthompsa			/* XXX different volume group with name clash! */
922176417Sthompsa			G_LLVM_DEBUG(0,
923176417Sthompsa			    "%s already exists, volume group not loaded", name);
924176417Sthompsa			return (EINVAL);
925176417Sthompsa		}
926176417Sthompsa	}
927176417Sthompsa
928176417Sthompsa	vg = malloc(sizeof(*vg), M_GLLVM, M_NOWAIT|M_ZERO);
929176417Sthompsa	if (vg == NULL)
930176417Sthompsa		return (ENOMEM);
931176417Sthompsa
932176417Sthompsa	strncpy(vg->vg_name, name, sizeof(vg->vg_name));
933176417Sthompsa	LIST_INIT(&vg->vg_pvs);
934176417Sthompsa	LIST_INIT(&vg->vg_lvs);
935176417Sthompsa
936176417Sthompsa#define	VOL_FOREACH(func, tok, buf, p)					\
937176417Sthompsa	while ((tok = strsep(buf, "\n")) != NULL) {			\
938176417Sthompsa		if (strstr(tok, "{")) {					\
939176417Sthompsa			func(buf, tok, p);				\
940176417Sthompsa			continue;					\
941176417Sthompsa		}							\
942176417Sthompsa		if (strstr(tok, "}"))					\
943176417Sthompsa			break;						\
944176417Sthompsa	}
945176417Sthompsa
946176417Sthompsa	while ((tok = strsep(&buf, "\n")) != NULL) {
947176417Sthompsa		if (strcmp(tok, "physical_volumes {") == 0) {
948176417Sthompsa			VOL_FOREACH(llvm_textconf_decode_pv, tok, &buf, vg);
949176417Sthompsa			continue;
950176417Sthompsa		}
951176417Sthompsa		if (strcmp(tok, "logical_volumes {") == 0) {
952176417Sthompsa			VOL_FOREACH(llvm_textconf_decode_lv, tok, &buf, vg);
953176417Sthompsa			continue;
954176417Sthompsa		}
955176417Sthompsa		if (strstr(tok, "{")) {
956176417Sthompsa			G_LLVM_DEBUG(2, "unknown section %s", tok);
957176417Sthompsa			continue;
958176417Sthompsa		}
959176417Sthompsa
960176417Sthompsa		/* parse 'key = value' lines */
961176417Sthompsa		if (strstr(tok, "=")) {
962176417Sthompsa			SPLIT(v, tok, "=");
963176417Sthompsa			GRAB_STR("id", v, tok, vg->vg_uuid, sizeof(vg->vg_uuid));
964176417Sthompsa			GRAB_INT("extent_size", v, tok, vg->vg_extentsize);
965176417Sthompsa			continue;
966176417Sthompsa		}
967176417Sthompsa	}
968176417Sthompsa	/* basic checking */
969176417Sthompsa	if (vg->vg_extentsize == 0)
970176417Sthompsa		goto bad;
971176417Sthompsa
972176417Sthompsa	md->md_vg = vg;
973176417Sthompsa	LIST_INSERT_HEAD(&vg_list, vg, vg_next);
974176417Sthompsa	G_LLVM_DEBUG(3, "vg: name=%s uuid=%s", vg->vg_name, vg->vg_uuid);
975176417Sthompsa	return(0);
976176417Sthompsa
977176417Sthompsabad:
978176417Sthompsa	g_llvm_free_vg(vg);
979176417Sthompsa	return (-1);
980176417Sthompsa}
981176417Sthompsa#undef	VOL_FOREACH
982176417Sthompsa
983176417Sthompsastatic int
984176417Sthompsallvm_textconf_decode_pv(char **buf, char *tok, struct g_llvm_vg *vg)
985176417Sthompsa{
986176417Sthompsa	struct g_llvm_pv	*pv;
987176417Sthompsa	char *v;
988194924Slulf	size_t len;
989176417Sthompsa
990176417Sthompsa	if (*buf == NULL || **buf == '\0')
991176417Sthompsa		return (EINVAL);
992176417Sthompsa
993176417Sthompsa	pv = malloc(sizeof(*pv), M_GLLVM, M_NOWAIT|M_ZERO);
994176417Sthompsa	if (pv == NULL)
995176417Sthompsa		return (ENOMEM);
996176417Sthompsa
997176417Sthompsa	pv->pv_vg = vg;
998176417Sthompsa	len = 0;
999176417Sthompsa	if (tok == NULL)
1000176417Sthompsa		goto bad;
1001194924Slulf	len = llvm_grab_name(pv->pv_name, tok);
1002176417Sthompsa	if (len == 0)
1003176417Sthompsa		goto bad;
1004176417Sthompsa
1005176417Sthompsa	while ((tok = strsep(buf, "\n")) != NULL) {
1006176417Sthompsa		if (strstr(tok, "{"))
1007176417Sthompsa			goto bad;
1008176417Sthompsa
1009176417Sthompsa		if (strstr(tok, "}"))
1010176417Sthompsa			break;
1011176417Sthompsa
1012176417Sthompsa		/* parse 'key = value' lines */
1013176417Sthompsa		if (strstr(tok, "=")) {
1014176417Sthompsa			SPLIT(v, tok, "=");
1015176417Sthompsa			GRAB_STR("id", v, tok, pv->pv_uuid, sizeof(pv->pv_uuid));
1016176417Sthompsa			GRAB_INT("pe_start", v, tok, pv->pv_start);
1017176417Sthompsa			GRAB_INT("pe_count", v, tok, pv->pv_count);
1018176417Sthompsa			continue;
1019176417Sthompsa		}
1020176417Sthompsa	}
1021176417Sthompsa	if (tok == NULL)
1022176417Sthompsa		goto bad;
1023176417Sthompsa	/* basic checking */
1024176417Sthompsa	if (pv->pv_count == 0)
1025176417Sthompsa		goto bad;
1026176417Sthompsa
1027176417Sthompsa	LIST_INSERT_HEAD(&vg->vg_pvs, pv, pv_next);
1028176417Sthompsa	G_LLVM_DEBUG(3, "pv: name=%s uuid=%s", pv->pv_name, pv->pv_uuid);
1029176417Sthompsa
1030176417Sthompsa	return (0);
1031176417Sthompsabad:
1032176417Sthompsa	free(pv, M_GLLVM);
1033176417Sthompsa	return (-1);
1034176417Sthompsa}
1035176417Sthompsa
1036176417Sthompsastatic int
1037176417Sthompsallvm_textconf_decode_lv(char **buf, char *tok, struct g_llvm_vg *vg)
1038176417Sthompsa{
1039176417Sthompsa	struct g_llvm_lv	*lv;
1040176417Sthompsa	struct g_llvm_segment *sg;
1041176417Sthompsa	char *v;
1042194924Slulf	size_t len;
1043176417Sthompsa
1044176417Sthompsa	if (*buf == NULL || **buf == '\0')
1045176417Sthompsa		return (EINVAL);
1046176417Sthompsa
1047176417Sthompsa	lv = malloc(sizeof(*lv), M_GLLVM, M_NOWAIT|M_ZERO);
1048176417Sthompsa	if (lv == NULL)
1049176417Sthompsa		return (ENOMEM);
1050176417Sthompsa
1051176417Sthompsa	lv->lv_vg = vg;
1052176417Sthompsa	LIST_INIT(&lv->lv_segs);
1053176417Sthompsa
1054176417Sthompsa	if (tok == NULL)
1055176417Sthompsa		goto bad;
1056194924Slulf	len = llvm_grab_name(lv->lv_name, tok);
1057176417Sthompsa	if (len == 0)
1058176417Sthompsa		goto bad;
1059176417Sthompsa
1060176417Sthompsa	while ((tok = strsep(buf, "\n")) != NULL) {
1061176417Sthompsa		if (strstr(tok, "{")) {
1062176417Sthompsa			if (strstr(tok, "segment")) {
1063176417Sthompsa				llvm_textconf_decode_sg(buf, tok, lv);
1064176417Sthompsa				continue;
1065176417Sthompsa			} else
1066176417Sthompsa				/* unexpected section */
1067176417Sthompsa				goto bad;
1068176417Sthompsa		}
1069176417Sthompsa
1070176417Sthompsa		if (strstr(tok, "}"))
1071176417Sthompsa			break;
1072176417Sthompsa
1073176417Sthompsa		/* parse 'key = value' lines */
1074176417Sthompsa		if (strstr(tok, "=")) {
1075176417Sthompsa			SPLIT(v, tok, "=");
1076176417Sthompsa			GRAB_STR("id", v, tok, lv->lv_uuid, sizeof(lv->lv_uuid));
1077176417Sthompsa			GRAB_INT("segment_count", v, tok, lv->lv_sgcount);
1078176417Sthompsa			continue;
1079176417Sthompsa		}
1080176417Sthompsa	}
1081176417Sthompsa	if (tok == NULL)
1082176417Sthompsa		goto bad;
1083176417Sthompsa	if (lv->lv_sgcount == 0 || lv->lv_sgcount != lv->lv_numsegs)
1084176417Sthompsa		/* zero or incomplete segment list */
1085176417Sthompsa		goto bad;
1086176417Sthompsa
1087176417Sthompsa	/* Optimize for only one segment on the pv */
1088176417Sthompsa	lv->lv_firstsg = LIST_FIRST(&lv->lv_segs);
1089176417Sthompsa	LIST_INSERT_HEAD(&vg->vg_lvs, lv, lv_next);
1090176417Sthompsa	G_LLVM_DEBUG(3, "lv: name=%s uuid=%s", lv->lv_name, lv->lv_uuid);
1091176417Sthompsa
1092176417Sthompsa	return (0);
1093176417Sthompsabad:
1094176417Sthompsa	while ((sg = LIST_FIRST(&lv->lv_segs)) != NULL) {
1095176417Sthompsa		LIST_REMOVE(sg, sg_next);
1096176417Sthompsa		free(sg, M_GLLVM);
1097176417Sthompsa	}
1098176417Sthompsa	free(lv, M_GLLVM);
1099176417Sthompsa	return (-1);
1100176417Sthompsa}
1101176417Sthompsa
1102176417Sthompsastatic int
1103176417Sthompsallvm_textconf_decode_sg(char **buf, char *tok, struct g_llvm_lv *lv)
1104176417Sthompsa{
1105176417Sthompsa	struct g_llvm_segment *sg;
1106176417Sthompsa	char *v;
1107176417Sthompsa	int count = 0;
1108176417Sthompsa
1109176417Sthompsa	if (*buf == NULL || **buf == '\0')
1110176417Sthompsa		return (EINVAL);
1111176417Sthompsa
1112176417Sthompsa	sg = malloc(sizeof(*sg), M_GLLVM, M_NOWAIT|M_ZERO);
1113176417Sthompsa	if (sg == NULL)
1114176417Sthompsa		return (ENOMEM);
1115176417Sthompsa
1116176417Sthompsa	while ((tok = strsep(buf, "\n")) != NULL) {
1117176417Sthompsa		/* only a single linear stripe is supported */
1118176417Sthompsa		if (strstr(tok, "stripe_count")) {
1119176417Sthompsa			SPLIT(v, tok, "=");
1120176417Sthompsa			GRAB_INT("stripe_count", v, tok, count);
1121176417Sthompsa			if (count != 1)
1122176417Sthompsa				goto bad;
1123176417Sthompsa		}
1124176417Sthompsa
1125176417Sthompsa		if (strstr(tok, "{"))
1126176417Sthompsa			goto bad;
1127176417Sthompsa
1128176417Sthompsa		if (strstr(tok, "}"))
1129176417Sthompsa			break;
1130176417Sthompsa
1131176417Sthompsa		if (strcmp(tok, "stripes = [") == 0) {
1132176417Sthompsa			tok = strsep(buf, "\n");
1133176417Sthompsa			if (tok == NULL)
1134176417Sthompsa				goto bad;
1135176417Sthompsa
1136176417Sthompsa			strsep(&tok, "\"");
1137176417Sthompsa			if (tok == NULL)
1138176417Sthompsa				goto bad;	/* missing open quotes */
1139176417Sthompsa			v = strsep(&tok, "\"");
1140176417Sthompsa			if (tok == NULL)
1141176417Sthompsa				goto bad;	/* missing close quotes */
1142176417Sthompsa			strncpy(sg->sg_pvname, v, sizeof(sg->sg_pvname));
1143176417Sthompsa			if (*tok != ',')
1144176417Sthompsa				goto bad;	/* missing comma for stripe */
1145176417Sthompsa			tok++;
1146176417Sthompsa
1147176417Sthompsa			sg->sg_pvstart = strtol(tok, &v, 10);
1148176417Sthompsa			if (v == tok)
1149176417Sthompsa				/* strtol did not eat any of the buffer */
1150176417Sthompsa				goto bad;
1151176417Sthompsa
1152176417Sthompsa			continue;
1153176417Sthompsa		}
1154176417Sthompsa
1155176417Sthompsa		/* parse 'key = value' lines */
1156176417Sthompsa		if (strstr(tok, "=")) {
1157176417Sthompsa			SPLIT(v, tok, "=");
1158176417Sthompsa			GRAB_INT("start_extent", v, tok, sg->sg_start);
1159176417Sthompsa			GRAB_INT("extent_count", v, tok, sg->sg_count);
1160176417Sthompsa			continue;
1161176417Sthompsa		}
1162176417Sthompsa	}
1163176417Sthompsa	if (tok == NULL)
1164176417Sthompsa		goto bad;
1165176417Sthompsa	/* basic checking */
1166176417Sthompsa	if (count != 1 || sg->sg_count == 0)
1167176417Sthompsa		goto bad;
1168176417Sthompsa
1169176417Sthompsa	sg->sg_end = sg->sg_start + sg->sg_count - 1;
1170176417Sthompsa	lv->lv_numsegs++;
1171176417Sthompsa	lv->lv_extentcount += sg->sg_count;
1172176417Sthompsa	LIST_INSERT_HEAD(&lv->lv_segs, sg, sg_next);
1173176417Sthompsa
1174176417Sthompsa	return (0);
1175176417Sthompsabad:
1176176417Sthompsa	free(sg, M_GLLVM);
1177176417Sthompsa	return (-1);
1178176417Sthompsa}
1179176417Sthompsa#undef	GRAB_INT
1180176417Sthompsa#undef	GRAB_STR
1181176417Sthompsa#undef	SPLIT
1182176417Sthompsa
1183176417Sthompsastatic struct g_class g_llvm_class = {
1184176417Sthompsa	.name = G_LLVM_CLASS_NAME,
1185176417Sthompsa	.version = G_VERSION,
1186176417Sthompsa	.init = g_llvm_init,
1187176417Sthompsa	.taste = g_llvm_taste,
1188176417Sthompsa	.destroy_geom = g_llvm_destroy_geom
1189176417Sthompsa};
1190176417Sthompsa
1191176417SthompsaDECLARE_GEOM_CLASS(g_llvm_class, g_linux_lvm);
1192