1139778Simp/*-
2190507Slulf *  Copyright (c) 2004, 2007 Lukas Ertl
3130389Sle *  All rights reserved.
4130389Sle *
5130389Sle * Redistribution and use in source and binary forms, with or without
6130389Sle * modification, are permitted provided that the following conditions
7130389Sle * are met:
8130389Sle * 1. Redistributions of source code must retain the above copyright
9130389Sle *    notice, this list of conditions and the following disclaimer.
10130389Sle * 2. Redistributions in binary form must reproduce the above copyright
11130389Sle *    notice, this list of conditions and the following disclaimer in the
12130389Sle *    documentation and/or other materials provided with the distribution.
13130389Sle *
14130389Sle * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15130389Sle * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16130389Sle * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17130389Sle * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
18130389Sle * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19130389Sle * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20130389Sle * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21130389Sle * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22130389Sle * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23130389Sle * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24130389Sle * SUCH DAMAGE.
25130389Sle *
26130389Sle */
27130389Sle
28130389Sle#include <sys/cdefs.h>
29130389Sle__FBSDID("$FreeBSD$");
30130389Sle
31223921Sae#include <sys/types.h>
32130389Sle#include <sys/libkern.h>
33130389Sle#include <sys/malloc.h>
34223921Sae#include <sys/sbuf.h>
35130389Sle
36130389Sle#include <geom/geom.h>
37130389Sle#include <geom/vinum/geom_vinum_var.h>
38130389Sle#include <geom/vinum/geom_vinum.h>
39130389Sle#include <geom/vinum/geom_vinum_share.h>
40130389Sle
41130389Slevoid	gv_lvi(struct gv_volume *, struct sbuf *, int);
42130389Slevoid	gv_lpi(struct gv_plex *, struct sbuf *, int);
43130389Slevoid	gv_lsi(struct gv_sd *, struct sbuf *, int);
44130389Slevoid	gv_ldi(struct gv_drive *, struct sbuf *, int);
45130389Sle
46130389Slevoid
47130389Slegv_list(struct g_geom *gp, struct gctl_req *req)
48130389Sle{
49130389Sle	struct gv_softc *sc;
50130389Sle	struct gv_drive *d;
51130389Sle	struct gv_plex *p;
52130389Sle	struct gv_sd *s;
53130389Sle	struct gv_volume *v;
54130389Sle	struct sbuf *sb;
55130389Sle	int *argc, i, *flags, type;
56130389Sle	char *arg, buf[20], *cmd;
57130389Sle
58130389Sle	argc = gctl_get_paraml(req, "argc", sizeof(*argc));
59130389Sle
60130389Sle	if (argc == NULL) {
61130389Sle		gctl_error(req, "no arguments given");
62130389Sle		return;
63130389Sle	}
64130389Sle
65130389Sle	flags = gctl_get_paraml(req, "flags", sizeof(*flags));
66185309Slulf	if (flags == NULL) {
67185309Slulf		gctl_error(req, "no flags given");
68185309Slulf		return;
69185309Slulf	}
70130389Sle
71130389Sle	sc = gp->softc;
72130389Sle
73130389Sle	sb = sbuf_new(NULL, NULL, GV_CFG_LEN, SBUF_FIXEDLEN);
74130389Sle
75130389Sle	/* Figure out which command was given. */
76130389Sle	cmd = gctl_get_param(req, "cmd", NULL);
77185309Slulf	if (cmd == NULL) {
78185309Slulf		gctl_error(req, "no command given");
79185309Slulf		return;
80185309Slulf	}
81130389Sle
82130389Sle	/* List specific objects or everything. */
83130389Sle	if (!strcmp(cmd, "list") || !strcmp(cmd, "l")) {
84130389Sle		if (*argc) {
85130389Sle			for (i = 0; i < *argc; i++) {
86130389Sle				snprintf(buf, sizeof(buf), "argv%d", i);
87130389Sle				arg = gctl_get_param(req, buf, NULL);
88130389Sle				if (arg == NULL)
89130389Sle					continue;
90130389Sle				type = gv_object_type(sc, arg);
91130389Sle				switch (type) {
92130389Sle				case GV_TYPE_VOL:
93130389Sle					v = gv_find_vol(sc, arg);
94130389Sle					gv_lvi(v, sb, *flags);
95130389Sle					break;
96130389Sle				case GV_TYPE_PLEX:
97130389Sle					p = gv_find_plex(sc, arg);
98130389Sle					gv_lpi(p, sb, *flags);
99130389Sle					break;
100130389Sle				case GV_TYPE_SD:
101130389Sle					s = gv_find_sd(sc, arg);
102130389Sle					gv_lsi(s, sb, *flags);
103130389Sle					break;
104130389Sle				case GV_TYPE_DRIVE:
105130389Sle					d = gv_find_drive(sc, arg);
106130389Sle					gv_ldi(d, sb, *flags);
107130389Sle					break;
108130389Sle				default:
109130389Sle					gctl_error(req, "unknown object '%s'",
110130389Sle					    arg);
111130389Sle					break;
112130389Sle				}
113130389Sle			}
114130389Sle		} else {
115130389Sle			gv_ld(gp, req, sb);
116130389Sle			sbuf_printf(sb, "\n");
117130389Sle			gv_lv(gp, req, sb);
118130389Sle			sbuf_printf(sb, "\n");
119130389Sle			gv_lp(gp, req, sb);
120130389Sle			sbuf_printf(sb, "\n");
121130389Sle			gv_ls(gp, req, sb);
122130389Sle		}
123130389Sle
124130389Sle	/* List drives. */
125130389Sle	} else if (!strcmp(cmd, "ld")) {
126130389Sle		if (*argc) {
127130389Sle			for (i = 0; i < *argc; i++) {
128130389Sle				snprintf(buf, sizeof(buf), "argv%d", i);
129130389Sle				arg = gctl_get_param(req, buf, NULL);
130130389Sle				if (arg == NULL)
131130389Sle					continue;
132130389Sle				type = gv_object_type(sc, arg);
133130389Sle				if (type != GV_TYPE_DRIVE) {
134130389Sle					gctl_error(req, "'%s' is not a drive",
135130389Sle					    arg);
136130389Sle					continue;
137130389Sle				} else {
138130389Sle					d = gv_find_drive(sc, arg);
139130389Sle					gv_ldi(d, sb, *flags);
140130389Sle				}
141130389Sle			}
142130389Sle		} else
143130389Sle			gv_ld(gp, req, sb);
144130389Sle
145130389Sle	/* List volumes. */
146130389Sle	} else if (!strcmp(cmd, "lv")) {
147130389Sle		if (*argc) {
148130389Sle			for (i = 0; i < *argc; i++) {
149130389Sle				snprintf(buf, sizeof(buf), "argv%d", i);
150130389Sle				arg = gctl_get_param(req, buf, NULL);
151130389Sle				if (arg == NULL)
152130389Sle					continue;
153130389Sle				type = gv_object_type(sc, arg);
154130389Sle				if (type != GV_TYPE_VOL) {
155130389Sle					gctl_error(req, "'%s' is not a volume",
156130389Sle					    arg);
157130389Sle					continue;
158130389Sle				} else {
159130389Sle					v = gv_find_vol(sc, arg);
160130389Sle					gv_lvi(v, sb, *flags);
161130389Sle				}
162130389Sle			}
163130389Sle		} else
164130389Sle			gv_lv(gp, req, sb);
165130389Sle
166130389Sle	/* List plexes. */
167130389Sle	} else if (!strcmp(cmd, "lp")) {
168130389Sle		if (*argc) {
169130389Sle			for (i = 0; i < *argc; i++) {
170130389Sle				snprintf(buf, sizeof(buf), "argv%d", i);
171130389Sle				arg = gctl_get_param(req, buf, NULL);
172130389Sle				if (arg == NULL)
173130389Sle					continue;
174130389Sle				type = gv_object_type(sc, arg);
175130389Sle				if (type != GV_TYPE_PLEX) {
176130389Sle					gctl_error(req, "'%s' is not a plex",
177130389Sle					    arg);
178130389Sle					continue;
179130389Sle				} else {
180130389Sle					p = gv_find_plex(sc, arg);
181130389Sle					gv_lpi(p, sb, *flags);
182130389Sle				}
183130389Sle			}
184130389Sle		} else
185130389Sle			gv_lp(gp, req, sb);
186130389Sle
187130389Sle	/* List subdisks. */
188130389Sle	} else if (!strcmp(cmd, "ls")) {
189130389Sle		if (*argc) {
190130389Sle			for (i = 0; i < *argc; i++) {
191130389Sle				snprintf(buf, sizeof(buf), "argv%d", i);
192130389Sle				arg = gctl_get_param(req, buf, NULL);
193130389Sle				if (arg == NULL)
194130389Sle					continue;
195130389Sle				type = gv_object_type(sc, arg);
196130389Sle				if (type != GV_TYPE_SD) {
197130389Sle					gctl_error(req, "'%s' is not a subdisk",
198130389Sle					    arg);
199130389Sle					continue;
200130389Sle				} else {
201130389Sle					s = gv_find_sd(sc, arg);
202130389Sle					gv_lsi(s, sb, *flags);
203130389Sle				}
204130389Sle			}
205130389Sle		} else
206130389Sle			gv_ls(gp, req, sb);
207130389Sle
208130389Sle	} else
209130389Sle		gctl_error(req, "unknown command '%s'", cmd);
210130389Sle
211130389Sle	sbuf_finish(sb);
212130389Sle	gctl_set_param(req, "config", sbuf_data(sb), sbuf_len(sb) + 1);
213130389Sle	sbuf_delete(sb);
214130389Sle}
215130389Sle
216130389Sle/* List one or more volumes. */
217130389Slevoid
218130389Slegv_lv(struct g_geom *gp, struct gctl_req *req, struct sbuf *sb)
219130389Sle{
220130389Sle	struct gv_softc *sc;
221130389Sle	struct gv_volume *v;
222130389Sle	int i, *flags;
223130389Sle
224130389Sle	sc = gp->softc;
225130389Sle	i = 0;
226130389Sle
227130389Sle	LIST_FOREACH(v, &sc->volumes, volume)
228130389Sle		i++;
229130389Sle
230130389Sle	sbuf_printf(sb, "%d volume%s:\n", i, i == 1 ? "" : "s");
231130389Sle
232130389Sle	if (i) {
233130389Sle		flags = gctl_get_paraml(req, "flags", sizeof(*flags));
234130389Sle		LIST_FOREACH(v, &sc->volumes, volume)
235130389Sle			gv_lvi(v, sb, *flags);
236130389Sle	}
237130389Sle}
238130389Sle
239130389Sle/* List a single volume. */
240130389Slevoid
241130389Slegv_lvi(struct gv_volume *v, struct sbuf *sb, int flags)
242130389Sle{
243130389Sle	struct gv_plex *p;
244130389Sle	int i;
245130389Sle
246130389Sle	if (flags & GV_FLAG_V) {
247130389Sle		sbuf_printf(sb, "Volume %s:\tSize: %jd bytes (%jd MB)\n",
248130389Sle		    v->name, (intmax_t)v->size, (intmax_t)v->size / MEGABYTE);
249130389Sle		sbuf_printf(sb, "\t\tState: %s\n", gv_volstate(v->state));
250130389Sle	} else {
251130389Sle		sbuf_printf(sb, "V %-21s State: %s\tPlexes: %7d\tSize: %s\n",
252130389Sle		    v->name, gv_volstate(v->state), v->plexcount,
253130389Sle		    gv_roughlength(v->size, 0));
254130389Sle	}
255130389Sle
256130389Sle	if (flags & GV_FLAG_VV) {
257130389Sle		i = 0;
258130389Sle		LIST_FOREACH(p, &v->plexes, in_volume) {
259130389Sle			sbuf_printf(sb, "\t\tPlex %2d:\t%s\t(%s), %s\n", i,
260130389Sle			    p->name, gv_plexstate(p->state),
261130389Sle			    gv_roughlength(p->size, 0));
262130389Sle			i++;
263130389Sle		}
264130389Sle	}
265130389Sle
266130389Sle	if (flags & GV_FLAG_R) {
267130389Sle		LIST_FOREACH(p, &v->plexes, in_volume)
268130389Sle			gv_lpi(p, sb, flags);
269130389Sle	}
270130389Sle}
271130389Sle
272130389Sle/* List one or more plexes. */
273130389Slevoid
274130389Slegv_lp(struct g_geom *gp, struct gctl_req *req, struct sbuf *sb)
275130389Sle{
276130389Sle	struct gv_softc *sc;
277130389Sle	struct gv_plex *p;
278130389Sle	int i, *flags;
279130389Sle
280130389Sle	sc = gp->softc;
281130389Sle	i = 0;
282130389Sle
283130389Sle	LIST_FOREACH(p, &sc->plexes, plex)
284130389Sle		i++;
285130389Sle
286130389Sle	sbuf_printf(sb, "%d plex%s:\n", i, i == 1 ? "" : "es");
287130389Sle
288130389Sle	if (i) {
289130389Sle		flags = gctl_get_paraml(req, "flags", sizeof(*flags));
290130389Sle		LIST_FOREACH(p, &sc->plexes, plex)
291130389Sle			gv_lpi(p, sb, *flags);
292130389Sle	}
293130389Sle}
294130389Sle
295130389Sle/* List a single plex. */
296130389Slevoid
297130389Slegv_lpi(struct gv_plex *p, struct sbuf *sb, int flags)
298130389Sle{
299130389Sle	struct gv_sd *s;
300130389Sle	int i;
301130389Sle
302130389Sle	if (flags & GV_FLAG_V) {
303130389Sle		sbuf_printf(sb, "Plex %s:\tSize:\t%9jd bytes (%jd MB)\n",
304130389Sle		    p->name, (intmax_t)p->size, (intmax_t)p->size / MEGABYTE);
305130389Sle		sbuf_printf(sb, "\t\tSubdisks: %8d\n", p->sdcount);
306190507Slulf		sbuf_printf(sb, "\t\tState: %s\n", gv_plexstate(p->state));
307190507Slulf		if ((p->flags & GV_PLEX_SYNCING) ||
308190507Slulf		    (p->flags & GV_PLEX_GROWING) ||
309190507Slulf		    (p->flags & GV_PLEX_REBUILDING)) {
310190507Slulf			sbuf_printf(sb, "\t\tSynced: ");
311190507Slulf			sbuf_printf(sb, "%16jd bytes (%d%%)\n",
312190507Slulf			    (intmax_t)p->synced,
313190507Slulf			    (p->size > 0) ? (int)((p->synced * 100) / p->size) :
314190507Slulf			    0);
315190507Slulf		}
316190507Slulf		sbuf_printf(sb, "\t\tOrganization: %s", gv_plexorg(p->org));
317130389Sle		if (gv_is_striped(p)) {
318130389Sle			sbuf_printf(sb, "\tStripe size: %s\n",
319130389Sle			    gv_roughlength(p->stripesize, 1));
320130389Sle		}
321190507Slulf		sbuf_printf(sb, "\t\tFlags: %d\n", p->flags);
322130389Sle		if (p->vol_sc != NULL) {
323130389Sle			sbuf_printf(sb, "\t\tPart of volume %s\n", p->volume);
324130389Sle		}
325130389Sle	} else {
326190507Slulf		sbuf_printf(sb, "P %-18s %2s State: ", p->name,
327190507Slulf		gv_plexorg_short(p->org));
328190507Slulf		if ((p->flags & GV_PLEX_SYNCING) ||
329190507Slulf		    (p->flags & GV_PLEX_GROWING) ||
330190507Slulf		    (p->flags & GV_PLEX_REBUILDING)) {
331190507Slulf			sbuf_printf(sb, "S %d%%\t", (int)((p->synced * 100) /
332190507Slulf			    p->size));
333190507Slulf		} else {
334190507Slulf			sbuf_printf(sb, "%s\t", gv_plexstate(p->state));
335190507Slulf		}
336190507Slulf		sbuf_printf(sb, "Subdisks: %5d\tSize: %s\n", p->sdcount,
337130389Sle		    gv_roughlength(p->size, 0));
338130389Sle	}
339130389Sle
340130389Sle	if (flags & GV_FLAG_VV) {
341130389Sle		i = 0;
342130389Sle		LIST_FOREACH(s, &p->subdisks, in_plex) {
343130389Sle			sbuf_printf(sb, "\t\tSubdisk %d:\t%s\n", i, s->name);
344130389Sle			sbuf_printf(sb, "\t\t  state: %s\tsize %11jd "
345130389Sle			    "(%jd MB)\n", gv_sdstate(s->state),
346130389Sle			    (intmax_t)s->size, (intmax_t)s->size / MEGABYTE);
347130389Sle			if (p->org == GV_PLEX_CONCAT) {
348130389Sle				sbuf_printf(sb, "\t\t\toffset %9jd (0x%jx)\n",
349130389Sle				    (intmax_t)s->plex_offset,
350130389Sle				    (intmax_t)s->plex_offset);
351130389Sle			}
352130389Sle			i++;
353130389Sle		}
354130389Sle	}
355130389Sle
356130389Sle	if (flags & GV_FLAG_R) {
357130389Sle		LIST_FOREACH(s, &p->subdisks, in_plex)
358130389Sle			gv_lsi(s, sb, flags);
359130389Sle	}
360130389Sle}
361130389Sle
362130389Sle/* List one or more subdisks. */
363130389Slevoid
364130389Slegv_ls(struct g_geom *gp, struct gctl_req *req, struct sbuf *sb)
365130389Sle{
366130389Sle	struct gv_softc *sc;
367130389Sle	struct gv_sd *s;
368130389Sle	int i, *flags;
369130389Sle
370130389Sle	sc = gp->softc;
371130389Sle	i = 0;
372130389Sle
373130389Sle	LIST_FOREACH(s, &sc->subdisks, sd)
374130389Sle		i++;
375130389Sle
376130389Sle	sbuf_printf(sb, "%d subdisk%s:\n", i, i == 1 ? "" : "s");
377130389Sle
378130389Sle	if (i) {
379130389Sle		flags = gctl_get_paraml(req, "flags", sizeof(*flags));
380130389Sle		LIST_FOREACH(s, &sc->subdisks, sd)
381130389Sle			gv_lsi(s, sb, *flags);
382130389Sle	}
383130389Sle}
384130389Sle
385130389Sle/* List a single subdisk. */
386130389Slevoid
387130389Slegv_lsi(struct gv_sd *s, struct sbuf *sb, int flags)
388130389Sle{
389130389Sle	if (flags & GV_FLAG_V) {
390130389Sle		sbuf_printf(sb, "Subdisk %s:\n", s->name);
391130389Sle		sbuf_printf(sb, "\t\tSize: %16jd bytes (%jd MB)\n",
392130389Sle		    (intmax_t)s->size, (intmax_t)s->size / MEGABYTE);
393130389Sle		sbuf_printf(sb, "\t\tState: %s\n", gv_sdstate(s->state));
394130389Sle
395135966Sle		if (s->state == GV_SD_INITIALIZING ||
396135966Sle		    s->state == GV_SD_REVIVING) {
397135966Sle			if (s->state == GV_SD_INITIALIZING)
398135966Sle				sbuf_printf(sb, "\t\tInitialized: ");
399135966Sle			else
400135966Sle				sbuf_printf(sb, "\t\tRevived: ");
401135966Sle
402135966Sle			sbuf_printf(sb, "%16jd bytes (%d%%)\n",
403135966Sle			    (intmax_t)s->initialized,
404130389Sle			    (int)((s->initialized * 100) / s->size));
405130389Sle		}
406130389Sle
407130389Sle		if (s->plex_sc != NULL) {
408130389Sle			sbuf_printf(sb, "\t\tPlex %s at offset %jd (%s)\n",
409130389Sle			    s->plex, (intmax_t)s->plex_offset,
410130389Sle			    gv_roughlength(s->plex_offset, 1));
411130389Sle		}
412130389Sle
413130389Sle		sbuf_printf(sb, "\t\tDrive %s (%s) at offset %jd (%s)\n",
414130389Sle		    s->drive,
415130389Sle		    s->drive_sc == NULL ? "*missing*" : s->drive_sc->name,
416130389Sle		    (intmax_t)s->drive_offset,
417130389Sle		    gv_roughlength(s->drive_offset, 1));
418190507Slulf		sbuf_printf(sb, "\t\tFlags: %d\n", s->flags);
419130389Sle	} else {
420130389Sle		sbuf_printf(sb, "S %-21s State: ", s->name);
421135966Sle		if (s->state == GV_SD_INITIALIZING ||
422135966Sle		    s->state == GV_SD_REVIVING) {
423135966Sle			if (s->state == GV_SD_INITIALIZING)
424135966Sle				sbuf_printf(sb, "I ");
425135966Sle			else
426135966Sle				sbuf_printf(sb, "R ");
427135966Sle			sbuf_printf(sb, "%d%%\t",
428130389Sle			    (int)((s->initialized * 100) / s->size));
429130389Sle		} else {
430130389Sle			sbuf_printf(sb, "%s\t", gv_sdstate(s->state));
431130389Sle		}
432130389Sle		sbuf_printf(sb, "D: %-12s Size: %s\n", s->drive,
433130389Sle		    gv_roughlength(s->size, 0));
434130389Sle	}
435130389Sle}
436130389Sle
437130389Sle/* List one or more drives. */
438130389Slevoid
439130389Slegv_ld(struct g_geom *gp, struct gctl_req *req, struct sbuf *sb)
440130389Sle{
441130389Sle	struct gv_softc *sc;
442130389Sle	struct gv_drive *d;
443130389Sle	int i, *flags;
444130389Sle
445130389Sle	sc = gp->softc;
446130389Sle	i = 0;
447130389Sle
448130389Sle	LIST_FOREACH(d, &sc->drives, drive)
449130389Sle		i++;
450130389Sle
451130389Sle	sbuf_printf(sb, "%d drive%s:\n", i, i == 1 ? "" : "s");
452130389Sle
453130389Sle	if (i) {
454130389Sle		flags = gctl_get_paraml(req, "flags", sizeof(*flags));
455130389Sle		LIST_FOREACH(d, &sc->drives, drive)
456130389Sle			gv_ldi(d, sb, *flags);
457130389Sle	}
458130389Sle}
459130389Sle
460130389Sle/* List a single drive. */
461130389Slevoid
462130389Slegv_ldi(struct gv_drive *d, struct sbuf *sb, int flags)
463130389Sle{
464130389Sle	struct gv_freelist *fl;
465130389Sle	struct gv_sd *s;
466130389Sle
467130389Sle	/* Verbose listing. */
468130389Sle	if (flags & GV_FLAG_V) {
469130389Sle		sbuf_printf(sb, "Drive %s:\tDevice %s\n", d->name, d->device);
470130389Sle		sbuf_printf(sb, "\t\tSize: %16jd bytes (%jd MB)\n",
471130389Sle		    (intmax_t)d->size, (intmax_t)d->size / MEGABYTE);
472130389Sle		sbuf_printf(sb, "\t\tUsed: %16jd bytes (%jd MB)\n",
473130389Sle		    (intmax_t)d->size - d->avail,
474130389Sle		    (intmax_t)(d->size - d->avail) / MEGABYTE);
475130389Sle		sbuf_printf(sb, "\t\tAvailable: %11jd bytes (%jd MB)\n",
476130389Sle		    (intmax_t)d->avail, (intmax_t)d->avail / MEGABYTE);
477130389Sle		sbuf_printf(sb, "\t\tState: %s\n", gv_drivestate(d->state));
478190507Slulf		sbuf_printf(sb, "\t\tFlags: %d\n", d->flags);
479130389Sle
480130389Sle		/* Be very verbose. */
481130389Sle		if (flags & GV_FLAG_VV) {
482130389Sle			sbuf_printf(sb, "\t\tFree list contains %d entries:\n",
483130389Sle			    d->freelist_entries);
484130389Sle			sbuf_printf(sb, "\t\t   Offset\t     Size\n");
485130389Sle			LIST_FOREACH(fl, &d->freelist, freelist)
486130389Sle				sbuf_printf(sb, "\t\t%9jd\t%9jd\n",
487130389Sle				    (intmax_t)fl->offset, (intmax_t)fl->size);
488130389Sle		}
489130389Sle	} else {
490130389Sle		sbuf_printf(sb, "D %-21s State: %s\t/dev/%s\tA: %jd/%jd MB "
491130389Sle		    "(%d%%)\n", d->name, gv_drivestate(d->state), d->device,
492130389Sle		    (intmax_t)d->avail / MEGABYTE, (intmax_t)d->size / MEGABYTE,
493190507Slulf		    d->size > 0 ? (int)((d->avail * 100) / d->size) : 0);
494130389Sle	}
495130389Sle
496130389Sle	/* Recursive listing. */
497130389Sle	if (flags & GV_FLAG_R) {
498130389Sle		LIST_FOREACH(s, &d->subdisks, from_drive)
499130389Sle			gv_lsi(s, sb, flags);
500130389Sle	}
501130389Sle}
502