/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
* Copyright 2014 Nexenta Systems, Inc. All rights reserved.
*/
#include <nfs/nfs4_kprot.h>
#include <sys/pathname.h>
static int rfs4_deleg_disabled;
#ifdef DEBUG
static int rfs4_test_cbgetattr_fail = 0;
int rfs4_cb_null;
int rfs4_cb_debug;
int rfs4_deleg_debug;
#endif
static void rfs4_recall_file(rfs4_file_t *,
bool_t, rfs4_client_t *);
static void rfs4_revoke_file(rfs4_file_t *);
static void rfs4_cb_chflush(rfs4_cbinfo_t *);
open_delegation_type4, int *);
/*
* Convert a universal address to an transport specific
* address using inet_pton.
*/
static int
{
unsigned char c;
for (i = len-1; i >= 0; i--) {
if (ua[i] == '.')
dots++;
if (dots == 2) {
ua[i] = '\0';
/*
* We use k to remember were to stick '.' back, since
* ua was kmem_allocateded from the pool len+1.
*/
k = i;
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;
ua[k] = '.';
return (0);
} else {
ua[k] = '.';
return (EINVAL);
}
}
}
return (EINVAL);
}
/*
* Update the delegation policy with the
* value of "new_policy"
*/
void
{
}
void
rfs4_hold_deleg_policy(void)
{
}
void
rfs4_rele_deleg_policy(void)
{
}
/*
* This free function is to be used when the client struct is being
* released and nothing at all is needed of the callback info any
* longer.
*/
void
{
/* Free old address if any */
if (addr)
if (netid)
if (addr)
if (netid)
if (cbp->cb_chc_free) {
}
}
/*
* The server uses this to check the callback path supplied by the
* client. The callback connection is marked "in progress" while this
* work is going on and then eventually marked either OK or FAILED.
* This work can be done as part of a separate thread and at the end
* of this the thread will exit or it may be done such that the caller
* will continue with other work.
*/
static void
{
/* If another thread is doing CB_NULL RPC then return */
return;
}
/* Mark the cbinfo as having a thread in the NULL callback */
/*
* Are there other threads still using the cbinfo client
* handles? If so, this thread must wait before going and
* mucking aroiund with the callback information
*/
/*
* This thread itself may find that new callback info has
* arrived and is set up to handle this case and redrive the
* call to the client's callback server.
*/
/*
* Free the old stuff if it exists; may be the first
* time through this path
*/
if (addr)
if (netid)
/* Get the program number */
/* Don't forget the protocol's "cb_ident" field */
/* no longer new */
/* get rid of the old client handles that may exist */
}
return;
}
/* mark rfs4_client_t as CALLBACK NULL in progress */
goto retry;
}
} else {
#ifdef DEBUG
rfs4_cb_null++;
#endif
}
/* Check to see if the client has specified new callback info */
goto retry; /* give the CB_NULL another chance */
}
}
/*
* Given a client struct, inspect the callback info to see if the
* callback path is up and available.
*
* If new callback path is available and no one has set it up then
* try to set it up. If setup is not successful after 5 tries (5 secs)
* then gives up and returns NULL.
*
* If callback path is being initialized, then wait for the CB_NULL RPC
* call to occur.
*/
static rfs4_cbinfo_t *
{
int retries = 0;
/*
* Looks like a new callback path may be available and
* noone has set it up.
*/
/*
* If callback path is no longer new, or it's being setup
* then stop and wait for it to be done.
*/
break;
if (++retries >= rfs4_max_setup_cb_tries)
return (NULL);
}
/* Is there a thread working on doing the CB_NULL RPC? */
/* If the callback path is not okay (up and running), just quit */
return (NULL);
}
/* Let someone know we are using the current callback info */
return (cbp);
}
/*
* The caller is done with the callback info. It may be that the
* caller's RPC failed and the NFSv4 client has actually provided new
* callback information. If so, let the caller know so they can
* advantage of this and maybe retry the RPC that originally failed.
*/
static int
{
/* The caller gets a chance to mark the callback info as bad */
if (newstate != CB_NOCHANGE)
}
/*
* A thread may be waiting on this one to finish and if so,
* let it know that it is okay to do the CB_NULL to the
* client's callback server.
*/
/*
* If this is the last thread to use the callback info and
* there is new callback information to try and no thread is
* there ready to do the CB_NULL, then return true to teh
* caller so they can do the CB_NULL
*/
return (cb_new);
}
/*
* Given the information in the callback info struct, create a client
* handle that can be used by the server for its callback path.
*/
static CLIENT *
{
int af;
char *devnam;
int size;
int useresvport = 0;
goto cb_init_out;
}
== 0) {
== 0) {
== 0) {
} else {
goto cb_init_out;
}
goto cb_init_out;
}
goto cb_init_out;
}
} else /* AF_INET6 */ {
}
if (uaddr2sockaddr(af,
goto cb_init_out;
}
}
/* turn off reserved port usage */
return (ch);
}
/*
* Iterate over the client handle cache and
* destroy it.
*/
static void
{
while (cbp->cb_chc_free) {
cbp->cb_chc_free--;
if (ch) {
}
}
}
/*
* Return a client handle, either from a the small
* rfs4_client_t cache or one that we just created.
*/
static CLIENT *
{
if (cbp->cb_chc_free) {
cbp->cb_chc_free--;
return (cbch);
}
/* none free so make it now */
return (cbch);
}
/*
* Return the client handle to the small cache or
* destroy it.
*/
static void
{
return;
}
/*
* cache maxed out of free entries, obliterate
* this client handle, destroy it, throw it away.
*/
}
/*
* With the supplied callback information - initialize the client
* callback data. If there is a callback in progress, save the
* callback info so that a thread can pick it up in the future.
*/
void
{
/* Set the call back for the client */
}
/* ready to save the new information but first free old, if exists */
} else {
}
}
/*
* The server uses this when processing SETCLIENTID_CONFIRM. Callback
* information may have been provided on SETCLIENTID and this call
* marks that information as confirmed and then starts a thread to
* test the callback path.
*/
void
{
return;
}
static void
{
}
/* ARGSUSED */
static void
{
}
static void
{
int i, arglen;
/*
* First free any special args alloc'd for specific ops.
*/
case OP_CB_RECALL:
break;
case OP_CB_GETATTR:
break;
default:
return;
}
}
if (resp)
}
/*
* General callback routine for the server to the client.
*/
static enum clnt_stat
{
/* start with this in case cb_getch() fails */
return (stat);
/* get a client handle */
/*
* reset the cb_ident since it may have changed in
* rfs4_cbinfo_hold()
*/
/* free client handle */
}
/*
* If the rele says that there may be new callback info then
* retry this sequence and it may succeed as a result of the
* new callback path
*/
if (rfs4_cbinfo_rele(cbp,
goto retry;
return (stat);
}
/*
* Used by the NFSv4 server to get attributes for a file while
* handling the case where a file has been write delegated. For the
* time being, VOP_GETATTR() is called and CB_GETATTR processing is
* not undertaken. This call site is maintained in case the server is
* updated in the future to handle write delegation space guarantees.
*/
{
int error;
}
/*
* integration of all NFS versions and the support of delegation. For
* now, just call the VOP_GETATTR(). If the NFSv4 server is enhanced
* in the future to provide space guarantees for write delegations
* then this call site should be expanded to interact with the client.
*/
int
{
}
/*
* Place the actual cb_recall otw call to client.
*/
static void
{
int numops;
int argoplist_size;
/*
* set up the compound args
*/
/* cb4_args.callback_ident is set in rfs4_do_callback() */
/*
* fill in the args struct
*/
/* Keep track of when we did this for observability */
/*
* Set up the timeout for the callback and make the actual call.
* Timeout will be 80% of the lease period for this server.
*/
timeout);
}
}
struct recall_arg {
};
static void
{
/*
* It is possible that before this thread starts
* the client has send us a return_delegation, and
* if that is the case we do not need to send the
* recall callback.
*/
struct recall_arg *, arg,
struct rfs4_deleg_state_t *, dsp,
struct rfs4_file_t *, fp);
}
/*
* Recall count may go negative if the parent thread that is
* creating the individual callback threads does not modify
* the recall_count field before the callback thread actually
* gets a response from the CB_RECALL
*/
}
struct master_recall_args {
};
static void
{
/* Recall already in progress ? */
return;
}
recall_count = 0;
/*
* if this delegation state
* is being reaped skip it
*/
continue;
}
/* hold for receiving thread */
recall_count++;
}
/*
* Recall count may go negative if the parent thread that is
* creating the individual callback threads does not modify
* the recall_count field before the callback thread actually
* gets a response from the CB_RECALL
*/
}
static void
{
return;
}
/*
* Mark the time we started the recall processing.
* If it has been previously recalled, do not reset the
* timer since this is used for the revocation decision.
*/
/* Client causing recall not always available */
if (cp)
}
void
{
/* First check to see if a revocation should occur */
if (elapsed1 > rfs4_lease_time &&
elapsed2 > rfs4_lease_time) {
return;
}
/*
* Next check to see if a recall should be done again
* so quickly.
*/
return;
}
}
/*
* rfs4_check_recall is called from rfs4_do_open to determine if the current
* open conflicts with the delegation.
* Return true if we need recall otherwise false.
* Assumes entry locks for sp and sp->rs_finfo are held.
*/
{
switch (dtype) {
case OPEN_DELEGATE_NONE:
/* Not currently delegated so there is nothing to do */
return (FALSE);
case OPEN_DELEGATE_READ:
/*
* If the access is only asking for READ then there is
* no conflict and nothing to do. If it is asking
* for write, then there will be conflict and the read
* delegation should be recalled.
*/
if (access == OPEN4_SHARE_ACCESS_READ)
return (FALSE);
else
return (TRUE);
case OPEN_DELEGATE_WRITE:
/* Check to see if this client has the delegation */
return (rfs4_is_deleg(sp));
}
return (FALSE);
}
/*
* Return the "best" allowable delegation available given the current
* delegation type and the desired access and deny modes on the file.
* At the point that this routine is called we know that the access and
* deny modes are consistent with the file modes.
*/
static open_delegation_type4
{
int readcnt = 0;
int writecnt = 0;
switch (dtype) {
case OPEN_DELEGATE_NONE:
/*
* Determine if more than just this OPEN have the file
* open and if so, no delegation may be provided to
* the client.
*/
if (access & OPEN4_SHARE_ACCESS_WRITE)
writecnt++;
if (access & OPEN4_SHARE_ACCESS_READ)
readcnt++;
return (OPEN_DELEGATE_NONE);
/*
* If the client is going to write, or if the client
* has exclusive access, return a write delegation.
*/
if ((access & OPEN4_SHARE_ACCESS_WRITE) ||
return (OPEN_DELEGATE_WRITE);
/*
* If we don't want to write or we've haven't denied read
* access to others, return a read delegation.
*/
if ((access & ~OPEN4_SHARE_ACCESS_WRITE) ||
(deny & ~OPEN4_SHARE_DENY_READ))
return (OPEN_DELEGATE_READ);
/* Shouldn't get here */
return (OPEN_DELEGATE_NONE);
case OPEN_DELEGATE_READ:
/*
* If the file is delegated for read but we wan't to
* write or deny others to read then we can't delegate
* the file. We shouldn't get here since the delegation should
* have been recalled already.
*/
if ((access & OPEN4_SHARE_ACCESS_WRITE) ||
return (OPEN_DELEGATE_NONE);
return (OPEN_DELEGATE_READ);
case OPEN_DELEGATE_WRITE:
return (OPEN_DELEGATE_WRITE);
}
/* Shouldn't get here */
return (OPEN_DELEGATE_NONE);
}
/*
* Given the desired delegation type and the "history" of the file
* determine the actual delegation type to return.
*/
static open_delegation_type4
{
if (rfs4_deleg_policy != SRV_NORMAL_DELEGATE)
return (OPEN_DELEGATE_NONE);
/*
* Has this file/delegation ever been recalled? If not then
* no further checks for a delegation race need to be done.
* However if a recall has occurred, then check to see if a
* client has caused its own delegation recall to occur. If
* not, then has a delegation for this file been returned
* recently? If so, then do not assign a new delegation to
* avoid a "delegation race" between the original client and
* the new/conflicting client.
*/
if (elapsed < rfs4_lease_time)
return (OPEN_DELEGATE_NONE);
}
}
/* Limit the number of read grants */
if (dtype == OPEN_DELEGATE_READ &&
return (OPEN_DELEGATE_NONE);
/*
* delegations the server will permit.
*/
return (dtype);
}
/*
* Try and grant a delegation for an open give the state. The routine
* returns the delegation type granted. This could be OPEN_DELEGATE_NONE.
*
* The state and associate file entry must be locked
*/
{
int no_delegation;
/* Is the server even providing delegations? */
return (NULL);
/* Check to see if delegations have been temporarily disabled */
if (no_delegation)
return (NULL);
/* Don't grant a delegation if a deletion is impending. */
return (NULL);
}
/*
* Don't grant a delegation if there are any lock manager
* if there are only read locks we should be able to grant a
* read-only delegation), but it's good enough for now.
*
* MT safety: the lock manager checks for conflicting delegations
* before processing a lock request. That check will block until
* we are done here. So if the lock manager acquires a lock after
* we decide to grant the delegation, the delegation will get
* immediately recalled (if there's a conflict), so we're safe.
*/
return (NULL);
}
/*
* Based on the type of delegation request passed in, take the
* appropriate action (DELEG_NONE is handled above)
*/
switch (dreq) {
case DELEG_READ:
case DELEG_WRITE:
/*
* The server "must" grant the delegation in this case.
* Client is using open previous
*/
*recall = 1;
break;
case DELEG_ANY:
/*
* If a valid callback path does not exist, no delegation may
* be granted.
*/
return (NULL);
/*
* If the original operation which caused time_rm_delayed
* to be set hasn't been retried and completed for one
* full lease period, clear it and allow delegations to
* get granted again.
*/
gethrestime_sec() >
/*
* If we are waiting for a delegation to be returned then
* don't delegate this file. We do this for correctness as
* well as if the file is being recalled we would likely
* recall this file again.
*/
return (NULL);
/* Get the "best" delegation candidate */
if (dtype == OPEN_DELEGATE_NONE)
return (NULL);
/*
* Based on policy and the history of the file get the
* actual delegation.
*/
if (dtype == OPEN_DELEGATE_NONE)
return (NULL);
break;
default:
return (NULL);
}
/* set the delegation for the state */
}
void
{
/*
* We need to allocate a new copy of the who string.
* this string will be freed by the rfs4_op_open dis_resfree
* routine. We need to do this allocation since replays will
* be allocated and rfs4_compound can't tell the difference from
* a replay and an inital open. N.B. if an ace is passed in, it
* the caller's responsibility to free it.
*/
/*
* Default is to deny all access, the client will have
* to contact the server. XXX Do we want to actually
* set a deny for every one, or do we simply want to
* construct an entity that will match no one?
*/
} else {
}
case OPEN_DELEGATE_NONE:
break;
case OPEN_DELEGATE_READ:
break;
case OPEN_DELEGATE_WRITE:
break;
}
}
/*
* Check if the file is delegated via the provided file struct.
* Return TRUE if it is delegated. This is intended for use by
*
* Note that if the file is found to have a delegation, it is
* recalled, unless the clientid of the caller matches the clientid of the
* delegation. If the caller has specified, there is a slight delay
* inserted in the hopes that the delegation will be returned quickly.
*/
{
/* Is delegation enabled? */
if (rfs4_deleg_policy == SRV_NEVER_DELEGATE)
return (FALSE);
/* do we have a delegation on this file? */
if (is_rm)
return (FALSE);
}
/*
* do we have a write delegation on this file or are we
* requesting write access to a file with any type of existing
* delegation?
*/
return (FALSE);
}
/*
* Does the requestor already own the delegation?
*/
return (FALSE);
}
}
if (!do_delay) {
return (TRUE);
}
return (TRUE);
}
}
if (is_rm)
return (FALSE);
}
/*
* Check if the file is delegated in the case of a v2 or v3 access.
* Return TRUE if it is delegated which in turn means that v2 should
* drop the request and in the case of v3 JUKEBOX should be returned.
*/
{
/* Is delegation enabled? */
if (rfs4_deleg_policy != SRV_NEVER_DELEGATE) {
}
}
}
return (rc);
}
/*
* Release a hold on the hold_grant counter which
* prevents delegation from being granted while a remove
* or a rename is in progress.
*/
void
{
if (rfs4_deleg_policy == SRV_NEVER_DELEGATE)
return;
}
/*
* State support for delegation.
* Set the state delegation type for this state;
* This routine is called from open via rfs4_grant_delegation and the entry
* locks on sp and sp->rs_finfo are assumed.
*/
static rfs4_deleg_state_t *
{
int ret;
int fflags = 0;
/* Shouldn't happen */
dtype != OPEN_DELEGATE_READ)) {
return (NULL);
}
/* Unlock to avoid deadlock */
return (NULL);
/*
* It is possible that since we dropped the lock
* in order to call finddeleg, the rfs4_file_t
* was marked such that we should not grant a
* delegation, if so bail out.
*/
return (NULL);
}
return (dsp);
} else {
return (NULL);
}
}
/*
* Check that this file has not been delegated to another
* client
*/
dtype != OPEN_DELEGATE_READ)) {
return (NULL);
}
/* vnevent_support returns 0 if file system supports vnevents */
return (NULL);
}
/* Calculate the fflags for this OPEN. */
*recall = 0;
/*
* Before granting a delegation we need to know if anyone else has
* opened the file in a conflicting mode. However, first we need to
* know how we opened the file to check the counts properly.
*/
if (dtype == OPEN_DELEGATE_READ) {
if (open_prev) {
*recall = 1;
} else {
return (NULL);
}
}
if (open_prev) {
*recall = 1;
} else {
(void *)fp);
return (NULL);
}
}
/*
* Because a client can hold onto a delegation after the
* file has been closed, we need to keep track of the
* access to this file. Otherwise the CIFS server would
* not know about the client accessing the file and could
* inappropriately grant an OPLOCK.
* fem_install() returns EBUSY when asked to install a
* OPUNIQ monitor more than once. Therefore, check the
* return code because we only want this done once.
*/
if (ret == 0)
} else { /* WRITE */
if (open_prev) {
*recall = 1;
} else {
return (NULL);
}
}
if (open_prev) {
*recall = 1;
} else {
(void *)fp);
return (NULL);
}
}
/*
* Because a client can hold onto a delegation after the
* file has been closed, we need to keep track of the
* access to this file. Otherwise the CIFS server would
* not know about the client accessing the file and could
* inappropriately grant an OPLOCK.
* fem_install() returns EBUSY when asked to install a
* OPUNIQ monitor more than once. Therefore, check the
* return code because we only want this done once.
*/
if (ret == 0)
}
/* Place on delegation list for file */
/* Update delegation stats for this file */
/* reset since this is a new delegation */
if (dtype == OPEN_DELEGATE_READ)
else
return (dsp);
}
/*
* State routine for the server when a delegation is returned.
*/
void
{
/* nothing to do if no longer on list */
return;
}
/* Remove state from recall list */
/* if file system was unshared, the vp will be NULL */
/*
* Once a delegation is no longer held by any client,
* the monitor is uninstalled. At this point, the
* client must send OPEN otw, so we don't need the
* reference on the vnode anymore. The open
* downgrade removes the reference put on earlier.
*/
if (dtypewas == OPEN_DELEGATE_READ) {
(void *)fp);
} else if (dtypewas == OPEN_DELEGATE_WRITE) {
(void *)fp);
}
}
}
case OPEN_DELEGATE_READ:
break;
case OPEN_DELEGATE_WRITE:
break;
default:
break;
}
/* used in the policy decision */
/*
* reset the time_recalled field so future delegations are not
* accidentally revoked
*/
}
}
static void
{
/*
* The lock for rfs4_file_t must be held when traversing the
* delegation list but that lock needs to be released to call
* rfs4_return_deleg()
*/
}
}
/*
* A delegation is assumed to be present on the file associated with
* "sp". Check to see if the delegation matches is associated with
* the same client as referenced by "sp". If it is not, TRUE is
* returned. If the delegation DOES match the client (or no
* delegation is present), return FALSE.
* Assume the state entry and file entry are locked.
*/
{
return (TRUE);
}
}
return (FALSE);
}
void
rfs4_disable_delegation(void)
{
}
void
rfs4_enable_delegation(void)
{
ASSERT(rfs4_deleg_disabled > 0);
}
void
{
}
void
{
}