xref: /freebsd/sys/geom/vinum/geom_vinum_create.c (revision ba3c1f5972d7b90feb6e6da47905ff2757e0fe57)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
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 			g_free(d->hdr);
144 			g_free(d);
145 			return (GV_ERR_CREATE);
146 		}
147 		g_topology_unlock();
148 		break;
149 	}
150 
151 	d->size = pp->mediasize - GV_DATA_START;
152 	d->avail = d->size;
153 	d->vinumconf = sc;
154 	LIST_INIT(&d->subdisks);
155 	LIST_INIT(&d->freelist);
156 
157 	/* The header might have been set during taste. */
158 	if (d->hdr == NULL) {
159 		hdr = g_malloc(sizeof(*hdr), M_WAITOK | M_ZERO);
160 		hdr->magic = GV_MAGIC;
161 		hdr->config_length = GV_CFG_LEN;
162 		getcredhostname(NULL, hdr->label.sysname, GV_HOSTNAME_LEN);
163 		strlcpy(hdr->label.name, d->name, sizeof(hdr->label.name));
164 		microtime(&hdr->label.date_of_birth);
165 		d->hdr = hdr;
166 	}
167 
168 	/* We also need a freelist entry. */
169 	fl = g_malloc(sizeof(struct gv_freelist), M_WAITOK | M_ZERO);
170 	fl->offset = GV_DATA_START;
171 	fl->size = d->avail;
172 	LIST_INSERT_HEAD(&d->freelist, fl, freelist);
173 	d->freelist_entries = 1;
174 
175 	if (gv_find_drive(sc, d->name) == NULL)
176 		LIST_INSERT_HEAD(&sc->drives, d, drive);
177 
178 	gv_set_drive_state(d, GV_DRIVE_UP, 0);
179 	return (0);
180 }
181 
182 int
183 gv_create_volume(struct gv_softc *sc, struct gv_volume *v)
184 {
185 	KASSERT(v != NULL, ("gv_create_volume: NULL v"));
186 
187 	v->vinumconf = sc;
188 	v->flags |= GV_VOL_NEWBORN;
189 	LIST_INIT(&v->plexes);
190 	LIST_INSERT_HEAD(&sc->volumes, v, volume);
191 	v->wqueue = g_malloc(sizeof(struct bio_queue_head), M_WAITOK | M_ZERO);
192 	bioq_init(v->wqueue);
193 	return (0);
194 }
195 
196 int
197 gv_create_plex(struct gv_softc *sc, struct gv_plex *p)
198 {
199 	struct gv_volume *v;
200 
201 	KASSERT(p != NULL, ("gv_create_plex: NULL p"));
202 
203 	/* Find the volume this plex should be attached to. */
204 	v = gv_find_vol(sc, p->volume);
205 	if (v == NULL) {
206 		G_VINUM_DEBUG(0, "create plex '%s': volume '%s' not found",
207 		    p->name, p->volume);
208 		g_free(p);
209 		return (GV_ERR_CREATE);
210 	}
211 	if (!(v->flags & GV_VOL_NEWBORN))
212 		p->flags |= GV_PLEX_ADDED;
213 	p->vol_sc = v;
214 	v->plexcount++;
215 	p->vinumconf = sc;
216 	p->synced = 0;
217 	p->flags |= GV_PLEX_NEWBORN;
218 	LIST_INSERT_HEAD(&v->plexes, p, in_volume);
219 	LIST_INIT(&p->subdisks);
220 	TAILQ_INIT(&p->packets);
221 	LIST_INSERT_HEAD(&sc->plexes, p, plex);
222 	p->bqueue = g_malloc(sizeof(struct bio_queue_head), M_WAITOK | M_ZERO);
223 	bioq_init(p->bqueue);
224 	p->wqueue = g_malloc(sizeof(struct bio_queue_head), M_WAITOK | M_ZERO);
225 	bioq_init(p->wqueue);
226 	p->rqueue = g_malloc(sizeof(struct bio_queue_head), M_WAITOK | M_ZERO);
227 	bioq_init(p->rqueue);
228 	return (0);
229 }
230 
231 int
232 gv_create_sd(struct gv_softc *sc, struct gv_sd *s)
233 {
234 	struct gv_plex *p;
235 	struct gv_drive *d;
236 
237 	KASSERT(s != NULL, ("gv_create_sd: NULL s"));
238 
239 	/* Find the drive where this subdisk should be put on. */
240 	d = gv_find_drive(sc, s->drive);
241 	if (d == NULL) {
242 		/*
243 		 * It's possible that the subdisk references a drive that
244 		 * doesn't exist yet (during the taste process), so create a
245 		 * practically empty "referenced" drive.
246 		 */
247 		if (s->flags & GV_SD_TASTED) {
248 			d = g_malloc(sizeof(struct gv_drive),
249 			    M_WAITOK | M_ZERO);
250 			d->flags |= GV_DRIVE_REFERENCED;
251 			strlcpy(d->name, s->drive, sizeof(d->name));
252 			gv_create_drive(sc, d);
253 		} else {
254 			G_VINUM_DEBUG(0, "create sd '%s': drive '%s' not found",
255 			    s->name, s->drive);
256 			g_free(s);
257 			return (GV_ERR_CREATE);
258 		}
259 	}
260 
261 	/* Find the plex where this subdisk belongs to. */
262 	p = gv_find_plex(sc, s->plex);
263 	if (p == NULL) {
264 		G_VINUM_DEBUG(0, "create sd '%s': plex '%s' not found",
265 		    s->name, s->plex);
266 		g_free(s);
267 		return (GV_ERR_CREATE);
268 	}
269 
270 	/*
271 	 * First we give the subdisk to the drive, to handle autosized
272 	 * values ...
273 	 */
274 	if (gv_sd_to_drive(s, d) != 0) {
275 		g_free(s);
276 		return (GV_ERR_CREATE);
277 	}
278 
279 	/*
280 	 * Then, we give the subdisk to the plex; we check if the
281 	 * given values are correct and maybe adjust them.
282 	 */
283 	if (gv_sd_to_plex(s, p) != 0) {
284 		G_VINUM_DEBUG(0, "unable to give sd '%s' to plex '%s'",
285 		    s->name, p->name);
286 		if (s->drive_sc && !(s->drive_sc->flags & GV_DRIVE_REFERENCED))
287 			LIST_REMOVE(s, from_drive);
288 		gv_free_sd(s);
289 		g_free(s);
290 		/*
291 		 * If this subdisk can't be created, we won't create
292 		 * the attached plex either, if it is also a new one.
293 		 */
294 		if (!(p->flags & GV_PLEX_NEWBORN))
295 			return (GV_ERR_CREATE);
296 		gv_rm_plex(sc, p);
297 		return (GV_ERR_CREATE);
298 	}
299 	s->flags |= GV_SD_NEWBORN;
300 
301 	s->vinumconf = sc;
302 	LIST_INSERT_HEAD(&sc->subdisks, s, sd);
303 
304 	return (0);
305 }
306 
307 /*
308  * Create a concatenated volume from specified drives or drivegroups.
309  */
310 void
311 gv_concat(struct g_geom *gp, struct gctl_req *req)
312 {
313 	struct gv_drive *d;
314 	struct gv_sd *s;
315 	struct gv_volume *v;
316 	struct gv_plex *p;
317 	struct gv_softc *sc;
318 	char *drive, buf[30], *vol;
319 	int *drives, dcount;
320 
321 	sc = gp->softc;
322 	dcount = 0;
323 	vol = gctl_get_param(req, "name", NULL);
324 	if (vol == NULL) {
325 		gctl_error(req, "volume name not given");
326 		return;
327 	}
328 
329 	drives = gctl_get_paraml(req, "drives", sizeof(*drives));
330 
331 	if (drives == NULL) {
332 		gctl_error(req, "drive names not given");
333 		return;
334 	}
335 
336 	/* First we create the volume. */
337 	v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO);
338 	strlcpy(v->name, vol, sizeof(v->name));
339 	v->state = GV_VOL_UP;
340 	gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0);
341 
342 	/* Then we create the plex. */
343 	p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO);
344 	snprintf(p->name, sizeof(p->name), "%s.p%d", v->name, v->plexcount);
345 	strlcpy(p->volume, v->name, sizeof(p->volume));
346 	p->org = GV_PLEX_CONCAT;
347 	p->stripesize = 0;
348 	gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0);
349 
350 	/* Drives are first (right now) priority */
351 	for (dcount = 0; dcount < *drives; dcount++) {
352 		snprintf(buf, sizeof(buf), "drive%d", dcount);
353 		drive = gctl_get_param(req, buf, NULL);
354 		d = gv_find_drive(sc, drive);
355 		if (d == NULL) {
356 			gctl_error(req, "No such drive '%s'", drive);
357 			continue;
358 		}
359 		s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO);
360 		snprintf(s->name, sizeof(s->name), "%s.s%d", p->name, dcount);
361 		strlcpy(s->plex, p->name, sizeof(s->plex));
362 		strlcpy(s->drive, drive, sizeof(s->drive));
363 		s->plex_offset = -1;
364 		s->drive_offset = -1;
365 		s->size = -1;
366 		gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0);
367 	}
368 	gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0);
369 	gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0);
370 }
371 
372 /*
373  * Create a mirrored volume from specified drives or drivegroups.
374  */
375 void
376 gv_mirror(struct g_geom *gp, struct gctl_req *req)
377 {
378 	struct gv_drive *d;
379 	struct gv_sd *s;
380 	struct gv_volume *v;
381 	struct gv_plex *p;
382 	struct gv_softc *sc;
383 	char *drive, buf[30], *vol;
384 	int *drives, *flags, dcount, pcount, scount;
385 
386 	sc = gp->softc;
387 	dcount = 0;
388 	scount = 0;
389 	pcount = 0;
390 	vol = gctl_get_param(req, "name", NULL);
391 	if (vol == NULL) {
392 		gctl_error(req, "volume name not given");
393 		return;
394 	}
395 
396 	flags = gctl_get_paraml(req, "flags", sizeof(*flags));
397 	drives = gctl_get_paraml(req, "drives", sizeof(*drives));
398 
399 	if (drives == NULL) {
400 		gctl_error(req, "drive names not given");
401 		return;
402 	}
403 
404 	/* We must have an even number of drives. */
405 	if (*drives % 2 != 0) {
406 		gctl_error(req, "mirror organization must have an even number "
407 		    "of drives");
408 		return;
409 	}
410 	if (*flags & GV_FLAG_S && *drives < 4) {
411 		gctl_error(req, "must have at least 4 drives for striped plex");
412 		return;
413 	}
414 
415 	/* First we create the volume. */
416 	v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO);
417 	strlcpy(v->name, vol, sizeof(v->name));
418 	v->state = GV_VOL_UP;
419 	gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0);
420 
421 	/* Then we create the plexes. */
422 	for (pcount = 0; pcount < 2; pcount++) {
423 		p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO);
424 		snprintf(p->name, sizeof(p->name), "%s.p%d", v->name,
425 		    pcount);
426 		strlcpy(p->volume, v->name, sizeof(p->volume));
427 		if (*flags & GV_FLAG_S) {
428 			p->org = GV_PLEX_STRIPED;
429 			p->stripesize = DEFAULT_STRIPESIZE;
430 		} else {
431 			p->org = GV_PLEX_CONCAT;
432 			p->stripesize = -1;
433 		}
434 		gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0);
435 
436 		/*
437 		 * We just gives each even drive to plex one, and each odd to
438 		 * plex two.
439 		 */
440 		scount = 0;
441 		for (dcount = pcount; dcount < *drives; dcount += 2) {
442 			snprintf(buf, sizeof(buf), "drive%d", dcount);
443 			drive = gctl_get_param(req, buf, NULL);
444 			d = gv_find_drive(sc, drive);
445 			if (d == NULL) {
446 				gctl_error(req, "No such drive '%s', aborting",
447 				    drive);
448 				scount++;
449 				break;
450 			}
451 			s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO);
452 			snprintf(s->name, sizeof(s->name), "%s.s%d", p->name,
453 			    scount);
454 			strlcpy(s->plex, p->name, sizeof(s->plex));
455 			strlcpy(s->drive, drive, sizeof(s->drive));
456 			s->plex_offset = -1;
457 			s->drive_offset = -1;
458 			s->size = -1;
459 			gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0);
460 			scount++;
461 		}
462 	}
463 	gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0);
464 	gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0);
465 }
466 
467 void
468 gv_raid5(struct g_geom *gp, struct gctl_req *req)
469 {
470 	struct gv_softc *sc;
471 	struct gv_drive *d;
472 	struct gv_volume *v;
473 	struct gv_plex *p;
474 	struct gv_sd *s;
475 	int *drives, *flags, dcount;
476 	char *vol, *drive, buf[30];
477 	off_t *stripesize;
478 
479 	sc = gp->softc;
480 
481 	vol = gctl_get_param(req, "name", NULL);
482 	if (vol == NULL) {
483 		gctl_error(req, "volume name not given");
484 		return;
485 	}
486 	flags = gctl_get_paraml(req, "flags", sizeof(*flags));
487 	drives = gctl_get_paraml(req, "drives", sizeof(*drives));
488 	stripesize = gctl_get_paraml(req, "stripesize", sizeof(*stripesize));
489 
490 	if (stripesize == NULL) {
491 		gctl_error(req, "no stripesize given");
492 		return;
493 	}
494 
495 	if (drives == NULL) {
496 		gctl_error(req, "drive names not given");
497 		return;
498 	}
499 
500 	/* We must have at least three drives. */
501 	if (*drives < 3) {
502 		gctl_error(req, "must have at least three drives for this "
503 		    "plex organisation");
504 		return;
505 	}
506 	/* First we create the volume. */
507 	v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO);
508 	strlcpy(v->name, vol, sizeof(v->name));
509 	v->state = GV_VOL_UP;
510 	gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0);
511 
512 	/* Then we create the plex. */
513 	p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO);
514 	snprintf(p->name, sizeof(p->name), "%s.p%d", v->name, v->plexcount);
515 	strlcpy(p->volume, v->name, sizeof(p->volume));
516 	p->org = GV_PLEX_RAID5;
517 	p->stripesize = *stripesize;
518 	gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0);
519 
520 	/* Create subdisks on drives. */
521 	for (dcount = 0; dcount < *drives; dcount++) {
522 		snprintf(buf, sizeof(buf), "drive%d", dcount);
523 		drive = gctl_get_param(req, buf, NULL);
524 		d = gv_find_drive(sc, drive);
525 		if (d == NULL) {
526 			gctl_error(req, "No such drive '%s'", drive);
527 			continue;
528 		}
529 		s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO);
530 		snprintf(s->name, sizeof(s->name), "%s.s%d", p->name, dcount);
531 		strlcpy(s->plex, p->name, sizeof(s->plex));
532 		strlcpy(s->drive, drive, sizeof(s->drive));
533 		s->plex_offset = -1;
534 		s->drive_offset = -1;
535 		s->size = -1;
536 		gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0);
537 	}
538 	gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0);
539 	gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0);
540 }
541 
542 /*
543  * Create a striped volume from specified drives or drivegroups.
544  */
545 void
546 gv_stripe(struct g_geom *gp, struct gctl_req *req)
547 {
548 	struct gv_drive *d;
549 	struct gv_sd *s;
550 	struct gv_volume *v;
551 	struct gv_plex *p;
552 	struct gv_softc *sc;
553 	char *drive, buf[30], *vol;
554 	int *drives, *flags, dcount;
555 
556 	sc = gp->softc;
557 	dcount = 0;
558 	vol = gctl_get_param(req, "name", NULL);
559 	if (vol == NULL) {
560 		gctl_error(req, "volume name not given");
561 		return;
562 	}
563 	flags = gctl_get_paraml(req, "flags", sizeof(*flags));
564 	drives = gctl_get_paraml(req, "drives", sizeof(*drives));
565 
566 	if (drives == NULL) {
567 		gctl_error(req, "drive names not given");
568 		return;
569 	}
570 
571 	/* We must have at least two drives. */
572 	if (*drives < 2) {
573 		gctl_error(req, "must have at least 2 drives");
574 		return;
575 	}
576 
577 	/* First we create the volume. */
578 	v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO);
579 	strlcpy(v->name, vol, sizeof(v->name));
580 	v->state = GV_VOL_UP;
581 	gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0);
582 
583 	/* Then we create the plex. */
584 	p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO);
585 	snprintf(p->name, sizeof(p->name), "%s.p%d", v->name, v->plexcount);
586 	strlcpy(p->volume, v->name, sizeof(p->volume));
587 	p->org = GV_PLEX_STRIPED;
588 	p->stripesize = 262144;
589 	gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0);
590 
591 	/* Create subdisks on drives. */
592 	for (dcount = 0; dcount < *drives; dcount++) {
593 		snprintf(buf, sizeof(buf), "drive%d", dcount);
594 		drive = gctl_get_param(req, buf, NULL);
595 		d = gv_find_drive(sc, drive);
596 		if (d == NULL) {
597 			gctl_error(req, "No such drive '%s'", drive);
598 			continue;
599 		}
600 		s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO);
601 		snprintf(s->name, sizeof(s->name), "%s.s%d", p->name, dcount);
602 		strlcpy(s->plex, p->name, sizeof(s->plex));
603 		strlcpy(s->drive, drive, sizeof(s->drive));
604 		s->plex_offset = -1;
605 		s->drive_offset = -1;
606 		s->size = -1;
607 		gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0);
608 	}
609 	gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0);
610 	gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0);
611 }
612