2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License, Version 1.0 only
2N/A * (the "License"). You may not use this file except in compliance
2N/A * with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
2N/A * Use is subject to license terms.
2N/A */
2N/A
2N/A#pragma ident "%Z%%M% %I% %E% SMI"
2N/A
2N/A/*
2N/A * Metadevice diskset interfaces
2N/A */
2N/A
2N/A#include "meta_set_prv.h"
2N/A#include <sys/lvm/md_crc.h>
2N/A#include <sys/lvm/mdmed.h>
2N/A
2N/A#include <sys/sysevent/eventdefs.h>
2N/A#include <sys/sysevent/svm.h>
2N/A
2N/A#define MALSIZ 32
2N/A
2N/Astatic int
2N/Aadd_lst(char ***listp, char *item)
2N/A{
2N/A int i, j;
2N/A
2N/A if (*listp) {
2N/A for (i = 0; (*listp)[i]; i++)
2N/A /* void */;
2N/A } else {
2N/A *listp = (char **)Zalloc(MALSIZ * sizeof (char *));
2N/A i = 0;
2N/A }
2N/A
2N/A (*listp)[i] = Strdup(item);
2N/A
2N/A if ((++i % MALSIZ) == 0) {
2N/A *listp = (char **)Realloc((void *)*listp,
2N/A (i + MALSIZ) * sizeof (char *));
2N/A for (j = i; j < (i + MALSIZ); j++)
2N/A (*listp)[j] = (char *)NULL;
2N/A }
2N/A return (i);
2N/A}
2N/A
2N/Astatic int
2N/Adel_lst(char ***listp)
2N/A{
2N/A int i;
2N/A
2N/A if (*listp) {
2N/A for (i = 0; (*listp)[i]; i++)
2N/A free((*listp)[i]);
2N/A free(*listp);
2N/A *listp = NULL;
2N/A return (1);
2N/A } else
2N/A return (0);
2N/A}
2N/A
2N/A
2N/Astatic int
2N/Avalidate_med_nodes(
2N/A mdsetname_t *sp,
2N/A md_h_arr_t *mhp,
2N/A md_error_t *ep
2N/A)
2N/A{
2N/A char *hostname;
2N/A char *nodename;
2N/A char *nm;
2N/A char *cp;
2N/A int i, j;
2N/A
2N/A
2N/A for (i = 0; i < MED_MAX_HOSTS; i++) {
2N/A if (mhp->n_lst[i].a_cnt == 0)
2N/A continue;
2N/A
2N/A for (j = 0; j < mhp->n_lst[i].a_cnt; j++) {
2N/A nm = mhp->n_lst[i].a_nm[j];
2N/A
2N/A for (cp = nm; *cp; cp++)
2N/A if (!isprint(*cp) ||
2N/A strchr(INVALID_IN_NAMES, *cp) != NULL)
2N/A return (mddserror(ep,
2N/A MDE_DS_INVALIDMEDNAME,
2N/A sp->setno, nm, NULL, sp->setname));
2N/A
2N/A if (clnt_med_hostname(nm, &hostname, ep))
2N/A return (-1);
2N/A
2N/A if (j == 0) {
2N/A if (strcmp(nm, hostname) != 0) {
2N/A Free(hostname);
2N/A return (mddserror(ep,
2N/A MDE_DS_NOTNODENAME, sp->setno, nm,
2N/A NULL, sp->setname));
2N/A }
2N/A nodename = nm;
2N/A } else {
2N/A if (strcmp(nodename, hostname) != 0) {
2N/A Free(hostname);
2N/A return (mddserror(ep,
2N/A MDE_DS_ALIASNOMATCH, sp->setno, nm,
2N/A nodename, sp->setname));
2N/A }
2N/A }
2N/A Free(hostname);
2N/A }
2N/A }
2N/A return (0);
2N/A}
2N/A
2N/A/*
2N/A * Exported Entry Points
2N/A */
2N/A
2N/Aint
2N/Ameta_set_addmeds(
2N/A mdsetname_t *sp,
2N/A int node_c,
2N/A char **node_v,
2N/A md_error_t *ep
2N/A)
2N/A{
2N/A md_set_desc *sd = NULL;
2N/A md_drive_desc *dd = NULL;
2N/A mddb_med_parm_t mp;
2N/A mddb_med_upd_parm_t mup;
2N/A md_h_arr_t t;
2N/A md_h_arr_t rb_t;
2N/A med_rec_t medr;
2N/A med_rec_t rb_medr;
2N/A char *cp;
2N/A char **n_l = NULL;
2N/A int n_c = 0;
2N/A int i, j;
2N/A sigset_t oldsigs;
2N/A md_setkey_t *cl_sk;
2N/A int rb_level = 0;
2N/A md_error_t xep = mdnullerror;
2N/A int rval = 0;
2N/A int max_meds;
2N/A md_mnnode_desc *nd;
2N/A int suspend1_flag = 0;
2N/A int lock_flag = 0;
2N/A
2N/A /* Initialize */
2N/A (void) memset(&t, '\0', sizeof (t));
2N/A t.n_cnt = node_c;
2N/A mdclrerror(ep);
2N/A
2N/A if ((sd = metaget_setdesc(sp, ep)) == NULL)
2N/A return (-1);
2N/A
2N/A /* Make sure we own the set */
2N/A if (meta_check_ownership(sp, ep) != 0)
2N/A return (-1);
2N/A
2N/A if ((max_meds = get_max_meds(ep)) == 0)
2N/A return (-1);
2N/A
2N/A /*
2N/A * The mediator information (which is part of the set record) is
2N/A * stored in the local mddbs of each node in the diskset.
2N/A * Each node's rpc.metad daemon reads in the set
2N/A * records from that node's local mddb and caches them
2N/A * internally. Any process needing diskset information contacts its
2N/A * local rpc.metad to get this information. Since each node in the
2N/A * diskset is independently reading the set information from its local
2N/A * mddb, the set records in the local mddbs must stay
2N/A * in-sync, so that all nodes have a consistent view of the diskset.
2N/A *
2N/A * For a multinode diskset, explicitly verify that all nodes in the
2N/A * diskset are ALIVE (i.e. are in the API membership list). Otherwise,
2N/A * fail this operation since all nodes must be ALIVE in order to add
2N/A * the mediator information to the set record in their local mddb.
2N/A * If a panic of this node leaves the local mddbs set records
2N/A * out-of-sync, the reconfig cycle will fix the local mddbs and
2N/A * force them back into synchronization.
2N/A */
2N/A if (MD_MNSET_DESC(sd)) {
2N/A nd = sd->sd_nodelist;
2N/A while (nd) {
2N/A if (!(nd->nd_flags & MD_MN_NODE_ALIVE)) {
2N/A (void) mddserror(ep, MDE_DS_NOTINMEMBERLIST,
2N/A sp->setno,
2N/A nd->nd_nodename, NULL, sp->setname);
2N/A return (-1);
2N/A }
2N/A nd = nd->nd_next;
2N/A }
2N/A }
2N/A
2N/A /* Parse the command line into a the md_h_arr_t structure */
2N/A for (i = 0; i < t.n_cnt; i++) {
2N/A cp = strtok(node_v[i], ",");
2N/A j = 0;
2N/A while (cp) {
2N/A if (strlen(cp) > (size_t)MD_MAX_NODENAME)
2N/A return (mddserror(ep, MDE_DS_NODENAMETOOLONG,
2N/A sp->setno, cp, NULL, sp->setname));
2N/A if (j >= MAX_HOST_ADDRS)
2N/A return (mddserror(ep, MDE_DS_TOOMANYALIAS,
2N/A sp->setno, cp, NULL, sp->setname));
2N/A
2N/A (void) strcpy(t.n_lst[i].a_nm[j], cp);
2N/A
2N/A j++;
2N/A
2N/A cp = strtok(NULL, ",");
2N/A }
2N/A t.n_lst[i].a_cnt = j;
2N/A }
2N/A
2N/A /* Make a list of nodes to check */
2N/A for (i = 0; i < t.n_cnt; i++)
2N/A for (j = 0; j < t.n_lst[i].a_cnt; j++)
2N/A n_c = add_lst(&n_l, t.n_lst[i].a_nm[j]);
2N/A
2N/A /* Make sure that there are no redundant nodes */
2N/A rval = nodesuniq(sp, n_c, n_l, ep);
2N/A
2N/A (void) del_lst(&n_l);
2N/A
2N/A if (rval != 0)
2N/A return (rval);
2N/A
2N/A /*
2N/A * Lock the set on current set members.
2N/A * Set locking done much earlier for MN diskset than for traditional
2N/A * diskset since lock_set and SUSPEND are used to protect against
2N/A * other metaset commands running on the other nodes.
2N/A */
2N/A if (MD_MNSET_DESC(sd)) {
2N/A /* Make sure we are blocking all signals */
2N/A if (procsigs(TRUE, &oldsigs, &xep) < 0)
2N/A mdclrerror(&xep);
2N/A nd = sd->sd_nodelist;
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A while (nd) {
2N/A if (clnt_lock_set(nd->nd_nodename, sp, ep)) {
2N/A rval = -1;
2N/A goto out;
2N/A }
2N/A lock_flag = 1;
2N/A nd = nd->nd_next;
2N/A }
2N/A /*
2N/A * Lock out other meta* commands by suspending
2N/A * class 1 messages across the diskset.
2N/A */
2N/A nd = sd->sd_nodelist;
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A while (nd) {
2N/A if (clnt_mdcommdctl(nd->nd_nodename,
2N/A COMMDCTL_SUSPEND, sp, MD_MSG_CLASS1,
2N/A MD_MSCF_NO_FLAGS, ep)) {
2N/A rval = -1;
2N/A goto out;
2N/A }
2N/A suspend1_flag = 1;
2N/A nd = nd->nd_next;
2N/A }
2N/A }
2N/A
2N/A if (validate_med_nodes(sp, &t, ep)) {
2N/A rval = -1;
2N/A goto out;
2N/A }
2N/A
2N/A /* Check existing mediators against new, if any */
2N/A if (sd->sd_med.n_cnt > 0) {
2N/A for (i = 0; i < max_meds; i++)
2N/A if (sd->sd_med.n_lst[i].a_cnt > 0)
2N/A n_c = add_lst(&n_l,
2N/A sd->sd_med.n_lst[i].a_nm[0]);
2N/A
2N/A for (i = 0; i < t.n_cnt; i++) {
2N/A if (strinlst(t.n_lst[i].a_nm[0], n_c, n_l)) {
2N/A (void) del_lst(&n_l);
2N/A (void) mddserror(ep, MDE_DS_ISMED, sp->setno,
2N/A t.n_lst[i].a_nm[0], NULL,
2N/A sp->setname);
2N/A rval = -1;
2N/A goto out;
2N/A }
2N/A }
2N/A (void) del_lst(&n_l);
2N/A }
2N/A
2N/A if ((t.n_cnt + sd->sd_med.n_cnt) > max_meds) {
2N/A (void) mderror(ep, MDE_TOOMANYMED, NULL);
2N/A rval = -1;
2N/A goto out;
2N/A }
2N/A
2N/A /* Copy the current mediator list for rollback */
2N/A rb_t = sd->sd_med; /* structure assignment */
2N/A
2N/A /* Setup the mediator record roll-back structure */
2N/A (void) memset(&rb_medr, '\0', sizeof (med_rec_t));
2N/A rb_medr.med_rec_mag = MED_REC_MAGIC;
2N/A rb_medr.med_rec_rev = MED_REC_REV;
2N/A rb_medr.med_rec_fl = 0;
2N/A rb_medr.med_rec_sn = sp->setno;
2N/A (void) strcpy(rb_medr.med_rec_snm, sp->setname);
2N/A if (MD_MNSET_DESC(sd)) {
2N/A /*
2N/A * For a MN diskset the mediator is not given a list of
2N/A * hosts in the set. Instead a generic name (multiowner) is
2N/A * given to the mediator which will allow any node to access
2N/A * the mediator data as long as it provides the correct
2N/A * setname and set number. In a MN diskset, the mediator
2N/A * data is only used when a first node joins the diskset
2N/A * and becomes the master of the MN diskset.
2N/A *
2N/A * The traditional diskset code keeps the host list in
2N/A * the mediator record up to date with respect to the host
2N/A * list in the traditional diskset. This keeps an unauthorized
2N/A * node in the traditional diskset from accessing the data
2N/A * in the mediator record and being able to 'take' the
2N/A * diskset.
2N/A *
2N/A * This additional check is needed in the traditional diskset
2N/A * since a panic during the metaset command can leave
2N/A * the diskset with some nodes thinking that an
2N/A * action has occurred and other nodes thinking the opposite.
2N/A * A node may have really been removed from a diskset, but
2N/A * that node doesn't realize this so this node must be
2N/A * blocked from using the mediator data when attempting
2N/A * to 'take' the diskset.
2N/A * (Traditional diskset code has each node's rpc.metad
2N/A * cleaning up from an inconsistent state without any
2N/A * knowledge from the other nodes in the diskset).
2N/A *
2N/A * In the MN diskset, the reconfig steps force a consistent
2N/A * state across all nodes in the diskset, so no node
2N/A * needs to be blocked from accessing the mediator data.
2N/A * This allow the MN diskset to use a common 'nodename'
2N/A * in the mediator record. This allows the mediator
2N/A * daemon to remain unchanged even though a large number of
2N/A * nodes are supported by the MN diskset.
2N/A */
2N/A (void) strlcpy(rb_medr.med_rec_nodes[0], MED_MN_CALLER,
2N/A MD_MAX_NODENAME_PLUS_1);
2N/A } else {
2N/A for (i = 0; i < MD_MAXSIDES; i++)
2N/A (void) strcpy(rb_medr.med_rec_nodes[i],
2N/A sd->sd_nodes[i]);
2N/A }
2N/A rb_medr.med_rec_meds = sd->sd_med; /* structure assigment */
2N/A (void) memset(&rb_medr.med_rec_data, '\0', sizeof (med_data_t));
2N/A rb_medr.med_rec_foff = 0;
2N/A crcgen(&rb_medr, &rb_medr.med_rec_cks, sizeof (med_rec_t), NULL);
2N/A
2N/A /* Merge new mediators into the set record */
2N/A for (i = 0; i < t.n_cnt; i++) {
2N/A for (j = 0; j < max_meds; j++) {
2N/A if (sd->sd_med.n_lst[j].a_cnt > 0)
2N/A continue;
2N/A sd->sd_med.n_lst[j] = t.n_lst[i];
2N/A SE_NOTIFY(EC_SVM_CONFIG, ESC_SVM_ADD, SVM_TAG_MEDIATOR,
2N/A sp->setno, j);
2N/A sd->sd_med.n_cnt++;
2N/A break;
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * Setup the kernel mediator list, which also validates that the
2N/A * hosts have valid IP addresses
2N/A */
2N/A (void) memset(&mp, '\0', sizeof (mddb_med_parm_t));
2N/A mp.med_setno = sp->setno;
2N/A
2N/A /* Copy the hostnames */
2N/A if (meta_h2hi(&sd->sd_med, &mp.med, ep)) {
2N/A rval = -1;
2N/A goto out;
2N/A }
2N/A
2N/A /* Resolve the IP addresses for the host list */
2N/A if (meta_med_hnm2ip(&mp.med, ep)) {
2N/A rval = -1;
2N/A goto out;
2N/A }
2N/A
2N/A /* Bring the mediator record up to date with the set record */
2N/A medr = rb_medr; /* structure assignment */
2N/A medr.med_rec_meds = sd->sd_med; /* structure assigment */
2N/A crcgen(&medr, &medr.med_rec_cks, sizeof (med_rec_t), NULL);
2N/A
2N/A /* END CHECK CODE */
2N/A
2N/A /* Lock the set on current set members */
2N/A if (!(MD_MNSET_DESC(sd))) {
2N/A /* all signals already blocked for MN disket */
2N/A md_rb_sig_handling_on();
2N/A for (i = 0; i < MD_MAXSIDES; i++) {
2N/A /* Skip empty slots */
2N/A if (sd->sd_nodes[i][0] == '\0')
2N/A continue;
2N/A
2N/A if (clnt_lock_set(sd->sd_nodes[i], sp, ep)) {
2N/A rval = -1;
2N/A goto out;
2N/A }
2N/A lock_flag = 1;
2N/A }
2N/A }
2N/A
2N/A RB_TEST(1, "meta_set_addmeds", ep)
2N/A
2N/A RB_PREEMPT;
2N/A rb_level = 1; /* level 1 */
2N/A
2N/A RB_TEST(2, "meta_set_addmeds", ep)
2N/A
2N/A /*
2N/A * Add the new mediator information to all hosts in the set.
2N/A * For MN diskset, each node sends mediator list to its kernel.
2N/A */
2N/A if (MD_MNSET_DESC(sd)) {
2N/A nd = sd->sd_nodelist;
2N/A while (nd) {
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A if (clnt_updmeds(nd->nd_nodename, sp, &sd->sd_med, ep))
2N/A goto rollback;
2N/A nd = nd->nd_next;
2N/A }
2N/A } else {
2N/A for (i = 0; i < MD_MAXSIDES; i++) {
2N/A /* Skip empty slots */
2N/A if (sd->sd_nodes[i][0] == '\0')
2N/A continue;
2N/A
2N/A if (clnt_updmeds(sd->sd_nodes[i], sp, &sd->sd_med, ep))
2N/A goto rollback;
2N/A }
2N/A }
2N/A
2N/A RB_TEST(3, "meta_set_addmeds", ep)
2N/A
2N/A RB_PREEMPT;
2N/A rb_level = 2; /* level 2 */
2N/A
2N/A RB_TEST(4, "meta_set_addmeds", ep)
2N/A
2N/A if ((dd = metaget_drivedesc(sp, (MD_BASICNAME_OK | PRINT_FAST),
2N/A ep)) == NULL) {
2N/A if (! mdisok(ep))
2N/A goto rollback;
2N/A }
2N/A
2N/A RB_TEST(5, "meta_set_addmeds", ep)
2N/A
2N/A RB_PREEMPT;
2N/A rb_level = 3; /* level 3 */
2N/A
2N/A RB_TEST(6, "meta_set_addmeds", ep)
2N/A
2N/A /* Inform the mediator hosts of the new information */
2N/A for (i = 0; i < max_meds; i++) {
2N/A if (sd->sd_med.n_lst[i].a_cnt == 0)
2N/A continue;
2N/A
2N/A /* medr contains new mediator node list */
2N/A if (clnt_med_upd_rec(&sd->sd_med.n_lst[i], sp, &medr, ep))
2N/A goto rollback;
2N/A }
2N/A
2N/A RB_TEST(7, "meta_set_addmeds", ep)
2N/A
2N/A RB_PREEMPT;
2N/A rb_level = 4; /* level 4 */
2N/A
2N/A RB_TEST(8, "meta_set_addmeds", ep)
2N/A
2N/A /* In MN diskset, mediator list updated in clnt_updmeds call */
2N/A if (dd != NULL) {
2N/A if (!(MD_MNSET_DESC(sd))) {
2N/A if (metaioctl(MD_MED_SET_LST, &mp, &mp.med_mde,
2N/A NULL) != 0) {
2N/A (void) mdstealerror(ep, &mp.med_mde);
2N/A goto rollback;
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * If only 50% mddbs available, mediator will be
2N/A * golden by this ioctl on a traditional diskset.
2N/A *
2N/A * On a MN disket, this only happens if the mediator
2N/A * add operation is executed on the master node.
2N/A * If a slave node is adding the mediator, the mediator
2N/A * won't be marked golden until the next mddb change.
2N/A */
2N/A (void) memset(&mup, '\0', sizeof (mddb_med_upd_parm_t));
2N/A mup.med_setno = sp->setno;
2N/A if (metaioctl(MD_MED_UPD_MED, &mup, &mup.med_mde, NULL) != 0)
2N/A mdclrerror(&mup.med_mde);
2N/A }
2N/A
2N/Aout:
2N/A if (suspend1_flag) {
2N/A /*
2N/A * Unlock diskset by resuming messages across the diskset.
2N/A * Just resume all classes so that resume is the same whether
2N/A * just one class was locked or all classes were locked.
2N/A */
2N/A nd = sd->sd_nodelist;
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A while (nd) {
2N/A if (clnt_mdcommdctl(nd->nd_nodename, COMMDCTL_RESUME,
2N/A sp, MD_MSG_CLASS0, MD_MSCF_NO_FLAGS, &xep)) {
2N/A if (rval == 0)
2N/A (void) mdstealerror(ep, &xep);
2N/A rval = -1;
2N/A mde_perror(ep, dgettext(TEXT_DOMAIN,
2N/A "Unable to resume rpc.mdcommd.\n"));
2N/A }
2N/A nd = nd->nd_next;
2N/A }
2N/A meta_ping_mnset(sp->setno);
2N/A }
2N/A if (lock_flag) {
2N/A cl_sk = cl_get_setkey(sp->setno, sp->setname);
2N/A if (MD_MNSET_DESC(sd)) {
2N/A nd = sd->sd_nodelist;
2N/A while (nd) {
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A if (clnt_unlock_set(nd->nd_nodename,
2N/A cl_sk, &xep)) {
2N/A if (rval == 0)
2N/A (void) mdstealerror(ep, &xep);
2N/A rval = -1;
2N/A }
2N/A nd = nd->nd_next;
2N/A }
2N/A } else {
2N/A for (i = 0; i < MD_MAXSIDES; i++) {
2N/A /* Skip empty slots */
2N/A if (sd->sd_nodes[i][0] == '\0')
2N/A continue;
2N/A
2N/A if (clnt_unlock_set(sd->sd_nodes[i],
2N/A cl_sk, &xep)) {
2N/A if (rval == 0)
2N/A (void) mdstealerror(ep, &xep);
2N/A rval = -1;
2N/A }
2N/A }
2N/A }
2N/A cl_set_setkey(NULL);
2N/A }
2N/A
2N/A metafreedrivedesc(&dd);
2N/A
2N/A if (MD_MNSET_DESC(sd)) {
2N/A /* release signals back to what they were on entry */
2N/A if (procsigs(FALSE, &oldsigs, &xep) < 0)
2N/A mdclrerror(&xep);
2N/A } else {
2N/A md_rb_sig_handling_off(md_got_sig(), md_which_sig());
2N/A }
2N/A
2N/A return (rval);
2N/A
2N/Arollback:
2N/A /* all signals already blocked for MN disket */
2N/A if (!(MD_MNSET_DESC(sd))) {
2N/A if (procsigs(TRUE, &oldsigs, &xep) < 0)
2N/A mdclrerror(&xep);
2N/A }
2N/A
2N/A rval = -1;
2N/A
2N/A /*
2N/A * level 4
2N/A * In MN diskset, mediator list updated in clnt_updmeds call
2N/A */
2N/A if (rb_level > 3 && (dd != NULL) && (!(MD_MNSET_DESC(sd)))) {
2N/A (void) memset(&mp, '\0', sizeof (mddb_med_parm_t));
2N/A mp.med_setno = sp->setno;
2N/A (void) meta_h2hi(&rb_t, &mp.med, &xep);
2N/A mdclrerror(&xep);
2N/A (void) meta_med_hnm2ip(&mp.med, &xep);
2N/A mdclrerror(&xep);
2N/A (void) metaioctl(MD_MED_SET_LST, &mp, &mp.med_mde, NULL);
2N/A }
2N/A
2N/A /* level 3 */
2N/A if (rb_level > 2) {
2N/A for (i = 0; i < max_meds; i++) {
2N/A if (sd->sd_med.n_lst[i].a_cnt == 0)
2N/A continue;
2N/A
2N/A /*
2N/A * rb_medr contains the rollback mediator node list.
2N/A * Send the rollback mediator information to the
2N/A * new mediator node list. If a node had this RPC
2N/A * called, but its node is not in the mediator node
2N/A * list, rpc.metamedd will delete the mediator
2N/A * record on that node.
2N/A */
2N/A if (clnt_med_upd_rec(&sd->sd_med.n_lst[i], sp,
2N/A &rb_medr, &xep))
2N/A mdclrerror(&xep);
2N/A }
2N/A }
2N/A
2N/A /* level 2 */
2N/A if (rb_level > 1) {
2N/A metafreedrivedesc(&dd);
2N/A }
2N/A
2N/A /* level 1 */
2N/A if (rb_level > 0) {
2N/A /* Delete mediator information from all hosts in the set */
2N/A if (MD_MNSET_DESC(sd)) {
2N/A nd = sd->sd_nodelist;
2N/A while (nd) {
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A if (clnt_updmeds(nd->nd_nodename, sp, &rb_t,
2N/A &xep))
2N/A mdclrerror(&xep);
2N/A nd = nd->nd_next;
2N/A }
2N/A } else {
2N/A for (i = 0; i < MD_MAXSIDES; i++) {
2N/A /* Skip empty slots */
2N/A if (sd->sd_nodes[i][0] == '\0')
2N/A continue;
2N/A
2N/A if (clnt_updmeds(sd->sd_nodes[i], sp, &rb_t,
2N/A &xep))
2N/A mdclrerror(&xep);
2N/A }
2N/A }
2N/A }
2N/A
2N/A /* level 0 */
2N/A if (suspend1_flag) {
2N/A /*
2N/A * Unlock diskset by resuming messages across the diskset.
2N/A * Just resume all classes so that resume is the same whether
2N/A * just one class was locked or all classes were locked.
2N/A */
2N/A nd = sd->sd_nodelist;
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A while (nd) {
2N/A if (clnt_mdcommdctl(nd->nd_nodename, COMMDCTL_RESUME,
2N/A sp, MD_MSG_CLASS0, MD_MSCF_NO_FLAGS, &xep)) {
2N/A mdclrerror(&xep);
2N/A mde_perror(ep, dgettext(TEXT_DOMAIN,
2N/A "Unable to resume rpc.mdcommd.\n"));
2N/A }
2N/A nd = nd->nd_next;
2N/A }
2N/A meta_ping_mnset(sp->setno);
2N/A }
2N/A if (lock_flag) {
2N/A cl_sk = cl_get_setkey(sp->setno, sp->setname);
2N/A if (MD_MNSET_DESC(sd)) {
2N/A nd = sd->sd_nodelist;
2N/A while (nd) {
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A if (clnt_unlock_set(nd->nd_nodename,
2N/A cl_sk, &xep)) {
2N/A mdclrerror(&xep);
2N/A }
2N/A nd = nd->nd_next;
2N/A }
2N/A } else {
2N/A for (i = 0; i < MD_MAXSIDES; i++) {
2N/A /* Skip empty slots */
2N/A if (sd->sd_nodes[i][0] == '\0')
2N/A continue;
2N/A
2N/A if (clnt_unlock_set(sd->sd_nodes[i],
2N/A cl_sk, &xep)) {
2N/A mdclrerror(&xep);
2N/A }
2N/A }
2N/A }
2N/A cl_set_setkey(NULL);
2N/A }
2N/A
2N/A /* release signals back to what they were on entry */
2N/A if (procsigs(FALSE, &oldsigs, &xep) < 0)
2N/A mdclrerror(&xep);
2N/A
2N/A if (!(MD_MNSET_DESC(sd))) {
2N/A md_rb_sig_handling_off(md_got_sig(), md_which_sig());
2N/A }
2N/A
2N/A return (rval);
2N/A}
2N/A
2N/Aint
2N/Ameta_set_deletemeds(
2N/A mdsetname_t *sp,
2N/A int node_c,
2N/A char **node_v,
2N/A int forceflg,
2N/A md_error_t *ep
2N/A)
2N/A{
2N/A md_set_desc *sd = NULL;
2N/A md_drive_desc *dd = NULL;
2N/A mddb_med_parm_t mp;
2N/A md_h_arr_t rb_t;
2N/A med_rec_t medr;
2N/A med_rec_t rb_medr;
2N/A int i, j;
2N/A char **n_l = NULL;
2N/A int n_c = 0;
2N/A sigset_t oldsigs;
2N/A md_setkey_t *cl_sk;
2N/A int rb_level = 0;
2N/A md_error_t xep = mdnullerror;
2N/A int rval = 0;
2N/A int max_meds;
2N/A md_mnnode_desc *nd;
2N/A int suspend1_flag = 0;
2N/A int lock_flag = 0;
2N/A
2N/A mdclrerror(ep);
2N/A
2N/A if ((sd = metaget_setdesc(sp, ep)) == NULL)
2N/A return (-1);
2N/A
2N/A /* Make sure we own the set */
2N/A if (meta_check_ownership(sp, ep) != 0)
2N/A return (-1);
2N/A
2N/A for (i = 0; i < node_c; i++)
2N/A if (strchr(node_v[i], ',') != NULL)
2N/A return (mderror(ep, MDE_ONLYNODENAME, node_v[i]));
2N/A
2N/A if (nodesuniq(sp, node_c, node_v, ep))
2N/A return (-1);
2N/A
2N/A if ((max_meds = get_max_meds(ep)) == 0)
2N/A return (-1);
2N/A
2N/A /*
2N/A * The mediator information (which is part of the set record) is
2N/A * stored in the local mddbs of each node in the diskset.
2N/A * Each node's rpc.metad daemon reads in the set
2N/A * records from that node's local mddb and caches them
2N/A * internally. Any process needing diskset information contacts its
2N/A * local rpc.metad to get this information. Since each node in the
2N/A * diskset is independently reading the set information from its local
2N/A * mddb, the set records in the local mddbs must stay
2N/A * in-sync, so that all nodes have a consistent view of the diskset.
2N/A *
2N/A * For a multinode diskset, explicitly verify that all nodes in the
2N/A * diskset are ALIVE (i.e. are in the API membership list). Otherwise,
2N/A * fail this operation since all nodes must be ALIVE in order to delete
2N/A * the mediator information from the set record in their local mddb.
2N/A * If a panic of this node leaves the local mddbs set records
2N/A * out-of-sync, the reconfig cycle will fix the local mddbs and
2N/A * force them back into synchronization.
2N/A */
2N/A if (MD_MNSET_DESC(sd)) {
2N/A nd = sd->sd_nodelist;
2N/A while (nd) {
2N/A if (!(nd->nd_flags & MD_MN_NODE_ALIVE)) {
2N/A (void) mddserror(ep, MDE_DS_NOTINMEMBERLIST,
2N/A sp->setno,
2N/A nd->nd_nodename, NULL, sp->setname);
2N/A return (-1);
2N/A }
2N/A nd = nd->nd_next;
2N/A }
2N/A }
2N/A
2N/A if (sd->sd_med.n_cnt == 0)
2N/A return (mderror(ep, MDE_NOMED, NULL));
2N/A
2N/A /* Make a list of nodes to check */
2N/A for (i = 0; i < max_meds; i++)
2N/A if (sd->sd_med.n_lst[i].a_cnt > 0)
2N/A n_c = add_lst(&n_l, sd->sd_med.n_lst[i].a_nm[0]);
2N/A
2N/A for (i = 0; i < node_c; i++) {
2N/A if (! strinlst(node_v[i], n_c, n_l)) {
2N/A (void) del_lst(&n_l);
2N/A return (mddserror(ep, MDE_DS_ISNOTMED, sp->setno,
2N/A node_v[i], NULL, sp->setname));
2N/A }
2N/A }
2N/A
2N/A (void) del_lst(&n_l);
2N/A
2N/A /* Save a copy of the current mediator information */
2N/A rb_t = sd->sd_med; /* structure assignment */
2N/A
2N/A /* Setup the mediator record for rollback */
2N/A (void) memset(&rb_medr, '\0', sizeof (med_rec_t));
2N/A rb_medr.med_rec_mag = MED_REC_MAGIC;
2N/A rb_medr.med_rec_rev = MED_REC_REV;
2N/A rb_medr.med_rec_fl = 0;
2N/A rb_medr.med_rec_sn = sp->setno;
2N/A (void) strcpy(rb_medr.med_rec_snm, sp->setname);
2N/A if (MD_MNSET_DESC(sd)) {
2N/A /*
2N/A * In MN diskset, use a generic nodename, multiowner, in the
2N/A * mediator record which allows any node to access mediator
2N/A * information. MN diskset reconfig cycle forces consistent
2N/A * view of set/node/drive/mediator information across all nodes
2N/A * in the MN diskset. This allows the relaxation of
2N/A * node name checking in rpc.metamedd for MN disksets.
2N/A *
2N/A * In the traditional diskset, only a node that is in the
2N/A * mediator record's diskset nodelist can access mediator
2N/A * data.
2N/A */
2N/A (void) strlcpy(rb_medr.med_rec_nodes[0], MED_MN_CALLER,
2N/A MD_MAX_NODENAME_PLUS_1);
2N/A } else {
2N/A for (i = 0; i < MD_MAXSIDES; i++)
2N/A (void) strcpy(rb_medr.med_rec_nodes[i],
2N/A sd->sd_nodes[i]);
2N/A }
2N/A rb_medr.med_rec_meds = sd->sd_med; /* structure assignment */
2N/A (void) memset(&rb_medr.med_rec_data, '\0', sizeof (med_data_t));
2N/A rb_medr.med_rec_foff = 0;
2N/A crcgen(&rb_medr, &rb_medr.med_rec_cks, sizeof (med_rec_t), NULL);
2N/A
2N/A /* Delete the mediators requested from the set */
2N/A for (i = 0; i < node_c; i++) {
2N/A for (j = 0; j < max_meds; j++) {
2N/A if (sd->sd_med.n_lst[j].a_cnt == 0)
2N/A continue;
2N/A if (strcmp(node_v[i],
2N/A sd->sd_med.n_lst[j].a_nm[0]) != 0)
2N/A continue;
2N/A SE_NOTIFY(EC_SVM_CONFIG, ESC_SVM_REMOVE,
2N/A SVM_TAG_MEDIATOR, sp->setno, j);
2N/A (void) memset(&sd->sd_med.n_lst[j], '\0',
2N/A sizeof (md_h_t));
2N/A sd->sd_med.n_cnt--;
2N/A break;
2N/A }
2N/A }
2N/A
2N/A medr = rb_medr; /* structure assignment */
2N/A medr.med_rec_meds = sd->sd_med; /* structure assignment */
2N/A crcgen(&medr, &medr.med_rec_cks, sizeof (med_rec_t), NULL);
2N/A
2N/A /* END CHECK CODE */
2N/A
2N/A /* Lock the set on current set members */
2N/A if (MD_MNSET_DESC(sd)) {
2N/A /* Make sure we are blocking all signals */
2N/A if (procsigs(TRUE, &oldsigs, &xep) < 0)
2N/A mdclrerror(&xep);
2N/A /*
2N/A * Lock the set on current set members.
2N/A * lock_set and SUSPEND are used to protect against
2N/A * other metaset commands running on the other nodes.
2N/A */
2N/A nd = sd->sd_nodelist;
2N/A while (nd) {
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A if (clnt_lock_set(nd->nd_nodename, sp, ep)) {
2N/A if (forceflg && strcmp(mynode(),
2N/A nd->nd_nodename) != 0) {
2N/A mdclrerror(ep);
2N/A nd = nd->nd_next;
2N/A continue;
2N/A }
2N/A rval = -1;
2N/A goto out;
2N/A }
2N/A lock_flag = 1;
2N/A nd = nd->nd_next;
2N/A }
2N/A /*
2N/A * Lock out other meta* commands by suspending
2N/A * class 1 messages across the diskset.
2N/A */
2N/A nd = sd->sd_nodelist;
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A while (nd) {
2N/A if (clnt_mdcommdctl(nd->nd_nodename,
2N/A COMMDCTL_SUSPEND, sp, MD_MSG_CLASS1,
2N/A MD_MSCF_NO_FLAGS, ep)) {
2N/A rval = -1;
2N/A goto out;
2N/A }
2N/A suspend1_flag = 1;
2N/A nd = nd->nd_next;
2N/A }
2N/A } else {
2N/A md_rb_sig_handling_on();
2N/A for (i = 0; i < MD_MAXSIDES; i++) {
2N/A /* Skip empty slots */
2N/A if (sd->sd_nodes[i][0] == '\0')
2N/A continue;
2N/A
2N/A if (clnt_lock_set(sd->sd_nodes[i], sp, ep)) {
2N/A if (forceflg &&
2N/A strcmp(mynode(), sd->sd_nodes[i]) != 0) {
2N/A mdclrerror(ep);
2N/A continue;
2N/A }
2N/A rval = -1;
2N/A goto out;
2N/A }
2N/A lock_flag = 1;
2N/A }
2N/A }
2N/A
2N/A RB_TEST(1, "meta_set_deletemeds", ep)
2N/A
2N/A RB_PREEMPT;
2N/A rb_level = 1; /* level 1 */
2N/A
2N/A RB_TEST(2, "meta_set_deletemeds", ep)
2N/A
2N/A /* Update the mediator information on all hosts in the set */
2N/A if (MD_MNSET_DESC(sd)) {
2N/A nd = sd->sd_nodelist;
2N/A while (nd) {
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A if (clnt_updmeds(nd->nd_nodename, sp, &sd->sd_med,
2N/A ep)) {
2N/A if (forceflg && strcmp(mynode(),
2N/A nd->nd_nodename) != 0) {
2N/A mdclrerror(ep);
2N/A continue;
2N/A }
2N/A goto rollback;
2N/A }
2N/A nd = nd->nd_next;
2N/A }
2N/A } else {
2N/A for (i = 0; i < MD_MAXSIDES; i++) {
2N/A /* Skip empty slots */
2N/A if (sd->sd_nodes[i][0] == '\0')
2N/A continue;
2N/A
2N/A if (clnt_updmeds(sd->sd_nodes[i], sp, &sd->sd_med,
2N/A ep)) {
2N/A if (forceflg && strcmp(mynode(),
2N/A sd->sd_nodes[i]) != 0) {
2N/A mdclrerror(ep);
2N/A continue;
2N/A }
2N/A goto rollback;
2N/A }
2N/A }
2N/A }
2N/A
2N/A RB_TEST(3, "meta_set_deletemeds", ep)
2N/A
2N/A RB_PREEMPT;
2N/A rb_level = 2; /* level 2 */
2N/A
2N/A RB_TEST(5, "meta_set_deletemeds", ep)
2N/A
2N/A if ((dd = metaget_drivedesc(sp, (MD_BASICNAME_OK | PRINT_FAST),
2N/A ep)) == NULL) {
2N/A if (! mdisok(ep))
2N/A goto rollback;
2N/A }
2N/A
2N/A RB_TEST(5, "meta_set_deletemeds", ep)
2N/A
2N/A RB_PREEMPT;
2N/A rb_level = 3; /* level 3 */
2N/A
2N/A RB_TEST(6, "meta_set_deletemeds", ep)
2N/A
2N/A if (dd != NULL) {
2N/A /*
2N/A * Set up the parameters to the call to update the
2N/A * kernel mediator list
2N/A */
2N/A (void) memset(&mp, '\0', sizeof (mddb_med_parm_t));
2N/A mp.med_setno = sp->setno;
2N/A if (meta_h2hi(&sd->sd_med, &mp.med, ep))
2N/A goto rollback;
2N/A
2N/A /* Resolve the IP addresses for the host list */
2N/A if (meta_med_hnm2ip(&mp.med, ep))
2N/A goto rollback;
2N/A
2N/A if (metaioctl(MD_MED_SET_LST, &mp, &mp.med_mde, NULL) != 0) {
2N/A (void) mdstealerror(ep, &mp.med_mde);
2N/A goto rollback;
2N/A }
2N/A }
2N/A
2N/A RB_TEST(7, "meta_set_deletemeds", ep)
2N/A
2N/A RB_PREEMPT;
2N/A rb_level = 4; /* level 4 */
2N/A
2N/A RB_TEST(8, "meta_set_deletemeds", ep)
2N/A
2N/A /* Inform the mediator hosts of the new status */
2N/A for (i = 0; i < max_meds; i++) {
2N/A if (rb_t.n_lst[i].a_cnt == 0)
2N/A continue;
2N/A
2N/A /*
2N/A * medr contains the new mediator node list.
2N/A * Send the new mediator information to the
2N/A * new mediator node list. If a node had this RPC
2N/A * called, but its node is no longer in the new mediator
2N/A * node list, rpc.metamedd will delete the mediator
2N/A * record on that node.
2N/A */
2N/A if (clnt_med_upd_rec(&rb_t.n_lst[i], sp, &medr, ep)) {
2N/A if ((forceflg && mdanyrpcerror(ep)) ||
2N/A mdisrpcerror(ep, RPC_PROGNOTREGISTERED)) {
2N/A mdclrerror(ep);
2N/A continue;
2N/A }
2N/A goto rollback;
2N/A }
2N/A }
2N/A
2N/Aout:
2N/A if (dd)
2N/A metafreedrivedesc(&dd);
2N/A
2N/A if (suspend1_flag) {
2N/A /*
2N/A * Unlock diskset by resuming messages across the diskset.
2N/A * Just resume all classes so that resume is the same whether
2N/A * just one class was locked or all classes were locked.
2N/A */
2N/A nd = sd->sd_nodelist;
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A while (nd) {
2N/A if (clnt_mdcommdctl(nd->nd_nodename, COMMDCTL_RESUME,
2N/A sp, MD_MSG_CLASS0, MD_MSCF_NO_FLAGS, &xep)) {
2N/A if (rval == 0)
2N/A (void) mdstealerror(ep, &xep);
2N/A rval = -1;
2N/A mde_perror(ep, dgettext(TEXT_DOMAIN,
2N/A "Unable to resume rpc.mdcommd.\n"));
2N/A }
2N/A nd = nd->nd_next;
2N/A }
2N/A meta_ping_mnset(sp->setno);
2N/A }
2N/A
2N/A cl_sk = cl_get_setkey(sp->setno, sp->setname);
2N/A if (lock_flag) {
2N/A if (MD_MNSET_DESC(sd)) {
2N/A nd = sd->sd_nodelist;
2N/A while (nd) {
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A if (clnt_unlock_set(nd->nd_nodename,
2N/A cl_sk, &xep)) {
2N/A if (forceflg &&
2N/A strcmp(mynode(),
2N/A nd->nd_nodename) != 0) {
2N/A mdclrerror(ep);
2N/A continue;
2N/A }
2N/A if (rval == 0)
2N/A (void) mdstealerror(ep, &xep);
2N/A rval = -1;
2N/A }
2N/A nd = nd->nd_next;
2N/A }
2N/A } else {
2N/A for (i = 0; i < MD_MAXSIDES; i++) {
2N/A /* Skip empty slots */
2N/A if (sd->sd_nodes[i][0] == '\0')
2N/A continue;
2N/A
2N/A if (clnt_unlock_set(sd->sd_nodes[i],
2N/A cl_sk, &xep)) {
2N/A if (forceflg &&
2N/A strcmp(mynode(),
2N/A sd->sd_nodes[i]) != 0) {
2N/A mdclrerror(ep);
2N/A continue;
2N/A }
2N/A if (rval == 0)
2N/A (void) mdstealerror(ep, &xep);
2N/A rval = -1;
2N/A }
2N/A }
2N/A }
2N/A }
2N/A cl_set_setkey(NULL);
2N/A
2N/A if (MD_MNSET_DESC(sd)) {
2N/A /* release signals back to what they were on entry */
2N/A if (procsigs(FALSE, &oldsigs, &xep) < 0)
2N/A mdclrerror(&xep);
2N/A } else {
2N/A md_rb_sig_handling_off(md_got_sig(), md_which_sig());
2N/A }
2N/A
2N/A return (rval);
2N/A
2N/Arollback:
2N/A /* all signals already blocked for MN disket */
2N/A if (!(MD_MNSET_DESC(sd))) {
2N/A if (procsigs(TRUE, &oldsigs, &xep) < 0)
2N/A mdclrerror(&xep);
2N/A }
2N/A
2N/A rval = -1;
2N/A
2N/A (void) del_lst(&n_l);
2N/A
2N/A /* level 4 */
2N/A if (rb_level > 4) {
2N/A for (i = 0; i < max_meds; i++) {
2N/A if (rb_t.n_lst[i].a_cnt == 0)
2N/A continue;
2N/A
2N/A /*
2N/A * rb_medr contains the rollback mediator node list.
2N/A * Send the rollback mediator information to the
2N/A * new mediator node list. This will recreate the
2N/A * mediator record on all nodes where the mediator
2N/A * record had been removed.
2N/A */
2N/A if (clnt_med_upd_rec(&rb_t.n_lst[i], sp, &rb_medr,
2N/A &xep))
2N/A mdclrerror(&xep);
2N/A }
2N/A }
2N/A
2N/A /* level 3 */
2N/A if (rb_level > 2 && dd != NULL) {
2N/A (void) memset(&mp, '\0', sizeof (mddb_med_parm_t));
2N/A mp.med_setno = sp->setno;
2N/A (void) meta_h2hi(&rb_t, &mp.med, &xep);
2N/A mdclrerror(&xep);
2N/A (void) meta_med_hnm2ip(&mp.med, &xep);
2N/A mdclrerror(&xep);
2N/A (void) metaioctl(MD_MED_SET_LST, &mp, &mp.med_mde, NULL);
2N/A }
2N/A
2N/A /* level 2 */
2N/A if (rb_level > 1) {
2N/A metafreedrivedesc(&dd);
2N/A }
2N/A
2N/A /* level 1 */
2N/A if (rb_level > 0) {
2N/A /* Delete mediator information from all hosts in the set */
2N/A if (MD_MNSET_DESC(sd)) {
2N/A nd = sd->sd_nodelist;
2N/A while (nd) {
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A if (clnt_updmeds(nd->nd_nodename, sp, &rb_t,
2N/A &xep))
2N/A mdclrerror(&xep);
2N/A nd = nd->nd_next;
2N/A }
2N/A } else {
2N/A for (i = 0; i < MD_MAXSIDES; i++) {
2N/A /* Skip empty slots */
2N/A if (sd->sd_nodes[i][0] == '\0')
2N/A continue;
2N/A
2N/A if (clnt_updmeds(sd->sd_nodes[i], sp, &rb_t,
2N/A &xep))
2N/A mdclrerror(&xep);
2N/A }
2N/A }
2N/A }
2N/A
2N/A /* level 0 */
2N/A cl_sk = cl_get_setkey(sp->setno, sp->setname);
2N/A /* Unlock the set */
2N/A /* Don't test lock flag since guaranteed to be set if in rollback */
2N/A if (MD_MNSET_DESC(sd)) {
2N/A /*
2N/A * Unlock diskset by resuming messages across the diskset.
2N/A * Just resume all classes so that resume is the same whether
2N/A * just one class was locked or all classes were locked.
2N/A */
2N/A if (suspend1_flag) {
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A nd = sd->sd_nodelist;
2N/A while (nd) {
2N/A if (clnt_mdcommdctl(nd->nd_nodename,
2N/A COMMDCTL_RESUME, sp, MD_MSG_CLASS0,
2N/A MD_MSCF_NO_FLAGS, &xep)) {
2N/A mde_perror(&xep, dgettext(TEXT_DOMAIN,
2N/A "Unable to resume rpc.mdcommd.\n"));
2N/A mdclrerror(&xep);
2N/A }
2N/A nd = nd->nd_next;
2N/A }
2N/A meta_ping_mnset(sp->setno);
2N/A }
2N/A nd = sd->sd_nodelist;
2N/A /* All nodes are guaranteed to be ALIVE */
2N/A while (nd) {
2N/A if (clnt_unlock_set(nd->nd_nodename, cl_sk, &xep))
2N/A mdclrerror(&xep);
2N/A nd = nd->nd_next;
2N/A }
2N/A } else {
2N/A for (i = 0; i < MD_MAXSIDES; i++) {
2N/A /* Skip empty slots */
2N/A if (sd->sd_nodes[i][0] == '\0')
2N/A continue;
2N/A
2N/A if (clnt_unlock_set(sd->sd_nodes[i], cl_sk, &xep))
2N/A mdclrerror(&xep);
2N/A }
2N/A }
2N/A cl_set_setkey(NULL);
2N/A
2N/A /* release signals back to what they were on entry */
2N/A if (procsigs(FALSE, &oldsigs, &xep) < 0)
2N/A mdclrerror(&xep);
2N/A
2N/A if (!(MD_MNSET_DESC(sd))) {
2N/A md_rb_sig_handling_off(md_got_sig(), md_which_sig());
2N/A }
2N/A
2N/A return (rval);
2N/A}