#include <nfs/nfs4_clnt.h>
#include <nfs/nfs4.h>
#include <nfs/nfs_clnt.h>
#include <nfs/rnode4.h>
#include <sys/cmn_err.h>
#include <sys/cred.h>
#include <sys/systm.h>
#define SECINFO_SUPPORT_COUNT 6
static char krb5_val[] = {'\x2A', '\x86', '\x48', '\x86', '\xF7', \
'\x12', '\x01', '\x02', '\x02'};
static sec_oid4 krb5_oid = {9, krb5_val};
static SECINFO4res *secinfo_support;
extern void sec_clnt_freeinfo(struct sec_data *);
#define NFS_FLAVOR_KRB5 390003
#define NFS_FLAVOR_KRB5I 390004
#define NFS_FLAVOR_KRB5P 390005
void
nfs4_secinfo_init(void)
{
secinfo4 *val;
int i;
secinfo_support = kmem_alloc(sizeof (SECINFO4res), KM_SLEEP);
secinfo_support->SECINFO4resok_len = SECINFO_SUPPORT_COUNT;
val = kmem_alloc(
secinfo_support->SECINFO4resok_len * sizeof (secinfo4),
KM_SLEEP);
val[0].flavor = AUTH_SYS;
val[0].flavor_info.oid.sec_oid4_len = 0;
val[0].flavor_info.oid.sec_oid4_val = NULL;
val[0].flavor_info.service = 0;
val[0].flavor_info.qop = 0;
for (i = 1; i <= 3; i++) {
val[i].flavor = RPCSEC_GSS;
val[i].flavor_info.oid = krb5_oid;
val[i].flavor_info.service = i;
val[i].flavor_info.qop = 0;
}
val[4].flavor = AUTH_DH;
val[4].flavor_info.oid.sec_oid4_len = 0;
val[4].flavor_info.oid.sec_oid4_val = NULL;
val[4].flavor_info.service = 0;
val[4].flavor_info.qop = 0;
val[5].flavor = AUTH_NONE;
val[5].flavor_info.oid.sec_oid4_len = 0;
val[5].flavor_info.oid.sec_oid4_val = NULL;
val[5].flavor_info.service = 0;
val[5].flavor_info.qop = 0;
#if !defined(lint)
ASSERT(SECINFO_SUPPORT_COUNT == 6);
#endif
secinfo_support->SECINFO4resok_val = val;
}
void
nfs4_secinfo_fini(void)
{
kmem_free(secinfo_support->SECINFO4resok_val,
secinfo_support->SECINFO4resok_len * sizeof (secinfo4));
kmem_free(secinfo_support, sizeof (SECINFO4res));
}
int
secinfo2nfsflavor(sec_oid4 *mech_oid, rpc_gss_svc_t service)
{
if (bcmp(mech_oid->sec_oid4_val, krb5_oid.sec_oid4_val,
krb5_oid.sec_oid4_len) != 0) {
return (0);
}
switch (service) {
case RPC_GSS_SVC_NONE:
return (NFS_FLAVOR_KRB5);
case RPC_GSS_SVC_INTEGRITY:
return (NFS_FLAVOR_KRB5I);
case RPC_GSS_SVC_PRIVACY:
return (NFS_FLAVOR_KRB5P);
default:
break;
}
return (0);
}
static sv_secinfo_t *
secinfo_create(servinfo4_t *svp, SECINFO4res *sec_info, char *servname)
{
uint_t i, seccnt, scnt;
sec_data_t *sdata;
sv_secinfo_t *sinfo;
uint_t len = sec_info->SECINFO4resok_len;
secinfo4 *value = sec_info->SECINFO4resok_val;
if (len == 0)
return (NULL);
seccnt = len;
if (!svp->sv_dhsec) {
for (i = 0; i < len; i++) {
if (value[i].flavor == AUTH_DH)
seccnt--;
}
}
if (seccnt == 0)
return (NULL);
sdata = kmem_alloc(sizeof (sec_data_t) * seccnt, KM_SLEEP);
scnt = 0;
for (i = 0; i < len; i++) {
secinfo4 *val = &value[i];
gss_clntdata_t *data;
rpcsec_gss_info *info;
sdata[scnt].flags = 0;
sdata[scnt].rpcflavor = val->flavor;
switch (val->flavor) {
case RPCSEC_GSS:
data = kmem_alloc(sizeof (gss_clntdata_t), KM_SLEEP);
data->realm[0] = '\0';
info = &val->flavor_info;
data->service = (rpc_gss_service_t)info->service;
data->qop = (uint_t)info->qop;
data->mechanism.length = info->oid.sec_oid4_len;
data->mechanism.elements =
kmem_alloc(info->oid.sec_oid4_len, KM_SLEEP);
bcopy(info->oid.sec_oid4_val,
data->mechanism.elements, info->oid.sec_oid4_len);
data->uname[0] = 'n'; data->uname[1] = 'f';
data->uname[2] = 's'; data->uname[3] = '\0';
(void) strcpy(data->inst, servname);
sdata[scnt].data = (caddr_t)data;
sdata[scnt].secmod =
secinfo2nfsflavor(&info->oid, info->service);
scnt++;
break;
case AUTH_DH:
if (svp->sv_dhsec) {
sdata[scnt] = *svp->sv_dhsec;
scnt++;
break;
}
continue;
default:
sdata[scnt].secmod = val->flavor;
sdata[scnt].data = NULL;
scnt++;
break;
}
}
ASSERT(seccnt == scnt);
sinfo = kmem_alloc(sizeof (sv_secinfo_t), KM_SLEEP);
sinfo->count = seccnt;
sinfo->sdata = sdata;
return (sinfo);
}
void
secinfo_free(sv_secinfo_t *secinfo)
{
int i;
if (secinfo == NULL)
return;
for (i = 0; i < secinfo->count; i++) {
if (secinfo->sdata[i].rpcflavor == RPCSEC_GSS) {
gss_clntdata_t *data = (gss_clntdata_t *)
secinfo->sdata[i].data;
(void) rpc_gss_secpurge((void *)&secinfo->sdata[i]);
kmem_free(data->mechanism.elements,
data->mechanism.length);
kmem_free(data, sizeof (gss_clntdata_t));
}
if (secinfo->sdata[i].rpcflavor == AUTH_DH) {
secinfo->sdata[i].data = NULL;
}
}
kmem_free(secinfo->sdata, sizeof (sec_data_t) * secinfo->count);
kmem_free(secinfo, sizeof (sv_secinfo_t));
}
static bool_t
secinfo_check(servinfo4_t *svp)
{
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_WRITER, 0);
if (svp->sv_secinfo == NULL) {
nfs_rw_exit(&svp->sv_lock);
return (FALSE);
}
svp->sv_secinfo->index++;
if (svp->sv_secinfo->index < svp->sv_secinfo->count) {
svp->sv_flags |= SV4_TRYSECINFO;
svp->sv_currsec =
&svp->sv_secinfo->sdata[svp->sv_secinfo->index];
nfs_rw_exit(&svp->sv_lock);
return (TRUE);
} else {
svp->sv_secinfo->index = 0;
svp->sv_flags &= ~SV4_TRYSECINFO;
svp->sv_currsec = NULL;
nfs_rw_exit(&svp->sv_lock);
return (FALSE);
}
}
static void
secinfo_update(servinfo4_t *svp, SECINFO4res *sec_info)
{
sv_secinfo_t *newsecinfo;
newsecinfo = secinfo_create(svp, sec_info, svp->sv_hostname);
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_WRITER, 0);
if (svp->sv_secinfo && svp->sv_secinfo != svp->sv_save_secinfo) {
secinfo_free(svp->sv_secinfo);
}
svp->sv_secinfo = newsecinfo;
if (svp->sv_secinfo) {
svp->sv_secinfo->index = 0;
svp->sv_flags |= SV4_TRYSECINFO;
svp->sv_currsec =
&svp->sv_secinfo->sdata[svp->sv_secinfo->index];
} else {
svp->sv_flags &= ~SV4_TRYSECINFO;
svp->sv_currsec = NULL;
}
nfs_rw_exit(&svp->sv_lock);
}
void
save_mnt_secinfo(servinfo4_t *svp)
{
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_WRITER, 0);
if (svp->sv_currsec) {
svp->sv_savesec = svp->sv_currsec;
svp->sv_save_secinfo = svp->sv_secinfo;
} else {
ASSERT(svp->sv_save_secinfo == NULL);
svp->sv_savesec = svp->sv_secdata;
}
nfs_rw_exit(&svp->sv_lock);
}
void
check_mnt_secinfo(servinfo4_t *svp, vnode_t *vp)
{
bool_t is_restore;
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_WRITER, 0);
is_restore = (vp == NULL || (RP_ISSTUB(VTOR4(vp)))) &&
svp->sv_save_secinfo &&
(svp->sv_secinfo != svp->sv_save_secinfo);
if (is_restore) {
secinfo_free(svp->sv_secinfo);
if (svp->sv_savesec == svp->sv_secdata) {
ASSERT(svp->sv_save_secinfo == NULL);
svp->sv_secinfo = NULL;
svp->sv_currsec = NULL;
} else {
ASSERT(svp->sv_save_secinfo != NULL);
svp->sv_secinfo = svp->sv_save_secinfo;
svp->sv_currsec = svp->sv_savesec;
}
} else {
if (svp->sv_save_secinfo &&
svp->sv_save_secinfo != svp->sv_secinfo)
secinfo_free(svp->sv_save_secinfo);
}
svp->sv_save_secinfo = NULL;
svp->sv_savesec = NULL;
nfs_rw_exit(&svp->sv_lock);
}
static int
secinfo_tryroot_otw(mntinfo4_t *mi, cred_t *cr)
{
COMPOUND4args_clnt args;
COMPOUND4res_clnt res;
nfs_argop4 argop;
int doqueue = 1;
bool_t needrecov = FALSE;
nfs4_error_t e = { 0, NFS4_OK, RPC_SUCCESS };
secinfo_update(mi->mi_curr_serv, secinfo_support);
args.ctag = TAG_PUTROOTFH;
args.array_len = 1;
args.array = &argop;
argop.argop = OP_PUTROOTFH;
retry:
NFS4_DEBUG(nfs4_client_call_debug, (CE_NOTE,
"secinfo_tryroot_otw: %s call, mi 0x%p",
needrecov ? "recov" : "first", (void*)mi));
rfs4call(mi, &args, &res, cr, &doqueue, RFSCALL_SOFT, &e);
needrecov = nfs4_needs_recovery(&e, FALSE, mi->mi_vfsp);
if (e.error && !needrecov) {
return (e.error);
}
if (res.status == NFS4ERR_WRONGSEC) {
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
if (secinfo_check(mi->mi_curr_serv))
goto retry;
return (geterrno4(res.status));
}
if (needrecov) {
NFS4_DEBUG(nfs4_client_recov_debug, (CE_NOTE,
"secinfo_tryroot_otw: let the caller retry\n"));
if (!e.error)
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
return (0);
}
if (res.status) {
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
return (geterrno4(res.status));
}
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
return (e.error);
}
static int
comp_total(char *inpath)
{
int tnum = 0;
char *slash;
while (*inpath != '\0') {
if (*inpath == '/') {
inpath++;
continue;
}
if ((slash = (char *)strchr(inpath, '/')) == NULL) {
tnum++;
break;
} else {
tnum++;
inpath = slash + 1;
}
}
return (tnum);
}
static void
comp_getn(char *inpath, int nth, component4 *comp)
{
char *path = inpath, *comp_start, *slash = NULL;
int count = 0;
while ((count != nth) && (*path != '\0')) {
comp_start = path;
while (*path == '/')
path++;
if (*path != '\0') {
comp_start = path;
count++;
}
if ((slash = strchr(path, '/')) == NULL)
break;
else
path = slash + 1;
}
if (count == nth) {
if (slash)
*slash = '\0';
comp->utf8string_len = strlen(comp_start);
comp->utf8string_val = comp_start;
if (comp_start != inpath) {
comp_start--;
*comp_start = '\0';
}
} else {
comp->utf8string_len = 0;
comp->utf8string_val = NULL;
}
}
static int
nfs4secinfo_otw(mntinfo4_t *mi, cred_t *cr, servinfo4_t *svp, int isrecov)
{
COMPOUND4args_clnt args;
COMPOUND4res_clnt res;
nfs_argop4 *argop;
nfs_resop4 *resop;
lookup4_param_t lookuparg;
uint_t path_len;
int doqueue;
int numops, num_argops;
char *tmp_path;
component4 comp;
uint_t ncomp, tcomp;
bool_t needrecov = FALSE;
nfs4_error_t e = { 0, NFS4_OK, RPC_SUCCESS };
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_READER, 0);
ncomp = tcomp = comp_total(svp->sv_path);
path_len = strlen(svp->sv_path);
nfs_rw_exit(&svp->sv_lock);
ASSERT(ncomp > 0);
retry:
tmp_path = kmem_alloc(path_len + 1, KM_SLEEP);
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_READER, 0);
bcopy(svp->sv_path, tmp_path, path_len + 1);
nfs_rw_exit(&svp->sv_lock);
comp_getn(tmp_path, ncomp, &comp);
args.ctag = TAG_SECINFO;
lookuparg.l4_getattrs = LKP4_NO_ATTRIBUTES;
lookuparg.argsp = &args;
lookuparg.resp = &res;
lookuparg.header_len = 1;
lookuparg.trailer_len = 1;
lookuparg.ga_bits = 0;
lookuparg.mi = mi;
(void) nfs4lookup_setup(tmp_path, &lookuparg, 0);
argop = args.array;
argop[0].argop = OP_PUTROOTFH;
num_argops = args.array_len;
argop[num_argops - 1].argop = OP_SECINFO;
argop[num_argops - 1].nfs_argop4_u.opsecinfo.name.utf8string_len =
comp.utf8string_len;
argop[num_argops - 1].nfs_argop4_u.opsecinfo.name.utf8string_val =
comp.utf8string_val;
doqueue = 1;
NFS4_DEBUG(nfs4_client_call_debug, (CE_NOTE,
"nfs4secinfo_otw: %s call, mi 0x%p",
needrecov ? "recov" : "first", (void*)mi));
rfs4call(mi, &args, &res, cr, &doqueue, RFSCALL_SOFT, &e);
needrecov = nfs4_needs_recovery(&e, FALSE, mi->mi_vfsp);
if (e.error && !needrecov) {
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop, lookuparg.arglen * sizeof (nfs_argop4));
kmem_free(tmp_path, path_len + 1);
return (e.error);
}
if (res.status == NFS4ERR_WRONGSEC) {
if (res.array_len == 1) {
ncomp = tcomp;
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop,
lookuparg.arglen * sizeof (nfs_argop4));
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
kmem_free(tmp_path, path_len + 1);
if (e.error = secinfo_tryroot_otw(mi, cr)) {
return (e.error);
}
goto retry;
}
ncomp = res.array_len - 1;
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop, lookuparg.arglen * sizeof (nfs_argop4));
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
kmem_free(tmp_path, path_len + 1);
goto retry;
}
if (needrecov) {
bool_t abort;
if (!isrecov) {
if (!e.error) {
e.error = geterrno4(res.status);
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
}
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop,
lookuparg.arglen * sizeof (nfs_argop4));
kmem_free(tmp_path, path_len + 1);
return (e.error);
}
NFS4_DEBUG(nfs4_client_recov_debug, (CE_NOTE,
"nfs4secinfo_otw: recovery in a recovery thread\n"));
abort = nfs4_start_recovery(&e, mi, NULL,
NULL, NULL, NULL, OP_SECINFO, NULL, NULL, NULL);
if (!e.error) {
e.error = geterrno4(res.status);
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
}
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop, lookuparg.arglen * sizeof (nfs_argop4));
kmem_free(tmp_path, path_len + 1);
if (abort == FALSE) {
return (0);
}
return (e.error);
}
if (res.status) {
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop, lookuparg.arglen * sizeof (nfs_argop4));
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
kmem_free(tmp_path, path_len + 1);
return (geterrno4(res.status));
}
numops = res.array_len;
resop = &res.array[numops-1];
ASSERT(resop->resop == OP_SECINFO);
if (resop->nfs_resop4_u.opsecinfo.SECINFO4resok_len == 0) {
nfs4args_lookup_free(argop, num_argops);
kmem_free(tmp_path, path_len + 1);
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
kmem_free(argop, num_argops * sizeof (nfs_argop4));
return (EACCES);
}
secinfo_update(mi->mi_curr_serv, &resop->nfs_resop4_u.opsecinfo);
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_READER, 0);
if (svp->sv_secinfo == NULL) {
nfs_rw_exit(&svp->sv_lock);
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop, lookuparg.arglen * sizeof (nfs_argop4));
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
kmem_free(tmp_path, path_len + 1);
return (EACCES);
}
nfs_rw_exit(&svp->sv_lock);
if (ncomp != tcomp) {
ncomp = tcomp;
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop, lookuparg.arglen * sizeof (nfs_argop4));
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
kmem_free(tmp_path, path_len + 1);
goto retry;
}
nfs4args_lookup_free(argop, num_argops);
kmem_free(argop, lookuparg.arglen * sizeof (nfs_argop4));
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
kmem_free(tmp_path, path_len + 1);
return (0);
}
int
nfs4_secinfo_path(mntinfo4_t *mi, cred_t *cr, int isrecov)
{
int error = 0;
int ncomp;
servinfo4_t *svp = mi->mi_curr_serv;
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_READER, 0);
ASSERT(svp->sv_path != NULL);
ncomp = comp_total(svp->sv_path);
if (ncomp == 0) {
if (svp->sv_secinfo == NULL) {
nfs_rw_exit(&svp->sv_lock);
secinfo_update(svp, secinfo_support);
return (0);
}
nfs_rw_exit(&svp->sv_lock);
if (secinfo_check(svp))
return (0);
return (EACCES);
}
nfs_rw_exit(&svp->sv_lock);
error = nfs4secinfo_otw(mi, cr, svp, isrecov);
if (error) {
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_WRITER, 0);
if (svp->sv_secinfo) {
if (svp->sv_save_secinfo == svp->sv_secinfo) {
svp->sv_save_secinfo = NULL;
svp->sv_savesec = NULL;
}
secinfo_free(svp->sv_secinfo);
svp->sv_secinfo = NULL;
svp->sv_currsec = NULL;
svp->sv_flags &= ~SV4_TRYSECINFO;
}
if (svp->sv_save_secinfo) {
secinfo_free(svp->sv_save_secinfo);
svp->sv_save_secinfo = NULL;
svp->sv_savesec = NULL;
}
nfs_rw_exit(&svp->sv_lock);
}
return (error);
}
int
nfs4_secinfo_fh_otw(mntinfo4_t *mi, nfs4_sharedfh_t *fh, char *nm, cred_t *cr)
{
COMPOUND4args_clnt args;
COMPOUND4res_clnt res;
nfs_argop4 argop[2];
nfs_resop4 *resop;
int num_argops, doqueue;
nfs4_error_t e = { 0, NFS4_OK, RPC_SUCCESS };
servinfo4_t *svp;
ASSERT(strlen(nm) > 0);
num_argops = 2;
args.ctag = TAG_SECINFO;
args.array_len = num_argops;
args.array = argop;
argop[0].argop = OP_CPUTFH;
argop[0].nfs_argop4_u.opcputfh.sfh = fh;
argop[1].argop = OP_CSECINFO;
argop[1].nfs_argop4_u.opcsecinfo.cname = nm;
doqueue = 1;
rfs4call(mi, &args, &res, cr, &doqueue, RFSCALL_SOFT, &e);
if (e.error)
return (e.error);
if (res.status) {
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
return (geterrno4(res.status));
}
resop = &res.array[1];
ASSERT(resop->resop == OP_SECINFO);
if (resop->nfs_resop4_u.opsecinfo.SECINFO4resok_len == 0) {
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
return (EACCES);
}
secinfo_update(mi->mi_curr_serv, &resop->nfs_resop4_u.opsecinfo);
svp = mi->mi_curr_serv;
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_READER, 0);
if (mi->mi_curr_serv->sv_secinfo == NULL) {
nfs_rw_exit(&svp->sv_lock);
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
return (EACCES);
}
nfs_rw_exit(&svp->sv_lock);
xdr_free(xdr_COMPOUND4res_clnt, (caddr_t)&res);
return (0);
}
int
nfs4_secinfo_vnode_otw(vnode_t *dvp, char *nm, cred_t *cr)
{
ASSERT(strlen(nm) > 0);
return (nfs4_secinfo_fh_otw(VTOMI4(dvp), VTOR4(dvp)->r_fh, nm, cr));
}
int
nfs4_secinfo_vnode(vnode_t *vp, cred_t *cr, int isrecov)
{
svnode_t *svp = VTOSV(vp);
char *nm;
int error = 0;
if (svp->sv_dfh) {
nm = fn_name(svp->sv_name);
error = nfs4_secinfo_fh_otw(VTOMI4(vp), svp->sv_dfh, nm, cr);
kmem_free(nm, MAXNAMELEN);
} else {
error = nfs4_secinfo_path(VTOMI4(vp), cr, isrecov);
}
return (error);
}
int
nfs4_secinfo_recov(mntinfo4_t *mi, vnode_t *vp1, vnode_t *vp2)
{
int error = 0;
cred_t *cr, *lcr = NULL;
servinfo4_t *svp = mi->mi_curr_serv;
(void) nfs_rw_enter_sig(&svp->sv_lock, RW_READER, 0);
if (! (svp->sv_flags & SV4_TRYSECDEFAULT)) {
error = geterrno4(NFS4ERR_WRONGSEC);
nfs_rw_exit(&svp->sv_lock);
} else {
cr = crgetcred();
if (svp->sv_secdata->uid != 0) {
lcr = crdup(cr);
(void) crsetugid(lcr, svp->sv_secdata->uid,
crgetgid(cr));
}
nfs_rw_exit(&svp->sv_lock);
if (vp1 == NULL && vp2 == NULL) {
error = nfs4_secinfo_path(mi, cr, TRUE);
if (lcr && error == EACCES)
error = nfs4_secinfo_path(mi, lcr, TRUE);
} else if (vp1) {
error = nfs4_secinfo_vnode(vp1, cr, TRUE);
if (lcr && error == EACCES)
error = nfs4_secinfo_vnode(vp1, lcr, TRUE);
}
crfree(cr);
if (lcr != NULL)
crfree(lcr);
}
mutex_enter(&mi->mi_lock);
mi->mi_recovflags &= ~MI4R_NEED_SECINFO;
mutex_exit(&mi->mi_lock);
return (error);
}