g_mirror_ctl.c revision 155174
1/*-
2 * Copyright (c) 2004-2005 Pawel Jakub Dawidek <pjd@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/sys/geom/mirror/g_mirror_ctl.c 155174 2006-02-01 12:06:01Z pjd $");
29
30#include <sys/param.h>
31#include <sys/systm.h>
32#include <sys/kernel.h>
33#include <sys/module.h>
34#include <sys/lock.h>
35#include <sys/mutex.h>
36#include <sys/bio.h>
37#include <sys/sysctl.h>
38#include <sys/malloc.h>
39#include <sys/bitstring.h>
40#include <vm/uma.h>
41#include <machine/atomic.h>
42#include <geom/geom.h>
43#include <sys/proc.h>
44#include <sys/kthread.h>
45#include <geom/mirror/g_mirror.h>
46
47
48static struct g_mirror_softc *
49g_mirror_find_device(struct g_class *mp, const char *name)
50{
51	struct g_mirror_softc *sc;
52	struct g_geom *gp;
53
54	g_topology_assert();
55	LIST_FOREACH(gp, &mp->geom, geom) {
56		sc = gp->softc;
57		if (sc == NULL)
58			continue;
59		if ((sc->sc_flags & G_MIRROR_DEVICE_FLAG_DESTROY) != 0)
60			continue;
61		if (strcmp(gp->name, name) == 0 ||
62		    strcmp(sc->sc_name, name) == 0) {
63			return (sc);
64		}
65	}
66	return (NULL);
67}
68
69static struct g_mirror_disk *
70g_mirror_find_disk(struct g_mirror_softc *sc, const char *name)
71{
72	struct g_mirror_disk *disk;
73
74	g_topology_assert();
75	LIST_FOREACH(disk, &sc->sc_disks, d_next) {
76		if (disk->d_consumer == NULL)
77			continue;
78		if (disk->d_consumer->provider == NULL)
79			continue;
80		if (strcmp(disk->d_consumer->provider->name, name) == 0)
81			return (disk);
82	}
83	return (NULL);
84}
85
86static void
87g_mirror_ctl_configure(struct gctl_req *req, struct g_class *mp)
88{
89	struct g_mirror_softc *sc;
90	struct g_mirror_disk *disk;
91	const char *name, *balancep;
92	intmax_t *slicep;
93	uint32_t slice;
94	uint8_t balance;
95	int *nargs, *autosync, *noautosync, *hardcode, *dynamic, do_sync = 0;
96
97	g_topology_assert();
98	nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
99	if (nargs == NULL) {
100		gctl_error(req, "No '%s' argument.", "nargs");
101		return;
102	}
103	if (*nargs != 1) {
104		gctl_error(req, "Invalid number of arguments.");
105		return;
106	}
107	name = gctl_get_asciiparam(req, "arg0");
108	if (name == NULL) {
109		gctl_error(req, "No 'arg%u' argument.", 0);
110		return;
111	}
112	sc = g_mirror_find_device(mp, name);
113	if (sc == NULL) {
114		gctl_error(req, "No such device: %s.", name);
115		return;
116	}
117	if (g_mirror_ndisks(sc, -1) < sc->sc_ndisks) {
118		gctl_error(req, "Not all disks connected.");
119		return;
120	}
121	balancep = gctl_get_asciiparam(req, "balance");
122	if (balancep == NULL) {
123		gctl_error(req, "No '%s' argument.", "balance");
124		return;
125	}
126	if (strcmp(balancep, "none") == 0)
127		balance = sc->sc_balance;
128	else {
129		if (balance_id(balancep) == -1) {
130			gctl_error(req, "Invalid balance algorithm.");
131			return;
132		}
133		balance = balance_id(balancep);
134	}
135	slicep = gctl_get_paraml(req, "slice", sizeof(*slicep));
136	if (slicep == NULL) {
137		gctl_error(req, "No '%s' argument.", "slice");
138		return;
139	}
140	if (*slicep == -1)
141		slice = sc->sc_slice;
142	else
143		slice = *slicep;
144	autosync = gctl_get_paraml(req, "autosync", sizeof(*autosync));
145	if (autosync == NULL) {
146		gctl_error(req, "No '%s' argument.", "autosync");
147		return;
148	}
149	noautosync = gctl_get_paraml(req, "noautosync", sizeof(*noautosync));
150	if (noautosync == NULL) {
151		gctl_error(req, "No '%s' argument.", "noautosync");
152		return;
153	}
154	hardcode = gctl_get_paraml(req, "hardcode", sizeof(*hardcode));
155	if (hardcode == NULL) {
156		gctl_error(req, "No '%s' argument.", "hardcode");
157		return;
158	}
159	dynamic = gctl_get_paraml(req, "dynamic", sizeof(*dynamic));
160	if (dynamic == NULL) {
161		gctl_error(req, "No '%s' argument.", "dynamic");
162		return;
163	}
164	if (sc->sc_balance == balance && sc->sc_slice == slice && !*autosync &&
165	    !*noautosync && !*hardcode && !*dynamic) {
166		gctl_error(req, "Nothing has changed.");
167		return;
168	}
169	if (*autosync && *noautosync) {
170		gctl_error(req, "'%s' and '%s' specified.", "autosync",
171		    "noautosync");
172		return;
173	}
174	if (*hardcode && *dynamic) {
175		gctl_error(req, "'%s' and '%s' specified.", "hardcode",
176		    "dynamic");
177		return;
178	}
179	sc->sc_balance = balance;
180	sc->sc_slice = slice;
181	if ((sc->sc_flags & G_MIRROR_DEVICE_FLAG_NOAUTOSYNC) != 0) {
182		if (*autosync) {
183			sc->sc_flags &= ~G_MIRROR_DEVICE_FLAG_NOAUTOSYNC;
184			do_sync = 1;
185		}
186	} else {
187		if (*noautosync)
188			sc->sc_flags |= G_MIRROR_DEVICE_FLAG_NOAUTOSYNC;
189	}
190	LIST_FOREACH(disk, &sc->sc_disks, d_next) {
191		if (do_sync) {
192			if (disk->d_state == G_MIRROR_DISK_STATE_SYNCHRONIZING)
193				disk->d_flags &= ~G_MIRROR_DISK_FLAG_FORCE_SYNC;
194		}
195		if (*hardcode)
196			disk->d_flags |= G_MIRROR_DISK_FLAG_HARDCODED;
197		else if (*dynamic)
198			disk->d_flags &= ~G_MIRROR_DISK_FLAG_HARDCODED;
199		g_mirror_update_metadata(disk);
200		if (do_sync) {
201			if (disk->d_state == G_MIRROR_DISK_STATE_STALE) {
202				g_mirror_event_send(disk,
203				    G_MIRROR_DISK_STATE_DISCONNECTED,
204				    G_MIRROR_EVENT_DONTWAIT);
205			}
206		}
207	}
208}
209
210static void
211g_mirror_ctl_rebuild(struct gctl_req *req, struct g_class *mp)
212{
213	struct g_mirror_metadata md;
214	struct g_mirror_softc *sc;
215	struct g_mirror_disk *disk;
216	struct g_provider *pp;
217	const char *name;
218	char param[16];
219	int error, *nargs;
220	u_int i;
221
222	g_topology_assert();
223	nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
224	if (nargs == NULL) {
225		gctl_error(req, "No '%s' argument.", "nargs");
226		return;
227	}
228	if (*nargs < 2) {
229		gctl_error(req, "Too few arguments.");
230		return;
231	}
232	name = gctl_get_asciiparam(req, "arg0");
233	if (name == NULL) {
234		gctl_error(req, "No 'arg%u' argument.", 0);
235		return;
236	}
237	sc = g_mirror_find_device(mp, name);
238	if (sc == NULL) {
239		gctl_error(req, "No such device: %s.", name);
240		return;
241	}
242
243	for (i = 1; i < (u_int)*nargs; i++) {
244		snprintf(param, sizeof(param), "arg%u", i);
245		name = gctl_get_asciiparam(req, param);
246		if (name == NULL) {
247			gctl_error(req, "No 'arg%u' argument.", i);
248			continue;
249		}
250		disk = g_mirror_find_disk(sc, name);
251		if (disk == NULL) {
252			gctl_error(req, "No such provider: %s.", name);
253			continue;
254		}
255		if (g_mirror_ndisks(sc, G_MIRROR_DISK_STATE_ACTIVE) == 1 &&
256		    disk->d_state == G_MIRROR_DISK_STATE_ACTIVE) {
257			/*
258			 * This is the last active disk. There will be nothing
259			 * to rebuild it from, so deny this request.
260			 */
261			gctl_error(req,
262			    "Provider %s is the last active provider in %s.",
263			    name, sc->sc_geom->name);
264			return;
265		}
266		/*
267		 * Do rebuild by resetting syncid, disconnecting the disk and
268		 * connecting it again.
269		 */
270		disk->d_sync.ds_syncid = 0;
271		if ((sc->sc_flags & G_MIRROR_DEVICE_FLAG_NOAUTOSYNC) != 0)
272			disk->d_flags |= G_MIRROR_DISK_FLAG_FORCE_SYNC;
273		g_mirror_update_metadata(disk);
274		pp = disk->d_consumer->provider;
275		error = g_mirror_read_metadata(disk->d_consumer, &md);
276		g_mirror_event_send(disk, G_MIRROR_DISK_STATE_DISCONNECTED,
277		    G_MIRROR_EVENT_WAIT);
278		if (error != 0) {
279			gctl_error(req, "Cannot read metadata from %s.",
280			    pp->name);
281			continue;
282		}
283		error = g_mirror_add_disk(sc, pp, &md);
284		if (error != 0) {
285			gctl_error(req, "Cannot reconnect component %s.",
286			    pp->name);
287			continue;
288		}
289	}
290}
291
292static void
293g_mirror_ctl_insert(struct gctl_req *req, struct g_class *mp)
294{
295	struct g_mirror_softc *sc;
296	struct g_mirror_disk *disk;
297	struct g_mirror_metadata md;
298	struct g_provider *pp;
299	struct g_consumer *cp;
300	intmax_t *priority;
301	const char *name;
302	char param[16];
303	u_char *sector;
304	u_int i, n;
305	int error, *nargs, *hardcode, *inactive;
306	struct {
307		struct g_provider	*provider;
308		struct g_consumer	*consumer;
309	} *disks;
310
311	g_topology_assert();
312	nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
313	if (nargs == NULL) {
314		gctl_error(req, "No '%s' argument.", "nargs");
315		return;
316	}
317	if (*nargs < 2) {
318		gctl_error(req, "Too few arguments.");
319		return;
320	}
321	priority = gctl_get_paraml(req, "priority", sizeof(*priority));
322	if (priority == NULL) {
323		gctl_error(req, "No '%s' argument.", "priority");
324		return;
325	}
326	inactive = gctl_get_paraml(req, "inactive", sizeof(*inactive));
327	if (inactive == NULL) {
328		gctl_error(req, "No '%s' argument.", "inactive");
329		return;
330	}
331	hardcode = gctl_get_paraml(req, "hardcode", sizeof(*hardcode));
332	if (hardcode == NULL) {
333		gctl_error(req, "No '%s' argument.", "hardcode");
334		return;
335	}
336	name = gctl_get_asciiparam(req, "arg0");
337	if (name == NULL) {
338		gctl_error(req, "No 'arg%u' argument.", 0);
339		return;
340	}
341	sc = g_mirror_find_device(mp, name);
342	if (sc == NULL) {
343		gctl_error(req, "No such device: %s.", name);
344		return;
345	}
346	if (g_mirror_ndisks(sc, -1) < sc->sc_ndisks) {
347		gctl_error(req, "Not all disks connected.");
348		return;
349	}
350
351	disks = g_malloc(sizeof(*disks) * (*nargs), M_WAITOK | M_ZERO);
352	for (i = 1, n = 0; i < (u_int)*nargs; i++) {
353		snprintf(param, sizeof(param), "arg%u", i);
354		name = gctl_get_asciiparam(req, param);
355		if (name == NULL) {
356			gctl_error(req, "No 'arg%u' argument.", i);
357			continue;
358		}
359		if (strncmp(name, "/dev/", strlen("/dev/")) == 0)
360			name += strlen("/dev/");
361		if (g_mirror_find_disk(sc, name) != NULL) {
362			gctl_error(req, "Provider %s already inserted.", name);
363			continue;
364		}
365		pp = g_provider_by_name(name);
366		if (pp == NULL) {
367			gctl_error(req, "Unknown provider %s.", name);
368			continue;
369		}
370		if (sc->sc_provider->mediasize >
371		    pp->mediasize - pp->sectorsize) {
372			gctl_error(req, "Provider %s too small.", name);
373			continue;
374		}
375		if ((sc->sc_provider->sectorsize % pp->sectorsize) != 0) {
376			gctl_error(req, "Invalid sectorsize of provider %s.",
377			    name);
378			continue;
379		}
380		cp = g_new_consumer(sc->sc_geom);
381		if (g_attach(cp, pp) != 0) {
382			g_destroy_consumer(cp);
383			gctl_error(req, "Cannot attach to provider %s.", name);
384			continue;
385		}
386		if (g_access(cp, 0, 1, 1) != 0) {
387			g_detach(cp);
388			g_destroy_consumer(cp);
389			gctl_error(req, "Cannot access provider %s.", name);
390			continue;
391		}
392		disks[n].provider = pp;
393		disks[n].consumer = cp;
394		n++;
395	}
396	if (n == 0) {
397		g_free(disks);
398		return;
399	}
400	sc->sc_ndisks += n;
401again:
402	for (i = 0; i < n; i++) {
403		if (disks[i].consumer == NULL)
404			continue;
405		g_mirror_fill_metadata(sc, NULL, &md);
406		md.md_priority = *priority;
407		if (*inactive)
408			md.md_dflags |= G_MIRROR_DISK_FLAG_INACTIVE;
409		pp = disks[i].provider;
410		if (*hardcode) {
411			strlcpy(md.md_provider, pp->name,
412			    sizeof(md.md_provider));
413		} else {
414			bzero(md.md_provider, sizeof(md.md_provider));
415		}
416		sector = g_malloc(pp->sectorsize, M_WAITOK);
417		mirror_metadata_encode(&md, sector);
418		error = g_write_data(disks[i].consumer,
419		    pp->mediasize - pp->sectorsize, sector, pp->sectorsize);
420		g_free(sector);
421		if (error != 0) {
422			gctl_error(req, "Cannot store metadata on %s.",
423			    pp->name);
424			g_access(disks[i].consumer, 0, -1, -1);
425			g_detach(disks[i].consumer);
426			g_destroy_consumer(disks[i].consumer);
427			disks[i].consumer = NULL;
428			disks[i].provider = NULL;
429			sc->sc_ndisks--;
430			goto again;
431		}
432	}
433	if (i == 0) {
434		/* All writes failed. */
435		g_free(disks);
436		return;
437	}
438	LIST_FOREACH(disk, &sc->sc_disks, d_next) {
439		g_mirror_update_metadata(disk);
440	}
441	/*
442	 * Release provider and wait for retaste.
443	 */
444	for (i = 0; i < n; i++) {
445		if (disks[i].consumer == NULL)
446			continue;
447		g_access(disks[i].consumer, 0, -1, -1);
448		g_detach(disks[i].consumer);
449		g_destroy_consumer(disks[i].consumer);
450	}
451	g_free(disks);
452}
453
454static void
455g_mirror_ctl_remove(struct gctl_req *req, struct g_class *mp)
456{
457	struct g_mirror_softc *sc;
458	struct g_mirror_disk *disk;
459	const char *name;
460	char param[16];
461	int *nargs;
462	u_int i;
463
464	g_topology_assert();
465	nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
466	if (nargs == NULL) {
467		gctl_error(req, "No '%s' argument.", "nargs");
468		return;
469	}
470	if (*nargs < 2) {
471		gctl_error(req, "Too few arguments.");
472		return;
473	}
474	name = gctl_get_asciiparam(req, "arg0");
475	if (name == NULL) {
476		gctl_error(req, "No 'arg%u' argument.", 0);
477		return;
478	}
479	sc = g_mirror_find_device(mp, name);
480	if (sc == NULL) {
481		gctl_error(req, "No such device: %s.", name);
482		return;
483	}
484	if (g_mirror_ndisks(sc, -1) < sc->sc_ndisks) {
485		gctl_error(req, "Not all disks connected.");
486		return;
487	}
488
489	for (i = 1; i < (u_int)*nargs; i++) {
490		snprintf(param, sizeof(param), "arg%u", i);
491		name = gctl_get_asciiparam(req, param);
492		if (name == NULL) {
493			gctl_error(req, "No 'arg%u' argument.", i);
494			continue;
495		}
496		disk = g_mirror_find_disk(sc, name);
497		if (disk == NULL) {
498			gctl_error(req, "No such provider: %s.", name);
499			continue;
500		}
501		g_mirror_event_send(disk, G_MIRROR_DISK_STATE_DESTROY,
502		    G_MIRROR_EVENT_WAIT);
503	}
504}
505
506static void
507g_mirror_ctl_deactivate(struct gctl_req *req, struct g_class *mp)
508{
509	struct g_mirror_softc *sc;
510	struct g_mirror_disk *disk;
511	const char *name;
512	char param[16];
513	int *nargs;
514	u_int i;
515
516	g_topology_assert();
517	nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
518	if (nargs == NULL) {
519		gctl_error(req, "No '%s' argument.", "nargs");
520		return;
521	}
522	if (*nargs < 2) {
523		gctl_error(req, "Too few arguments.");
524		return;
525	}
526	name = gctl_get_asciiparam(req, "arg0");
527	if (name == NULL) {
528		gctl_error(req, "No 'arg%u' argument.", 0);
529		return;
530	}
531	sc = g_mirror_find_device(mp, name);
532	if (sc == NULL) {
533		gctl_error(req, "No such device: %s.", name);
534		return;
535	}
536
537	for (i = 1; i < (u_int)*nargs; i++) {
538		snprintf(param, sizeof(param), "arg%u", i);
539		name = gctl_get_asciiparam(req, param);
540		if (name == NULL) {
541			gctl_error(req, "No 'arg%u' argument.", i);
542			continue;
543		}
544		disk = g_mirror_find_disk(sc, name);
545		if (disk == NULL) {
546			gctl_error(req, "No such provider: %s.", name);
547			continue;
548		}
549		disk->d_flags |= G_MIRROR_DISK_FLAG_INACTIVE;
550		disk->d_flags &= ~G_MIRROR_DISK_FLAG_FORCE_SYNC;
551		g_mirror_update_metadata(disk);
552		sc->sc_bump_id |= G_MIRROR_BUMP_SYNCID;
553		g_mirror_event_send(disk, G_MIRROR_DISK_STATE_DISCONNECTED,
554		    G_MIRROR_EVENT_WAIT);
555	}
556}
557
558static void
559g_mirror_ctl_forget(struct gctl_req *req, struct g_class *mp)
560{
561	struct g_mirror_softc *sc;
562	struct g_mirror_disk *disk;
563	const char *name;
564	char param[16];
565	int *nargs;
566	u_int i;
567
568	g_topology_assert();
569	nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
570	if (nargs == NULL) {
571		gctl_error(req, "No '%s' argument.", "nargs");
572		return;
573	}
574	if (*nargs < 1) {
575		gctl_error(req, "Missing device(s).");
576		return;
577	}
578
579	for (i = 0; i < (u_int)*nargs; i++) {
580		snprintf(param, sizeof(param), "arg%u", i);
581		name = gctl_get_asciiparam(req, param);
582		if (name == NULL) {
583			gctl_error(req, "No 'arg%u' argument.", i);
584			return;
585		}
586		sc = g_mirror_find_device(mp, name);
587		if (sc == NULL) {
588			gctl_error(req, "No such device: %s.", name);
589			return;
590		}
591		if (g_mirror_ndisks(sc, -1) == sc->sc_ndisks) {
592			G_MIRROR_DEBUG(1,
593			    "All disks connected in %s, skipping.",
594			    sc->sc_name);
595			continue;
596		}
597		sc->sc_ndisks = g_mirror_ndisks(sc, -1);
598		LIST_FOREACH(disk, &sc->sc_disks, d_next) {
599			g_mirror_update_metadata(disk);
600		}
601	}
602}
603
604static void
605g_mirror_ctl_stop(struct gctl_req *req, struct g_class *mp)
606{
607	struct g_mirror_softc *sc;
608	int *force, *nargs, error;
609	const char *name;
610	char param[16];
611	u_int i;
612
613	g_topology_assert();
614
615	nargs = gctl_get_paraml(req, "nargs", sizeof(*nargs));
616	if (nargs == NULL) {
617		gctl_error(req, "No '%s' argument.", "nargs");
618		return;
619	}
620	if (*nargs < 1) {
621		gctl_error(req, "Missing device(s).");
622		return;
623	}
624	force = gctl_get_paraml(req, "force", sizeof(*force));
625	if (force == NULL) {
626		gctl_error(req, "No '%s' argument.", "force");
627		return;
628	}
629
630	for (i = 0; i < (u_int)*nargs; i++) {
631		snprintf(param, sizeof(param), "arg%u", i);
632		name = gctl_get_asciiparam(req, param);
633		if (name == NULL) {
634			gctl_error(req, "No 'arg%u' argument.", i);
635			return;
636		}
637		sc = g_mirror_find_device(mp, name);
638		if (sc == NULL) {
639			gctl_error(req, "No such device: %s.", name);
640			return;
641		}
642		error = g_mirror_destroy(sc, *force);
643		if (error != 0) {
644			gctl_error(req, "Cannot destroy device %s (error=%d).",
645			    sc->sc_geom->name, error);
646			return;
647		}
648	}
649}
650
651void
652g_mirror_config(struct gctl_req *req, struct g_class *mp, const char *verb)
653{
654	uint32_t *version;
655
656	g_topology_assert();
657
658	version = gctl_get_paraml(req, "version", sizeof(*version));
659	if (version == NULL) {
660		gctl_error(req, "No '%s' argument.", "version");
661		return;
662	}
663	if (*version != G_MIRROR_VERSION) {
664		gctl_error(req, "Userland and kernel parts are out of sync.");
665		return;
666	}
667
668	if (strcmp(verb, "configure") == 0)
669		g_mirror_ctl_configure(req, mp);
670	else if (strcmp(verb, "rebuild") == 0)
671		g_mirror_ctl_rebuild(req, mp);
672	else if (strcmp(verb, "insert") == 0)
673		g_mirror_ctl_insert(req, mp);
674	else if (strcmp(verb, "remove") == 0)
675		g_mirror_ctl_remove(req, mp);
676	else if (strcmp(verb, "deactivate") == 0)
677		g_mirror_ctl_deactivate(req, mp);
678	else if (strcmp(verb, "forget") == 0)
679		g_mirror_ctl_forget(req, mp);
680	else if (strcmp(verb, "stop") == 0)
681		g_mirror_ctl_stop(req, mp);
682	else
683		gctl_error(req, "Unknown verb.");
684}
685