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