smb_node.c revision a90cf9f29973990687fa61de9f1f6ea22e924e40
/*
* 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 2014 Nexenta Systems, Inc. All rights reserved.
*/
/*
* SMB Node State Machine
* ----------------------
*
*
* +----------- Creation/Allocation
* |
* | T0
* |
* v
* +----------------------------+
* | SMB_NODE_STATE_AVAILABLE |
* +----------------------------+
* |
* | T1
* |
* v
* +-----------------------------+
* | SMB_NODE_STATE_DESTROYING |
* +-----------------------------+
* |
* |
* | T2
* |
*
* 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 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 T2
*
* 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_kproto.h>
#include <smbsrv/smb_fsops.h>
#include <smbsrv/smb_kstat.h>
#include <sys/extdirent.h>
#include <sys/pathname.h>
#include <fs/fs_reparse.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 *);
static void smb_node_init_system(smb_node_t *);
/* round sz to DEV_BSIZE block */
static smb_node_t *smb_root_node;
/*
* 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.
*/
void
smb_node_init(void)
{
int i;
if (smb_node_cache != NULL)
return;
for (i = 0; i <= SMBND_HASH_MASK; i++) {
}
/*
* The node cache is shared by all zones, so the smb_root_node
* must represent the real (global zone) rootdir.
* Note intentional use of kcred here.
*/
&hashkey);
}
/*
* 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_root_node != NULL) {
}
if (smb_node_cache == NULL)
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
* dnode (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 (unode)
/*
* 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. */
}
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 (dnode) {
}
if (unode) {
}
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);
fnode);
(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 n_dnode,
* as smb_node_lookup() takes a hold on dnode.
*/
void
{
case SMB_NODE_STATE_AVAILABLE:
/*
* Check if the file was deleted
*/
}
}
return;
default:
SMB_PANIC();
}
}
}
static void
{
int rc = 0;
if (smb_node_is_dir(node))
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();
}
}
/*
* in *svrootp. Also create nodes leading to this directory.
*/
int
{
int error;
if (smb_root_node == NULL)
return (ENOENT);
/*
* We're getting smb nodes below the zone root here,
* so need to use kcred, not zone_kcred().
*/
return (error);
}
/*
* Helper function for smb_node_set_delete_on_close(). Assumes node is a dir.
* Return 0 if this is an empty dir. Otherwise return a NT_STATUS code.
* We distinguish between readdir failure and non-empty dir by returning
* different values.
*/
static uint32_t
{
char *name;
union {
char *u_bufptr;
} u;
return (NT_STATUS_CANNOT_DELETE);
if (bsize == 0)
return (NT_STATUS_CANNOT_DELETE);
if (edp) {
} else {
}
return (NT_STATUS_DIRECTORY_NOT_EMPTY);
}
return (0);
}
/*
* 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 rc = 0;
return (NT_STATUS_CANNOT_DELETE);
return (NT_STATUS_CANNOT_DELETE);
}
/*
* If the directory is not empty we should fail setting del-on-close
* with STATUS_DIRECTORY_NOT_EMPTY. see MS's
* "File System Behavior Overview" doc section 4.3.2
*/
if (smb_node_is_dir(node)) {
if (status != 0) {
return (status);
}
}
return (NT_STATUS_CANNOT_DELETE);
}
/*
* Tell any change notify calls to close their handles
* and get out of the way. FILE_ACTION_DELETE_PENDING
* is a special, internal-only action for this purpose.
*/
return (NT_STATUS_SUCCESS);
}
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);
}
}
return (NT_STATUS_SUCCESS);
}
{
if (smb_node_is_dir(node))
return (NT_STATUS_SUCCESS);
if (smb_node_is_reparse(node))
return (NT_STATUS_ACCESS_DENIED);
/*
* intra-CIFS check
*/
while (of) {
switch (status) {
case NT_STATUS_INVALID_HANDLE:
case NT_STATUS_SUCCESS:
break;
default:
return (status);
}
}
return (NT_STATUS_SUCCESS);
}
/*
* smb_node_share_check
*
* Returns: TRUE - ofiles have non-zero share access
* B_FALSE - ofile with share access NONE.
*/
{
if (of)
return (status);
}
/*
* SMB Change Notification
*/
void
{
(void) smb_fem_fcn_install(node);
}
void
{
}
void
{
/*
* These two events come as a pair:
* FILE_ACTION_RENAMED_OLD_NAME
* FILE_ACTION_RENAMED_NEW_NAME
* Only do the parent notify for "new".
*/
if (action == FILE_ACTION_RENAMED_OLD_NAME)
return;
}
/*
* smb_node_notify_parents
*
* Iterate up the directory tree notifying any parent
* directories that are being watched for changes in
* their sub directories.
* Stop at the root node, which has a NULL parent node.
*/
void
{
/* cd .. */
}
}
/*
* 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
{
}
void
{
}
void
{
}
/*
* smb_node_inc_open_ofiles
*/
void
{
}
/*
* smb_node_dec_open_ofiles
* returns new value
*/
{
}
/*
* smb_node_inc_opening_count
*/
void
{
}
/*
* smb_node_dec_opening_count
*/
void
{
}
/*
* smb_node_getmntpath
*/
int
{
int err;
return (ENOENT);
/* NULL is passed in as we want to start at "/" */
return (err);
}
/*
* smb_node_getshrpath
*
* Determine the absolute pathname of 'node' within the share (tree).
* For example if the node represents file "test1.txt" in directory
* "dir1" the pathname would be: \dir1\test1.txt
*/
int
{
int rc;
return (rc);
}
/*
* smb_node_getpath
*
* Determine the absolute pathname of 'node' from 'rootvp'.
*
* Using vnodetopath is only reliable for directory nodes (due to
* its reliance on the DNLC for non-directory nodes). Thus, if node
* represents a file, construct the pathname for the parent dnode
* and append filename.
* If node represents a named stream, construct the pathname for the
* associated unnamed stream and append the stream name.
*
* The pathname returned in buf will be '/' separated.
*/
int
{
int rc;
/* find path to directory node */
if (rootvp) {
} else {
}
if (rc != 0)
return (rc);
/* append filename if necessary */
if (!smb_node_is_dir(unode)) {
}
/* append named stream name if necessary */
if (SMB_IS_STREAM(node))
return (rc);
}
/*
* smb_node_alloc
*/
static smb_node_t *
char *od_name,
{
node->n_pending_dosattr = 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
{
#ifdef _KERNEL
if (node->n_audit_buf) {
}
#else /* _KERNEL */
#endif /* _KERNEL */
}
static smb_llist_t *
{
}
{
}
{
}
{
}
{
}
{
}
{
}
{
}
/*
* smb_node_file_is_readonly
*
* Checks if the file (which node represents) is marked readonly
* in the filesystem. No account is taken of any pending readonly
* in the node, which must be handled by the callers.
* (See SMB_OFILE_IS_READONLY and SMB_PATHFILE_IS_READONLY)
*/
{
return (B_FALSE); /* pipes */
return (B_TRUE);
}
/*
* smb_node_setattr
*
* The sr may be NULL, for example when closing an ofile.
* The ofile may be NULL, for example when a client request
* specifies the file by pathname.
*
* Returns: errno
*
* Timestamps
*
* Windows and Unix have different models for timestamp updates.
* [MS-FSA 2.1.5.14 Server Requests Setting of File Information]
*
* An open "handle" in Windows can control whether and when
* any timestamp updates happen for that handle. For example,
* timestamps set via some handle are no longer updated by I/O
* operations on that handle. In Unix we don't really have any
* way to avoid the timestamp updates that the file system does.
* Therefore, we need to make some compromises, and simulate the
* more important parts of the Windows file system semantics.
*
* For example, when an SMB client sets file times, set those
* times in the file system (so the change will be visible to
* other clients, at least until they change again) but we also
* make those times "sticky" in our open handle, and reapply
* those times when the handle is closed. That reapply on close
* simulates the Windows behavior where the timestamp updates
* would be discontinued after they were set. These "sticky"
* attributes are returned in any query on the handle where
* they are stored.
*
* Other than the above, the file system layer takes care of the
* normal time stamp updates, such as updating the mtime after a
* write, and ctime after an attribute change.
*
* Dos Attributes are stored persistently, but with a twist:
* In Windows, when you set the "read-only" bit on some file,
* existing writable handles to that file continue to have
* write access. (because access check happens at open)
* If we were to set the read-only bit directly, we would
* cause errors in subsequent writes on any of our open
* (and writable) file handles. So here too, we have to
* simulate the Windows behavior. We keep the read-only
* bit "pending" in the smb_node (so it will be visible in
* any new opens of the file) and apply it on close.
*
* File allocation size is also simulated, and not persistent.
* When the file allocation size is set it is first rounded up
* to block size. If the file size is smaller than the allocation
* size the file is truncated by setting the filesize to allocsz.
*/
int
{
int rc;
/* set attributes specified in attr */
return (0); /* nothing to do (caller bug?) */
/*
* Allocation size and EOF position interact.
* We don't persistently store the allocation size
* but make it look like we do while there are opens.
* Note: We update the caller's attr in the cases
* where they're setting only one of allocsz|size.
*/
case SMB_AT_ALLOCSZ:
/*
* Setting the allocation size but not EOF position.
* Get the current EOF in tmp_attr and (if necessary)
* truncate to the (rounded up) allocation size.
* Using kcred here because if we don't have access,
* we want to fail at setattr below and not here.
*/
if (rc != 0)
return (rc);
/* truncate the file to allocsz */
}
break;
case SMB_AT_SIZE:
/*
* Setting the EOF position but not allocation size.
* If the new EOF position would be greater than
* the allocation size, increase the latter.
*/
attr->sa_allocsz =
}
break;
case SMB_AT_ALLOCSZ | SMB_AT_SIZE:
/*
* Setting both. Increase alloc size if needed.
*/
attr->sa_allocsz =
break;
default:
break;
}
/*
* If we have an open file, and we set the size,
* then set the "written" flag so that at close,
* we can force an mtime update.
*/
/*
* When operating on an open file, some settable attributes
* become "sticky" in the open file object until close.
* (see above re. timestamps)
*/
smb_attr_t *pa;
if (times_mask & SMB_AT_ATIME)
if (times_mask & SMB_AT_MTIME)
if (times_mask & SMB_AT_CTIME)
if (times_mask & SMB_AT_CRTIME)
/*
* The f_pending_attr times are reapplied in
* smb_ofile_close().
*/
}
/*
* After this point, tmp_attr is what we will actually
* store in the file system _now_, which may differ
* from the callers attr and f_pending_attr w.r.t.
* the DOS readonly flag etc.
*/
if (((tmp_attr.sa_dosattr &
FILE_ATTRIBUTE_READONLY) != 0) &&
(node->n_open_count != 0)) {
/* Delay setting readonly */
} else {
node->n_pending_dosattr = 0;
}
}
/*
* Simulate n_allocsz persistence only while
* there are opens. See smb_node_getattr
*/
node->n_open_count != 0)
}
if (rc != 0)
return (rc);
}
return (0);
}
/*
* smb_node_getattr
*
* Get attributes from the file system and apply any smb-specific
* overrides for size, dos attributes and timestamps
*
* When node->n_pending_readonly is set on a node, pretend that
* we've already set this node readonly at the filesystem level.
* We can't actually do that until all writable handles are closed
* or those writable handles would suddenly loose their access.
*
* Returns: errno
*/
int
{
int rc;
/* Deal with some interdependencies */
if (rc != 0)
return (rc);
/*
* When there are open handles, and one of them has
* set the DOS readonly flag (in n_pending_dosattr),
* it will not have been stored in the file system.
* In this case use n_pending_dosattr. Note that
* n_pending_dosattr has only the settable bits,
* (setattr masks it with smb_vop_dosattr_settable)
* so we need to keep any non-settable bits we got
* from the file-system above.
*/
if (node->n_pending_dosattr) {
}
if (attr->sa_dosattr == 0) {
}
}
/*
* Also fix-up sa_allocsz, which is not persistent.
* When there are no open files, allocsz is faked.
* While there are open files, we pretend we have a
* persistent allocation size in n_allocsz, and
* keep that up-to-date here, increasing it when
* we see the file size grow past it.
*/
if (isdir) {
attr->sa_allocsz = 0;
} else if (node->n_open_count == 0) {
attr->sa_allocsz =
} else {
}
}
if (isdir) {
}
/*
* getattr with an ofile gets any "pending" times that
* might have been previously set via this ofile.
* This is what makes these times "sticky".
*/
smb_attr_t *pa;
}
return (0);
}
#ifndef _KERNEL
#endif /* _KERNEL */
/*
* Check to see if the node represents a reparse point.
* If yes, whether the reparse point contains a DFS link.
*/
static void
{
char *rec_type;
return;
return;
return;
}
break;
}
}
}
/*
* smb_node_init_system
*
* If the node represents a special system file set NODE_FLAG_SYSTEM.
* System files:
* - any node whose parent dnode has NODE_FLAG_SYSTEM set
* - any node whose associated unnamed stream node (unode) has
* NODE_FLAG_SYSTEM set
* - .$EXTEND at root of share (quota management)
*/
static void
{
return;
}
return;
}
}
}