/*
* 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 2013 Nexenta Systems, Inc. All rights reserved.
*/
/*
* Lock range service functions process SMB lock and and unlock
* requests for a file by applying lock rules and marks file range
* as locked if the lock is successful otherwise return proper
* error code.
*/
#include <smbsrv/smb_kproto.h>
#include <smbsrv/smb_fsops.h>
extern caller_context_t smb_ct;
smb_llist_t *, uint64_t *);
static void smb_lock_destroy(smb_lock_t *);
static void smb_lock_free(smb_lock_t *);
/*
* Return the number of range locks on the specified ofile.
*/
{
++count;
}
return (count);
}
/*
* smb_unlock_range
*
* locates lock range performed for corresponding to unlock request.
*
* NT_STATUS_SUCCESS - Lock range performed successfully.
* !NT_STATUS_SUCCESS - Error in unlock range operation.
*/
{
/* Apply unlocking rules */
if (status != NT_STATUS_SUCCESS) {
/*
* If lock range is not matching in the list
* return error.
*/
return (status);
}
return (status);
}
/*
* smb_lock_range
*
* Checks for integrity of file lock operation for the given range of file data.
* This is performed by applying lock rules with all the elements of the node
* lock list.
*
* Break shared (levelII) oplocks. If there is an exclusive oplock, it is
* owned by this ofile and therefore should not be broken.
*
* The function returns with new lock added if lock request is non-conflicting
* with existing range lock for the file. Otherwise smb request is filed
* without returning.
*
* NT_STATUS_SUCCESS - Lock range performed successfully.
* !NT_STATUS_SUCCESS - Error in lock range operation.
*/
{
for (;;) {
/* Apply locking rules */
if ((result == NT_STATUS_CANCELLED) ||
(result == NT_STATUS_SUCCESS) ||
(result == NT_STATUS_RANGE_NOT_LOCKED)) {
break;
} else if (timeout == 0) {
break;
}
/*
* Call smb_lock_wait holding write lock for
* node lock list. smb_lock_wait will release
* this lock if it blocks.
*/
if (rc == 0) {
break;
}
if (rc == -1)
timeout = 0;
}
if (result != NT_STATUS_SUCCESS) {
/*
* Under certain conditions NT_STATUS_FILE_LOCK_CONFLICT
* should be returned instead of NT_STATUS_LOCK_NOT_GRANTED.
* All of this appears to be specific to SMB1
*/
/*
* Locks with timeouts always return
* NT_STATUS_FILE_LOCK_CONFLICT
*/
if (lock_has_timeout)
/*
* Locks starting higher than 0xef000000 that do not
* have the MSB set always return
* NT_STATUS_FILE_LOCK_CONFLICT
*/
}
/*
* If the last lock attempt to fail on this file handle
* started at the same offset as this one then return
* NT_STATUS_FILE_LOCK_CONFLICT
*/
}
}
/* Update last lock failed offset */
} else {
/*
* don't insert into the CIFS lock list unless the
* posix lock worked
*/
else
}
if (result == NT_STATUS_SUCCESS)
return (result);
}
/*
* smb_lock_range_access
*
* scans node lock list
* to check if there is any overlapping lock. Overlapping
* lock is allowed only under same session and client pid.
*
* Return values
* NT_STATUS_SUCCESS lock access granted.
* NT_STATUS_FILE_LOCK_CONFLICT access denied due to lock conflict.
*/
int
{
/* Search for any applicable lock */
/* Lock does not overlap */
continue;
continue;
continue;
break;
}
return (status);
}
void
{
/*
* Move locks matching the specified file from the node->n_lock_list
* to a temporary list (holding the lock the entire time) then
* destroy all the matching locks. We can't call smb_lock_destroy
* while we are holding the lock for node->n_lock_list because we will
* deadlock and we can't drop the lock because the list contents might
* change (for example nxtl might get removed on another thread).
*/
while (lock) {
}
}
while (lock) {
}
}
void
{
if (status32 == NT_STATUS_CANCELLED)
else
}
/*
* An SMB variant of nbl_conflict().
*
* SMB prevents remove or rename when conflicting locks exist
* (unlike NFS, which is why we can't just use nbl_conflict).
*
* Returns:
* NT_STATUS_SHARING_VIOLATION - nbl_share_conflict
* NT_STATUS_FILE_LOCK_CONFLICT - nbl_lock_conflict
* NT_STATUS_SUCCESS - operation can proceed
*
* NB: This function used to also check the list of ofiles,
* via: smb_lock_range_access() but we _can't_ do that here
* due to lock order constraints between node->n_lock_list
* and node->vp->vnbllock (taken via nvl_start_crit).
* They must be taken in that order, and in here, we
* already hold vp->vnbllock.
*/
{
int svmand;
if (smb_node_is_dir(node))
return (NT_STATUS_SUCCESS);
return (NT_STATUS_SHARING_VIOLATION);
/*
* When checking for lock conflicts, rename and remove
*/
op = NBL_READWRITE;
svmand = 1;
return (NT_STATUS_FILE_LOCK_CONFLICT);
return (NT_STATUS_SUCCESS);
}
/*
* smb_lock_posix_unlock
*
* checks if the current unlock request is in another lock and repeatedly calls
* smb_is_range_unlocked on a sliding basis to unlock all bits of the lock
* that are not in other locks
*
*/
static void
{
new_mark = 0;
for (;;) {
if (can_unlock) {
if (new_mark) {
new_unlock = *lock;
} else {
new_unlock = *lock;
break;
}
} else if (new_mark) {
} else {
break;
}
}
}
/*
* smb_lock_range_overlap
*
* Checks if lock range(start, length) overlaps range in lock structure.
*
* Zero-length byte range locks actually affect no single byte of the stream,
* meaning they can still be accessed even with such locks in place. However,
* they do conflict with other ranges in the following manner:
* conflict will only exist if the positive-length range contains the
* zero-length range's offset but doesn't start at it
*
* return values:
* 0 - Lock range doesn't overlap
* 1 - Lock range overlaps.
*/
#define RANGE_NO_OVERLAP 0
static int
{
if (length == 0) {
return (RANGE_OVERLAP);
return (RANGE_NO_OVERLAP);
}
/* The following test is intended to catch roll over locks. */
return (RANGE_OVERLAP);
return (RANGE_OVERLAP);
return (RANGE_OVERLAP);
return (RANGE_NO_OVERLAP);
}
/*
* smb_lock_range_lckrules
*
* Lock range rules:
* 1. Overlapping read locks are allowed if the
* current locks in the region are only read locks
* irrespective of pid of smb client issuing lock request.
*
* 2. Read lock in the overlapped region of write lock
* are allowed if the pervious lock is performed by the
* same pid and connection.
*
* return status:
* NT_STATUS_SUCCESS - Input lock range adapts to lock rules.
* NT_STATUS_LOCK_NOT_GRANTED - Input lock conflicts lock rules.
* NT_STATUS_CANCELLED - Error in processing lock rules
*/
static uint32_t
smb_lock_t **clockp)
{
/* Check if file is closed */
if (!smb_ofile_is_open(file)) {
return (NT_STATUS_RANGE_NOT_LOCKED);
}
/* Caller must hold lock for node->n_lock_list */
continue;
/*
* Check to see if lock in the overlapping record
* is only read lock. Current finding is read
* locks can overlapped irrespective of pids.
*/
continue;
}
/*
* When the read lock overlaps write lock, check if
* allowed.
*/
continue;
}
}
/* Conflict in overlapping lock element */
break;
}
return (status);
}
/*
* smb_lock_wait
*
* Wait operation for smb overlapping lock to be released. Caller must hold
* write lock for node->n_lock_list so that the set of active locks can't
* change unexpectedly. The lock for node->n_lock_list will be released
* within this function during the sleep after the lock dependency has
* been recorded.
*
* return value
*
* 0 The request was canceled.
* -1 The timeout was reached.
* >0 Condition met.
*/
static clock_t
{
case SMB_REQ_STATE_ACTIVE:
/*
* Wait up till the timeout time keeping track of actual
* time waited for possible retry failure.
*/
/*
* The conflict list (l_conflict_list) for a lock contains
* all the locks that are blocked by and in conflict with
* that lock. Add the new lock to the conflict list for the
* active lock.
*
* l_conflict_list is currently a fancy way of representing
* the references/dependencies on a lock. It could be
* replaced with a reference count but this approach
* has the advantage that MDB can display the lock
* dependencies at any point in time. In the future
* we should be able to leverage the list to implement
* an asynchronous locking model.
*
* l_blocked_by is the reverse of the conflict list. It
* points to the lock that the new lock conflicts with.
* As currently implemented this value is purely for
* debug purposes -- there are windows of time when
* l_blocked_by may be non-NULL even though there is no
* conflict list
*/
if (SMB_LOCK_INDEFINITE_WAIT(b_lock)) {
} else {
}
rc = 0;
} else {
}
break;
default:
rc = 0;
break;
}
return (rc);
}
/*
* smb_lock_range_ulckrules
*
* 1. Unlock should be performed at exactly matching ends.
* This has been changed because overlapping ends is
* allowed and there is no other precise way of locating
* lock entity in node lock list.
*
* 2. Unlock is failed if there is no corresponding lock exists.
*
* Return values
*
* NT_STATUS_SUCCESS Unlock request matches lock record
* pointed by 'nodelock' lock structure.
*
* NT_STATUS_RANGE_NOT_LOCKED Unlock request doen't match any
* of lock record in node lock request or
* error in unlock range processing.
*/
static uint32_t
{
/* Caller must hold lock for node->n_lock_list */
break;
}
}
return (status);
}
static smb_lock_t *
{
/*
* Calculate the absolute end time so that we can use it
* in cv_timedwait.
*/
return (lock);
}
static void
{
}
/*
* smb_lock_destroy
*
* Caller must hold node->n_lock_list
*/
static void
{
/*
* Caller must hold node->n_lock_list lock.
*/
/*
* The cv_broadcast above should wake up any locks that previous
* had conflicts with this lock. Wait for the locking threads
* to remove their references to this lock.
*/
}
/*
* smb_is_range_unlocked
*
* Checks if the current unlock byte range request overlaps another lock
* This function is used to determine where POSIX unlocks should be
* applied.
*
* The return code and the value of new_mark must be interpreted as
* follows:
*
* B_TRUE and (new_mark == 0):
* This is the last or only lock left to be unlocked
*
* B_TRUE and (new_mark > 0):
* The range from start to new_mark can be unlocked
*
* B_FALSE and (new_mark == 0):
* The unlock can't be performed and we are done
*
* B_FALSE and (new_mark > 0),
* The range from start to new_mark can't be unlocked
* Start should be reset to new_mark for the next pass
*/
static boolean_t
{
*new_mark = 0;
while (lk) {
continue;
}
continue;
}
/*
* there is no overlap for the first 2 cases
* check next node
*/
continue;
}
continue;
}
/* this range is completely locked */
return (B_FALSE);
}
/* the first part of this range is locked */
return (B_FALSE);
}
/* this piece is unlocked */
if (low_water_mark > lk_start)
}
}
if (low_water_mark != MAXOFFSET_T) {
return (B_TRUE);
}
/* the range is completely unlocked */
return (B_TRUE);
}