/*
* 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 (c) 2012 by Delphix. All rights reserved.
* Copyright 2013 Nexenta Systems, Inc. All rights reserved.
* Copyright 2014 Joyent, Inc. All rights reserved.
*/
/*
* NFS Lock Manager service functions (nlm_do_...)
* Called from nlm_rpc_svc.c wrappers.
*
* Source code derived from FreeBSD nlm_prot_impl.c
*/
#include <rpc/pmap_prot.h>
#include <rpc/pmap_clnt.h>
#include <rpc/rpcb_prot.h>
#include <rpcsvc/nlm_prot.h>
#include <rpcsvc/sm_inter.h>
#include <nfs/nfs_clnt.h>
#include "nlm_impl.h"
struct nlm_block_cb_data {
};
/*
* Invoke an asyncronous RPC callbeck
* (used when NLM server needs to reply to MSG NLM procedure).
*/
do { \
\
\
NLM_ERR("NLM: %s callback failed: " \
} \
\
static void nlm_block(
/*
* Convert a lock from network to local form, and
* check for valid range (no overflow).
*/
static int
{
return (EINVAL);
return (EINVAL);
} else {
/*
* Check range for 64-bit client (no overflow).
* Again allow len == ~0 to mean lock to EOF.
*/
if (len == MAX_U_OFFSET_T)
len = 0;
return (EINVAL);
}
/* l_pad */
return (0);
}
/*
* Convert an fhandle into a vnode.
* Uses the file id (fh_len + fh_data) in the fhandle to get the vnode.
* WARNING: users of this routine must do a VN_RELE on the vnode when they
* are done with it.
* This is just like nfs_fhtovp() but without the exportinfo argument.
*/
static vnode_t *
{
int error;
return (NULL);
/* LINTED E_BAD_PTR_CAST_ALIGN */
return (NULL);
return (vp);
}
/*
* Gets vnode from client's filehandle
* NOTE: Holds vnode, it _must_ be explicitly
* released by VN_RELE().
*/
static vnode_t *
{
/*
* Get a vnode pointer for the given NFS file handle.
* Note that it could be an NFSv2 or NFSv3 handle,
* which means the size might vary. (don't copy)
*/
return (NULL);
/* We know this is aligned (kmem_alloc) */
/* LINTED E_BAD_PTR_CAST_ALIGN */
/*
* converting fhandles. Check the NFSv3 file handle size. The lockmgr
* is not used for NFS v4.
*/
return (NULL);
}
/*
* Get vhold from client's filehandle, but in contrast to
* The function tries to check some access rights as well.
*
* NOTE: vhold object _must_ be explicitly released by
* nlm_vhold_release().
*/
static struct nlm_vhold *
{
return (NULL);
/*
* Both nlm_fh_to_vp() and nlm_vhold_get()
* do VN_HOLD(), so we need to drop one
* reference on vnode.
*/
return (nvp);
}
/* ******************************************************************* */
/*
* NLM implementation details, called from the RPC svc code.
*/
/*
* Call-back from NFS statd, used to notify that one of our
* hosts had a status change. The host can be either an
* NFS client, NFS server or both.
* According to NSM protocol description, the state is a
* number that is increases monotonically each time the
* state of host changes. An even number indicates that
* the host is down, while an odd number indicates that
* the host is up.
*
* reported by the NSM, we launch notification handlers
* every time the state is changed. The reason we why do so
* is that client and server can talk to each other using
* connectionless transport and it's easy to lose packet
* containing NSM notification with status number update.
*
* In nlm_host_monitor(), we put the sysid in the private data
* that statd carries in this callback, so we can easliy find
* the host this call applies to.
*/
/* ARGSUSED */
void
{
struct nlm_globals *g;
return;
nlm_host_release(g, host);
}
/*
* Another available call-back for NFS statd.
* Not currently used.
*/
/* ARGSUSED */
void
{
ASSERT(0);
}
/*
* NLM_TEST, NLM_TEST_MSG,
* NLM4_TEST, NLM4_TEST_MSG,
* Client inquiry about locks, non-blocking.
*/
void
{
struct nlm_globals *g;
char *netid;
char *name;
int error;
return;
}
if (error != 0) {
goto out;
}
}
goto out;
}
if (NLM_IN_GRACE(g)) {
goto out;
}
/* Convert to local form. */
if (error) {
goto out;
}
/* BSD: VOP_ADVLOCK(nv->nv_vp, NULL, F_GETLK, &fl, F_REMOTE); */
if (error) {
goto out;
}
goto out;
}
/*
* This lock "test" fails due to a conflicting lock.
*
* If this is a v1 client, make sure the conflicting
* lock range we report can be expressed with 32-bit
* offsets. The lock range requested was expressed
* as 32-bit offset and length, so at least part of
* the conflicting lock should lie below MAX_UOFF32.
* If the conflicting lock extends past that, we'll
* trim the range to end at MAX_UOFF32 so this lock
* can be represented in a 32-bit response. Check
* the start also (paranoid, but a low cost check).
*/
}
/*
* Build the nlm4_holder result structure.
*
* Note that lh->oh is freed via xdr_free,
* xdr_nlm4_holder, xdr_netobj, xdr_bytes.
*/
out:
/*
* If we have a callback function, use that to
* deliver the response via another RPC call.
*/
nlm_host_release(g, host);
}
/*
* NLM_LOCK, NLM_LOCK_MSG, NLM_NM_LOCK
* NLM4_LOCK, NLM4_LOCK_MSG, NLM4_NM_LOCK
*
* Client request to set a lock, possibly blocking.
*
* If the lock needs to block, we return status blocked to
* this RPC call, and then later call back the client with
* a "granted" callback. Tricky aspects of this include:
* sending a reply before this function returns, and then
* borrowing this thread from the RPC service pool for the
* wait on the lock and doing the later granted callback.
*
* We also have to keep a list of locks (pending + granted)
* both to handle retransmitted requests, and to keep the
* vnodes for those locks active.
*/
void
{
struct nlm_globals *g;
char *netid;
char *name;
goto doreply;
}
/*
* If we may need to do _msg_ call needing an RPC
* callback, get the RPC client handle now,
* so we know if we can bind to the NLM service on
* this client.
*
* Note: host object carries transport type.
* One client using multiple transports gets
* separate sysids for each of its transports.
*/
if (error != 0) {
goto doreply;
}
}
/*
* During the "grace period", only allow reclaim.
*/
goto doreply;
}
/*
* Check whether we missed host shutdown event
*/
/*
* Get a hold on the vnode for a lock operation.
* Only lock() and share() need vhold objects.
*/
goto doreply;
}
/* Convert to local form. */
if (error) {
goto doreply;
}
/*
* Try to lock non-blocking first. If we succeed
* getting the lock, we can reply with the granted
* status directly and avoid the complications of
* making the "granted" RPC callback later.
*
* This also let's us find out now about some
* possible errors like EROFS, etc.
*/
switch (error) {
case 0:
/* Got it without waiting! */
do_mon_req = TRUE;
break;
/* EINPROGRESS too? */
case EAGAIN:
/* We did not get the lock. Should we block? */
break;
}
/*
* Should block. Try to reserve this thread
* so we can use it to wait for the lock and
* later send the granted message. If this
* reservation fails, say "no resources".
*/
break;
}
/*
* OK, can detach this thread, so this call
* will block below (after we reply).
*/
do_blocking = TRUE;
do_mon_req = TRUE;
break;
case ENOLCK:
/* Failed for lack of resources. */
break;
case EROFS:
/* read-only file system */
break;
case EFBIG:
/* file too big */
break;
case EDEADLK:
/* dead lock condition */
break;
default:
break;
}
/*
* We get one of two function pointers; one for a
* normal RPC reply, and another for doing an RPC
* "callback" _res reply for a _msg function.
* Use either of those to send the reply now.
*
* If sending this reply fails, just leave the
* lock in the list for retransmitted requests.
* Cleanup is via unlock or host rele (statmon).
*/
/* i.e. nlm_lock_1_reply */
}
/*
* The reply has been sent to the client.
* Start monitoring this client (maybe).
*
* Note that the non-monitored (NM) calls pass grant_cb=NULL
* indicating that the client doesn't support RPC callbacks.
* No monitoring for these (lame) clients.
*/
if (do_blocking) {
/*
* We need to block on this lock, and when that
* completes, do the granted RPC call. Note that
* we "reserved" this thread above, so we can now
* "detach" it from the RPC SVC pool, allowing it
* to block indefinitely if needed.
*/
}
nlm_host_release(g, host);
}
/*
* Helper for nlm_do_lock(), partly for observability,
* (we'll see a call blocked in this function) and
* because nlm_do_lock() was getting quite long.
*/
static void
{
int error;
/*
* Keep a list of blocked locks on nh_pending, and use it
* to cancel these threads in nlm_destroy_client_pending.
*
* Check to see if this lock is already in the list
* and if not, add an entry for it. Allocate first,
* then if we don't insert, free the new one.
* Caller already has vp held.
*/
if (error != 0) {
/*
* Sleeping lock request with given fl is already
* registered by someone else. This means that
* some other thread is handling the request, let
* him to do its work.
*/
return;
}
/* BSD: VOP_ADVLOCK(vp, NULL, F_SETLK, fl, F_REMOTE); */
if (error != 0) {
/*
* We failed getting the lock, but have no way to
* tell the client about that. Let 'em time out.
*/
return;
}
/*
* Do the "granted" call-back to the client.
*/
}
/*
* The function that is used as flk callback when NLM server
* sets new sleeping lock. The function unregisters NLM
* sleeping lock request (nlm_slreq) associated with the
* sleeping lock _before_ lock becomes active. It prevents
* potential race condition between nlm_block() and
* nlm_do_cancel().
*/
static callb_cpr_t *
{
if (when == FLK_AFTER_SLEEP) {
}
return (0);
}
/*
* NLM_CANCEL, NLM_CANCEL_MSG,
* NLM4_CANCEL, NLM4_CANCEL_MSG,
* Client gives up waiting for a blocking lock.
*/
void
{
struct nlm_globals *g;
char *netid;
char *name;
int error;
return;
}
if (error != 0) {
goto out;
}
}
if (NLM_IN_GRACE(g)) {
goto out;
}
goto out;
}
/* Convert to local form. */
if (error) {
goto out;
}
if (error != 0) {
/*
* There's no sleeping lock request corresponding
* to the lock. Then requested sleeping lock
* doesn't exist.
*/
goto out;
}
out:
/*
* If we have a callback function, use that to
* deliver the response via another RPC call.
*/
nlm_host_release(g, host);
}
/*
* NLM_UNLOCK, NLM_UNLOCK_MSG,
* NLM4_UNLOCK, NLM4_UNLOCK_MSG,
* Client removes one of their locks.
*/
void
{
struct nlm_globals *g;
char *netid;
char *name;
int error;
/*
* NLM_UNLOCK operation doesn't have an error code
* denoting that operation failed, so we always
* return nlm4_granted except when the server is
* in a grace period.
*/
return;
if (error != 0)
goto out;
}
if (NLM_IN_GRACE(g)) {
goto out;
}
goto out;
/* Convert to local form. */
if (error)
goto out;
/* BSD: VOP_ADVLOCK(nv->nv_vp, NULL, F_UNLCK, &fl, F_REMOTE); */
out:
/*
* If we have a callback function, use that to
* deliver the response via another RPC call.
*/
nlm_host_release(g, host);
}
/*
* NLM_GRANTED, NLM_GRANTED_MSG,
* NLM4_GRANTED, NLM4_GRANTED_MSG,
*
* This service routine is special. It's the only one that's
* really part of our NLM _client_ support, used by _servers_
* to "call back" when a blocking lock from this NLM client
* is granted by the server. In this case, we _know_ there is
* already an nlm_host allocated and held by the client code.
* We want to find that nlm_host here.
*
* Over in nlm_call_lock(), the client encoded the sysid for this
* server in the "owner handle" netbuf sent with our lock request.
* We can now use that to find the nlm_host object we used there.
* (NB: The owner handle is opaque to the server.)
*/
void
{
struct nlm_globals *g;
int error;
return;
return;
if (error != 0)
goto out;
}
if (NLM_IN_GRACE(g)) {
goto out;
}
if (error == 0)
out:
/*
* If we have a callback function, use that to
* deliver the response via another RPC call.
*/
nlm_host_release(g, host);
}
/*
* NLM_FREE_ALL, NLM4_FREE_ALL
*
* Destroy all lock state for the calling client.
*/
void
{
struct nlm_globals *g;
/* Serialize calls to clean locks. */
mutex_enter(&g->clean_lock);
/*
* Find all hosts that have the given node name and put them on a
* local list.
*/
mutex_enter(&g->lock);
/*
* If needed take the host out of the idle list since
* we are taking a reference.
*/
nh_link);
}
}
}
mutex_exit(&g->lock);
/* Free locks for all hosts on the local list. */
while (!TAILQ_EMPTY(&host_list)) {
/*
* Note that this does not do client-side cleanup.
* We want to do that ONLY if statd tells us the
* server has restarted.
*/
nlm_host_release(g, hostp);
}
mutex_exit(&g->clean_lock);
(void) res;
(void) sr;
}
static void
{
default:
case fsa_NONE:
break;
case fsa_R:
break;
case fsa_W:
break;
case fsa_RW:
break;
}
default:
case fsm_DN:
break;
case fsm_DR:
break;
case fsm_DW:
break;
case fsm_DRW:
break;
}
}
/*
* NLM_SHARE, NLM4_SHARE
*
* Request a DOS-style share reservation
*/
void
{
struct nlm_globals *g;
char *netid;
char *name;
int error;
return;
}
goto out;
}
/*
* Get holded vnode when on lock operation.
* Only lock() and share() need vhold objects.
*/
goto out;
}
/* Convert to local form. */
if (error == 0) {
nlm_host_monitor(g, host, 0);
} else {
}
out:
nlm_host_release(g, host);
}
/*
* NLM_UNSHARE, NLM4_UNSHARE
*
* Release a DOS-style share reservation
*/
void
{
struct nlm_globals *g;
char *netid;
int error;
return;
}
if (NLM_IN_GRACE(g)) {
goto out;
}
goto out;
}
/* Convert to local form. */
(void) error;
out:
nlm_host_release(g, host);
}
/*
* NLM wrapper to VOP_FRLOCK that checks the validity of the lock before
* invoking the vnode operation.
*/
static int
{
return (EOVERFLOW);
}
}