]> CyberLeo.Net >> Repos - FreeBSD/releng/8.1.git/blob - sys/geom/vinum/geom_vinum_create.c
Copy stable/8 to releng/8.1 in preparation for 8.1-RC1.
[FreeBSD/releng/8.1.git] / sys / geom / vinum / geom_vinum_create.c
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': couldn't 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': couldn't 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, *flags, 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 names not given");      
324                 return;
325         }
326
327         flags = gctl_get_paraml(req, "flags", sizeof(*flags));
328         drives = gctl_get_paraml(req, "drives", sizeof(*drives));
329
330         if (drives == NULL) { 
331                 gctl_error(req, "drive names not given");
332                 return;
333         }
334
335         /* First we create the volume. */
336         v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO);
337         strlcpy(v->name, vol, sizeof(v->name));
338         v->state = GV_VOL_UP;
339         gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0);
340
341         /* Then we create the plex. */
342         p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO);
343         snprintf(p->name, sizeof(p->name), "%s.p%d", v->name, v->plexcount);
344         strlcpy(p->volume, v->name, sizeof(p->volume));
345         p->org = GV_PLEX_CONCAT;
346         p->stripesize = 0;
347         gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0);
348
349         /* Drives are first (right now) priority */
350         for (dcount = 0; dcount < *drives; dcount++) {
351                 snprintf(buf, sizeof(buf), "drive%d", dcount);
352                 drive = gctl_get_param(req, buf, NULL);
353                 d = gv_find_drive(sc, drive);
354                 if (d == NULL) {
355                         gctl_error(req, "No such drive '%s'", drive);
356                         continue;
357                 }
358                 s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO);
359                 snprintf(s->name, sizeof(s->name), "%s.s%d", p->name, dcount);
360                 strlcpy(s->plex, p->name, sizeof(s->plex));
361                 strlcpy(s->drive, drive, sizeof(s->drive));
362                 s->plex_offset = -1;
363                 s->drive_offset = -1;
364                 s->size = -1;
365                 gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0);
366         }
367         gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0);
368         gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0);
369 }
370
371 /*
372  * Create a mirrored volume from specified drives or drivegroups.
373  */
374 void
375 gv_mirror(struct g_geom *gp, struct gctl_req *req)
376 {
377         struct gv_drive *d;
378         struct gv_sd *s;
379         struct gv_volume *v;
380         struct gv_plex *p;
381         struct gv_softc *sc;
382         char *drive, buf[30], *vol;
383         int *drives, *flags, dcount, pcount, scount;
384
385         sc = gp->softc;
386         dcount = 0;
387         scount = 0;
388         pcount = 0;
389         vol = gctl_get_param(req, "name", NULL);
390         if (vol == NULL) {
391                 gctl_error(req, "volume's not given");  
392                 return;
393         }
394
395         flags = gctl_get_paraml(req, "flags", sizeof(*flags));
396         drives = gctl_get_paraml(req, "drives", sizeof(*drives));
397
398         if (drives == NULL) { 
399                 gctl_error(req, "drives not given");
400                 return;
401         }
402
403         /* We must have an even number of drives. */
404         if (*drives % 2 != 0) {
405                 gctl_error(req, "mirror organization must have an even number "
406                     "of drives");
407                 return;
408         }
409         if (*flags & GV_FLAG_S && *drives < 4) {
410                 gctl_error(req, "must have at least 4 drives for striped plex");
411                 return;
412         }
413
414         /* First we create the volume. */
415         v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO);
416         strlcpy(v->name, vol, sizeof(v->name));
417         v->state = GV_VOL_UP;
418         gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0);
419
420         /* Then we create the plexes. */
421         for (pcount = 0; pcount < 2; pcount++) {
422                 p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO);
423                 snprintf(p->name, sizeof(p->name), "%s.p%d", v->name,
424                     pcount);
425                 strlcpy(p->volume, v->name, sizeof(p->volume));
426                 if (*flags & GV_FLAG_S) {
427                         p->org = GV_PLEX_STRIPED;
428                         p->stripesize = DEFAULT_STRIPESIZE;
429                 } else {
430                         p->org = GV_PLEX_CONCAT;
431                         p->stripesize = -1;
432                 }
433                 gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0);
434
435                 /*
436                  * We just gives each even drive to plex one, and each odd to
437                  * plex two.
438                  */
439                 scount = 0;
440                 for (dcount = pcount; dcount < *drives; dcount += 2) {
441                         snprintf(buf, sizeof(buf), "drive%d", dcount);
442                         drive = gctl_get_param(req, buf, NULL);
443                         d = gv_find_drive(sc, drive);
444                         if (d == NULL) {
445                                 gctl_error(req, "No such drive '%s', aborting",
446                                     drive);
447                                 scount++;
448                                 break;
449                         }
450                         s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO);
451                         snprintf(s->name, sizeof(s->name), "%s.s%d", p->name,
452                             scount);
453                         strlcpy(s->plex, p->name, sizeof(s->plex));
454                         strlcpy(s->drive, drive, sizeof(s->drive));
455                         s->plex_offset = -1;
456                         s->drive_offset = -1;
457                         s->size = -1;
458                         gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0);
459                         scount++;
460                 }
461         }
462         gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0);
463         gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0);
464 }
465
466 void
467 gv_raid5(struct g_geom *gp, struct gctl_req *req)
468 {
469         struct gv_softc *sc;
470         struct gv_drive *d;
471         struct gv_volume *v;
472         struct gv_plex *p;
473         struct gv_sd *s;
474         int *drives, *flags, dcount;
475         char *vol, *drive, buf[30];
476         off_t *stripesize;
477
478         dcount = 0;
479         sc = gp->softc;
480
481         vol = gctl_get_param(req, "name", NULL);
482         if (vol == NULL) {
483                 gctl_error(req, "volume's 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, "drives 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, pcount;
555
556         sc = gp->softc;
557         dcount = 0;
558         pcount = 0;
559         vol = gctl_get_param(req, "name", NULL);
560         if (vol == NULL) {
561                 gctl_error(req, "volume's 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, "drives 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 }