nlm_client.c revision 5cd496e3c5514951ae23bdd897cb29b202e2ff62
/*
* Copyright (c) 2008 Isilon Inc http://www.isilon.com/
* Authors: Doug Rabson <dfr@rabson.org>
* Developed with Red Inc: Alfred Perlstein <alfred@freebsd.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Copyright 2011 Nexenta Systems, Inc. All rights reserved.
* Copyright (c) 2012 by Delphix. All rights reserved.
* Copyright (c) 2014, Joyent, Inc. All rights reserved.
*/
/*
* Client-side support for (NFS) VOP_FRLOCK, VOP_SHRLOCK.
* (called via klmops.c: lm_frlock, lm4_frlock)
*
* Source code derived from FreeBSD nlm_advlock.c
*/
#include <rpcsvc/nlm_prot.h>
#include <nfs/nfs_clnt.h>
#include "nlm_impl.h"
/* Extra flags for nlm_call_lock() - xflags */
#define NLM_X_RECLAIM 1
#define NLM_X_BLOCKING 2
/*
* Max. number of retries nlm_call_cancel() does
* when NLM server is in grace period or doesn't
* respond correctly.
*/
#define NLM_CANCEL_NRETRS 5
/*
* Determines wether given lock "flp" is safe.
* The lock is considered to be safe when it
* acquires the whole file (i.e. its start
* and len are zeroes).
*/
#define NLM_FLOCK_IS_SAFE(flp) \
static int nlm_map_status(nlm4_stats);
static int nlm_map_clnt_stat(enum clnt_stat);
static void nlm_send_siglost(pid_t);
struct flk_callback *, int, bool_t);
static void nlm_init_lock(struct nlm4_lock *,
struct nlm_owner_handle *);
struct flk_callback *, int, int);
struct netobj *, int);
struct netobj *, int);
static int nlm_call_cancel(struct nlm4_lockargs *,
struct nlm_host *, int);
static void nlm_init_share(struct nlm4_share *,
struct netobj *, int, int);
struct netobj *, int);
/*
* on the given server represented by hostp.
* The function is called from a dedicated thread
* when server reports us that it's entered grace
* period.
*/
void
{
do {
error = 0;
/*
* We cancel all sleeping locks that were
* done by the host, because we don't allow
* reclamation of sleeping locks. The reason
* we do this is that allowing of sleeping locks
* reclamation can potentially break locks recovery
* order.
*
* Imagine that we have two client machines A and B
* and an NLM server machine. A adds a non sleeping
* lock to the file F and aquires this file. Machine
* B in its turn adds sleeping lock to the file
* F and blocks because F is already aquired by
* the machine A. Then server crashes and after the
* reboot it notifies its clients about the crash.
* If we would allow sleeping locks reclamation,
* there would be possible that machine B recovers
* its lock faster than machine A (by some reason).
* So that B aquires the file F after server crash and
* machine A (that by some reason recovers slower) fails
* to recover its non sleeping lock. Thus the original
* locks order becames broken.
*/
/*
* Try to reclaim all active locks we have
*/
if (error == 0) {
continue;
break;
} else {
/*
* Critical error occurred, the lock
* can not be recovered, just take it away.
*/
}
}
if (restart) {
/*
* Lock reclamation fucntion reported us that
* the server state was changed (again), so
* try to repeat the whole reclamation process.
*/
continue;
}
if (error == 0) {
continue;
break;
} else {
/* Failed to reclaim share */
}
}
}
/*
* nlm_frlock --
* NFS advisory byte-range locks.
* Called in klmops.c
*
* keep track of remote locks granted by some server, so we
* can reclaim those locks after a server restarts. We can
* also sometimes use this as a cache of lock information.
*
* Was: nlm_advlock()
*/
/* ARGSUSED */
int
{
servinfo_t *sv;
const char *netid;
int error;
struct nlm_globals *g;
NLM_ERR("nlm_frlock: unknown NFS netid");
return (ENOSYS);
}
return (ENOSYS);
/*
* Purge cached attributes in order to make sure that
* future calls of convoff()/VOP_GETATTR() will get the
* latest data.
*/
/* Now flk0 is the zero-based lock request. */
switch (cmd) {
case F_GETLK:
break;
case F_SETLK:
case F_SETLKW:
if (error == 0)
nlm_host_monitor(g, hostp, 0);
break;
default:
break;
}
nlm_host_release(g, hostp);
return (error);
}
static int
{
int error;
/*
* Check local (cached) locks first.
* If we find one, no need for RPC.
*/
if (error != 0)
return (error);
return (0);
}
/* Not found locally. Try remote. */
if (error != 0)
return (error);
if (error != 0)
return (error);
/*
* Update the caller's *flkp with information
* on the conflicting lock (or lack thereof).
*/
} else {
/*
* Found a conflicting lock. Set the
* caller's *flkp with the info, first
* converting to the caller's whence.
*/
}
return (0);
}
static int
{
if (error != 0)
return (error);
/*
* NFS v2 clients should not request locks where any part
* of the lock range is beyond 0xffffffff. The NFS code
* checks that (see nfs_frlock, flk_check_lock_data), but
* as that's outside this module, let's check here too.
* This check ensures that we will be able to convert this
* lock request into 32-bit form without change, and that
* (more importantly) when the granted call back arrives,
* it's unchanged when converted back into 64-bit form.
* If this lock range were to change in any way during
* either of those conversions, the "granted" call back
* from the NLM server would not find our sleeping lock.
*/
return (EINVAL);
}
/*
* Fill in l_sysid for the local locking calls.
* Also, let's not trust the caller's l_pid.
*/
/*
* Purge local (cached) lock information first,
* then clear the remote lock.
*/
return (error);
}
if (!do_block) {
/*
* This is a non-blocking "set" request,
* so we can check locally first, and
* sometimes avoid an RPC call.
*/
/* Found a conflicting lock. */
return (EAGAIN);
}
xflags = 0;
} else {
}
if (error != 0)
return (error);
/*
* Save the lock locally. This should not fail,
* because the server is authoritative about locks
* and it just told us we have the lock!
*/
if (error != 0) {
/*
* That's unexpected situation. Just ignore the error.
*/
NLM_WARN("nlm_frlock_setlk: Failed to set local lock. "
"[err=%d]\n", error);
error = 0;
}
return (error);
}
/*
* given host. Report to the processes that own
* cancelled locks that they are removed by force
* by sending SIGLOST.
*/
void
{
/*
* Destroy all active locks
*/
if (error == 0)
}
/*
* Destroy all active share reservations
*/
if (error == 0)
}
}
/*
* The function determines whether the lock "fl" can
* be safely applied to the file vnode "vp" corresponds to.
* The lock can be "safely" applied if all the conditions
* above are held:
* - It's not a mandatory lock
* - The vnode wasn't mapped by anyone
* - The vnode was mapped, but it hasn't any locks on it.
* - The vnode was mapped and all locks it has occupies
* the whole file.
*/
int
{
int err;
return (0);
if (err != 0)
return (0);
/* NLM4 doesn't allow mandatory file locking */
return (0);
return (1);
}
/*
* The function determines whether it's safe to map
* a file correspoding to vnode vp.
* The mapping is considered to be "safe" if file
* either has no any locks on it or all locks it
* has occupy the whole file.
*/
int
{
struct nlm_globals *g;
int safe = 1;
/* Check active locks at first */
safe = 0;
}
if (!safe)
return (safe);
/* Then check sleeping locks if any */
mutex_enter(&g->lock);
safe = 0;
break;
}
}
mutex_exit(&g->lock);
return (safe);
}
int
{
struct nlm_globals *g;
int has_slocks = FALSE;
mutex_enter(&g->lock);
has_slocks = TRUE;
break;
}
}
mutex_exit(&g->lock);
return (has_slocks);
}
void
{
int sysid = 0;
servinfo_t *sv;
const char *netid;
struct nlm_globals *g;
}
}
}
}
/*
* The BSD code had functions here to "reclaim" (destroy)
* remote locks when a vnode is being forcibly destroyed.
* We just keep vnodes around until statd tells us the
* client has gone away.
*/
static int
{
/*
* If the remote NSM state changes during recovery, the host
* must have rebooted a second time. In that case, we must
* restart the recovery.
*/
if (state != orig_state)
return (ERESTART);
if (error != 0)
return (error);
}
/*
* Get local lock information for some NFS server.
*
* This gets (checks for) a local conflicting lock.
* Note: Modifies passed flock, if a conflict is found,
* but the caller expects that.
*/
static int
{
}
/*
* Set local lock information for some NFS server.
*
* Called after a lock request (set or clear) succeeded. We record the
* details in the local lock manager. Note that since the remote
* server has granted the lock, we can be sure that it doesn't
* conflict with any other locks we have in the local lock manager.
*
* Since it is possible that host may also make NLM client requests to
* our NLM server, we use a different sysid value to record our own
* client locks.
*
* Note that since it is possible for us to receive replies from the
* server in a different order than the locks were granted (e.g. if
* many local threads are contending for the same lock), we must use a
* blocking operation when registering with the local lock manager.
* We expect that any actual wait will be rare and short hence we
* ignore signals for this.
*/
static int
{
}
/*
* Cancel local lock and send send SIGLOST signal
* to the lock owner.
*
* NOTE: modifies flp
*/
static void
{
}
/*
* Do NLM_LOCK call.
* Was: nlm_setlock()
*
* of rnode->r_lkserlock which should be released before nlm_call_lock()
* sleeps on waiting lock and acquired when it wakes up.
*/
static int
{
struct nlm4_lockargs args;
struct nlm_owner_handle oh;
struct nlm_globals *g;
int error = 0;
if (xflags & NLM_X_BLOCKING) {
}
for (;;) {
enum nlm4_stats nlm_err;
if (error != 0) {
goto out;
}
if (error != 0) {
continue;
goto out;
}
if (nlm_err == nlm4_denied_grace_period) {
goto out;
}
if (error != 0)
goto out;
continue;
}
switch (nlm_err) {
case nlm4_granted:
case nlm4_blocked:
error = 0;
break;
case nlm4_denied:
NLM_WARN("nlm_call_lock: got nlm4_denied for "
"blocking lock\n");
}
break;
default:
}
/*
* If we deal with either non-blocking lock or
* with a blocking locks that wasn't blocked on
* the server side (by some reason), our work
* is finished.
*/
nlm_err != nlm4_blocked ||
error != 0)
goto out;
/*
* Before releasing the r_lkserlock of rnode, we should
* check whether the new lock is "safe". If it's not
* safe, disable caching for the given vnode. That is done
* for sleeping locks only that are waiting for a GRANT reply
* from the NLM server.
*
* NOTE: the vnode cache can be enabled back later if an
* unsafe lock will be merged with existent locks so that
* it will become safe. This condition is checked in the
* NFSv3 code (see nfs_lockcompletion).
*/
if (!NLM_FLOCK_IS_SAFE(flp)) {
}
/*
* The server should call us back with a
* granted message when the lock succeeds.
* In order to deal with broken servers,
* lost granted messages, or server reboots,
* we will also re-try every few seconds.
*
* Note: We're supposed to call these
* flk_invoke_callbacks when blocking.
* Take care on rnode->r_lkserlock, we should
* release it before going to sleep.
*/
/*
* NFS expects that we return with rnode->r_lkserlock
* locked on write, lock it back.
*
* NOTE: nfs_rw_enter_sig() can be either interruptible
* or not. It depends on options of NFS mount. Here
* we're _always_ uninterruptible (independently of mount
* options), because nfs_frlock/nfs3_frlock expects that
* we return with rnode->r_lkserlock acquired. So we don't
* want our lock attempt to be interrupted by a signal.
*/
if (error == 0) {
break;
/*
* We need to call the server to cancel our
* lock request.
*/
break;
} else {
/*
* Timeout happened, resend the lock request to
* the server. Well, we're a bit paranoid here,
* but keep in mind previous request could lost
* (especially with conectionless transport).
*/
continue;
}
}
/*
* We could disable the vnode cache for the given _sleeping_
* (codition: nslp != NULL) lock if it was unsafe. Normally,
* nfs_lockcompletion() function can enable the vnode cache
* back if the lock becomes safe after activativation. But it
* will not happen if any error occurs on the locking path.
*
* Here we enable the vnode cache back if the error occurred
* and if there aren't any unsafe locks on the given vnode.
* Note that if error happened, sleeping lock was derigistered.
*/
}
out:
nlm_slock_unregister(g, nslp);
return (error);
}
/*
* Do NLM_CANCEL call.
* Helper for nlm_call_lock() error recovery.
*/
static int
{
/*
* Unlike all other nlm_call_* functions, nlm_call_cancel
* doesn't spin forever until it gets reasonable response
* from NLM server. It makes limited number of retries and
* if server doesn't send a reasonable reply, it returns an
* error. It behaves like that because it's called from nlm_call_lock
* with blocked signals and thus it can not be interrupted from
* user space.
*/
if (error != 0)
return (ENOLCK);
if (error != 0) {
continue;
return (error);
}
/*
* There was nothing to cancel. We are going to go ahead
* and assume we got the lock.
*/
case nlm_denied:
/*
* The server has recently rebooted. Treat this as a
* successful cancellation.
*/
case nlm4_denied_grace_period:
/*
* We managed to cancel.
*/
case nlm4_granted:
error = 0;
break;
default:
/*
* Broken server implementation. Can't really do
* anything here.
*/
break;
}
break;
}
return (error);
}
/*
* Do NLM_UNLOCK call.
* Was: nlm_clearlock
*/
static int
{
struct nlm4_unlockargs args;
struct nlm_owner_handle oh;
enum nlm4_stats nlm_err;
int error;
for (;;) {
if (error != 0)
return (ENOLCK);
if (error != 0) {
continue;
return (error);
}
if (nlm_err == nlm4_denied_grace_period) {
if (error != 0)
return (error);
continue;
}
break;
}
/* special cases */
switch (nlm_err) {
case nlm4_denied:
break;
default:
break;
}
return (error);
}
/*
* Do NLM_TEST call.
* Was: nlm_getlock()
*/
static int
{
struct nlm4_testargs args;
struct nlm4_holder h;
struct nlm_owner_handle oh;
enum nlm4_stats nlm_err;
int error;
for (;;) {
struct nlm4_testres res;
if (error != 0)
return (ENOLCK);
if (error != 0) {
continue;
return (error);
}
if (nlm_err == nlm4_denied_grace_period) {
if (error != 0)
return (error);
continue;
}
break;
}
switch (nlm_err) {
case nlm4_granted:
error = 0;
break;
case nlm4_denied:
error = 0;
break;
default:
break;
}
return (error);
}
static void
struct nlm_owner_handle *oh)
{
/* Caller converts to zero-base. */
}
/* ************************************************************** */
int
{
servinfo_t *sv;
const char *netid;
int error;
struct nlm_globals *g;
NLM_ERR("nlm_shrlock: unknown NFS netid\n");
return (ENOSYS);
}
return (ENOSYS);
/*
* Fill in s_sysid for the local locking calls.
* Also, let's not trust the caller's l_pid.
*/
/*
* Purge local (cached) share information first,
* then clear the remote share.
*/
goto out;
}
if (error != 0)
goto out;
/*
* Save the share locally. This should not fail,
* because the server is authoritative about shares
* and it just told us we have the share reservation!
*/
if (error != 0) {
/*
* Oh oh, we really don't expect an error here.
*/
error = 0;
}
nlm_host_monitor(g, host, 0);
out:
nlm_host_release(g, host);
return (error);
}
static int
{
if (state != orig_state) {
/*
* It seems that NLM server rebooted while
* we were busy with recovery.
*/
return (ERESTART);
}
if (error != 0)
return (error);
}
/*
* Set local share information for some NFS server.
*
* Called after a share request (set or clear) succeeded. We record
* the details in the local lock manager. Note that since the remote
* server has granted the share, we can be sure that it doesn't
* conflict with any other shares we have in the local lock manager.
*
* Since it is possible that host may also make NLM client requests to
* our NLM server, we use a different sysid value to record our own
* client shares.
*/
int
{
}
static void
{
}
/*
* Do NLM_SHARE call.
* Was: nlm_setshare()
*/
static int
{
struct nlm4_shareargs args;
enum nlm4_stats nlm_err;
int error;
for (;;) {
struct nlm4_shareres res;
if (error != 0)
return (ENOLCK);
if (error != 0) {
continue;
return (error);
}
if (nlm_err == nlm4_denied_grace_period) {
return (ENOLCK);
if (error != 0)
return (error);
continue;
}
break;
}
switch (nlm_err) {
case nlm4_granted:
error = 0;
break;
case nlm4_blocked:
case nlm4_denied:
break;
case nlm4_denied_nolocks:
case nlm4_deadlck:
break;
default:
break;
}
return (error);
}
/*
* Do NLM_UNSHARE call.
*/
static int
{
struct nlm4_shareargs args;
enum nlm4_stats nlm_err;
int error;
for (;;) {
struct nlm4_shareres res;
if (error != 0)
return (ENOLCK);
if (error != 0) {
continue;
return (error);
}
if (nlm_err == nlm4_denied_grace_period) {
if (error != 0)
return (error);
continue;
}
break;
}
switch (nlm_err) {
case nlm4_granted:
error = 0;
break;
case nlm4_denied:
break;
case nlm4_denied_nolocks:
break;
default:
break;
}
return (error);
}
static void
{
default:
case F_NODNY:
break;
case F_RDDNY:
break;
case F_WRDNY:
break;
case F_RWDNY:
break;
}
default:
case 0: /* seen with F_UNSHARE */
break;
case F_RDACC:
break;
case F_WRACC:
break;
case F_RWACC:
break;
}
}
/*
* Initialize filehandle according to the version
* of NFS vnode was created on. The version of
* NLM that can be used with given NFS version
* is saved to lm_vers.
*/
static int
{
/*
* Too bad the NFS code doesn't just carry the FH
* in a netobj or a netbuf.
*/
case NFS_V3:
/* See nfs3_frlock() */
break;
case NFS_VERSION:
/* See nfs_frlock() */
/* LINTED E_BAD_PTR_CAST_ALIGN */
break;
default:
return (ENOSYS);
}
return (0);
}
/*
* Send SIGLOST to the process identified by pid.
* NOTE: called when NLM decides to remove lock
* or share reservation ownder by the process
* by force.
*/
static void
{
proc_t *p;
if (p != NULL)
}
static int
{
switch (stat) {
case RPC_SUCCESS:
return (0);
case RPC_TIMEDOUT:
case RPC_PROGUNAVAIL:
return (EAGAIN);
case RPC_INTR:
return (EINTR);
default:
return (EINVAL);
}
}
static int
{
switch (stat) {
case nlm4_granted:
return (0);
case nlm4_denied:
return (EAGAIN);
case nlm4_denied_nolocks:
return (ENOLCK);
case nlm4_blocked:
return (EAGAIN);
case nlm4_denied_grace_period:
return (EAGAIN);
case nlm4_deadlck:
return (EDEADLK);
case nlm4_rofs:
return (EROFS);
case nlm4_stale_fh:
return (ESTALE);
case nlm4_fbig:
return (EFBIG);
case nlm4_failed:
return (EACCES);
default:
return (EINVAL);
}
}