xref: /freebsd/sys/geom/vinum/geom_vinum_create.c (revision b0b1dbdd)
1 /*-
2  * Copyright (c) 2007 Lukas Ertl
3  * Copyright (c) 2007, 2009 Ulf Lilleengen
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 #include <sys/param.h>
32 #include <sys/bio.h>
33 #include <sys/conf.h>
34 #include <sys/jail.h>
35 #include <sys/kernel.h>
36 #include <sys/malloc.h>
37 #include <sys/systm.h>
38 
39 #include <geom/geom.h>
40 #include <geom/vinum/geom_vinum_var.h>
41 #include <geom/vinum/geom_vinum.h>
42 
43 #define DEFAULT_STRIPESIZE	262144
44 
45 /*
46  * Create a new drive object, either by user request, during taste of the drive
47  * itself, or because it was referenced by a subdisk during taste.
48  */
49 int
50 gv_create_drive(struct gv_softc *sc, struct gv_drive *d)
51 {
52 	struct g_geom *gp;
53 	struct g_provider *pp;
54 	struct g_consumer *cp, *cp2;
55 	struct gv_drive *d2;
56 	struct gv_hdr *hdr;
57 	struct gv_freelist *fl;
58 
59 	KASSERT(d != NULL, ("gv_create_drive: NULL d"));
60 
61 	gp = sc->geom;
62 
63 	pp = NULL;
64 	cp = cp2 = NULL;
65 
66 	/* The drive already has a consumer if it was tasted before. */
67 	if (d->consumer != NULL) {
68 		cp = d->consumer;
69 		cp->private = d;
70 		pp = cp->provider;
71 	} else if (!(d->flags & GV_DRIVE_REFERENCED)) {
72 		if (gv_find_drive(sc, d->name) != NULL) {
73 			G_VINUM_DEBUG(0, "drive '%s' already exists", d->name);
74 			g_free(d);
75 			return (GV_ERR_CREATE);
76 		}
77 
78 		if (gv_find_drive_device(sc, d->device) != NULL) {
79 			G_VINUM_DEBUG(0, "provider '%s' already in use by "
80 			    "gvinum", d->device);
81 			return (GV_ERR_CREATE);
82 		}
83 
84 		pp = g_provider_by_name(d->device);
85 		if (pp == NULL) {
86 			G_VINUM_DEBUG(0, "create '%s': device '%s' disappeared",
87 			    d->name, d->device);
88 			g_free(d);
89 			return (GV_ERR_CREATE);
90 		}
91 
92 		g_topology_lock();
93 		cp = g_new_consumer(gp);
94 		if (g_attach(cp, pp) != 0) {
95 			g_destroy_consumer(cp);
96 			g_topology_unlock();
97 			G_VINUM_DEBUG(0, "create drive '%s': unable to attach",
98 			    d->name);
99 			g_free(d);
100 			return (GV_ERR_CREATE);
101 		}
102 		g_topology_unlock();
103 
104 		d->consumer = cp;
105 		cp->private = d;
106 	}
107 
108 	/*
109 	 * If this was just a "referenced" drive, we're almost finished, but
110 	 * insert this drive not on the head of the drives list, as
111 	 * gv_drive_is_newer() expects a "real" drive from LIST_FIRST().
112 	 */
113 	if (d->flags & GV_DRIVE_REFERENCED) {
114 		snprintf(d->device, sizeof(d->device), "???");
115 		d2 = LIST_FIRST(&sc->drives);
116 		if (d2 == NULL)
117 			LIST_INSERT_HEAD(&sc->drives, d, drive);
118 		else
119 			LIST_INSERT_AFTER(d2, d, drive);
120 		return (0);
121 	}
122 
123 	/*
124 	 * Update access counts of the new drive to those of an already
125 	 * existing drive.
126 	 */
127 	LIST_FOREACH(d2, &sc->drives, drive) {
128 		if ((d == d2) || (d2->consumer == NULL))
129 			continue;
130 
131 		cp2 = d2->consumer;
132 		g_topology_lock();
133 		if ((cp2->acr || cp2->acw || cp2->ace) &&
134 		    (g_access(cp, cp2->acr, cp2->acw, cp2->ace) != 0)) {
135 			g_detach(cp);
136 			g_destroy_consumer(cp);
137 			g_topology_unlock();
138 			G_VINUM_DEBUG(0, "create drive '%s': unable to update "
139 			    "access counts", d->name);
140 			if (d->hdr != NULL)
141 				g_free(d->hdr);
142 			g_free(d);
143 			return (GV_ERR_CREATE);
144 		}
145 		g_topology_unlock();
146 		break;
147 	}
148 
149 	d->size = pp->mediasize - GV_DATA_START;
150 	d->avail = d->size;
151 	d->vinumconf = sc;
152 	LIST_INIT(&d->subdisks);
153 	LIST_INIT(&d->freelist);
154 
155 	/* The header might have been set during taste. */
156 	if (d->hdr == NULL) {
157 		hdr = g_malloc(sizeof(*hdr), M_WAITOK | M_ZERO);
158 		hdr->magic = GV_MAGIC;
159 		hdr->config_length = GV_CFG_LEN;
160 		getcredhostname(NULL, hdr->label.sysname, GV_HOSTNAME_LEN);
161 		strlcpy(hdr->label.name, d->name, sizeof(hdr->label.name));
162 		microtime(&hdr->label.date_of_birth);
163 		d->hdr = hdr;
164 	}
165 
166 	/* We also need a freelist entry. */
167 	fl = g_malloc(sizeof(struct gv_freelist), M_WAITOK | M_ZERO);
168 	fl->offset = GV_DATA_START;
169 	fl->size = d->avail;
170 	LIST_INSERT_HEAD(&d->freelist, fl, freelist);
171 	d->freelist_entries = 1;
172 
173 	if (gv_find_drive(sc, d->name) == NULL)
174 		LIST_INSERT_HEAD(&sc->drives, d, drive);
175 
176 	gv_set_drive_state(d, GV_DRIVE_UP, 0);
177 	return (0);
178 }
179 
180 int
181 gv_create_volume(struct gv_softc *sc, struct gv_volume *v)
182 {
183 	KASSERT(v != NULL, ("gv_create_volume: NULL v"));
184 
185 	v->vinumconf = sc;
186 	v->flags |= GV_VOL_NEWBORN;
187 	LIST_INIT(&v->plexes);
188 	LIST_INSERT_HEAD(&sc->volumes, v, volume);
189 	v->wqueue = g_malloc(sizeof(struct bio_queue_head), M_WAITOK | M_ZERO);
190 	bioq_init(v->wqueue);
191 	return (0);
192 }
193 
194 int
195 gv_create_plex(struct gv_softc *sc, struct gv_plex *p)
196 {
197 	struct gv_volume *v;
198 
199 	KASSERT(p != NULL, ("gv_create_plex: NULL p"));
200 
201 	/* Find the volume this plex should be attached to. */
202 	v = gv_find_vol(sc, p->volume);
203 	if (v == NULL) {
204 		G_VINUM_DEBUG(0, "create plex '%s': volume '%s' not found",
205 		    p->name, p->volume);
206 		g_free(p);
207 		return (GV_ERR_CREATE);
208 	}
209 	if (!(v->flags & GV_VOL_NEWBORN))
210 		p->flags |= GV_PLEX_ADDED;
211 	p->vol_sc = v;
212 	v->plexcount++;
213 	p->vinumconf = sc;
214 	p->synced = 0;
215 	p->flags |= GV_PLEX_NEWBORN;
216 	LIST_INSERT_HEAD(&v->plexes, p, in_volume);
217 	LIST_INIT(&p->subdisks);
218 	TAILQ_INIT(&p->packets);
219 	LIST_INSERT_HEAD(&sc->plexes, p, plex);
220 	p->bqueue = g_malloc(sizeof(struct bio_queue_head), M_WAITOK | M_ZERO);
221 	bioq_init(p->bqueue);
222 	p->wqueue = g_malloc(sizeof(struct bio_queue_head), M_WAITOK | M_ZERO);
223 	bioq_init(p->wqueue);
224 	p->rqueue = g_malloc(sizeof(struct bio_queue_head), M_WAITOK | M_ZERO);
225 	bioq_init(p->rqueue);
226 	return (0);
227 }
228 
229 int
230 gv_create_sd(struct gv_softc *sc, struct gv_sd *s)
231 {
232 	struct gv_plex *p;
233 	struct gv_drive *d;
234 
235 	KASSERT(s != NULL, ("gv_create_sd: NULL s"));
236 
237 	/* Find the drive where this subdisk should be put on. */
238 	d = gv_find_drive(sc, s->drive);
239 	if (d == NULL) {
240 		/*
241 		 * It's possible that the subdisk references a drive that
242 		 * doesn't exist yet (during the taste process), so create a
243 		 * practically empty "referenced" drive.
244 		 */
245 		if (s->flags & GV_SD_TASTED) {
246 			d = g_malloc(sizeof(struct gv_drive),
247 			    M_WAITOK | M_ZERO);
248 			d->flags |= GV_DRIVE_REFERENCED;
249 			strlcpy(d->name, s->drive, sizeof(d->name));
250 			gv_create_drive(sc, d);
251 		} else {
252 			G_VINUM_DEBUG(0, "create sd '%s': drive '%s' not found",
253 			    s->name, s->drive);
254 			g_free(s);
255 			return (GV_ERR_CREATE);
256 		}
257 	}
258 
259 	/* Find the plex where this subdisk belongs to. */
260 	p = gv_find_plex(sc, s->plex);
261 	if (p == NULL) {
262 		G_VINUM_DEBUG(0, "create sd '%s': plex '%s' not found",
263 		    s->name, s->plex);
264 		g_free(s);
265 		return (GV_ERR_CREATE);
266 	}
267 
268 	/*
269 	 * First we give the subdisk to the drive, to handle autosized
270 	 * values ...
271 	 */
272 	if (gv_sd_to_drive(s, d) != 0) {
273 		g_free(s);
274 		return (GV_ERR_CREATE);
275 	}
276 
277 	/*
278 	 * Then, we give the subdisk to the plex; we check if the
279 	 * given values are correct and maybe adjust them.
280 	 */
281 	if (gv_sd_to_plex(s, p) != 0) {
282 		G_VINUM_DEBUG(0, "unable to give sd '%s' to plex '%s'",
283 		    s->name, p->name);
284 		if (s->drive_sc && !(s->drive_sc->flags & GV_DRIVE_REFERENCED))
285 			LIST_REMOVE(s, from_drive);
286 		gv_free_sd(s);
287 		g_free(s);
288 		/*
289 		 * If this subdisk can't be created, we won't create
290 		 * the attached plex either, if it is also a new one.
291 		 */
292 		if (!(p->flags & GV_PLEX_NEWBORN))
293 			return (GV_ERR_CREATE);
294 		gv_rm_plex(sc, p);
295 		return (GV_ERR_CREATE);
296 	}
297 	s->flags |= GV_SD_NEWBORN;
298 
299 	s->vinumconf = sc;
300 	LIST_INSERT_HEAD(&sc->subdisks, s, sd);
301 
302 	return (0);
303 }
304 
305 /*
306  * Create a concatenated volume from specified drives or drivegroups.
307  */
308 void
309 gv_concat(struct g_geom *gp, struct gctl_req *req)
310 {
311 	struct gv_drive *d;
312 	struct gv_sd *s;
313 	struct gv_volume *v;
314 	struct gv_plex *p;
315 	struct gv_softc *sc;
316 	char *drive, buf[30], *vol;
317 	int *drives, dcount;
318 
319 	sc = gp->softc;
320 	dcount = 0;
321 	vol = gctl_get_param(req, "name", NULL);
322 	if (vol == NULL) {
323 		gctl_error(req, "volume name not given");
324 		return;
325 	}
326 
327 	drives = gctl_get_paraml(req, "drives", sizeof(*drives));
328 
329 	if (drives == NULL) {
330 		gctl_error(req, "drive names not given");
331 		return;
332 	}
333 
334 	/* First we create the volume. */
335 	v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO);
336 	strlcpy(v->name, vol, sizeof(v->name));
337 	v->state = GV_VOL_UP;
338 	gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0);
339 
340 	/* Then we create the plex. */
341 	p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO);
342 	snprintf(p->name, sizeof(p->name), "%s.p%d", v->name, v->plexcount);
343 	strlcpy(p->volume, v->name, sizeof(p->volume));
344 	p->org = GV_PLEX_CONCAT;
345 	p->stripesize = 0;
346 	gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0);
347 
348 	/* Drives are first (right now) priority */
349 	for (dcount = 0; dcount < *drives; dcount++) {
350 		snprintf(buf, sizeof(buf), "drive%d", dcount);
351 		drive = gctl_get_param(req, buf, NULL);
352 		d = gv_find_drive(sc, drive);
353 		if (d == NULL) {
354 			gctl_error(req, "No such drive '%s'", drive);
355 			continue;
356 		}
357 		s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO);
358 		snprintf(s->name, sizeof(s->name), "%s.s%d", p->name, dcount);
359 		strlcpy(s->plex, p->name, sizeof(s->plex));
360 		strlcpy(s->drive, drive, sizeof(s->drive));
361 		s->plex_offset = -1;
362 		s->drive_offset = -1;
363 		s->size = -1;
364 		gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0);
365 	}
366 	gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0);
367 	gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0);
368 }
369 
370 /*
371  * Create a mirrored volume from specified drives or drivegroups.
372  */
373 void
374 gv_mirror(struct g_geom *gp, struct gctl_req *req)
375 {
376 	struct gv_drive *d;
377 	struct gv_sd *s;
378 	struct gv_volume *v;
379 	struct gv_plex *p;
380 	struct gv_softc *sc;
381 	char *drive, buf[30], *vol;
382 	int *drives, *flags, dcount, pcount, scount;
383 
384 	sc = gp->softc;
385 	dcount = 0;
386 	scount = 0;
387 	pcount = 0;
388 	vol = gctl_get_param(req, "name", NULL);
389 	if (vol == NULL) {
390 		gctl_error(req, "volume name not given");
391 		return;
392 	}
393 
394 	flags = gctl_get_paraml(req, "flags", sizeof(*flags));
395 	drives = gctl_get_paraml(req, "drives", sizeof(*drives));
396 
397 	if (drives == NULL) {
398 		gctl_error(req, "drive names not given");
399 		return;
400 	}
401 
402 	/* We must have an even number of drives. */
403 	if (*drives % 2 != 0) {
404 		gctl_error(req, "mirror organization must have an even number "
405 		    "of drives");
406 		return;
407 	}
408 	if (*flags & GV_FLAG_S && *drives < 4) {
409 		gctl_error(req, "must have at least 4 drives for striped plex");
410 		return;
411 	}
412 
413 	/* First we create the volume. */
414 	v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO);
415 	strlcpy(v->name, vol, sizeof(v->name));
416 	v->state = GV_VOL_UP;
417 	gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0);
418 
419 	/* Then we create the plexes. */
420 	for (pcount = 0; pcount < 2; pcount++) {
421 		p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO);
422 		snprintf(p->name, sizeof(p->name), "%s.p%d", v->name,
423 		    pcount);
424 		strlcpy(p->volume, v->name, sizeof(p->volume));
425 		if (*flags & GV_FLAG_S) {
426 			p->org = GV_PLEX_STRIPED;
427 			p->stripesize = DEFAULT_STRIPESIZE;
428 		} else {
429 			p->org = GV_PLEX_CONCAT;
430 			p->stripesize = -1;
431 		}
432 		gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0);
433 
434 		/*
435 		 * We just gives each even drive to plex one, and each odd to
436 		 * plex two.
437 		 */
438 		scount = 0;
439 		for (dcount = pcount; dcount < *drives; dcount += 2) {
440 			snprintf(buf, sizeof(buf), "drive%d", dcount);
441 			drive = gctl_get_param(req, buf, NULL);
442 			d = gv_find_drive(sc, drive);
443 			if (d == NULL) {
444 				gctl_error(req, "No such drive '%s', aborting",
445 				    drive);
446 				scount++;
447 				break;
448 			}
449 			s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO);
450 			snprintf(s->name, sizeof(s->name), "%s.s%d", p->name,
451 			    scount);
452 			strlcpy(s->plex, p->name, sizeof(s->plex));
453 			strlcpy(s->drive, drive, sizeof(s->drive));
454 			s->plex_offset = -1;
455 			s->drive_offset = -1;
456 			s->size = -1;
457 			gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0);
458 			scount++;
459 		}
460 	}
461 	gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0);
462 	gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0);
463 }
464 
465 void
466 gv_raid5(struct g_geom *gp, struct gctl_req *req)
467 {
468 	struct gv_softc *sc;
469 	struct gv_drive *d;
470 	struct gv_volume *v;
471 	struct gv_plex *p;
472 	struct gv_sd *s;
473 	int *drives, *flags, dcount;
474 	char *vol, *drive, buf[30];
475 	off_t *stripesize;
476 
477 	sc = gp->softc;
478 
479 	vol = gctl_get_param(req, "name", NULL);
480 	if (vol == NULL) {
481 		gctl_error(req, "volume name not given");
482 		return;
483 	}
484 	flags = gctl_get_paraml(req, "flags", sizeof(*flags));
485 	drives = gctl_get_paraml(req, "drives", sizeof(*drives));
486 	stripesize = gctl_get_paraml(req, "stripesize", sizeof(*stripesize));
487 
488 	if (stripesize == NULL) {
489 		gctl_error(req, "no stripesize given");
490 		return;
491 	}
492 
493 	if (drives == NULL) {
494 		gctl_error(req, "drive names not given");
495 		return;
496 	}
497 
498 	/* We must have at least three drives. */
499 	if (*drives < 3) {
500 		gctl_error(req, "must have at least three drives for this "
501 		    "plex organisation");
502 		return;
503 	}
504 	/* First we create the volume. */
505 	v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO);
506 	strlcpy(v->name, vol, sizeof(v->name));
507 	v->state = GV_VOL_UP;
508 	gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0);
509 
510 	/* Then we create the plex. */
511 	p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO);
512 	snprintf(p->name, sizeof(p->name), "%s.p%d", v->name, v->plexcount);
513 	strlcpy(p->volume, v->name, sizeof(p->volume));
514 	p->org = GV_PLEX_RAID5;
515 	p->stripesize = *stripesize;
516 	gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0);
517 
518 	/* Create subdisks on drives. */
519 	for (dcount = 0; dcount < *drives; dcount++) {
520 		snprintf(buf, sizeof(buf), "drive%d", dcount);
521 		drive = gctl_get_param(req, buf, NULL);
522 		d = gv_find_drive(sc, drive);
523 		if (d == NULL) {
524 			gctl_error(req, "No such drive '%s'", drive);
525 			continue;
526 		}
527 		s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO);
528 		snprintf(s->name, sizeof(s->name), "%s.s%d", p->name, dcount);
529 		strlcpy(s->plex, p->name, sizeof(s->plex));
530 		strlcpy(s->drive, drive, sizeof(s->drive));
531 		s->plex_offset = -1;
532 		s->drive_offset = -1;
533 		s->size = -1;
534 		gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0);
535 	}
536 	gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0);
537 	gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0);
538 }
539 
540 /*
541  * Create a striped volume from specified drives or drivegroups.
542  */
543 void
544 gv_stripe(struct g_geom *gp, struct gctl_req *req)
545 {
546 	struct gv_drive *d;
547 	struct gv_sd *s;
548 	struct gv_volume *v;
549 	struct gv_plex *p;
550 	struct gv_softc *sc;
551 	char *drive, buf[30], *vol;
552 	int *drives, *flags, dcount, pcount;
553 
554 	sc = gp->softc;
555 	dcount = 0;
556 	pcount = 0;
557 	vol = gctl_get_param(req, "name", NULL);
558 	if (vol == NULL) {
559 		gctl_error(req, "volume name not given");
560 		return;
561 	}
562 	flags = gctl_get_paraml(req, "flags", sizeof(*flags));
563 	drives = gctl_get_paraml(req, "drives", sizeof(*drives));
564 
565 	if (drives == NULL) {
566 		gctl_error(req, "drive names not given");
567 		return;
568 	}
569 
570 	/* We must have at least two drives. */
571 	if (*drives < 2) {
572 		gctl_error(req, "must have at least 2 drives");
573 		return;
574 	}
575 
576 	/* First we create the volume. */
577 	v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO);
578 	strlcpy(v->name, vol, sizeof(v->name));
579 	v->state = GV_VOL_UP;
580 	gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0);
581 
582 	/* Then we create the plex. */
583 	p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO);
584 	snprintf(p->name, sizeof(p->name), "%s.p%d", v->name, v->plexcount);
585 	strlcpy(p->volume, v->name, sizeof(p->volume));
586 	p->org = GV_PLEX_STRIPED;
587 	p->stripesize = 262144;
588 	gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0);
589 
590 	/* Create subdisks on drives. */
591 	for (dcount = 0; dcount < *drives; dcount++) {
592 		snprintf(buf, sizeof(buf), "drive%d", dcount);
593 		drive = gctl_get_param(req, buf, NULL);
594 		d = gv_find_drive(sc, drive);
595 		if (d == NULL) {
596 			gctl_error(req, "No such drive '%s'", drive);
597 			continue;
598 		}
599 		s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO);
600 		snprintf(s->name, sizeof(s->name), "%s.s%d", p->name, dcount);
601 		strlcpy(s->plex, p->name, sizeof(s->plex));
602 		strlcpy(s->drive, drive, sizeof(s->drive));
603 		s->plex_offset = -1;
604 		s->drive_offset = -1;
605 		s->size = -1;
606 		gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0);
607 	}
608 	gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0);
609 	gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0);
610 }
611