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$"); 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 48 static struct g_mirror_softc * 49 g_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 69 static struct g_mirror_disk * 70 g_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 86 static void 87 g_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 210 static void 211 g_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 292 static void 293 g_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; 401 again: 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 454 static void 455 g_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 506 static void 507 g_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 558 static void 559 g_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 604 static void 605 g_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 651 void 652 g_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