]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/geom/vinum/geom_vinum_create.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.git] / sys / geom / vinum / geom_vinum_create.c
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/vinum/geom_vinum_var.h>
43 #include <geom/vinum/geom_vinum.h>
44
45 #define DEFAULT_STRIPESIZE      262144
46
47 /*
48  * Create a new drive object, either by user request, during taste of the drive
49  * itself, or because it was referenced by a subdisk during taste.
50  */
51 int
52 gv_create_drive(struct gv_softc *sc, struct gv_drive *d)
53 {
54         struct g_geom *gp;
55         struct g_provider *pp;
56         struct g_consumer *cp, *cp2;
57         struct gv_drive *d2;
58         struct gv_hdr *hdr;
59         struct gv_freelist *fl;
60
61         KASSERT(d != NULL, ("gv_create_drive: NULL d"));
62
63         gp = sc->geom;
64
65         pp = NULL;
66         cp = cp2 = NULL;
67
68         /* The drive already has a consumer if it was tasted before. */
69         if (d->consumer != NULL) {
70                 cp = d->consumer;
71                 cp->private = d;
72                 pp = cp->provider;
73         } else if (!(d->flags & GV_DRIVE_REFERENCED)) {
74                 if (gv_find_drive(sc, d->name) != NULL) {
75                         G_VINUM_DEBUG(0, "drive '%s' already exists", d->name);
76                         g_free(d);
77                         return (GV_ERR_CREATE);
78                 }
79
80                 if (gv_find_drive_device(sc, d->device) != NULL) {
81                         G_VINUM_DEBUG(0, "provider '%s' already in use by "
82                             "gvinum", d->device);
83                         return (GV_ERR_CREATE);
84                 }
85
86                 pp = g_provider_by_name(d->device);
87                 if (pp == NULL) {
88                         G_VINUM_DEBUG(0, "create '%s': device '%s' disappeared",
89                             d->name, d->device);
90                         g_free(d);
91                         return (GV_ERR_CREATE);
92                 }
93
94                 g_topology_lock();
95                 cp = g_new_consumer(gp);
96                 if (g_attach(cp, pp) != 0) {
97                         g_destroy_consumer(cp);
98                         g_topology_unlock();
99                         G_VINUM_DEBUG(0, "create drive '%s': unable to attach",
100                             d->name);
101                         g_free(d);
102                         return (GV_ERR_CREATE);
103                 }
104                 g_topology_unlock();
105
106                 d->consumer = cp;
107                 cp->private = d;
108         }
109
110         /*
111          * If this was just a "referenced" drive, we're almost finished, but
112          * insert this drive not on the head of the drives list, as
113          * gv_drive_is_newer() expects a "real" drive from LIST_FIRST().
114          */
115         if (d->flags & GV_DRIVE_REFERENCED) {
116                 snprintf(d->device, sizeof(d->device), "???");
117                 d2 = LIST_FIRST(&sc->drives);
118                 if (d2 == NULL)
119                         LIST_INSERT_HEAD(&sc->drives, d, drive);
120                 else
121                         LIST_INSERT_AFTER(d2, d, drive);
122                 return (0);
123         }
124
125         /*
126          * Update access counts of the new drive to those of an already
127          * existing drive.
128          */
129         LIST_FOREACH(d2, &sc->drives, drive) {
130                 if ((d == d2) || (d2->consumer == NULL))
131                         continue;
132
133                 cp2 = d2->consumer;
134                 g_topology_lock();
135                 if ((cp2->acr || cp2->acw || cp2->ace) &&
136                     (g_access(cp, cp2->acr, cp2->acw, cp2->ace) != 0)) {
137                         g_detach(cp);
138                         g_destroy_consumer(cp);
139                         g_topology_unlock();
140                         G_VINUM_DEBUG(0, "create drive '%s': unable to update "
141                             "access counts", d->name);
142                         if (d->hdr != NULL)
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, 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 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 }