#include <sys/systm.h>
#include <rpc/auth.h>
#include <rpc/clnt.h>
#include <nfs/nfs4_kprot.h>
#include <nfs/nfs4.h>
#include <nfs/lm.h>
#include <sys/cmn_err.h>
#include <sys/disp.h>
#include <sys/sdt.h>
#include <sys/pathname.h>
#include <sys/strsubr.h>
#include <sys/ddi.h>
#include <sys/vnode.h>
#include <sys/sdt.h>
#include <inet/common.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#define MAX_READ_DELEGATIONS 5
static int rfs4_deleg_disabled;
static int rfs4_max_setup_cb_tries = 5;
#ifdef DEBUG
int rfs4_cb_null;
int rfs4_cb_debug;
int rfs4_deleg_debug;
#endif
static void rfs4_recall_file(rfs4_file_t *,
void (*recall)(rfs4_deleg_state_t *, bool_t),
bool_t, rfs4_client_t *);
static void rfs4_revoke_file(rfs4_file_t *);
static void rfs4_cb_chflush(rfs4_cbinfo_t *);
static CLIENT *rfs4_cb_getch(rfs4_cbinfo_t *);
static void rfs4_cb_freech(rfs4_cbinfo_t *, CLIENT *, bool_t);
static rfs4_deleg_state_t *rfs4_deleg_state(rfs4_state_t *,
open_delegation_type4, int *);
static int
uaddr2sockaddr(int af, char *ua, void *ap, in_port_t *pp)
{
int dots = 0, i, j, len, k;
unsigned char c;
in_port_t port = 0;
len = strlen(ua);
for (i = len-1; i >= 0; i--) {
if (ua[i] == '.')
dots++;
if (dots == 2) {
ua[i] = '\0';
k = i;
if (inet_pton(af, ua, ap) == 1) {
c = 0;
for (j = i+1; j < len; j++) {
if (ua[j] == '.') {
port = c << 8;
c = 0;
} else if (ua[j] >= '0' &&
ua[j] <= '9') {
c *= 10;
c += ua[j] - '0';
} else {
ua[k] = '.';
return (EINVAL);
}
}
port += c;
*pp = htons(port);
ua[k] = '.';
return (0);
} else {
ua[k] = '.';
return (EINVAL);
}
}
}
return (EINVAL);
}
void
rfs4_set_deleg_policy(nfs4_srv_t *nsrv4, srv_deleg_policy_t new_policy)
{
rw_enter(&nsrv4->deleg_policy_lock, RW_WRITER);
nsrv4->nfs4_deleg_policy = new_policy;
rw_exit(&nsrv4->deleg_policy_lock);
}
void
rfs4_hold_deleg_policy(nfs4_srv_t *nsrv4)
{
rw_enter(&nsrv4->deleg_policy_lock, RW_READER);
}
void
rfs4_rele_deleg_policy(nfs4_srv_t *nsrv4)
{
rw_exit(&nsrv4->deleg_policy_lock);
}
srv_deleg_policy_t
nfs4_get_deleg_policy()
{
nfs4_srv_t *nsrv4 = nfs4_get_srv();
return (nsrv4->nfs4_deleg_policy);
}
void
rfs4_cbinfo_free(rfs4_cbinfo_t *cbp)
{
char *addr = cbp->cb_callback.cb_location.r_addr;
char *netid = cbp->cb_callback.cb_location.r_netid;
if (addr)
kmem_free(addr, strlen(addr) + 1);
if (netid)
kmem_free(netid, strlen(netid) + 1);
addr = cbp->cb_newer.cb_callback.cb_location.r_addr;
netid = cbp->cb_newer.cb_callback.cb_location.r_netid;
if (addr)
kmem_free(addr, strlen(addr) + 1);
if (netid)
kmem_free(netid, strlen(netid) + 1);
if (cbp->cb_chc_free) {
rfs4_cb_chflush(cbp);
}
}
static void
rfs4_do_cb_null(rfs4_client_t *cp)
{
struct timeval tv;
CLIENT *ch;
rfs4_cbstate_t newstate;
rfs4_cbinfo_t *cbp = &cp->rc_cbinfo;
mutex_enter(cbp->cb_lock);
if (cbp->cb_nullcaller == TRUE) {
mutex_exit(cbp->cb_lock);
rfs4_client_rele(cp);
zthread_exit();
}
cbp->cb_nullcaller = TRUE;
while (cbp->cb_refcnt != 0)
cv_wait(cbp->cb_cv_nullcaller, cbp->cb_lock);
retry:
if (cbp->cb_newer.cb_new == TRUE &&
cbp->cb_newer.cb_confirmed == TRUE) {
char *addr = cbp->cb_callback.cb_location.r_addr;
char *netid = cbp->cb_callback.cb_location.r_netid;
if (addr)
kmem_free(addr, strlen(addr) + 1);
if (netid)
kmem_free(netid, strlen(netid) + 1);
cbp->cb_callback.cb_location.r_addr =
cbp->cb_newer.cb_callback.cb_location.r_addr;
cbp->cb_newer.cb_callback.cb_location.r_addr = NULL;
cbp->cb_callback.cb_location.r_netid =
cbp->cb_newer.cb_callback.cb_location.r_netid;
cbp->cb_newer.cb_callback.cb_location.r_netid = NULL;
cbp->cb_callback.cb_program =
cbp->cb_newer.cb_callback.cb_program;
cbp->cb_newer.cb_callback.cb_program = 0;
cbp->cb_ident = cbp->cb_newer.cb_ident;
cbp->cb_newer.cb_ident = 0;
cbp->cb_newer.cb_new = FALSE;
cbp->cb_newer.cb_confirmed = FALSE;
rfs4_cb_chflush(cbp);
cbp->cb_state = CB_NONE;
cbp->cb_timefailed = 0;
cbp->cb_notified_of_cb_path_down = TRUE;
}
if (cbp->cb_state != CB_NONE) {
cv_broadcast(cbp->cb_cv);
cbp->cb_nullcaller = FALSE;
mutex_exit(cbp->cb_lock);
rfs4_client_rele(cp);
zthread_exit();
}
cbp->cb_state = CB_INPROG;
mutex_exit(cbp->cb_lock);
if ((ch = rfs4_cb_getch(cbp)) == NULL) {
mutex_enter(cbp->cb_lock);
cbp->cb_state = CB_BAD;
cbp->cb_timefailed = gethrestime_sec();
goto retry;
}
tv.tv_sec = 30;
tv.tv_usec = 0;
if (clnt_call(ch, CB_NULL, xdr_void, NULL, xdr_void, NULL, tv) != 0) {
newstate = CB_BAD;
} else {
newstate = CB_OK;
#ifdef DEBUG
rfs4_cb_null++;
#endif
}
mutex_enter(cbp->cb_lock);
rfs4_cb_freech(cbp, ch, TRUE);
if (cbp->cb_newer.cb_new == TRUE &&
cbp->cb_newer.cb_confirmed == TRUE) {
goto retry;
}
cbp->cb_state = newstate;
if (cbp->cb_state == CB_BAD)
cbp->cb_timefailed = gethrestime_sec();
cv_broadcast(cbp->cb_cv);
cbp->cb_nullcaller = FALSE;
mutex_exit(cbp->cb_lock);
rfs4_client_rele(cp);
zthread_exit();
}
static rfs4_cbinfo_t *
rfs4_cbinfo_hold(rfs4_client_t *cp)
{
rfs4_cbinfo_t *cbp = &cp->rc_cbinfo;
int retries = 0;
mutex_enter(cbp->cb_lock);
while (cbp->cb_newer.cb_new == TRUE && cbp->cb_nullcaller == FALSE) {
mutex_exit(cbp->cb_lock);
rfs4_dbe_hold(cp->rc_dbe);
rfs4_do_cb_null(cp);
mutex_enter(cbp->cb_lock);
if (cbp->cb_newer.cb_new == FALSE || cbp->cb_nullcaller == TRUE)
break;
mutex_exit(cbp->cb_lock);
if (++retries >= rfs4_max_setup_cb_tries)
return (NULL);
delay(hz);
mutex_enter(cbp->cb_lock);
}
if (cbp->cb_nullcaller == TRUE)
cv_wait(cbp->cb_cv, cbp->cb_lock);
if (cbp->cb_state != CB_OK) {
mutex_exit(cbp->cb_lock);
return (NULL);
}
cbp->cb_refcnt++;
mutex_exit(cbp->cb_lock);
return (cbp);
}
static int
rfs4_cbinfo_rele(rfs4_cbinfo_t *cbp, rfs4_cbstate_t newstate)
{
int cb_new = FALSE;
mutex_enter(cbp->cb_lock);
if (newstate != CB_NOCHANGE)
cbp->cb_state = newstate;
if (newstate == CB_FAILED) {
cbp->cb_timefailed = gethrestime_sec();
cbp->cb_notified_of_cb_path_down = FALSE;
}
cbp->cb_refcnt--;
if (cbp->cb_refcnt == 0 && cbp->cb_nullcaller)
cv_broadcast(cbp->cb_cv_nullcaller);
if (cbp->cb_refcnt == 0 &&
cbp->cb_nullcaller == FALSE &&
cbp->cb_newer.cb_new == TRUE &&
cbp->cb_newer.cb_confirmed == TRUE)
cb_new = TRUE;
mutex_exit(cbp->cb_lock);
return (cb_new);
}
static CLIENT *
rfs4_cbch_init(rfs4_cbinfo_t *cbp)
{
struct knetconfig knc;
vnode_t *vp;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
void *addr, *taddr;
in_port_t *pp;
int af;
char *devnam;
struct netbuf nb;
int size;
CLIENT *ch = NULL;
int useresvport = 0;
mutex_enter(cbp->cb_lock);
if (cbp->cb_callback.cb_location.r_netid == NULL ||
cbp->cb_callback.cb_location.r_addr == NULL) {
goto cb_init_out;
}
if (strcmp(cbp->cb_callback.cb_location.r_netid, "tcp") == 0) {
knc.knc_semantics = NC_TPI_COTS;
knc.knc_protofmly = "inet";
knc.knc_proto = "tcp";
devnam = "/dev/tcp";
af = AF_INET;
} else if (strcmp(cbp->cb_callback.cb_location.r_netid, "udp")
== 0) {
knc.knc_semantics = NC_TPI_CLTS;
knc.knc_protofmly = "inet";
knc.knc_proto = "udp";
devnam = "/dev/udp";
af = AF_INET;
} else if (strcmp(cbp->cb_callback.cb_location.r_netid, "tcp6")
== 0) {
knc.knc_semantics = NC_TPI_COTS;
knc.knc_protofmly = "inet6";
knc.knc_proto = "tcp";
devnam = "/dev/tcp6";
af = AF_INET6;
} else if (strcmp(cbp->cb_callback.cb_location.r_netid, "udp6")
== 0) {
knc.knc_semantics = NC_TPI_CLTS;
knc.knc_protofmly = "inet6";
knc.knc_proto = "udp";
devnam = "/dev/udp6";
af = AF_INET6;
} else {
goto cb_init_out;
}
if (lookupname(devnam, UIO_SYSSPACE, FOLLOW, NULLVPP, &vp) != 0) {
goto cb_init_out;
}
if (vp->v_type != VCHR) {
VN_RELE(vp);
goto cb_init_out;
}
knc.knc_rdev = vp->v_rdev;
VN_RELE(vp);
if (af == AF_INET) {
size = sizeof (addr4);
bzero(&addr4, size);
addr4.sin_family = (sa_family_t)af;
addr = &addr4.sin_addr;
pp = &addr4.sin_port;
taddr = &addr4;
} else {
size = sizeof (addr6);
bzero(&addr6, size);
addr6.sin6_family = (sa_family_t)af;
addr = &addr6.sin6_addr;
pp = &addr6.sin6_port;
taddr = &addr6;
}
if (uaddr2sockaddr(af,
cbp->cb_callback.cb_location.r_addr, addr, pp)) {
goto cb_init_out;
}
nb.maxlen = nb.len = size;
nb.buf = (char *)taddr;
if (clnt_tli_kcreate(&knc, &nb, cbp->cb_callback.cb_program,
NFS_CB, 0, 0, curthread->t_cred, &ch)) {
ch = NULL;
}
(void) CLNT_CONTROL(ch, CLSET_BINDRESVPORT, (char *)&useresvport);
cb_init_out:
mutex_exit(cbp->cb_lock);
return (ch);
}
static void
rfs4_cb_chflush(rfs4_cbinfo_t *cbp)
{
CLIENT *ch;
while (cbp->cb_chc_free) {
cbp->cb_chc_free--;
ch = cbp->cb_chc[cbp->cb_chc_free];
cbp->cb_chc[cbp->cb_chc_free] = NULL;
if (ch) {
if (ch->cl_auth)
auth_destroy(ch->cl_auth);
clnt_destroy(ch);
}
}
}
static CLIENT *
rfs4_cb_getch(rfs4_cbinfo_t *cbp)
{
CLIENT *cbch = NULL;
uint32_t zilch = 0;
mutex_enter(cbp->cb_lock);
if (cbp->cb_chc_free) {
cbp->cb_chc_free--;
cbch = cbp->cb_chc[ cbp->cb_chc_free ];
mutex_exit(cbp->cb_lock);
(void) CLNT_CONTROL(cbch, CLSET_XID, (char *)&zilch);
return (cbch);
}
mutex_exit(cbp->cb_lock);
cbch = rfs4_cbch_init(cbp);
return (cbch);
}
static void
rfs4_cb_freech(rfs4_cbinfo_t *cbp, CLIENT *ch, bool_t lockheld)
{
if (lockheld == FALSE)
mutex_enter(cbp->cb_lock);
if (cbp->cb_chc_free < RFS4_CBCH_MAX) {
cbp->cb_chc[ cbp->cb_chc_free++ ] = ch;
if (lockheld == FALSE)
mutex_exit(cbp->cb_lock);
return;
}
if (lockheld == FALSE)
mutex_exit(cbp->cb_lock);
if (ch->cl_auth)
auth_destroy(ch->cl_auth);
clnt_destroy(ch);
}
void
rfs4_client_setcb(rfs4_client_t *cp, cb_client4 *cb, uint32_t cb_ident)
{
char *addr = NULL;
char *netid = NULL;
rfs4_cbinfo_t *cbp = &cp->rc_cbinfo;
size_t len;
if (cb->cb_location.r_addr && cb->cb_location.r_addr[0] != '\0' &&
cb->cb_location.r_netid && cb->cb_location.r_netid[0] != '\0') {
len = strlen(cb->cb_location.r_addr) + 1;
addr = kmem_alloc(len, KM_SLEEP);
bcopy(cb->cb_location.r_addr, addr, len);
len = strlen(cb->cb_location.r_netid) + 1;
netid = kmem_alloc(len, KM_SLEEP);
bcopy(cb->cb_location.r_netid, netid, len);
}
mutex_enter(cbp->cb_lock);
cbp->cb_newer.cb_callback.cb_program = cb->cb_program;
if (cbp->cb_newer.cb_callback.cb_location.r_addr != NULL)
kmem_free(cbp->cb_newer.cb_callback.cb_location.r_addr,
strlen(cbp->cb_newer.cb_callback.cb_location.r_addr) + 1);
cbp->cb_newer.cb_callback.cb_location.r_addr = addr;
if (cbp->cb_newer.cb_callback.cb_location.r_netid != NULL)
kmem_free(cbp->cb_newer.cb_callback.cb_location.r_netid,
strlen(cbp->cb_newer.cb_callback.cb_location.r_netid) + 1);
cbp->cb_newer.cb_callback.cb_location.r_netid = netid;
cbp->cb_newer.cb_ident = cb_ident;
if (addr && *addr && netid && *netid) {
cbp->cb_newer.cb_new = TRUE;
cbp->cb_newer.cb_confirmed = FALSE;
} else {
cbp->cb_newer.cb_new = FALSE;
cbp->cb_newer.cb_confirmed = FALSE;
}
mutex_exit(cbp->cb_lock);
}
void
rfs4_deleg_cb_check(rfs4_client_t *cp)
{
if (cp->rc_cbinfo.cb_newer.cb_new == FALSE)
return;
cp->rc_cbinfo.cb_newer.cb_confirmed = TRUE;
rfs4_dbe_hold(cp->rc_dbe);
(void) zthread_create(NULL, 0, rfs4_do_cb_null, cp, 0,
minclsyspri);
}
static void
rfs4args_cb_recall_free(nfs_cb_argop4 *argop)
{
CB_RECALL4args *rec_argp;
rec_argp = &argop->nfs_cb_argop4_u.opcbrecall;
if (rec_argp->fh.nfs_fh4_val)
kmem_free(rec_argp->fh.nfs_fh4_val, rec_argp->fh.nfs_fh4_len);
}
static void
rfs4args_cb_getattr_free(nfs_cb_argop4 *argop)
{
CB_GETATTR4args *argp;
argp = &argop->nfs_cb_argop4_u.opcbgetattr;
if (argp->fh.nfs_fh4_val)
kmem_free(argp->fh.nfs_fh4_val, argp->fh.nfs_fh4_len);
}
static void
rfs4freeargres(CB_COMPOUND4args *args, CB_COMPOUND4res *resp)
{
int i, arglen;
nfs_cb_argop4 *argop;
arglen = args->array_len;
argop = args->array;
for (i = 0; i < arglen; i++, argop++) {
switch (argop->argop) {
case OP_CB_RECALL:
rfs4args_cb_recall_free(argop);
break;
case OP_CB_GETATTR:
rfs4args_cb_getattr_free(argop);
break;
default:
return;
}
}
if (args->tag.utf8string_len > 0)
UTF8STRING_FREE(args->tag)
kmem_free(args->array, arglen * sizeof (nfs_cb_argop4));
if (resp)
xdr_free(xdr_CB_COMPOUND4res, (caddr_t)resp);
}
static enum clnt_stat
rfs4_do_callback(rfs4_client_t *cp, CB_COMPOUND4args *args,
CB_COMPOUND4res *res, struct timeval timeout)
{
rfs4_cbinfo_t *cbp;
CLIENT *ch;
enum clnt_stat stat = RPC_FAILED;
res->tag.utf8string_val = NULL;
res->array = NULL;
retry:
cbp = rfs4_cbinfo_hold(cp);
if (cbp == NULL)
return (stat);
if ((ch = rfs4_cb_getch(cbp)) != NULL) {
args->callback_ident = cbp->cb_ident;
stat = clnt_call(ch, CB_COMPOUND, xdr_CB_COMPOUND4args_srv,
(caddr_t)args, xdr_CB_COMPOUND4res,
(caddr_t)res, timeout);
rfs4_cb_freech(cbp, ch, FALSE);
}
if (rfs4_cbinfo_rele(cbp,
(stat == RPC_SUCCESS ? CB_NOCHANGE : CB_FAILED)) == TRUE)
goto retry;
return (stat);
}
nfsstat4
rfs4_vop_getattr(vnode_t *vp, vattr_t *vap, int flag, cred_t *cr)
{
int error;
error = VOP_GETATTR(vp, vap, flag, cr, NULL);
return (puterrno4(error));
}
int
rfs4_delegated_getattr(vnode_t *vp, vattr_t *vap, int flag, cred_t *cr)
{
return (VOP_GETATTR(vp, vap, flag, cr, NULL));
}
static void
rfs4_do_cb_recall(rfs4_deleg_state_t *dsp, bool_t trunc)
{
CB_COMPOUND4args cb4_args;
CB_COMPOUND4res cb4_res;
CB_RECALL4args *rec_argp;
CB_RECALL4res *rec_resp;
nfs_cb_argop4 *argop;
int numops;
int argoplist_size;
struct timeval timeout;
nfs_fh4 *fhp;
enum clnt_stat call_stat;
numops = 1;
argoplist_size = numops * sizeof (nfs_cb_argop4);
argop = kmem_zalloc(argoplist_size, KM_SLEEP);
argop->argop = OP_CB_RECALL;
rec_argp = &argop->nfs_cb_argop4_u.opcbrecall;
(void) str_to_utf8("cb_recall", &cb4_args.tag);
cb4_args.minorversion = CB4_MINORVERSION;
cb4_args.array_len = numops;
cb4_args.array = argop;
bcopy(&dsp->rds_delegid.stateid, &rec_argp->stateid, sizeof (stateid4));
rec_argp->truncate = trunc;
fhp = &dsp->rds_finfo->rf_filehandle;
rec_argp->fh.nfs_fh4_val = kmem_alloc(sizeof (char) *
fhp->nfs_fh4_len, KM_SLEEP);
nfs_fh4_copy(fhp, &rec_argp->fh);
dsp->rds_time_recalled = gethrestime_sec();
timeout.tv_sec = (rfs4_lease_time * 80) / 100;
timeout.tv_usec = 0;
DTRACE_NFSV4_3(cb__recall__start, rfs4_client_t *, dsp->rds_client,
rfs4_deleg_state_t *, dsp, CB_RECALL4args *, rec_argp);
call_stat = rfs4_do_callback(dsp->rds_client, &cb4_args, &cb4_res,
timeout);
rec_resp = (cb4_res.array_len == 0) ? NULL :
&cb4_res.array[0].nfs_cb_resop4_u.opcbrecall;
DTRACE_NFSV4_3(cb__recall__done, rfs4_client_t *, dsp->rds_client,
rfs4_deleg_state_t *, dsp, CB_RECALL4res *, rec_resp);
if (call_stat != RPC_SUCCESS || cb4_res.status != NFS4_OK) {
rfs4_return_deleg(dsp, TRUE);
}
rfs4freeargres(&cb4_args, &cb4_res);
}
struct recall_arg {
rfs4_deleg_state_t *dsp;
void (*recall)(rfs4_deleg_state_t *, bool_t trunc);
bool_t trunc;
};
static void
do_recall(struct recall_arg *arg)
{
rfs4_deleg_state_t *dsp = arg->dsp;
rfs4_file_t *fp = dsp->rds_finfo;
callb_cpr_t cpr_info;
kmutex_t cpr_lock;
mutex_init(&cpr_lock, NULL, MUTEX_DEFAULT, NULL);
CALLB_CPR_INIT(&cpr_info, &cpr_lock, callb_generic_cpr, "nfsv4Recall");
if (dsp->rds_dtype != OPEN_DELEGATE_NONE) {
DTRACE_PROBE3(nfss__i__recall,
struct recall_arg *, arg,
struct rfs4_deleg_state_t *, dsp,
struct rfs4_file_t *, fp);
if (arg->recall)
(void) (*arg->recall)(dsp, arg->trunc);
}
mutex_enter(fp->rf_dinfo.rd_recall_lock);
fp->rf_dinfo.rd_recall_count--;
if (fp->rf_dinfo.rd_recall_count == 0)
cv_signal(fp->rf_dinfo.rd_recall_cv);
mutex_exit(fp->rf_dinfo.rd_recall_lock);
mutex_enter(&cpr_lock);
CALLB_CPR_EXIT(&cpr_info);
mutex_destroy(&cpr_lock);
rfs4_deleg_state_rele(dsp);
kmem_free(arg, sizeof (struct recall_arg));
zthread_exit();
}
struct master_recall_args {
rfs4_file_t *fp;
void (*recall)(rfs4_deleg_state_t *, bool_t);
bool_t trunc;
};
static void
do_recall_file(struct master_recall_args *map)
{
rfs4_file_t *fp = map->fp;
rfs4_deleg_state_t *dsp;
struct recall_arg *arg;
callb_cpr_t cpr_info;
kmutex_t cpr_lock;
int32_t recall_count;
rfs4_dbe_lock(fp->rf_dbe);
mutex_enter(fp->rf_dinfo.rd_recall_lock);
if (fp->rf_dinfo.rd_recall_count != 0) {
mutex_exit(fp->rf_dinfo.rd_recall_lock);
rfs4_dbe_rele_nolock(fp->rf_dbe);
rfs4_dbe_unlock(fp->rf_dbe);
kmem_free(map, sizeof (struct master_recall_args));
zthread_exit();
}
mutex_exit(fp->rf_dinfo.rd_recall_lock);
mutex_init(&cpr_lock, NULL, MUTEX_DEFAULT, NULL);
CALLB_CPR_INIT(&cpr_info, &cpr_lock, callb_generic_cpr, "v4RecallFile");
recall_count = 0;
for (dsp = list_head(&fp->rf_delegstatelist); dsp != NULL;
dsp = list_next(&fp->rf_delegstatelist, dsp)) {
rfs4_dbe_lock(dsp->rds_dbe);
if (rfs4_dbe_is_invalid(dsp->rds_dbe)) {
rfs4_dbe_unlock(dsp->rds_dbe);
continue;
}
rfs4_dbe_hold(dsp->rds_dbe);
rfs4_dbe_unlock(dsp->rds_dbe);
arg = kmem_alloc(sizeof (struct recall_arg), KM_SLEEP);
arg->recall = map->recall;
arg->trunc = map->trunc;
arg->dsp = dsp;
recall_count++;
(void) zthread_create(NULL, 0, do_recall, arg, 0,
minclsyspri);
}
rfs4_dbe_unlock(fp->rf_dbe);
mutex_enter(fp->rf_dinfo.rd_recall_lock);
fp->rf_dinfo.rd_recall_count += recall_count;
while (fp->rf_dinfo.rd_recall_count)
cv_wait(fp->rf_dinfo.rd_recall_cv, fp->rf_dinfo.rd_recall_lock);
mutex_exit(fp->rf_dinfo.rd_recall_lock);
DTRACE_PROBE1(nfss__i__recall_done, rfs4_file_t *, fp);
rfs4_file_rele(fp);
kmem_free(map, sizeof (struct master_recall_args));
mutex_enter(&cpr_lock);
CALLB_CPR_EXIT(&cpr_info);
mutex_destroy(&cpr_lock);
zthread_exit();
}
static void
rfs4_recall_file(rfs4_file_t *fp,
void (*recall)(rfs4_deleg_state_t *, bool_t trunc),
bool_t trunc, rfs4_client_t *cp)
{
struct master_recall_args *args;
rfs4_dbe_lock(fp->rf_dbe);
if (fp->rf_dinfo.rd_dtype == OPEN_DELEGATE_NONE) {
rfs4_dbe_unlock(fp->rf_dbe);
return;
}
rfs4_dbe_hold(fp->rf_dbe);
if (fp->rf_dinfo.rd_time_recalled == 0)
fp->rf_dinfo.rd_time_recalled = gethrestime_sec();
fp->rf_dinfo.rd_ever_recalled = TRUE;
if (cp)
fp->rf_dinfo.rd_conflicted_client = cp->rc_clientid;
rfs4_dbe_unlock(fp->rf_dbe);
args = kmem_alloc(sizeof (struct master_recall_args), KM_SLEEP);
args->fp = fp;
args->recall = recall;
args->trunc = trunc;
(void) zthread_create(NULL, 0, do_recall_file, args, 0,
minclsyspri);
}
void
rfs4_recall_deleg(rfs4_file_t *fp, bool_t trunc, rfs4_client_t *cp)
{
time_t elapsed1, elapsed2;
if (fp->rf_dinfo.rd_time_recalled != 0) {
elapsed1 = gethrestime_sec() - fp->rf_dinfo.rd_time_recalled;
elapsed2 = gethrestime_sec() - fp->rf_dinfo.rd_time_lastwrite;
if (elapsed1 > rfs4_lease_time &&
elapsed2 > rfs4_lease_time) {
rfs4_revoke_file(fp);
return;
}
if (elapsed1 <= ((rfs4_lease_time * 20) / 100))
return;
}
rfs4_recall_file(fp, rfs4_do_cb_recall, trunc, cp);
}
bool_t
rfs4_check_recall(rfs4_state_t *sp, uint32_t access)
{
open_delegation_type4 dtype = sp->rs_finfo->rf_dinfo.rd_dtype;
switch (dtype) {
case OPEN_DELEGATE_NONE:
return (FALSE);
case OPEN_DELEGATE_READ:
if (access == OPEN4_SHARE_ACCESS_READ)
return (FALSE);
else
return (TRUE);
case OPEN_DELEGATE_WRITE:
return (rfs4_is_deleg(sp));
}
return (FALSE);
}
static open_delegation_type4
rfs4_check_delegation(rfs4_state_t *sp, rfs4_file_t *fp)
{
open_delegation_type4 dtype = fp->rf_dinfo.rd_dtype;
uint32_t access = sp->rs_share_access;
uint32_t deny = sp->rs_share_deny;
int readcnt = 0;
int writecnt = 0;
switch (dtype) {
case OPEN_DELEGATE_NONE:
if (access & OPEN4_SHARE_ACCESS_WRITE)
writecnt++;
if (access & OPEN4_SHARE_ACCESS_READ)
readcnt++;
if (fp->rf_access_read > readcnt ||
fp->rf_access_write > writecnt)
return (OPEN_DELEGATE_NONE);
if ((access & OPEN4_SHARE_ACCESS_WRITE) ||
(deny & (OPEN4_SHARE_DENY_READ | OPEN4_SHARE_DENY_WRITE)))
return (OPEN_DELEGATE_WRITE);
if ((access & ~OPEN4_SHARE_ACCESS_WRITE) ||
(deny & ~OPEN4_SHARE_DENY_READ))
return (OPEN_DELEGATE_READ);
return (OPEN_DELEGATE_NONE);
case OPEN_DELEGATE_READ:
if ((access & OPEN4_SHARE_ACCESS_WRITE) ||
(deny & OPEN4_SHARE_DENY_READ))
return (OPEN_DELEGATE_NONE);
return (OPEN_DELEGATE_READ);
case OPEN_DELEGATE_WRITE:
return (OPEN_DELEGATE_WRITE);
}
return (OPEN_DELEGATE_NONE);
}
static open_delegation_type4
rfs4_delegation_policy(nfs4_srv_t *nsrv4, open_delegation_type4 dtype,
rfs4_dinfo_t *dinfo, clientid4 cid)
{
time_t elapsed;
if (nsrv4->nfs4_deleg_policy != SRV_NORMAL_DELEGATE)
return (OPEN_DELEGATE_NONE);
if (dinfo->rd_ever_recalled == TRUE) {
if (dinfo->rd_conflicted_client != cid) {
elapsed = gethrestime_sec() - dinfo->rd_time_returned;
if (elapsed < rfs4_lease_time)
return (OPEN_DELEGATE_NONE);
}
}
if (dtype == OPEN_DELEGATE_READ &&
dinfo->rd_rdgrants > MAX_READ_DELEGATIONS)
return (OPEN_DELEGATE_NONE);
return (dtype);
}
rfs4_deleg_state_t *
rfs4_grant_delegation(delegreq_t dreq, rfs4_state_t *sp, int *recall)
{
nfs4_srv_t *nsrv4;
rfs4_file_t *fp = sp->rs_finfo;
open_delegation_type4 dtype;
int no_delegation;
ASSERT(rfs4_dbe_islocked(sp->rs_dbe));
ASSERT(rfs4_dbe_islocked(fp->rf_dbe));
nsrv4 = nfs4_get_srv();
if (nsrv4->nfs4_deleg_policy == SRV_NEVER_DELEGATE ||
dreq == DELEG_NONE) {
return (NULL);
}
mutex_enter(&nsrv4->deleg_lock);
no_delegation = rfs4_deleg_disabled;
mutex_exit(&nsrv4->deleg_lock);
if (no_delegation)
return (NULL);
if (fp->rf_dinfo.rd_hold_grant > 0) {
return (NULL);
}
if (lm_vp_active(fp->rf_vp)) {
return (NULL);
}
switch (dreq) {
case DELEG_READ:
case DELEG_WRITE:
dtype = (open_delegation_type4)dreq;
*recall = 1;
break;
case DELEG_ANY:
if (sp->rs_owner->ro_client->rc_cbinfo.cb_state != CB_OK)
return (NULL);
if (fp->rf_dinfo.rd_time_rm_delayed > 0 &&
gethrestime_sec() >
fp->rf_dinfo.rd_time_rm_delayed + rfs4_lease_time)
fp->rf_dinfo.rd_time_rm_delayed = 0;
if (fp->rf_dinfo.rd_time_recalled != 0 ||
fp->rf_dinfo.rd_time_rm_delayed != 0)
return (NULL);
dtype = rfs4_check_delegation(sp, fp);
if (dtype == OPEN_DELEGATE_NONE)
return (NULL);
dtype = rfs4_delegation_policy(nsrv4, dtype, &fp->rf_dinfo,
sp->rs_owner->ro_client->rc_clientid);
if (dtype == OPEN_DELEGATE_NONE)
return (NULL);
break;
default:
return (NULL);
}
return (rfs4_deleg_state(sp, dtype, recall));
}
void
rfs4_set_deleg_response(rfs4_deleg_state_t *dsp, open_delegation4 *dp,
nfsace4 *ace, int recall)
{
open_write_delegation4 *wp;
open_read_delegation4 *rp;
nfs_space_limit4 *spl;
nfsace4 nace;
if (ace == NULL) {
nace.type = ACE4_ACCESS_DENIED_ACE_TYPE;
nace.flag = 0;
nace.access_mask = ACE4_VALID_MASK_BITS;
(void) str_to_utf8(ACE4_WHO_EVERYONE, &nace.who);
} else {
nace.type = ace->type;
nace.flag = ace->flag;
nace.access_mask = ace->access_mask;
(void) utf8_copy(&ace->who, &nace.who);
}
dp->delegation_type = dsp->rds_dtype;
switch (dsp->rds_dtype) {
case OPEN_DELEGATE_NONE:
break;
case OPEN_DELEGATE_READ:
rp = &dp->open_delegation4_u.read;
rp->stateid = dsp->rds_delegid.stateid;
rp->recall = (bool_t)recall;
rp->permissions = nace;
break;
case OPEN_DELEGATE_WRITE:
wp = &dp->open_delegation4_u.write;
wp->stateid = dsp->rds_delegid.stateid;
wp->recall = (bool_t)recall;
spl = &wp->space_limit;
spl->limitby = NFS_LIMIT_SIZE;
spl->nfs_space_limit4_u.filesize = 0;
wp->permissions = nace;
break;
}
}
bool_t
rfs4_check_delegated_byfp(int mode, rfs4_file_t *fp,
bool_t trunc, bool_t do_delay, bool_t is_rm, clientid4 *cp)
{
rfs4_deleg_state_t *dsp;
nfs4_srv_t *nsrv4 = nfs4_get_srv();
if (nsrv4->nfs4_deleg_policy == SRV_NEVER_DELEGATE)
return (FALSE);
rfs4_dbe_lock(fp->rf_dbe);
if (fp->rf_dinfo.rd_dtype == OPEN_DELEGATE_NONE) {
if (is_rm)
fp->rf_dinfo.rd_hold_grant++;
rfs4_dbe_unlock(fp->rf_dbe);
return (FALSE);
}
if (mode == FWRITE || fp->rf_dinfo.rd_dtype == OPEN_DELEGATE_WRITE) {
if (cp != NULL) {
dsp = list_head(&fp->rf_delegstatelist);
if (dsp == NULL) {
rfs4_dbe_unlock(fp->rf_dbe);
return (FALSE);
}
if (dsp->rds_client->rc_clientid == *(cp)) {
rfs4_dbe_unlock(fp->rf_dbe);
return (FALSE);
}
}
rfs4_dbe_unlock(fp->rf_dbe);
rfs4_recall_deleg(fp, trunc, NULL);
if (!do_delay) {
rfs4_dbe_lock(fp->rf_dbe);
fp->rf_dinfo.rd_time_rm_delayed = gethrestime_sec();
rfs4_dbe_unlock(fp->rf_dbe);
return (TRUE);
}
delay(NFS4_DELEGATION_CONFLICT_DELAY);
rfs4_dbe_lock(fp->rf_dbe);
if (fp->rf_dinfo.rd_dtype != OPEN_DELEGATE_NONE) {
fp->rf_dinfo.rd_time_rm_delayed = gethrestime_sec();
rfs4_dbe_unlock(fp->rf_dbe);
return (TRUE);
}
}
if (is_rm)
fp->rf_dinfo.rd_hold_grant++;
rfs4_dbe_unlock(fp->rf_dbe);
return (FALSE);
}
bool_t
rfs4_check_delegated(int mode, vnode_t *vp, bool_t trunc)
{
nfs4_srv_t *nsrv4;
rfs4_file_t *fp;
bool_t create = FALSE;
bool_t rc = FALSE;
nsrv4 = nfs4_get_srv();
rfs4_hold_deleg_policy(nsrv4);
if (nsrv4->nfs4_deleg_policy != SRV_NEVER_DELEGATE) {
fp = rfs4_findfile(vp, NULL, &create);
if (fp != NULL) {
if (rfs4_check_delegated_byfp(mode, fp, trunc,
TRUE, FALSE, NULL)) {
rc = TRUE;
}
rfs4_file_rele(fp);
}
}
rfs4_rele_deleg_policy(nsrv4);
return (rc);
}
void
rfs4_clear_dont_grant(rfs4_file_t *fp)
{
nfs4_srv_t *nsrv4 = nfs4_get_srv();
if (nsrv4->nfs4_deleg_policy == SRV_NEVER_DELEGATE)
return;
rfs4_dbe_lock(fp->rf_dbe);
ASSERT(fp->rf_dinfo.rd_hold_grant > 0);
fp->rf_dinfo.rd_hold_grant--;
fp->rf_dinfo.rd_time_rm_delayed = 0;
rfs4_dbe_unlock(fp->rf_dbe);
}
static rfs4_deleg_state_t *
rfs4_deleg_state(rfs4_state_t *sp, open_delegation_type4 dtype, int *recall)
{
rfs4_file_t *fp = sp->rs_finfo;
bool_t create = TRUE;
rfs4_deleg_state_t *dsp;
vnode_t *vp;
int open_prev = *recall;
int ret;
int fflags = 0;
ASSERT(rfs4_dbe_islocked(sp->rs_dbe));
ASSERT(rfs4_dbe_islocked(fp->rf_dbe));
if (fp->rf_dinfo.rd_recall_count != 0 ||
(fp->rf_dinfo.rd_dtype == OPEN_DELEGATE_READ &&
dtype != OPEN_DELEGATE_READ)) {
return (NULL);
}
rfs4_dbe_unlock(fp->rf_dbe);
rfs4_dbe_unlock(sp->rs_dbe);
dsp = rfs4_finddeleg(sp, &create);
rfs4_dbe_lock(sp->rs_dbe);
rfs4_dbe_lock(fp->rf_dbe);
if (dsp == NULL)
return (NULL);
if (fp->rf_dinfo.rd_hold_grant > 0) {
rfs4_deleg_state_rele(dsp);
return (NULL);
}
if (create == FALSE) {
if (sp->rs_owner->ro_client == dsp->rds_client &&
dsp->rds_dtype == dtype) {
return (dsp);
} else {
rfs4_deleg_state_rele(dsp);
return (NULL);
}
}
if (fp->rf_dinfo.rd_recall_count != 0 ||
fp->rf_dinfo.rd_dtype == OPEN_DELEGATE_WRITE ||
(fp->rf_dinfo.rd_dtype == OPEN_DELEGATE_READ &&
dtype != OPEN_DELEGATE_READ)) {
rfs4_deleg_state_rele(dsp);
return (NULL);
}
vp = fp->rf_vp;
if (vnevent_support(vp, NULL)) {
rfs4_deleg_state_rele(dsp);
return (NULL);
}
if (sp->rs_share_access & OPEN4_SHARE_ACCESS_READ)
fflags |= FREAD;
if (sp->rs_share_access & OPEN4_SHARE_ACCESS_WRITE)
fflags |= FWRITE;
*recall = 0;
if (dtype == OPEN_DELEGATE_READ) {
if (((fflags & FWRITE) && vn_has_other_opens(vp, V_WRITE)) ||
(((fflags & FWRITE) == 0) && vn_is_opened(vp, V_WRITE)) ||
vn_is_mapped(vp, V_WRITE)) {
if (open_prev) {
*recall = 1;
} else {
rfs4_deleg_state_rele(dsp);
return (NULL);
}
}
ret = fem_install(vp, deleg_rdops, (void *)fp, OPUNIQ,
rfs4_mon_hold, rfs4_mon_rele);
if (((fflags & FWRITE) && vn_has_other_opens(vp, V_WRITE)) ||
(((fflags & FWRITE) == 0) && vn_is_opened(vp, V_WRITE)) ||
vn_is_mapped(vp, V_WRITE)) {
if (open_prev) {
*recall = 1;
} else {
(void) fem_uninstall(vp, deleg_rdops,
(void *)fp);
rfs4_deleg_state_rele(dsp);
return (NULL);
}
}
if (ret == 0)
vn_open_upgrade(vp, FREAD);
} else {
if (((fflags & FWRITE) && vn_has_other_opens(vp, V_WRITE)) ||
(((fflags & FWRITE) == 0) && vn_is_opened(vp, V_WRITE)) ||
((fflags & FREAD) && vn_has_other_opens(vp, V_READ)) ||
(((fflags & FREAD) == 0) && vn_is_opened(vp, V_READ)) ||
vn_is_mapped(vp, V_RDORWR)) {
if (open_prev) {
*recall = 1;
} else {
rfs4_deleg_state_rele(dsp);
return (NULL);
}
}
ret = fem_install(vp, deleg_wrops, (void *)fp, OPUNIQ,
rfs4_mon_hold, rfs4_mon_rele);
if (((fflags & FWRITE) && vn_has_other_opens(vp, V_WRITE)) ||
(((fflags & FWRITE) == 0) && vn_is_opened(vp, V_WRITE)) ||
((fflags & FREAD) && vn_has_other_opens(vp, V_READ)) ||
(((fflags & FREAD) == 0) && vn_is_opened(vp, V_READ)) ||
vn_is_mapped(vp, V_RDORWR)) {
if (open_prev) {
*recall = 1;
} else {
(void) fem_uninstall(vp, deleg_wrops,
(void *)fp);
rfs4_deleg_state_rele(dsp);
return (NULL);
}
}
if (ret == 0)
vn_open_upgrade(vp, FREAD|FWRITE);
}
ASSERT(!list_link_active(&dsp->rds_node));
list_insert_tail(&fp->rf_delegstatelist, dsp);
dsp->rds_dtype = fp->rf_dinfo.rd_dtype = dtype;
fp->rf_dinfo.rd_time_lastgrant = gethrestime_sec();
fp->rf_dinfo.rd_conflicted_client = 0;
fp->rf_dinfo.rd_ever_recalled = FALSE;
if (dtype == OPEN_DELEGATE_READ)
fp->rf_dinfo.rd_rdgrants++;
else
fp->rf_dinfo.rd_wrgrants++;
return (dsp);
}
void
rfs4_return_deleg(rfs4_deleg_state_t *dsp, bool_t revoked)
{
rfs4_file_t *fp = dsp->rds_finfo;
open_delegation_type4 dtypewas;
rfs4_dbe_lock(fp->rf_dbe);
if (!list_link_active(&dsp->rds_node)) {
rfs4_dbe_unlock(fp->rf_dbe);
return;
}
list_remove(&fp->rf_delegstatelist, dsp);
if (list_is_empty(&fp->rf_delegstatelist)) {
dtypewas = fp->rf_dinfo.rd_dtype;
fp->rf_dinfo.rd_dtype = OPEN_DELEGATE_NONE;
rfs4_dbe_cv_broadcast(fp->rf_dbe);
if (fp->rf_vp != NULL) {
if (dtypewas == OPEN_DELEGATE_READ) {
(void) fem_uninstall(fp->rf_vp, deleg_rdops,
(void *)fp);
vn_open_downgrade(fp->rf_vp, FREAD);
} else if (dtypewas == OPEN_DELEGATE_WRITE) {
(void) fem_uninstall(fp->rf_vp, deleg_wrops,
(void *)fp);
vn_open_downgrade(fp->rf_vp, FREAD|FWRITE);
}
}
}
switch (dsp->rds_dtype) {
case OPEN_DELEGATE_READ:
fp->rf_dinfo.rd_rdgrants--;
break;
case OPEN_DELEGATE_WRITE:
fp->rf_dinfo.rd_wrgrants--;
break;
default:
break;
}
fp->rf_dinfo.rd_time_returned = gethrestime_sec();
if ((fp->rf_dinfo.rd_rdgrants + fp->rf_dinfo.rd_wrgrants) == 0)
fp->rf_dinfo.rd_time_recalled = 0;
rfs4_dbe_unlock(fp->rf_dbe);
rfs4_dbe_lock(dsp->rds_dbe);
dsp->rds_dtype = OPEN_DELEGATE_NONE;
if (revoked == TRUE)
dsp->rds_time_revoked = gethrestime_sec();
rfs4_dbe_invalidate(dsp->rds_dbe);
rfs4_dbe_unlock(dsp->rds_dbe);
if (revoked == TRUE) {
rfs4_dbe_lock(dsp->rds_client->rc_dbe);
dsp->rds_client->rc_deleg_revoked++;
rfs4_dbe_unlock(dsp->rds_client->rc_dbe);
}
}
static void
rfs4_revoke_file(rfs4_file_t *fp)
{
rfs4_deleg_state_t *dsp;
rfs4_dbe_lock(fp->rf_dbe);
while ((dsp = list_head(&fp->rf_delegstatelist)) != NULL) {
rfs4_dbe_hold(dsp->rds_dbe);
rfs4_dbe_unlock(fp->rf_dbe);
rfs4_return_deleg(dsp, TRUE);
rfs4_deleg_state_rele(dsp);
rfs4_dbe_lock(fp->rf_dbe);
}
rfs4_dbe_unlock(fp->rf_dbe);
}
bool_t
rfs4_is_deleg(rfs4_state_t *sp)
{
rfs4_deleg_state_t *dsp;
rfs4_file_t *fp = sp->rs_finfo;
rfs4_client_t *cp = sp->rs_owner->ro_client;
ASSERT(rfs4_dbe_islocked(fp->rf_dbe));
for (dsp = list_head(&fp->rf_delegstatelist); dsp != NULL;
dsp = list_next(&fp->rf_delegstatelist, dsp)) {
if (cp != dsp->rds_client) {
return (TRUE);
}
}
return (FALSE);
}
void
rfs4_disable_delegation(void)
{
nfs4_srv_t *nsrv4;
nsrv4 = nfs4_get_srv();
mutex_enter(&nsrv4->deleg_lock);
rfs4_deleg_disabled++;
mutex_exit(&nsrv4->deleg_lock);
}
void
rfs4_enable_delegation(void)
{
nfs4_srv_t *nsrv4;
nsrv4 = nfs4_get_srv();
mutex_enter(&nsrv4->deleg_lock);
ASSERT(rfs4_deleg_disabled > 0);
rfs4_deleg_disabled--;
mutex_exit(&nsrv4->deleg_lock);
}
void
rfs4_mon_hold(void *arg)
{
rfs4_file_t *fp = arg;
rfs4_dbe_hold(fp->rf_dbe);
}
void
rfs4_mon_rele(void *arg)
{
rfs4_file_t *fp = arg;
rfs4_dbe_rele_nolock(fp->rf_dbe);
}