1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26 #pragma ident "%Z%%M% %I% %E% SMI"
27
28 /*
29 * Metadevice database utility.
30 */
31
32 #include <meta.h>
33 #define MDDB
34 #include <sys/lvm/md_mddb.h>
35 #include <sdssc.h>
36
37 enum mddb_cmd {none, attach, detach, patch, infolong, infoshort};
38
39 extern int procsigs(int block, sigset_t *oldsigs, md_error_t *ep);
40
41 static void
usage(mdsetname_t * sp,char * string)42 usage(
43 mdsetname_t *sp,
44 char *string
45 )
46 {
47 if ((string != NULL) && (*string != '\0'))
48 md_eprintf("%s\n", string);
49
50 (void) fprintf(stderr, gettext(
51 "usage: %s [-s setname] -a [options] mddbnnn\n"
52 " %s [-s setname] -a [options] device ...\n"
53 " %s [-s setname] -d [options] mddbnnn\n"
54 " %s [-s setname] -d [options] device ...\n"
55 " %s [-s setname] -i \n"
56 " %s -p [options] [ mddb.cf-file ]\n"
57 "options:\n"
58 "-c count number of replicas (for use with -a only)\n"
59 "-f force adding or deleting of replicas\n"
60 "-k filename alternate /etc/system file\n"
61 "-l length specify size of replica (for use with -a only)\n"),
62 myname, myname, myname, myname, myname, myname);
63
64 md_exit(sp, (string == NULL) ? 0 : 1);
65 }
66
67 static mdname_t *
make_dbname(mdsetname_t * sp,mdnamelist_t ** nlp,char * name,md_error_t * ep)68 make_dbname(
69 mdsetname_t *sp,
70 mdnamelist_t **nlp,
71 char *name,
72 md_error_t *ep
73 )
74 {
75 mdname_t *np;
76
77 if ((np = metaname(&sp, name, LOGICAL_DEVICE, ep)) == NULL)
78 return (NULL);
79
80 return (metanamelist_append(nlp, np));
81 }
82
83 static mdnamelist_t *
get_dbnames_fromfile(mdsetname_t * sp,mdnamelist_t ** nlp,char * tabname,int * dbsize,int * dbcnt,int * default_size,md_error_t * ep)84 get_dbnames_fromfile(
85 mdsetname_t *sp,
86 mdnamelist_t **nlp,
87 char *tabname,
88 int *dbsize,
89 int *dbcnt,
90 int *default_size,
91 md_error_t *ep
92 )
93 {
94 md_tab_t *tabp = NULL;
95 md_tab_line_t *linep = NULL;
96 int argc;
97 char **argv;
98 char *context;
99 int save = optind;
100 int c;
101
102 /* look in md.tab */
103 if ((tabp = meta_tab_parse(NULL, ep)) == NULL) {
104 if (! mdissyserror(ep, ENOENT))
105 mde_perror(ep, "");
106 mdclrerror(ep);
107 return (NULL);
108 }
109
110 if ((linep = meta_tab_find(sp, tabp, tabname, TAB_MDDB)) == NULL) {
111 (void) mdsyserror(ep, ENOENT, tabname);
112 goto out;
113 }
114 argc = linep->argc;
115 argv = linep->argv;
116 context = linep->context;
117
118 /* parse up entry */
119 optind = 1;
120 opterr = 1;
121 while ((c = getopt(argc, argv, "c:l:")) != -1) {
122 switch (c) {
123 case 'c':
124 if (sscanf(optarg, "%d", dbcnt) != 1) {
125 md_eprintf("%s: %s\n",
126 context, gettext("bad format"));
127 usage(sp, "");
128 }
129 break;
130
131 case 'l':
132 if (sscanf(optarg, "%d", dbsize) != 1) {
133 md_eprintf("%s: %s\n",
134 context, gettext("bad format"));
135 usage(sp, "");
136 }
137 *default_size = FALSE;
138 break;
139
140 default:
141 usage(sp, "");
142 }
143 }
144 argc -= optind;
145 argv += optind;
146 for (; (argc > 0); --argc, ++argv) {
147 char *token = argv[0];
148
149 if (make_dbname(sp, nlp, token, ep) == NULL) {
150 metafreenamelist(*nlp);
151 *nlp = NULL;
152 goto out;
153 }
154 }
155
156 /* cleanup, return list */
157 out:
158 if (tabp != NULL)
159 meta_tab_free(tabp);
160 optind = save;
161 return (*nlp);
162 }
163
164 /*
165 * built list of all devices which are to be detached
166 */
167 static mdnamelist_t *
build_a_namelist(mdsetname_t * sp,int argc,char ** argv,md_error_t * ep)168 build_a_namelist(
169 mdsetname_t *sp,
170 int argc,
171 char **argv,
172 md_error_t *ep
173 )
174 {
175 int i;
176 int dbsize, dbcnt, default_size;
177 mdnamelist_t *dbnlp = NULL;
178
179 for (i = 0; i < argc; i++) {
180 if (strncmp(argv[i], "mddb", 4) == 0) {
181 if (get_dbnames_fromfile(sp, &dbnlp, argv[i],
182 &dbsize, &dbcnt, &default_size, ep) == NULL) {
183 /* don't freelist here - already been done */
184 return (NULL);
185 }
186 continue;
187 }
188 if (make_dbname(sp, &dbnlp, argv[i], ep) == NULL) {
189 metafreenamelist(dbnlp);
190 return (NULL);
191 }
192 }
193
194 return (dbnlp);
195 }
196
197
198 /*
199 * built the next list of devices which are to be attached
200 * that have the same size and count of replicas.
201 */
202 static mdnamelist_t *
build_next_namelist(mdsetname_t * sp,int argc,char ** argv,int * arg_index,int * dbsize,int * dbcnt,int * default_size,md_error_t * ep)203 build_next_namelist(
204 mdsetname_t *sp,
205 int argc,
206 char **argv,
207 int *arg_index,
208 int *dbsize,
209 int *dbcnt,
210 int *default_size,
211 md_error_t *ep
212 )
213 {
214 int i;
215 mdnamelist_t *dbnlp = NULL;
216
217 for (i = *arg_index; i < argc; i++) {
218 if (strncmp(argv[i], "mddb", 4) == 0) {
219 /*
220 * If we have stuff in the namelist
221 * return it before processing the mddb entry.
222 */
223 if (dbnlp) {
224 *arg_index = i;
225 return (dbnlp);
226 }
227 if (get_dbnames_fromfile(sp, &dbnlp, argv[i],
228 dbsize, dbcnt, default_size, ep) == NULL) {
229 /* don't freelist here - already been done */
230 return (NULL);
231 }
232 *arg_index = i + 1;
233 return (dbnlp);
234 }
235 if (make_dbname(sp, &dbnlp, argv[i], ep) == NULL) {
236 metafreenamelist(dbnlp);
237 return (NULL);
238 }
239 }
240 *arg_index = argc;
241 return (dbnlp);
242 }
243
244
245 static int
chngdb(mdsetname_t * sp,enum mddb_cmd cmd,int argc,char * argv[],uint_t options,md_error_t * ep)246 chngdb(
247 mdsetname_t *sp,
248 enum mddb_cmd cmd,
249 int argc,
250 char *argv[],
251 uint_t options,
252 md_error_t *ep
253 )
254 {
255 int c;
256 int i;
257 md_error_t xep = mdnullerror;
258 mdnamelist_t *dbnlp = NULL;
259 int dbsize = MD_DBSIZE;
260 int maxblks = MDDB_MAXBLKS;
261 int minblks = MDDB_MINBLKS;
262 int dbcnt = 1;
263 mdforceopts_t force = MDFORCE_NONE;
264 int rval = 0;
265 char *sysfilename = NULL;
266 int default_size = TRUE;
267 md_set_desc *sd;
268 md_setkey_t *cl_sk;
269 md_mnnode_desc *nd;
270 int suspend1_flag = 0;
271
272 /* reset and parse args */
273 optind = 1;
274 opterr = 1;
275 while ((c = getopt(argc, argv, "ac:dfk:pl:s:")) != -1) {
276 switch (c) {
277 case 'a':
278 break;
279 case 'c':
280 if (sscanf(optarg, "%d", &dbcnt) != 1) {
281 md_eprintf("%s: %s\n",
282 optarg, gettext("bad format"));
283 usage(sp, "");
284 }
285 break;
286 case 'd':
287 break;
288 case 'f':
289 force = MDFORCE_LOCAL;
290 break;
291 case 'k':
292 sysfilename = optarg;
293 break;
294 case 'l':
295 if (sscanf(optarg, "%d", &dbsize) != 1) {
296 md_eprintf("%s: %s\n",
297 optarg, gettext("bad format"));
298 usage(sp, "");
299 }
300 default_size = FALSE;
301 break;
302 case 'p':
303 break;
304 case 's':
305 break;
306 default:
307 usage(sp, "");
308 }
309 }
310
311 /*
312 * If it is a multinode diskset, use appropriate metadb size.
313 */
314 if (! metaislocalset(sp)) {
315 if ((sd = metaget_setdesc(sp, ep)) == NULL)
316 return (-1);
317
318 if (MD_MNSET_DESC(sd)) {
319 maxblks = MDDB_MN_MAXBLKS;
320 minblks = MDDB_MN_MINBLKS;
321 if (default_size)
322 dbsize = MD_MN_DBSIZE;
323 }
324 }
325
326 if (dbsize > maxblks)
327 usage(sp, gettext("size (-l) is too big"));
328
329
330 if (dbsize < minblks)
331 usage(sp, gettext("size (-l) is too small"));
332
333 if (dbcnt < 1)
334 usage(sp, gettext(
335 "count (-c) must be 1 or more"));
336
337
338 argc -= optind;
339 argv += optind;
340 if (argc <= 0) {
341 usage(sp, gettext(
342 "no devices specified to attach or detach"));
343 }
344
345 if (! metaislocalset(sp)) {
346
347 if (MD_MNSET_DESC(sd)) {
348 md_error_t xep = mdnullerror;
349 sigset_t sigs;
350
351 /* Make sure we are blocking all signals */
352 if (procsigs(TRUE, &sigs, &xep) < 0)
353 mdclrerror(&xep);
354
355 /*
356 * Lock out other metaset or metadb commands
357 * across the diskset.
358 */
359 nd = sd->sd_nodelist;
360 while (nd) {
361 if ((force & MDFORCE_LOCAL) &&
362 strcmp(nd->nd_nodename, mynode()) != 0) {
363 nd = nd->nd_next;
364 continue;
365 }
366
367 if (!(nd->nd_flags & MD_MN_NODE_ALIVE)) {
368 nd = nd->nd_next;
369 continue;
370 }
371
372 if (clnt_lock_set(nd->nd_nodename, sp, ep)) {
373 rval = -1;
374 goto done;
375 }
376 nd = nd->nd_next;
377 }
378 /*
379 * Lock out other meta* commands by suspending
380 * class 1 messages across the diskset.
381 */
382 nd = sd->sd_nodelist;
383 while (nd) {
384 if (!(nd->nd_flags & MD_MN_NODE_ALIVE)) {
385 nd = nd->nd_next;
386 continue;
387 }
388
389 if (clnt_mdcommdctl(nd->nd_nodename,
390 COMMDCTL_SUSPEND, sp, MD_MSG_CLASS1,
391 MD_MSCF_NO_FLAGS, ep)) {
392 rval = -1;
393 goto done;
394 }
395 suspend1_flag = 1;
396 nd = nd->nd_next;
397 }
398 } else {
399 /* Lock the set on current set members */
400 for (i = 0; i < MD_MAXSIDES; i++) {
401 /* Skip empty slots */
402 if (sd->sd_nodes[i][0] == '\0')
403 continue;
404
405 if ((force & MDFORCE_LOCAL) &&
406 strcmp(sd->sd_nodes[i], mynode()) != 0)
407 continue;
408
409 if (clnt_lock_set(sd->sd_nodes[i], sp, ep)) {
410 rval = -1;
411 goto done;
412 }
413 }
414 }
415
416 force |= MDFORCE_SET_LOCKED;
417 options |= MDCHK_SET_LOCKED;
418 }
419
420 if (cmd == detach) {
421 if ((dbnlp = build_a_namelist(sp, argc, argv, ep)) == NULL) {
422 rval = -1;
423 goto done;
424 }
425
426 rval = meta_db_detach(sp, dbnlp, force, sysfilename, ep);
427
428 metafreenamelist(dbnlp);
429 }
430
431 if (cmd == attach) {
432 daddr_t nblks = 0;
433 int arg_index = 0;
434 int saved_dbsize = dbsize;
435 int saved_dbcnt = dbcnt;
436 int saved_default_size = default_size;
437
438 if (force & MDFORCE_LOCAL)
439 options |= MDCHK_SET_FORCE;
440
441 if (default_size)
442 if ((nblks = meta_db_minreplica(sp, ep)) < 0)
443 mdclrerror(ep);
444 /*
445 * Loop through build a new namelist
446 * for each "mddb" entry or the devices list
447 * on the command line. This allows each "mddb"
448 * entry to have unique dbsize and dbcnt.
449 */
450 while (arg_index < argc) {
451
452 dbnlp = build_next_namelist(sp, argc, argv,
453 &arg_index, &dbsize, &dbcnt, &default_size, ep);
454 if (dbnlp == NULL) {
455 rval = -1;
456 goto done;
457 }
458 /*
459 * If using the default size,
460 * then let's adjust the default to the minimum
461 * size currently in use.
462 */
463 if (default_size && (nblks > 0))
464 dbsize = nblks; /* adjust replica size */
465
466 if (dbsize > maxblks)
467 usage(sp, gettext("size (-l) is too big"));
468
469 rval = meta_db_attach(sp, dbnlp, options, NULL, dbcnt,
470 dbsize, sysfilename, ep);
471 if (rval) {
472 metafreenamelist(dbnlp);
473 break;
474 }
475 dbsize = saved_dbsize;
476 dbcnt = saved_dbcnt;
477 default_size = saved_default_size;
478
479 metafreenamelist(dbnlp);
480 }
481 }
482
483 done:
484 if (! metaislocalset(sp)) {
485 cl_sk = cl_get_setkey(sp->setno, sp->setname);
486 if (MD_MNSET_DESC(sd)) {
487 /*
488 * Unlock diskset by resuming
489 * class 1 messages across the diskset.
490 */
491 if (suspend1_flag) {
492 nd = sd->sd_nodelist;
493 while (nd) {
494 if (!(nd->nd_flags &
495 MD_MN_NODE_ALIVE)) {
496 nd = nd->nd_next;
497 continue;
498 }
499
500 if (clnt_mdcommdctl(nd->nd_nodename,
501 COMMDCTL_RESUME, sp,
502 MD_MSG_CLASS1,
503 MD_MSCF_NO_FLAGS, &xep)) {
504 mde_perror(&xep, "");
505 mdclrerror(&xep);
506 }
507 nd = nd->nd_next;
508 }
509 }
510 nd = sd->sd_nodelist;
511 while (nd) {
512 if ((force & MDFORCE_LOCAL) &&
513 strcmp(nd->nd_nodename, mynode()) != 0) {
514 nd = nd->nd_next;
515 continue;
516 }
517 if (!(nd->nd_flags & MD_MN_NODE_ALIVE)) {
518 nd = nd->nd_next;
519 continue;
520 }
521
522 if (clnt_unlock_set(nd->nd_nodename, cl_sk,
523 &xep))
524 mdclrerror(&xep);
525 nd = nd->nd_next;
526 }
527 } else {
528 for (i = 0; i < MD_MAXSIDES; i++) {
529 /* Skip empty slots */
530 if (sd->sd_nodes[i][0] == '\0')
531 continue;
532
533 if ((force & MDFORCE_LOCAL) &&
534 strcmp(sd->sd_nodes[i], mynode()) != 0)
535 continue;
536
537 if (clnt_unlock_set(sd->sd_nodes[i], cl_sk,
538 &xep))
539 mdclrerror(&xep);
540 }
541 }
542 cl_set_setkey(NULL);
543 }
544
545 return (rval);
546 }
547
548 static int
info(mdsetname_t * sp,enum mddb_cmd cmd,int print_headers,int print_footers,md_error_t * ep)549 info(
550 mdsetname_t *sp,
551 enum mddb_cmd cmd,
552 int print_headers,
553 int print_footers,
554 md_error_t *ep
555 )
556 {
557 md_replicalist_t *rlp = NULL;
558 md_replicalist_t *rl;
559 md_replica_t *r;
560 int i;
561 char *unk_str = NULL;
562
563 /* get list of replicas, quit if none */
564 if (metareplicalist(sp, (MD_BASICNAME_OK | PRINT_FAST), &rlp, ep) < 0)
565 return (-1);
566 else if (rlp == NULL)
567 return (0);
568
569 if (print_headers) {
570 (void) printf("\t%5.5s\t\t%9.9s\t%11.11s\n", gettext("flags"),
571 gettext("first blk"), gettext("block count"));
572 }
573
574 unk_str = gettext("unknown");
575 for (rl = rlp; rl != NULL; rl = rl->rl_next) {
576 r = rl->rl_repp;
577
578 for (i = 0; i < MDDB_FLAGS_LEN; i++) {
579 if (r->r_flags & (1 << i))
580 (void) putchar(MDDB_FLAGS_STRING[i]);
581 else
582 (void) putchar(' ');
583 }
584
585 if ((r->r_blkno == -1) && (r->r_nblk == -1)) {
586 (void) printf("\t%7.7s\t\t%7.7s\t", unk_str, unk_str);
587 } else if (r->r_nblk == -1) {
588 (void) printf("\t%ld\t\t%7.7s\t", r->r_blkno, unk_str);
589 } else {
590 (void) printf("\t%ld\t\t%ld\t", r->r_blkno, r->r_nblk);
591 }
592
593 (void) printf("\t%s\n", r->r_namep->bname);
594
595 }
596
597 metafreereplicalist(rlp);
598
599 if (cmd == infoshort)
600 return (0);
601
602 if (!print_footers)
603 return (0);
604
605 (void) printf(gettext(
606 " r - replica does not have device relocation information\n"
607 " o - replica active prior to last mddb configuration change\n"
608 " u - replica is up to date\n"
609 " l - locator for this replica was read successfully\n"
610 " c - replica's location was in %s\n"
611 " p - replica's location was patched in kernel\n"
612 " m - replica is master, this is replica selected as input\n"
613 " t - tagged data is associated with the replica\n"
614 " W - replica has device write errors\n"
615 " a - replica is active, commits are occurring to this replica\n"
616 " M - replica had problem with master blocks\n"
617 " D - replica had problem with data blocks\n"
618 " F - replica had format problems\n"
619 " S - replica is too small to hold current data base\n"
620 " R - replica had device read errors\n"
621 " B - tagged data associated with the replica is not valid\n"),
622 META_DBCONF);
623 return (0);
624 }
625
626 int
main(int argc,char ** argv)627 main(int argc, char **argv)
628 {
629 mdsetname_t *sp = NULL;
630 int c;
631 enum mddb_cmd cmd = none;
632 char *sname = MD_LOCAL_NAME;
633 char *cffilename = NULL;
634 char *sysfilename = NULL;
635 int forceflg = FALSE;
636 mdchkopts_t options = 0;
637 md_error_t status = mdnullerror;
638 md_error_t *ep = &status;
639 int error;
640 md_set_desc *sd;
641 int multi_node = 0;
642
643 /*
644 * Get the locale set up before calling any other routines
645 * with messages to ouput. Just in case we're not in a build
646 * environment, make sure that TEXT_DOMAIN gets set to
647 * something.
648 */
649 #if !defined(TEXT_DOMAIN)
650 #define TEXT_DOMAIN "SYS_TEST"
651 #endif
652 (void) setlocale(LC_ALL, "");
653 (void) textdomain(TEXT_DOMAIN);
654
655 if (sdssc_bind_library() == SDSSC_OKAY)
656 if (sdssc_cmd_proxy(argc, argv, SDSSC_PROXY_PRIMARY,
657 &error) == SDSSC_PROXY_DONE)
658 exit(error);
659
660 /* parse args */
661 optind = 1;
662 opterr = 1;
663
664 /* initialize */
665 if (md_init(argc, argv, 0, 1, ep) != 0) {
666 mde_perror(ep, "");
667 md_exit(sp, 1);
668 }
669
670 /* parse args */
671 optind = 1;
672 opterr = 1;
673 while ((c = getopt(argc, argv, "ac:dfhik:l:ps:?")) != -1) {
674 switch (c) {
675 case 'a':
676 cmd = attach;
677 break;
678 case 'c':
679 break;
680 case 'd':
681 cmd = detach;
682 break;
683 case 'f':
684 forceflg = TRUE;
685 break;
686 case 'h':
687 usage(sp, (char *)0);
688 break;
689 case 'i':
690 cmd = infolong;
691 break;
692 case 'k':
693 sysfilename = optarg;
694 break;
695 case 'l':
696 break;
697 case 'p':
698 cmd = patch;
699 break;
700 case 's':
701 sname = optarg;
702 break;
703
704 case '?':
705 if (optopt == '?')
706 usage(sp, NULL);
707 /*FALLTHROUGH*/
708 default:
709 usage(sp, gettext("unknown command"));
710 }
711 }
712 if (cmd == none)
713 cmd = infoshort;
714
715 /* get set context */
716 if ((sp = metasetname(sname, ep)) == NULL) {
717 mde_perror(ep, "");
718 md_exit(sp, 1);
719 }
720
721 /* print status */
722 if (cmd == infoshort || cmd == infolong) {
723 if (optind != argc)
724 usage(sp, gettext(
725 "too many arguments"));
726
727 if (info(sp, cmd, 1, 1, ep)) {
728 mde_perror(ep, "");
729 md_exit(sp, 1);
730 }
731
732 if (meta_smf_isonline(meta_smf_getmask(), ep) == 0) {
733 mde_perror(ep, "");
734 md_exit(sp, 1);
735 }
736
737 md_exit(sp, 0);
738 }
739
740 if (meta_check_root(ep) != 0) {
741 mde_perror(ep, "");
742 md_exit(sp, 1);
743 }
744
745 if (! metaislocalset(sp)) {
746 if ((sd = metaget_setdesc(sp, ep)) == NULL) {
747 mde_perror(ep, "");
748 md_exit(sp, 1);
749 }
750 if (MD_MNSET_DESC(sd)) {
751 multi_node = 1;
752 }
753 }
754
755 /*
756 * Adjust lock for traditional and local diskset.
757 *
758 * A MN diskset does not use the set meta_lock but instead
759 * uses the clnt_lock of rpc.metad and the suspend/resume
760 * feature of the rpc.mdcommd. Can't use set meta_lock since
761 * class 1 messages are grabbing this lock and if this thread
762 * is holding the set meta_lock then no rpc.mdcommd suspend
763 * can occur.
764 */
765 if ((!multi_node) && (meta_lock(sp, TRUE, ep) != 0)) {
766 mde_perror(ep, "");
767 md_exit(sp, 1);
768 }
769
770 /* check for ownership */
771 if (meta_check_ownership(sp, ep) != 0) {
772 mde_perror(ep, "");
773 md_exit(sp, 1);
774 }
775
776 /* snarf MDDB locations */
777 if (cmd != patch) {
778 if (meta_setup_db_locations(ep) != 0) {
779 if (! mdismddberror(ep, MDE_DB_STALE)) {
780 if (forceflg == FALSE) {
781 mde_perror(ep, "");
782 md_exit(sp, 1);
783 }
784 options = MDCHK_ALLOW_NODBS;
785 }
786 mdclrerror(ep);
787 }
788 }
789
790 /* patch MDDB locations */
791 if (cmd == patch) {
792 if (optind < (argc - 1)) {
793 usage(sp, gettext(
794 "too many arguments to -p"));
795 }
796
797 if (optind == (argc - 1))
798 cffilename = argv[optind];
799
800 if (metaislocalset(sp)) {
801 if (meta_db_patch(sysfilename, cffilename, 1, ep)) {
802 mde_perror(ep, "");
803 md_exit(sp, 1);
804 }
805 }
806 }
807
808 /* add/delete replicas */
809 if (cmd == attach || cmd == detach) {
810 if (chngdb(sp, cmd, argc, argv, options, ep)) {
811 mde_perror(ep, "");
812 md_exit(sp, 1);
813 }
814 }
815
816 md_exit(sp, 0);
817 /*NOTREACHED*/
818 return (0);
819 }
820