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