smb_node.c revision b1352070d318187b41b088da3533692976f3f225
/*
* 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.
*/
/*
* SMB Node State Machine
* ----------------------
*
*
* +----------- Creation/Allocation
* |
* | T0
* |
* v
* +----------------------------+ T1
* | SMB_NODE_STATE_AVAILABLE |--------------------+
* +----------------------------+ |
* | ^ |
* | | v
* | | T2 +-------------------------------+
* | |<---------| SMB_NODE_STATE_OPLOCK_GRANTED |
* | | +-------------------------------+
* | T5 | |
* | | | T3
* | | v
* | | T4 +--------------------------------+
* | +----------| SMB_NODE_STATE_OPLOCK_BREAKING |
* | +--------------------------------+
* |
* v
* +-----------------------------+
* | SMB_NODE_STATE_DESTROYING |
* +-----------------------------+
* |
* |
* | T6
* |
*
* Transition T0
*
* This transition occurs in smb_node_lookup(). If the node looked for is
* not found in the has table a new node is created. The reference count is
* initialized to 1 and the state initialized to SMB_NODE_STATE_AVAILABLE.
*
* Transition T1
*
* This transition occurs smb_oplock_acquire() during an OPEN.
*
* Transition T2
*
* This transition occurs in smb_oplock_release(). The events triggering
* it are:
*
* - LockingAndX sent by the client that was granted the oplock.
* - Closing of the file.
*
* Transition T3
*
* This transition occurs in smb_oplock_break(). The events triggering
* it are:
*
* - Another client wants to open the file.
* - A client is trying to delete the file.
* - A client is trying to rename the file.
*
* Transition T4
*
* This transition occurs in smb_oplock_release or smb_oplock_break(). The
* events triggering it are:
*
* - The client that was granting the oplock releases it (close or
* LockingAndx).
* - The time alloted to release the oplock expired.
*
* Transition T5
*
* This transition occurs in smb_node_release(). If the reference count
* drops to zero the state is moved to SMB_NODE_STATE_DESTROYING and no more
* reference count will be given out for that node.
*
* Transition T6
*
* This transition occurs in smb_node_release(). The structure is deleted.
*
* Comments
* --------
*
* The reason the smb node has 2 states is the following synchronization
* rule:
*
* There's a mutex embedded in the node used to protect its fields and
* there's a lock embedded in the bucket of the hash table the node belongs
* to. To increment or to decrement the reference count the mutex must be
* entered. To insert the node into the bucket and to remove it from the
* bucket the lock must be entered in RW_WRITER mode. When both (mutex and
* lock) have to be entered, the lock has always to be entered first then
* the mutex. This prevents a deadlock between smb_node_lookup() and
* smb_node_release() from occurring. However, in smb_node_release() when the
* reference count drops to zero and triggers the deletion of the node, the
* mutex has to be released before entering the lock of the bucket (to
* remove the node). This creates a window during which the node that is
* about to be freed could be given out by smb_node_lookup(). To close that
* window the node is moved to the state SMB_NODE_STATE_DESTROYING before
* releasing the mutex. That way, even if smb_node_lookup() finds it, the
* state will indicate that the node should be treated as non existent (of
* protection of the mutex).
*/
#include <smbsrv/smb_incl.h>
#include <smbsrv/smb_fsops.h>
#include <smbsrv/smb_kstat.h>
#include <sys/pathname.h>
uint32_t smb_is_executable(char *);
static void smb_node_delete_on_close(smb_node_t *);
static void smb_node_create_audit_buf(smb_node_t *, int);
static void smb_node_destroy_audit_buf(smb_node_t *);
static void smb_node_audit(smb_node_t *);
static void smb_node_free(smb_node_t *);
static int smb_node_constructor(void *, void *, int);
static void smb_node_destructor(void *, void *);
/*
* smb_node_init
*
* Initialization of the SMB node layer.
*
* This function is not multi-thread safe. The caller must make sure only one
* thread makes the call.
*/
int
smb_node_init(void)
{
int i;
if (smb_node_initialized)
return (0);
for (i = 0; i <= SMBND_HASH_MASK; i++) {
}
return (0);
}
/*
* smb_node_fini
*
* This function is not multi-thread safe. The caller must make sure only one
* thread makes the call.
*/
void
smb_node_fini(void)
{
int i;
if (!smb_node_initialized)
return;
#ifdef DEBUG
for (i = 0; i <= SMBND_HASH_MASK; i++) {
/*
* The following sequence is just intended for sanity check.
* This will have to be modified when the code goes into
* production.
*
* The SMB node hash table should be emtpy at this point. If the
* hash table is not empty a panic will be triggered.
*
* The reason why SMB nodes are still remaining in the hash
* table is problably due to a mismatch between calls to
* smb_node_lookup() and smb_node_release(). You must track that
* down.
*/
}
#endif
for (i = 0; i <= SMBND_HASH_MASK; i++) {
}
}
/*
* smb_node_lookup()
*
* NOTE: This routine should only be called by the file system interface layer,
* and not by SMB.
*
* smb_node_lookup() is called upon successful lookup, mkdir, and create
* (for both non-streams and streams). In each of these cases, a held vnode is
* passed into this routine. If a new smb_node is created it will take its
* own hold on the vnode. The caller's hold therefore still belongs to, and
* should be released by, the caller.
*
* A reference is taken on the smb_node whether found in the hash table
* or newly created.
*
* If an smb_node needs to be created, a reference is also taken on the
* dir_snode (if passed in).
*
* See smb_node_release() for details on the release of these references.
*/
/*ARGSUSED*/
struct smb_request *sr,
struct open_param *op,
char *od_name,
{
int error;
/*
* smb_vop_getattr() is called here instead of smb_fsop_getattr(),
* because the node may not yet exist. We also do not want to call
* it with the list lock held.
*/
if (unnamed_node)
/*
* This getattr is performed on behalf of the server
* that's why kcred is used not the user's cred
*/
if (error)
return (NULL);
/*
* The fsid for a file is that of the tree, even
* if the file resides in a different mountpoint
* under the share.
*/
} else {
/*
* This should be getting executed only for the
* tree root smb_node.
*/
}
for (;;) {
while (node) {
smb_node_t *, node);
case SMB_NODE_STATE_AVAILABLE:
/* The node was found. */
node);
}
return (node);
/*
* Although the node exists it is about
* to be destroyed. We act as it hasn't
* been found.
*/
break;
default:
/*
* Although the node exists it is in an
* unknown state. We act as it hasn't
* been found.
*/
ASSERT(0);
break;
}
}
}
continue;
}
break;
}
if (op)
if (dir_snode) {
}
if (unnamed_node) {
}
return (node);
}
/*
* smb_stream_node_lookup()
*
* Note: stream_name (the name that will be stored in the "od_name" field
* of a stream's smb_node) is the same as the on-disk name for the stream
* except that it does not have SMB_STREAM_PREFIX prepended.
*/
{
if (xattrdir_node == NULL)
return (NULL);
(void) smb_node_release(xattrdir_node);
return (snode);
}
/*
* This function should be called whenever a reference is needed on an
* smb_node pointer. The copy of an smb_node pointer from one non-local
* data structure to another requires a reference to be taken on the smb_node
* (unless the usage is localized). Each data structure deallocation routine
* will call smb_node_release() on its smb_node pointers.
*
* In general, an smb_node pointer residing in a structure should never be
* stale. A node pointer may be NULL, however, and care should be taken
* prior to calling smb_node_ref(), which ASSERTs that the pointer is valid.
* Care also needs to be taken with respect to racing deallocations of a
* structure.
*/
void
{
case SMB_NODE_STATE_AVAILABLE:
break;
default:
SMB_PANIC();
}
}
/*
* smb_node_lookup() takes a hold on an smb_node, whether found in the
* hash table or newly created. This hold is expected to be released
* in the following manner.
*
* smb_node_lookup() takes an address of an smb_node pointer. This should
* be getting passed down via a lookup (whether path name or component), mkdir,
* create. If the original smb_node pointer resides in a data structure, then
* the deallocation routine for the data structure is responsible for calling
* smb_node_release() on the smb_node pointer. Alternatively,
* smb_node_release() can be called as soon as the smb_node pointer is no longer
* needed. In this case, callers are responsible for setting an embedded
* pointer to NULL if it is known that the last reference is being released.
*
* If the passed-in address of the smb_node pointer belongs to a local variable,
* then the caller with the local variable should call smb_node_release()
* directly.
*
* smb_node_release() itself will call smb_node_release() on a node's dir_snode,
* as smb_node_lookup() takes a hold on dir_snode.
*/
void
{
case SMB_NODE_STATE_AVAILABLE:
/*
* Check if the file was deleted
*/
}
if (node->unnamed_stream_node) {
}
return;
default:
SMB_PANIC();
}
}
}
static void
{
int rc = 0;
else
}
if (rc != 0)
}
/*
* smb_node_rename()
*
*/
void
char *to_name)
{
case SMB_NODE_STATE_AVAILABLE:
/*
* XXX Need to update attributes?
*/
break;
default:
SMB_PANIC();
}
}
int
{
int error;
if (error) {
return (error);
}
return (0);
}
/*
* smb_node_get_size
*/
{
return (0);
else
return (size);
}
static int
{
return (-1);
return (1);
/* Seconds are equal compare tv_nsec */
return (-1);
}
/*
* smb_node_set_time
*
* This function will update the time stored in the node and
* set the appropriate flags. If there is nothing to update,
* the function will return without any updates. The update
* is only in the node level and the attribute in the file system
* will be updated when client close the file.
*/
void
{
if (what == 0)
return;
return;
if ((what & SMB_AT_CRTIME) &&
crtime) != 0) {
}
if ((what & SMB_AT_MTIME) &&
mtime) != 0) {
}
if ((what & SMB_AT_ATIME) &&
atime) != 0) {
}
/*
* The ctime handling is trickier. It has three scenarios.
* 1. Only ctime need to be set and it is the same as the ctime
* stored in the node. (update not necessary)
* 2. The ctime is the same as the ctime stored in the node but
* is not the only time need to be set. (update required)
* 3. The ctime need to be set and is not the same as the ctime
* stored in the node. (update required)
* Unlike other time setting, the ctime needs to be set even when
* it is the same as the ctime in the node if there are other time
* needs to be set (#2). This will ensure the ctime not being
* updated when other times are being updated in the file system.
*
* Retained file rules:
*
* 1. Don't add SMB_AT_CTIME to node->what by default because the
* request will be rejected by filesystem
* 2. 'what' SMB_AT_CTIME shouldn't be set for retained files, i.e.
* any request for changing ctime on these files should have
* been already rejected
*/
if (what & SMB_AT_CTIME) {
if ((what == SMB_AT_CTIME) &&
ctime) == 0) {
} else {
}
} else {
}
}
{
}
{
}
{
}
{
}
/*
* smb_node_set_dosattr
*
* Parse the specified DOS attributes and, if they have been modified,
* update the node cache. This call should be followed by a
* smb_sync_fsattr() call to write the attribute changes to filesystem.
*/
void
{
}
}
/*
* smb_node_get_dosattr()
*
* This function is used to provide clients with information as to whether
* the readonly bit is set. Hence both the node attribute cache (which
* reflects the on-disk attributes) and node->readonly_creator (which
* reflects whether a readonly set is pending from a readonly create) are
* checked. In the latter case, the readonly attribute should be visible to
* all clients even though the readonly creator fid is immune to the readonly
* bit until close.
*/
{
if (node->readonly_creator)
if (!dosattr)
return (dosattr);
}
/*
* When DeleteOnClose is set on an smb_node, the common open code will
* reject subsequent open requests for the file. Observation of Windows
* 2000 indicates that subsequent opens should be allowed (assuming
* there would be no sharing violation) until the file is closed using
* the fid on which the DeleteOnClose was requested.
*
* If there are multiple opens with delete-on-close create options,
* whichever the first file handle is closed will trigger the node to be
* marked as delete-on-close. The credentials of that ofile will be used
* as the delete-on-close credentials of the node.
*/
int
{
int rc = -1;
rc = 0;
}
return (rc);
}
void
{
node->n_delete_on_close_flags = 0;
}
}
/*
* smb_node_open_check
*
* check file sharing rules for current open request
* against all existing opens for a file.
*
* Returns NT_STATUS_SHARING_VIOLATION if there is any
* sharing conflict, otherwise returns NT_STATUS_SUCCESS.
*/
{
while (of) {
switch (status) {
case NT_STATUS_INVALID_HANDLE:
case NT_STATUS_SUCCESS:
break;
default:
return (status);
}
}
return (NT_STATUS_SUCCESS);
}
{
/*
* Intra-CIFS check
*/
while (of) {
switch (status) {
case NT_STATUS_INVALID_HANDLE:
case NT_STATUS_SUCCESS:
break;
default:
return (status);
}
}
/*
* system-wide share check
*/
return (NT_STATUS_SHARING_VIOLATION);
else
return (NT_STATUS_SUCCESS);
}
{
return (NT_STATUS_SUCCESS);
/*
* intra-CIFS check
*/
while (of) {
switch (status) {
case NT_STATUS_INVALID_HANDLE:
case NT_STATUS_SUCCESS:
break;
default:
return (status);
}
}
/*
* system-wide share check
*/
return (NT_STATUS_SHARING_VIOLATION);
else
return (NT_STATUS_SUCCESS);
}
void
{
}
}
/*
* smb_node_start_crit()
*
* Enter critical region for share reservations.
* See comments above smb_fsop_shrlock().
*/
void
{
}
/*
* smb_node_end_crit()
*
* Exit critical region for share reservations.
*/
void
{
}
int
{
}
void
{
}
void
{
}
void
{
}
{
return (cntr);
}
void
{
}
void
{
}
void
{
node->n_open_count++;
}
void
{
node->n_open_count--;
}
{
return (cnt);
}
/*
* smb_node_alloc
*/
static smb_node_t *
char *od_name,
{
node->n_orig_uid = 0;
node->waiting_event = 0;
node->n_open_count = 0;
node->n_delete_on_close_flags = 0;
return (node);
}
/*
* smb_node_free
*/
static void
{
}
/*
* smb_node_constructor
*/
static int
{
return (0);
}
/*
* smb_node_destructor
*/
static void
{
}
/*
* smb_node_create_audit_buf
*/
static void
{
if (smb_audit_flags & SMB_AUDIT_NODE) {
}
}
/*
* smb_node_destroy_audit_buf
*/
static void
{
}
}
/*
* smb_node_audit
*
* This function saves the calling stack in the audit buffer of the node passed
* in.
*/
static void
{
if (node->n_audit_buf) {
}
}
static smb_llist_t *
{
}