xref: /freebsd/sys/geom/mountver/g_mountver.c (revision b3f9d8c804bde2bf1199a558a06b81c434fb47df)
1 /*-
2  * Copyright (c) 2010 Edward Tomasz Napierala <trasz@FreeBSD.org>
3  * Copyright (c) 2004-2006 Pawel Jakub Dawidek <pjd@FreeBSD.org>
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 AUTHORS 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 AUTHORS 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/systm.h>
33 #include <sys/kernel.h>
34 #include <sys/module.h>
35 #include <sys/lock.h>
36 #include <sys/mutex.h>
37 #include <sys/bio.h>
38 #include <sys/disk.h>
39 #include <sys/sysctl.h>
40 #include <sys/malloc.h>
41 #include <sys/eventhandler.h>
42 #include <geom/geom.h>
43 #include <geom/mountver/g_mountver.h>
44 
45 
46 SYSCTL_DECL(_kern_geom);
47 SYSCTL_NODE(_kern_geom, OID_AUTO, mountver, CTLFLAG_RW,
48     0, "GEOM_MOUNTVER stuff");
49 static u_int g_mountver_debug = 0;
50 static u_int g_mountver_check_ident = 1;
51 SYSCTL_UINT(_kern_geom_mountver, OID_AUTO, debug, CTLFLAG_RW,
52     &g_mountver_debug, 0, "Debug level");
53 SYSCTL_UINT(_kern_geom_mountver, OID_AUTO, check_ident, CTLFLAG_RW,
54     &g_mountver_check_ident, 0, "Check disk ident when reattaching");
55 
56 static eventhandler_tag g_mountver_pre_sync = NULL;
57 
58 static void g_mountver_queue(struct bio *bp);
59 static void g_mountver_orphan(struct g_consumer *cp);
60 static int g_mountver_destroy(struct g_geom *gp, boolean_t force);
61 static g_taste_t g_mountver_taste;
62 static int g_mountver_destroy_geom(struct gctl_req *req, struct g_class *mp,
63     struct g_geom *gp);
64 static void g_mountver_config(struct gctl_req *req, struct g_class *mp,
65     const char *verb);
66 static void g_mountver_dumpconf(struct sbuf *sb, const char *indent,
67     struct g_geom *gp, struct g_consumer *cp, struct g_provider *pp);
68 static void g_mountver_init(struct g_class *mp);
69 static void g_mountver_fini(struct g_class *mp);
70 
71 struct g_class g_mountver_class = {
72 	.name = G_MOUNTVER_CLASS_NAME,
73 	.version = G_VERSION,
74 	.ctlreq = g_mountver_config,
75 	.taste = g_mountver_taste,
76 	.destroy_geom = g_mountver_destroy_geom,
77 	.init = g_mountver_init,
78 	.fini = g_mountver_fini
79 };
80 
81 static void
82 g_mountver_done(struct bio *bp)
83 {
84 	struct g_mountver_softc *sc;
85 	struct g_geom *gp;
86 	struct bio *pbp;
87 
88 	if (bp->bio_error != ENXIO) {
89 		g_std_done(bp);
90 		return;
91 	}
92 
93 	/*
94 	 * When the device goes away, it's possible that few requests
95 	 * will be completed with ENXIO before g_mountver_orphan()
96 	 * gets called.  To work around that, we have to queue requests
97 	 * that failed with ENXIO, in order to send them later.
98 	 */
99 	gp = bp->bio_from->geom;
100 	sc = gp->softc;
101 
102 	pbp = bp->bio_parent;
103 	KASSERT(pbp->bio_to == LIST_FIRST(&gp->provider),
104 	    ("parent request was for someone else"));
105 	g_destroy_bio(bp);
106 	pbp->bio_inbed++;
107 	g_mountver_queue(pbp);
108 }
109 
110 static void
111 g_mountver_send(struct bio *bp)
112 {
113 	struct g_geom *gp;
114 	struct bio *cbp;
115 
116 	gp = bp->bio_to->geom;
117 
118 	cbp = g_clone_bio(bp);
119 	if (cbp == NULL) {
120 		g_io_deliver(bp, ENOMEM);
121 		return;
122 	}
123 
124 	cbp->bio_done = g_mountver_done;
125 	g_io_request(cbp, LIST_FIRST(&gp->consumer));
126 }
127 
128 static void
129 g_mountver_queue(struct bio *bp)
130 {
131 	struct g_mountver_softc *sc;
132 	struct g_geom *gp;
133 
134 	gp = bp->bio_to->geom;
135 	sc = gp->softc;
136 
137 	mtx_lock(&sc->sc_mtx);
138 	TAILQ_INSERT_TAIL(&sc->sc_queue, bp, bio_queue);
139 	mtx_unlock(&sc->sc_mtx);
140 }
141 
142 static void
143 g_mountver_send_queued(struct g_geom *gp)
144 {
145 	struct g_mountver_softc *sc;
146 	struct bio *bp;
147 
148 	sc = gp->softc;
149 
150 	mtx_lock(&sc->sc_mtx);
151 	while ((bp = TAILQ_FIRST(&sc->sc_queue)) != NULL) {
152 		TAILQ_REMOVE(&sc->sc_queue, bp, bio_queue);
153 		G_MOUNTVER_LOGREQ(bp, "Sending queued request.");
154 		g_mountver_send(bp);
155 	}
156 	mtx_unlock(&sc->sc_mtx);
157 }
158 
159 static void
160 g_mountver_discard_queued(struct g_geom *gp)
161 {
162 	struct g_mountver_softc *sc;
163 	struct bio *bp;
164 
165 	sc = gp->softc;
166 
167 	mtx_lock(&sc->sc_mtx);
168 	while ((bp = TAILQ_FIRST(&sc->sc_queue)) != NULL) {
169 		TAILQ_REMOVE(&sc->sc_queue, bp, bio_queue);
170 		G_MOUNTVER_LOGREQ(bp, "Discarding queued request.");
171 		g_io_deliver(bp, ENXIO);
172 	}
173 	mtx_unlock(&sc->sc_mtx);
174 }
175 
176 static void
177 g_mountver_start(struct bio *bp)
178 {
179 	struct g_mountver_softc *sc;
180 	struct g_geom *gp;
181 
182 	gp = bp->bio_to->geom;
183 	sc = gp->softc;
184 	G_MOUNTVER_LOGREQ(bp, "Request received.");
185 
186 	/*
187 	 * It is possible that some bios were returned with ENXIO, even though
188 	 * orphaning didn't happen yet.  In that case, queue all subsequent
189 	 * requests in order to maintain ordering.
190 	 */
191 	if (sc->sc_orphaned || !TAILQ_EMPTY(&sc->sc_queue)) {
192 		G_MOUNTVER_LOGREQ(bp, "Queueing request.");
193 		g_mountver_queue(bp);
194 		if (!sc->sc_orphaned)
195 			g_mountver_send_queued(gp);
196 	} else {
197 		G_MOUNTVER_LOGREQ(bp, "Sending request.");
198 		g_mountver_send(bp);
199 	}
200 }
201 
202 static int
203 g_mountver_access(struct g_provider *pp, int dr, int dw, int de)
204 {
205 	struct g_mountver_softc *sc;
206 	struct g_geom *gp;
207 	struct g_consumer *cp;
208 
209 	g_topology_assert();
210 
211 	gp = pp->geom;
212 	cp = LIST_FIRST(&gp->consumer);
213 	sc = gp->softc;
214 	if (sc == NULL && dr <= 0 && dw <= 0 && de <= 0)
215 		return (0);
216 	KASSERT(sc != NULL, ("Trying to access withered provider \"%s\".", pp->name));
217 
218 	sc->sc_access_r += dr;
219 	sc->sc_access_w += dw;
220 	sc->sc_access_e += de;
221 
222 	if (sc->sc_orphaned)
223 		return (0);
224 
225 	return (g_access(cp, dr, dw, de));
226 }
227 
228 static int
229 g_mountver_create(struct gctl_req *req, struct g_class *mp, struct g_provider *pp)
230 {
231 	struct g_mountver_softc *sc;
232 	struct g_geom *gp;
233 	struct g_provider *newpp;
234 	struct g_consumer *cp;
235 	char name[64];
236 	int error;
237 	int identsize = DISK_IDENT_SIZE;
238 
239 	g_topology_assert();
240 
241 	gp = NULL;
242 	newpp = NULL;
243 	cp = NULL;
244 
245 	snprintf(name, sizeof(name), "%s%s", pp->name, G_MOUNTVER_SUFFIX);
246 	LIST_FOREACH(gp, &mp->geom, geom) {
247 		if (strcmp(gp->name, name) == 0) {
248 			gctl_error(req, "Provider %s already exists.", name);
249 			return (EEXIST);
250 		}
251 	}
252 	gp = g_new_geomf(mp, name);
253 	if (gp == NULL) {
254 		gctl_error(req, "Cannot create geom %s.", name);
255 		return (ENOMEM);
256 	}
257 	sc = g_malloc(sizeof(*sc), M_WAITOK | M_ZERO);
258 	mtx_init(&sc->sc_mtx, "gmountver", NULL, MTX_DEF);
259 	TAILQ_INIT(&sc->sc_queue);
260 	sc->sc_provider_name = strdup(pp->name, M_GEOM);
261 	gp->softc = sc;
262 	gp->start = g_mountver_start;
263 	gp->orphan = g_mountver_orphan;
264 	gp->access = g_mountver_access;
265 	gp->dumpconf = g_mountver_dumpconf;
266 
267 	newpp = g_new_providerf(gp, gp->name);
268 	if (newpp == NULL) {
269 		gctl_error(req, "Cannot create provider %s.", name);
270 		error = ENOMEM;
271 		goto fail;
272 	}
273 	newpp->mediasize = pp->mediasize;
274 	newpp->sectorsize = pp->sectorsize;
275 
276 	cp = g_new_consumer(gp);
277 	if (cp == NULL) {
278 		gctl_error(req, "Cannot create consumer for %s.", gp->name);
279 		error = ENOMEM;
280 		goto fail;
281 	}
282 	error = g_attach(cp, pp);
283 	if (error != 0) {
284 		gctl_error(req, "Cannot attach to provider %s.", pp->name);
285 		goto fail;
286 	}
287 	error = g_access(cp, 1, 0, 0);
288 	if (error != 0) {
289 		gctl_error(req, "Cannot access provider %s.", pp->name);
290 		goto fail;
291 	}
292 	error = g_io_getattr("GEOM::ident", cp, &identsize, sc->sc_ident);
293 	g_access(cp, -1, 0, 0);
294 	if (error != 0) {
295 		if (g_mountver_check_ident) {
296 			gctl_error(req, "Cannot get disk ident from %s; error = %d.", pp->name, error);
297 			goto fail;
298 		}
299 
300 		G_MOUNTVER_DEBUG(0, "Cannot get disk ident from %s; error = %d.", pp->name, error);
301 		sc->sc_ident[0] = '\0';
302 	}
303 
304 	g_error_provider(newpp, 0);
305 	G_MOUNTVER_DEBUG(0, "Device %s created.", gp->name);
306 	return (0);
307 fail:
308 	if (sc->sc_provider_name != NULL)
309 		g_free(sc->sc_provider_name);
310 	if (cp != NULL) {
311 		if (cp->provider != NULL)
312 			g_detach(cp);
313 		g_destroy_consumer(cp);
314 	}
315 	if (newpp != NULL)
316 		g_destroy_provider(newpp);
317 	if (gp != NULL) {
318 		if (gp->softc != NULL)
319 			g_free(gp->softc);
320 		g_destroy_geom(gp);
321 	}
322 	return (error);
323 }
324 
325 static int
326 g_mountver_destroy(struct g_geom *gp, boolean_t force)
327 {
328 	struct g_mountver_softc *sc;
329 	struct g_provider *pp;
330 
331 	g_topology_assert();
332 	if (gp->softc == NULL)
333 		return (ENXIO);
334 	sc = gp->softc;
335 	pp = LIST_FIRST(&gp->provider);
336 	if (pp != NULL && (pp->acr != 0 || pp->acw != 0 || pp->ace != 0)) {
337 		if (force) {
338 			G_MOUNTVER_DEBUG(0, "Device %s is still open, so it "
339 			    "can't be definitely removed.", pp->name);
340 		} else {
341 			G_MOUNTVER_DEBUG(1, "Device %s is still open (r%dw%de%d).",
342 			    pp->name, pp->acr, pp->acw, pp->ace);
343 			return (EBUSY);
344 		}
345 	} else {
346 		G_MOUNTVER_DEBUG(0, "Device %s removed.", gp->name);
347 	}
348 	g_orphan_provider(pp, ENXIO);
349 	g_mountver_discard_queued(gp);
350 	g_free(sc->sc_provider_name);
351 	g_free(gp->softc);
352 	gp->softc = NULL;
353 	g_wither_geom(gp, ENXIO);
354 
355 	return (0);
356 }
357 
358 static int
359 g_mountver_destroy_geom(struct gctl_req *req, struct g_class *mp, struct g_geom *gp)
360 {
361 
362 	return (g_mountver_destroy(gp, 0));
363 }
364 
365 static void
366 g_mountver_ctl_create(struct gctl_req *req, struct g_class *mp)
367 {
368 	struct g_provider *pp;
369 	const char *name;
370 	char param[16];
371 	int i, *nargs;
372 
373 	g_topology_assert();
374 
375 	nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
376 	if (nargs == NULL) {
377 		gctl_error(req, "No '%s' argument", "nargs");
378 		return;
379 	}
380 	if (*nargs <= 0) {
381 		gctl_error(req, "Missing device(s).");
382 		return;
383 	}
384 	for (i = 0; i < *nargs; i++) {
385 		snprintf(param, sizeof(param), "arg%d", i);
386 		name = gctl_get_asciiparam(req, param);
387 		if (name == NULL) {
388 			gctl_error(req, "No 'arg%d' argument", i);
389 			return;
390 		}
391 		if (strncmp(name, "/dev/", strlen("/dev/")) == 0)
392 			name += strlen("/dev/");
393 		pp = g_provider_by_name(name);
394 		if (pp == NULL) {
395 			G_MOUNTVER_DEBUG(1, "Provider %s is invalid.", name);
396 			gctl_error(req, "Provider %s is invalid.", name);
397 			return;
398 		}
399 		if (g_mountver_create(req, mp, pp) != 0)
400 			return;
401 	}
402 }
403 
404 static struct g_geom *
405 g_mountver_find_geom(struct g_class *mp, const char *name)
406 {
407 	struct g_geom *gp;
408 
409 	LIST_FOREACH(gp, &mp->geom, geom) {
410 		if (strcmp(gp->name, name) == 0)
411 			return (gp);
412 	}
413 	return (NULL);
414 }
415 
416 static void
417 g_mountver_ctl_destroy(struct gctl_req *req, struct g_class *mp)
418 {
419 	int *nargs, *force, error, i;
420 	struct g_geom *gp;
421 	const char *name;
422 	char param[16];
423 
424 	g_topology_assert();
425 
426 	nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
427 	if (nargs == NULL) {
428 		gctl_error(req, "No '%s' argument", "nargs");
429 		return;
430 	}
431 	if (*nargs <= 0) {
432 		gctl_error(req, "Missing device(s).");
433 		return;
434 	}
435 	force = gctl_get_paraml(req, "force", sizeof(*force));
436 	if (force == NULL) {
437 		gctl_error(req, "No 'force' argument");
438 		return;
439 	}
440 
441 	for (i = 0; i < *nargs; i++) {
442 		snprintf(param, sizeof(param), "arg%d", i);
443 		name = gctl_get_asciiparam(req, param);
444 		if (name == NULL) {
445 			gctl_error(req, "No 'arg%d' argument", i);
446 			return;
447 		}
448 		if (strncmp(name, "/dev/", strlen("/dev/")) == 0)
449 			name += strlen("/dev/");
450 		gp = g_mountver_find_geom(mp, name);
451 		if (gp == NULL) {
452 			G_MOUNTVER_DEBUG(1, "Device %s is invalid.", name);
453 			gctl_error(req, "Device %s is invalid.", name);
454 			return;
455 		}
456 		error = g_mountver_destroy(gp, *force);
457 		if (error != 0) {
458 			gctl_error(req, "Cannot destroy device %s (error=%d).",
459 			    gp->name, error);
460 			return;
461 		}
462 	}
463 }
464 
465 static void
466 g_mountver_orphan(struct g_consumer *cp)
467 {
468 	struct g_mountver_softc *sc;
469 
470 	g_topology_assert();
471 
472 	sc = cp->geom->softc;
473 	sc->sc_orphaned = 1;
474 	if (cp->acr > 0 || cp->acw > 0 || cp->ace > 0)
475 		g_access(cp, -cp->acr, -cp->acw, -cp->ace);
476 	g_detach(cp);
477 	G_MOUNTVER_DEBUG(0, "%s is offline.  Mount verification in progress.", sc->sc_provider_name);
478 }
479 
480 static int
481 g_mountver_ident_matches(struct g_geom *gp)
482 {
483 	struct g_consumer *cp;
484 	struct g_mountver_softc *sc;
485 	char ident[DISK_IDENT_SIZE];
486 	int error, identsize = DISK_IDENT_SIZE;
487 
488 	sc = gp->softc;
489 	cp = LIST_FIRST(&gp->consumer);
490 
491 	if (g_mountver_check_ident == 0)
492 		return (0);
493 
494 	error = g_access(cp, 1, 0, 0);
495 	if (error != 0) {
496 		G_MOUNTVER_DEBUG(0, "Cannot access %s; "
497 		    "not attaching; error = %d.", gp->name, error);
498 		return (1);
499 	}
500 	error = g_io_getattr("GEOM::ident", cp, &identsize, ident);
501 	g_access(cp, -1, 0, 0);
502 	if (error != 0) {
503 		G_MOUNTVER_DEBUG(0, "Cannot get disk ident for %s; "
504 		    "not attaching; error = %d.", gp->name, error);
505 		return (1);
506 	}
507 	if (strcmp(ident, sc->sc_ident) != 0) {
508 		G_MOUNTVER_DEBUG(1, "Disk ident for %s (\"%s\") is different "
509 		    "from expected \"%s\", not attaching.", gp->name, ident,
510 		    sc->sc_ident);
511 		return (1);
512 	}
513 
514 	return (0);
515 }
516 
517 static struct g_geom *
518 g_mountver_taste(struct g_class *mp, struct g_provider *pp, int flags __unused)
519 {
520 	struct g_mountver_softc *sc;
521 	struct g_consumer *cp;
522 	struct g_geom *gp;
523 	int error;
524 
525 	g_topology_assert();
526 	g_trace(G_T_TOPOLOGY, "%s(%s, %s)", __func__, mp->name, pp->name);
527 	G_MOUNTVER_DEBUG(2, "Tasting %s.", pp->name);
528 
529 	/*
530 	 * Let's check if device already exists.
531 	 */
532 	LIST_FOREACH(gp, &mp->geom, geom) {
533 		sc = gp->softc;
534 		if (sc == NULL)
535 			continue;
536 
537 		/* Already attached? */
538 		if (pp == LIST_FIRST(&gp->provider))
539 			return (NULL);
540 
541 		if (sc->sc_orphaned && strcmp(pp->name, sc->sc_provider_name) == 0)
542 			break;
543 	}
544 	if (gp == NULL)
545 		return (NULL);
546 
547 	cp = LIST_FIRST(&gp->consumer);
548 	g_attach(cp, pp);
549 	error = g_mountver_ident_matches(gp);
550 	if (error != 0) {
551 		g_detach(cp);
552 		return (NULL);
553 	}
554 	if (sc->sc_access_r > 0 || sc->sc_access_w > 0 || sc->sc_access_e > 0) {
555 		error = g_access(cp, sc->sc_access_r, sc->sc_access_w, sc->sc_access_e);
556 		if (error != 0) {
557 			G_MOUNTVER_DEBUG(0, "Cannot access %s; error = %d.", pp->name, error);
558 			g_detach(cp);
559 			return (NULL);
560 		}
561 	}
562 	g_mountver_send_queued(gp);
563 	sc->sc_orphaned = 0;
564 	G_MOUNTVER_DEBUG(0, "%s has completed mount verification.", sc->sc_provider_name);
565 
566 	return (gp);
567 }
568 
569 static void
570 g_mountver_config(struct gctl_req *req, struct g_class *mp, const char *verb)
571 {
572 	uint32_t *version;
573 
574 	g_topology_assert();
575 
576 	version = gctl_get_paraml(req, "version", sizeof(*version));
577 	if (version == NULL) {
578 		gctl_error(req, "No '%s' argument.", "version");
579 		return;
580 	}
581 	if (*version != G_MOUNTVER_VERSION) {
582 		gctl_error(req, "Userland and kernel parts are out of sync.");
583 		return;
584 	}
585 
586 	if (strcmp(verb, "create") == 0) {
587 		g_mountver_ctl_create(req, mp);
588 		return;
589 	} else if (strcmp(verb, "destroy") == 0) {
590 		g_mountver_ctl_destroy(req, mp);
591 		return;
592 	}
593 
594 	gctl_error(req, "Unknown verb.");
595 }
596 
597 static void
598 g_mountver_dumpconf(struct sbuf *sb, const char *indent, struct g_geom *gp,
599     struct g_consumer *cp, struct g_provider *pp)
600 {
601 	struct g_mountver_softc *sc;
602 
603 	if (pp != NULL || cp != NULL)
604 		return;
605 
606 	sc = gp->softc;
607 	sbuf_printf(sb, "%s<State>%s</State>\n", indent,
608 	    sc->sc_orphaned ? "OFFLINE" : "ONLINE");
609 	sbuf_printf(sb, "%s<Provider-Name>%s</Provider-Name>\n", indent, sc->sc_provider_name);
610 	sbuf_printf(sb, "%s<Disk-Ident>%s</Disk-Ident>\n", indent, sc->sc_ident);
611 }
612 
613 static void
614 g_mountver_shutdown_pre_sync(void *arg, int howto)
615 {
616 	struct g_class *mp;
617 	struct g_geom *gp, *gp2;
618 
619 	mp = arg;
620 	DROP_GIANT();
621 	g_topology_lock();
622 	LIST_FOREACH_SAFE(gp, &mp->geom, geom, gp2)
623 		g_mountver_destroy(gp, 1);
624 	g_topology_unlock();
625 	PICKUP_GIANT();
626 }
627 
628 static void
629 g_mountver_init(struct g_class *mp)
630 {
631 
632 	g_mountver_pre_sync = EVENTHANDLER_REGISTER(shutdown_pre_sync,
633 	    g_mountver_shutdown_pre_sync, mp, SHUTDOWN_PRI_FIRST);
634 	if (g_mountver_pre_sync == NULL)
635 		G_MOUNTVER_DEBUG(0, "Warning! Cannot register shutdown event.");
636 }
637 
638 static void
639 g_mountver_fini(struct g_class *mp)
640 {
641 
642 	if (g_mountver_pre_sync != NULL)
643 		EVENTHANDLER_DEREGISTER(shutdown_pre_sync, g_mountver_pre_sync);
644 }
645 
646 DECLARE_GEOM_CLASS(g_mountver_class, g_mountver);
647