]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - sys/geom/vinum/geom_vinum_create.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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': 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                         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, 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 name not given");       
324                 return;
325         }
326
327         drives = gctl_get_paraml(req, "drives", sizeof(*drives));
328
329         if (drives == NULL) { 
330                 gctl_error(req, "drive names not given");
331                 return;
332         }
333
334         /* First we create the volume. */
335         v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO);
336         strlcpy(v->name, vol, sizeof(v->name));
337         v->state = GV_VOL_UP;
338         gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0);
339
340         /* Then we create the plex. */
341         p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO);
342         snprintf(p->name, sizeof(p->name), "%s.p%d", v->name, v->plexcount);
343         strlcpy(p->volume, v->name, sizeof(p->volume));
344         p->org = GV_PLEX_CONCAT;
345         p->stripesize = 0;
346         gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0);
347
348         /* Drives are first (right now) priority */
349         for (dcount = 0; dcount < *drives; dcount++) {
350                 snprintf(buf, sizeof(buf), "drive%d", dcount);
351                 drive = gctl_get_param(req, buf, NULL);
352                 d = gv_find_drive(sc, drive);
353                 if (d == NULL) {
354                         gctl_error(req, "No such drive '%s'", drive);
355                         continue;
356                 }
357                 s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO);
358                 snprintf(s->name, sizeof(s->name), "%s.s%d", p->name, dcount);
359                 strlcpy(s->plex, p->name, sizeof(s->plex));
360                 strlcpy(s->drive, drive, sizeof(s->drive));
361                 s->plex_offset = -1;
362                 s->drive_offset = -1;
363                 s->size = -1;
364                 gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0);
365         }
366         gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0);
367         gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0);
368 }
369
370 /*
371  * Create a mirrored volume from specified drives or drivegroups.
372  */
373 void
374 gv_mirror(struct g_geom *gp, struct gctl_req *req)
375 {
376         struct gv_drive *d;
377         struct gv_sd *s;
378         struct gv_volume *v;
379         struct gv_plex *p;
380         struct gv_softc *sc;
381         char *drive, buf[30], *vol;
382         int *drives, *flags, dcount, pcount, scount;
383
384         sc = gp->softc;
385         dcount = 0;
386         scount = 0;
387         pcount = 0;
388         vol = gctl_get_param(req, "name", NULL);
389         if (vol == NULL) {
390                 gctl_error(req, "volume name not given");       
391                 return;
392         }
393
394         flags = gctl_get_paraml(req, "flags", sizeof(*flags));
395         drives = gctl_get_paraml(req, "drives", sizeof(*drives));
396
397         if (drives == NULL) { 
398                 gctl_error(req, "drive names not given");
399                 return;
400         }
401
402         /* We must have an even number of drives. */
403         if (*drives % 2 != 0) {
404                 gctl_error(req, "mirror organization must have an even number "
405                     "of drives");
406                 return;
407         }
408         if (*flags & GV_FLAG_S && *drives < 4) {
409                 gctl_error(req, "must have at least 4 drives for striped plex");
410                 return;
411         }
412
413         /* First we create the volume. */
414         v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO);
415         strlcpy(v->name, vol, sizeof(v->name));
416         v->state = GV_VOL_UP;
417         gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0);
418
419         /* Then we create the plexes. */
420         for (pcount = 0; pcount < 2; pcount++) {
421                 p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO);
422                 snprintf(p->name, sizeof(p->name), "%s.p%d", v->name,
423                     pcount);
424                 strlcpy(p->volume, v->name, sizeof(p->volume));
425                 if (*flags & GV_FLAG_S) {
426                         p->org = GV_PLEX_STRIPED;
427                         p->stripesize = DEFAULT_STRIPESIZE;
428                 } else {
429                         p->org = GV_PLEX_CONCAT;
430                         p->stripesize = -1;
431                 }
432                 gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0);
433
434                 /*
435                  * We just gives each even drive to plex one, and each odd to
436                  * plex two.
437                  */
438                 scount = 0;
439                 for (dcount = pcount; dcount < *drives; dcount += 2) {
440                         snprintf(buf, sizeof(buf), "drive%d", dcount);
441                         drive = gctl_get_param(req, buf, NULL);
442                         d = gv_find_drive(sc, drive);
443                         if (d == NULL) {
444                                 gctl_error(req, "No such drive '%s', aborting",
445                                     drive);
446                                 scount++;
447                                 break;
448                         }
449                         s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO);
450                         snprintf(s->name, sizeof(s->name), "%s.s%d", p->name,
451                             scount);
452                         strlcpy(s->plex, p->name, sizeof(s->plex));
453                         strlcpy(s->drive, drive, sizeof(s->drive));
454                         s->plex_offset = -1;
455                         s->drive_offset = -1;
456                         s->size = -1;
457                         gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0);
458                         scount++;
459                 }
460         }
461         gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0);
462         gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0);
463 }
464
465 void
466 gv_raid5(struct g_geom *gp, struct gctl_req *req)
467 {
468         struct gv_softc *sc;
469         struct gv_drive *d;
470         struct gv_volume *v;
471         struct gv_plex *p;
472         struct gv_sd *s;
473         int *drives, *flags, dcount;
474         char *vol, *drive, buf[30];
475         off_t *stripesize;
476
477         sc = gp->softc;
478
479         vol = gctl_get_param(req, "name", NULL);
480         if (vol == NULL) {
481                 gctl_error(req, "volume name not given");       
482                 return;
483         }
484         flags = gctl_get_paraml(req, "flags", sizeof(*flags));
485         drives = gctl_get_paraml(req, "drives", sizeof(*drives));
486         stripesize = gctl_get_paraml(req, "stripesize", sizeof(*stripesize));
487
488         if (stripesize == NULL) {
489                 gctl_error(req, "no stripesize given");
490                 return;
491         }
492
493         if (drives == NULL) {
494                 gctl_error(req, "drive names not given");
495                 return;
496         }
497
498         /* We must have at least three drives. */
499         if (*drives < 3) {
500                 gctl_error(req, "must have at least three drives for this "
501                     "plex organisation");
502                 return;
503         }
504         /* First we create the volume. */
505         v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO);
506         strlcpy(v->name, vol, sizeof(v->name));
507         v->state = GV_VOL_UP;
508         gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0);
509
510         /* Then we create the plex. */
511         p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO);
512         snprintf(p->name, sizeof(p->name), "%s.p%d", v->name, v->plexcount);
513         strlcpy(p->volume, v->name, sizeof(p->volume));
514         p->org = GV_PLEX_RAID5;
515         p->stripesize = *stripesize;
516         gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0);
517
518         /* Create subdisks on drives. */
519         for (dcount = 0; dcount < *drives; dcount++) {
520                 snprintf(buf, sizeof(buf), "drive%d", dcount);
521                 drive = gctl_get_param(req, buf, NULL);
522                 d = gv_find_drive(sc, drive);
523                 if (d == NULL) {
524                         gctl_error(req, "No such drive '%s'", drive);
525                         continue;
526                 }
527                 s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO);
528                 snprintf(s->name, sizeof(s->name), "%s.s%d", p->name, dcount);
529                 strlcpy(s->plex, p->name, sizeof(s->plex));
530                 strlcpy(s->drive, drive, sizeof(s->drive));
531                 s->plex_offset = -1;
532                 s->drive_offset = -1;
533                 s->size = -1;
534                 gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0);
535         }
536         gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0);
537         gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0);
538 }
539
540 /*
541  * Create a striped volume from specified drives or drivegroups.
542  */
543 void
544 gv_stripe(struct g_geom *gp, struct gctl_req *req)
545 {
546         struct gv_drive *d;
547         struct gv_sd *s;
548         struct gv_volume *v;
549         struct gv_plex *p;
550         struct gv_softc *sc;
551         char *drive, buf[30], *vol;
552         int *drives, *flags, dcount, pcount;
553
554         sc = gp->softc;
555         dcount = 0;
556         pcount = 0;
557         vol = gctl_get_param(req, "name", NULL);
558         if (vol == NULL) {
559                 gctl_error(req, "volume name not given");       
560                 return;
561         }
562         flags = gctl_get_paraml(req, "flags", sizeof(*flags));
563         drives = gctl_get_paraml(req, "drives", sizeof(*drives));
564
565         if (drives == NULL) { 
566                 gctl_error(req, "drive names not given");
567                 return;
568         }
569
570         /* We must have at least two drives. */
571         if (*drives < 2) {
572                 gctl_error(req, "must have at least 2 drives");
573                 return;
574         }
575
576         /* First we create the volume. */
577         v = g_malloc(sizeof(*v), M_WAITOK | M_ZERO);
578         strlcpy(v->name, vol, sizeof(v->name));
579         v->state = GV_VOL_UP;
580         gv_post_event(sc, GV_EVENT_CREATE_VOLUME, v, NULL, 0, 0);
581
582         /* Then we create the plex. */
583         p = g_malloc(sizeof(*p), M_WAITOK | M_ZERO);
584         snprintf(p->name, sizeof(p->name), "%s.p%d", v->name, v->plexcount);
585         strlcpy(p->volume, v->name, sizeof(p->volume));
586         p->org = GV_PLEX_STRIPED;
587         p->stripesize = 262144;
588         gv_post_event(sc, GV_EVENT_CREATE_PLEX, p, NULL, 0, 0);
589
590         /* Create subdisks on drives. */
591         for (dcount = 0; dcount < *drives; dcount++) {
592                 snprintf(buf, sizeof(buf), "drive%d", dcount);
593                 drive = gctl_get_param(req, buf, NULL);
594                 d = gv_find_drive(sc, drive);
595                 if (d == NULL) {
596                         gctl_error(req, "No such drive '%s'", drive);
597                         continue;
598                 }
599                 s = g_malloc(sizeof(*s), M_WAITOK | M_ZERO);
600                 snprintf(s->name, sizeof(s->name), "%s.s%d", p->name, dcount);
601                 strlcpy(s->plex, p->name, sizeof(s->plex));
602                 strlcpy(s->drive, drive, sizeof(s->drive));
603                 s->plex_offset = -1;
604                 s->drive_offset = -1;
605                 s->size = -1;
606                 gv_post_event(sc, GV_EVENT_CREATE_SD, s, NULL, 0, 0);
607         }
608         gv_post_event(sc, GV_EVENT_SETUP_OBJECTS, sc, NULL, 0, 0);
609         gv_post_event(sc, GV_EVENT_SAVE_CONFIG, sc, NULL, 0, 0);
610 }