smbd: Remove sconn->using_smb2
[samba.git] / source3 / smbd / close.c
index 919db9c4d809efcb0b45c8be71431cdd8227a176..987a0ed51835db7c63bf41f309d3eb095cc5493a 100644 (file)
@@ -26,6 +26,7 @@
 #include "locking/share_mode_lock.h"
 #include "smbd/smbd.h"
 #include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
 #include "smbd/scavenger.h"
 #include "fake_file.h"
 #include "transfer_file.h"
@@ -33,6 +34,7 @@
 #include "messages.h"
 #include "../librpc/gen_ndr/open_files.h"
 #include "lib/util/tevent_ntstatus.h"
+#include "source3/smbd/dir.h"
 
 /****************************************************************************
  Run a file if it is a magic script.
@@ -157,7 +159,7 @@ NTSTATUS delete_all_streams(connection_struct *conn,
        TALLOC_CTX *frame = talloc_stackframe();
        NTSTATUS status;
 
-       status = vfs_streaminfo(conn, NULL, smb_fname, talloc_tos(),
+       status = vfs_fstreaminfo(smb_fname->fsp, talloc_tos(),
                                &num_streams, &stream_info);
 
        if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
@@ -167,7 +169,7 @@ NTSTATUS delete_all_streams(connection_struct *conn,
        }
 
        if (!NT_STATUS_IS_OK(status)) {
-               DEBUG(10, ("vfs_streaminfo failed: %s\n",
+               DEBUG(10, ("vfs_fstreaminfo failed: %s\n",
                           nt_errstr(status)));
                goto fail;
        }
@@ -188,15 +190,16 @@ NTSTATUS delete_all_streams(connection_struct *conn,
                        continue;
                }
 
-               smb_fname_stream = synthetic_smb_fname(talloc_tos(),
-                                       smb_fname->base_name,
-                                       stream_info[i].name,
-                                       NULL,
-                                       smb_fname->twrp,
-                                       (smb_fname->flags &
-                                               ~SMB_FILENAME_POSIX_PATH));
-
-               if (smb_fname_stream == NULL) {
+               status = synthetic_pathref(talloc_tos(),
+                                          conn->cwd_fsp,
+                                          smb_fname->base_name,
+                                          stream_info[i].name,
+                                          NULL,
+                                          smb_fname->twrp,
+                                          (smb_fname->flags &
+                                           ~SMB_FILENAME_POSIX_PATH),
+                                          &smb_fname_stream);
+               if (!NT_STATUS_IS_OK(status)) {
                        DEBUG(0, ("talloc_aprintf failed\n"));
                        status = NT_STATUS_NO_MEMORY;
                        goto fail;
@@ -239,11 +242,10 @@ static bool has_other_nonposix_opens_fn(
        if (e->name_hash != fsp->name_hash) {
                return false;
        }
-       if ((fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) &&
-           (e->flags & SHARE_MODE_FLAG_POSIX_OPEN)) {
+       if (e->flags & SHARE_MODE_FLAG_POSIX_OPEN) {
                return false;
        }
-       if (e->share_file_id == fsp->fh->gen_id) {
+       if (e->share_file_id == fh_get_gen_id(fsp->fh)) {
                struct server_id self = messaging_server_id(
                        fsp->conn->sconn->msg_ctx);
                if (server_id_equal(&self, &e->pid)) {
@@ -272,69 +274,65 @@ bool has_other_nonposix_opens(struct share_mode_lock *lck,
        return state.found_another;
 }
 
-/****************************************************************************
- Deal with removing a share mode on last close.
-****************************************************************************/
+struct close_share_mode_lock_state {
+       struct share_mode_entry_prepare_state prepare_state;
+       const char *object_type;
+       struct files_struct *fsp;
+       enum file_close_type close_type;
+       bool delete_object;
+       bool got_tokens;
+       const struct security_unix_token *del_token;
+       const struct security_token *del_nt_token;
+       bool reset_delete_on_close;
+       share_mode_entry_prepare_unlock_fn_t cleanup_fn;
+};
 
-static NTSTATUS close_remove_share_mode(files_struct *fsp,
-                                       enum file_close_type close_type)
+static void close_share_mode_lock_prepare(struct share_mode_lock *lck,
+                                         bool *keep_locked,
+                                         void *private_data)
 {
-       connection_struct *conn = fsp->conn;
-       bool delete_file = false;
-       bool changed_user = false;
-       struct share_mode_lock *lck = NULL;
-       NTSTATUS status = NT_STATUS_OK;
-       NTSTATUS tmp_status;
-       struct file_id id;
-       const struct security_unix_token *del_token = NULL;
-       const struct security_token *del_nt_token = NULL;
-       bool got_tokens = false;
+       struct close_share_mode_lock_state *state =
+               (struct close_share_mode_lock_state *)private_data;
+       struct files_struct *fsp = state->fsp;
        bool normal_close;
-       int ret;
-
-       /* Ensure any pending write time updates are done. */
-       if (fsp->update_write_time_event) {
-               fsp_flush_write_time_update(fsp);
-       }
+       bool ok;
 
        /*
-        * Lock the share entries, and determine if we should delete
-        * on close. If so delete whilst the lock is still in effect.
-        * This prevents race conditions with the file being created. JRA.
+        * By default drop the g_lock again if we leave the
+        * tdb chainlock.
         */
-
-       lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
-       if (lck == NULL) {
-               DEBUG(0, ("close_remove_share_mode: Could not get share mode "
-                         "lock for file %s\n", fsp_str_dbg(fsp)));
-               return NT_STATUS_INVALID_PARAMETER;
-       }
-
-       /* Remove the oplock before potentially deleting the file. */
-       if(fsp->oplock_type) {
-               remove_oplock(fsp);
+       *keep_locked = false;
+
+       if (fsp->oplock_type != NO_OPLOCK) {
+               ok = remove_share_oplock(lck, fsp);
+               if (!ok) {
+                       struct file_id_buf buf;
+
+                       DBG_ERR("failed to remove share oplock for "
+                               "%s %s, %s, %s\n",
+                               state->object_type,
+                               fsp_str_dbg(fsp), fsp_fnum_dbg(fsp),
+                               file_id_str_buf(fsp->file_id, &buf));
+               }
        }
 
        if (fsp->fsp_flags.write_time_forced) {
                NTTIME mtime = share_mode_changed_write_time(lck);
                struct timespec ts = nt_time_to_full_timespec(mtime);
 
-               DEBUG(10,("close_remove_share_mode: write time forced "
-                       "for file %s\n",
-                       fsp_str_dbg(fsp)));
+               DBG_DEBUG("write time forced for %s %s\n",
+                         state->object_type, fsp_str_dbg(fsp));
                set_close_write_time(fsp, ts);
        } else if (fsp->fsp_flags.update_write_time_on_close) {
                /* Someone had a pending write. */
                if (is_omit_timespec(&fsp->close_write_time)) {
-                       DEBUG(10,("close_remove_share_mode: update to current time "
-                               "for file %s\n",
-                               fsp_str_dbg(fsp)));
+                       DBG_DEBUG("update to current time for %s %s\n",
+                                 state->object_type, fsp_str_dbg(fsp));
                        /* Update to current time due to "normal" write. */
                        set_close_write_time(fsp, timespec_current());
                } else {
-                       DEBUG(10,("close_remove_share_mode: write time pending "
-                               "for file %s\n",
-                               fsp_str_dbg(fsp)));
+                       DBG_DEBUG("write time pending for %s %s\n",
+                                 state->object_type, fsp_str_dbg(fsp));
                        /* Update to time set on close call. */
                        set_close_write_time(fsp, fsp->close_write_time);
                }
@@ -342,24 +340,16 @@ static NTSTATUS close_remove_share_mode(files_struct *fsp,
 
        if (fsp->fsp_flags.initial_delete_on_close &&
                        !is_delete_on_close_set(lck, fsp->name_hash)) {
-               struct auth_session_info *session_info = NULL;
-
                /* Initial delete on close was set and no one else
                 * wrote a real delete on close. */
 
-               status = smbXsrv_session_info_lookup(conn->sconn->client,
-                                                    fsp->vuid,
-                                                    &session_info);
-               if (!NT_STATUS_IS_OK(status)) {
-                       return NT_STATUS_INTERNAL_ERROR;
-               }
                fsp->fsp_flags.delete_on_close = true;
                set_delete_on_close_lck(fsp, lck,
-                                       session_info->security_token,
-                                       session_info->unix_token);
+                                       fsp->conn->session_info->security_token,
+                                       fsp->conn->session_info->unix_token);
        }
 
-       delete_file = is_delete_on_close_set(lck, fsp->name_hash) &&
+       state->delete_object = is_delete_on_close_set(lck, fsp->name_hash) &&
                !has_other_nonposix_opens(lck, fsp);
 
        /*
@@ -367,9 +357,113 @@ static NTSTATUS close_remove_share_mode(files_struct *fsp,
         * reference to a file.
         */
 
-       normal_close = (close_type == NORMAL_CLOSE || close_type == SHUTDOWN_CLOSE);
+       normal_close = (state->close_type == NORMAL_CLOSE || state->close_type == SHUTDOWN_CLOSE);
+       if (!normal_close) {
+               /*
+                * Never try to delete the file/directory for ERROR_CLOSE
+                */
+               state->delete_object = false;
+       }
+
+       if (!state->delete_object) {
+               ok = del_share_mode(lck, fsp);
+               if (!ok) {
+                       DBG_ERR("Could not delete share entry for %s %s\n",
+                               state->object_type, fsp_str_dbg(fsp));
+               }
+               return;
+       }
+
+       /*
+        * We're going to remove the file/directory
+        * so keep the g_lock after the tdb chainlock
+        * is left, so we hold the share_mode_lock
+        * also during the deletion
+        */
+       *keep_locked = true;
+
+       state->got_tokens = get_delete_on_close_token(lck, fsp->name_hash,
+                                       &state->del_nt_token, &state->del_token);
+       if (state->close_type != ERROR_CLOSE) {
+               SMB_ASSERT(state->got_tokens);
+       }
+}
+
+static void close_share_mode_lock_cleanup(struct share_mode_lock *lck,
+                                         void *private_data)
+{
+       struct close_share_mode_lock_state *state =
+               (struct close_share_mode_lock_state *)private_data;
+       struct files_struct *fsp = state->fsp;
+       bool ok;
+
+       if (state->reset_delete_on_close) {
+               reset_delete_on_close_lck(fsp, lck);
+       }
 
-       if (!normal_close || !delete_file) {
+       ok = del_share_mode(lck, fsp);
+       if (!ok) {
+               DBG_ERR("Could not delete share entry for %s %s\n",
+                       state->object_type, fsp_str_dbg(fsp));
+       }
+}
+
+/****************************************************************************
+ Deal with removing a share mode on last close.
+****************************************************************************/
+
+static NTSTATUS close_remove_share_mode(files_struct *fsp,
+                                       enum file_close_type close_type)
+{
+       connection_struct *conn = fsp->conn;
+       struct close_share_mode_lock_state lck_state = {};
+       bool changed_user = false;
+       NTSTATUS status = NT_STATUS_OK;
+       NTSTATUS tmp_status;
+       NTSTATUS ulstatus;
+       struct file_id id;
+       struct smb_filename *parent_fname = NULL;
+       struct smb_filename *base_fname = NULL;
+       int ret;
+
+       /* Ensure any pending write time updates are done. */
+       if (fsp->update_write_time_event) {
+               fsp_flush_write_time_update(fsp);
+       }
+
+       /*
+        * Lock the share entries, and determine if we should delete
+        * on close. If so delete whilst the lock is still in effect.
+        * This prevents race conditions with the file being created. JRA.
+        */
+
+       lck_state = (struct close_share_mode_lock_state) {
+               .fsp                    = fsp,
+               .object_type            = "file",
+               .close_type             = close_type,
+       };
+
+       status = share_mode_entry_prepare_lock_del(&lck_state.prepare_state,
+                                                  fsp->file_id,
+                                                  close_share_mode_lock_prepare,
+                                                  &lck_state);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_ERR("share_mode_entry_prepare_lock_del() failed for %s - %s\n",
+                       fsp_str_dbg(fsp), nt_errstr(status));
+               return status;
+       }
+
+       /* Remove the oplock before potentially deleting the file. */
+       if (fsp->oplock_type != NO_OPLOCK) {
+               release_file_oplock(fsp);
+       }
+
+       /*
+        * NT can set delete_on_close of the last open
+        * reference to a file.
+        */
+
+       if (!lck_state.delete_object) {
                status = NT_STATUS_OK;
                goto done;
        }
@@ -377,6 +471,7 @@ static NTSTATUS close_remove_share_mode(files_struct *fsp,
        /*
         * Ok, we have to delete the file
         */
+       lck_state.cleanup_fn = close_share_mode_lock_cleanup;
 
        DEBUG(5,("close_remove_share_mode: file %s. Delete on close was set "
                 "- deleting file.\n", fsp_str_dbg(fsp)));
@@ -386,28 +481,26 @@ static NTSTATUS close_remove_share_mode(files_struct *fsp,
         */
        fsp->fsp_flags.update_write_time_on_close = false;
 
-       got_tokens = get_delete_on_close_token(lck, fsp->name_hash,
-                                       &del_nt_token, &del_token);
-       SMB_ASSERT(got_tokens);
-
-       if (!unix_token_equal(del_token, get_current_utok(conn))) {
+       if (lck_state.got_tokens &&
+           !unix_token_equal(lck_state.del_token, get_current_utok(conn)))
+       {
                /* Become the user who requested the delete. */
 
                DEBUG(5,("close_remove_share_mode: file %s. "
                        "Change user to uid %u\n",
                        fsp_str_dbg(fsp),
-                       (unsigned int)del_token->uid));
+                       (unsigned int)lck_state.del_token->uid));
 
                if (!push_sec_ctx()) {
                        smb_panic("close_remove_share_mode: file %s. failed to push "
                                  "sec_ctx.\n");
                }
 
-               set_sec_ctx(del_token->uid,
-                           del_token->gid,
-                           del_token->ngroups,
-                           del_token->groups,
-                           del_nt_token);
+               set_sec_ctx(lck_state.del_token->uid,
+                           lck_state.del_token->gid,
+                           lck_state.del_token->ngroups,
+                           lck_state.del_token->groups,
+                           lck_state.del_nt_token);
 
                changed_user = true;
        }
@@ -445,7 +538,7 @@ static NTSTATUS close_remove_share_mode(files_struct *fsp,
        }
 
        if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
-           && !is_ntfs_stream_smb_fname(fsp->fsp_name)) {
+           && !fsp_is_alternate_stream(fsp)) {
 
                status = delete_all_streams(conn, fsp->fsp_name);
 
@@ -457,26 +550,35 @@ static NTSTATUS close_remove_share_mode(files_struct *fsp,
        }
 
        if (fsp->fsp_flags.kernel_share_modes_taken) {
-               int ret_flock;
-
                /*
                 * A file system sharemode could block the unlink;
                 * remove filesystem sharemodes first.
                 */
-               ret_flock = SMB_VFS_KERNEL_FLOCK(fsp, 0, 0);
-               if (ret_flock == -1) {
-                       DBG_INFO("removing kernel flock for %s failed: %s\n",
-                                 fsp_str_dbg(fsp), strerror(errno));
+               ret = SMB_VFS_FILESYSTEM_SHAREMODE(fsp, 0, 0);
+               if (ret == -1) {
+                       DBG_INFO("Removing file system sharemode for %s "
+                                "failed: %s\n",
+                                fsp_str_dbg(fsp), strerror(errno));
                }
 
                fsp->fsp_flags.kernel_share_modes_taken = false;
        }
 
+       status = parent_pathref(talloc_tos(),
+                               conn->cwd_fsp,
+                               fsp->fsp_name,
+                               &parent_fname,
+                               &base_fname);
+       if (!NT_STATUS_IS_OK(status)) {
+               goto done;
+       }
 
        ret = SMB_VFS_UNLINKAT(conn,
-                       conn->cwd_fsp,
-                       fsp->fsp_name,
-                       0);
+                              parent_fname->fsp,
+                              base_fname,
+                              0);
+       TALLOC_FREE(parent_fname);
+       base_fname = NULL;
        if (ret != 0) {
                /*
                 * This call can potentially fail as another smbd may
@@ -501,7 +603,8 @@ static NTSTATUS close_remove_share_mode(files_struct *fsp,
         */
 
        fsp->fsp_flags.delete_on_close = false;
-       reset_delete_on_close_lck(fsp, lck);
+       fsp->fsp_flags.fstat_before_close = false;
+       lck_state.reset_delete_on_close = true;
 
  done:
 
@@ -511,25 +614,25 @@ static NTSTATUS close_remove_share_mode(files_struct *fsp,
        }
 
        if (fsp->fsp_flags.kernel_share_modes_taken) {
-               int ret_flock;
-
                /* remove filesystem sharemodes */
-               ret_flock = SMB_VFS_KERNEL_FLOCK(fsp, 0, 0);
-               if (ret_flock == -1) {
-                       DEBUG(2, ("close_remove_share_mode: removing kernel "
-                                 "flock for %s failed: %s\n",
-                                 fsp_str_dbg(fsp), strerror(errno)));
+               ret = SMB_VFS_FILESYSTEM_SHAREMODE(fsp, 0, 0);
+               if (ret == -1) {
+                       DBG_INFO("Removing file system sharemode for "
+                                "%s failed: %s\n",
+                                fsp_str_dbg(fsp), strerror(errno));
                }
        }
 
-       if (!del_share_mode(lck, fsp)) {
-               DEBUG(0, ("close_remove_share_mode: Could not delete share "
-                         "entry for file %s\n", fsp_str_dbg(fsp)));
+       ulstatus = share_mode_entry_prepare_unlock(&lck_state.prepare_state,
+                                                  lck_state.cleanup_fn,
+                                                  &lck_state);
+       if (!NT_STATUS_IS_OK(ulstatus)) {
+               DBG_ERR("share_mode_entry_prepare_unlock() failed for %s - %s\n",
+                       fsp_str_dbg(fsp), nt_errstr(ulstatus));
+               smb_panic("share_mode_entry_prepare_unlock() failed!");
        }
 
-       TALLOC_FREE(lck);
-
-       if (delete_file) {
+       if (lck_state.delete_object) {
                /*
                 * Do the notification after we released the share
                 * mode lock. Inside notify_fname we take out another
@@ -561,11 +664,33 @@ void set_close_write_time(struct files_struct *fsp, struct timespec ts)
        fsp->close_write_time = ts;
 }
 
+static void update_write_time_on_close_share_mode_fn(struct share_mode_lock *lck,
+                                                    void *private_data)
+{
+       struct files_struct *fsp =
+               talloc_get_type_abort(private_data,
+               struct files_struct);
+       NTTIME share_mtime = share_mode_changed_write_time(lck);
+
+       /*
+        * On close if we're changing the real file time we
+        * must update it in the open file db too.
+        */
+       share_mode_set_old_write_time(lck, fsp->close_write_time);
+
+       /*
+        * Close write times overwrite sticky write times
+        * so we must replace any sticky write time here.
+        */
+       if (!null_nttime(share_mtime)) {
+               share_mode_set_changed_write_time(lck, fsp->close_write_time);
+       }
+}
+
 static NTSTATUS update_write_time_on_close(struct files_struct *fsp)
 {
        struct smb_file_time ft;
        NTSTATUS status;
-       struct share_mode_lock *lck = NULL;
 
        init_smb_file_time(&ft);
 
@@ -589,31 +714,14 @@ static NTSTATUS update_write_time_on_close(struct files_struct *fsp)
        }
 
        /*
-        * get_existing_share_mode_lock() isn't really the right
-        * call here, as we're being called after
-        * close_remove_share_mode() inside close_normal_file()
-        * so it's quite normal to not have an existing share
-        * mode here. However, get_share_mode_lock() doesn't
-        * work because that will create a new share mode if
-        * one doesn't exist - so stick with this call (just
-        * ignore any error we get if the share mode doesn't
-        * exist.
+        * We're being called after close_remove_share_mode() inside
+        * close_normal_file() so it's quite normal to not have an
+        * existing share. So just ignore the result of
+        * share_mode_do_locked_vfs_denied()...
         */
-
-       lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
-       if (lck) {
-               NTTIME share_mtime = share_mode_changed_write_time(lck);
-               /* On close if we're changing the real file time we
-                * must update it in the open file db too. */
-               (void)set_write_time(fsp->file_id, fsp->close_write_time);
-
-               /* Close write times overwrite sticky write times
-                  so we must replace any sticky write time here. */
-               if (!null_nttime(share_mtime)) {
-                       (void)set_sticky_write_time(fsp->file_id, fsp->close_write_time);
-               }
-               TALLOC_FREE(lck);
-       }
+       share_mode_do_locked_vfs_denied(fsp->file_id,
+                                       update_write_time_on_close_share_mode_fn,
+                                       fsp);
 
        ft.mtime = fsp->close_write_time;
        /* As this is a close based update, we are not directly changing the
@@ -655,7 +763,7 @@ static void assert_no_pending_aio(struct files_struct *fsp,
                /*
                 * fsp->aio_requests and the contents (fsp->aio_requests[x])
                 * are both independently owned by fsp and are not in a
-                * talloc heirarchy. This allows the fsp->aio_requests array to
+                * talloc hierarchy. This allows the fsp->aio_requests array to
                 * be reallocated independently of the array contents so it can
                 * grow on demand.
                 *
@@ -706,6 +814,8 @@ static NTSTATUS close_normal_file(struct smb_request *req, files_struct *fsp,
        connection_struct *conn = fsp->conn;
        bool is_durable = false;
 
+       SMB_ASSERT(fsp->fsp_flags.is_fsa);
+
        assert_no_pending_aio(fsp, close_type);
 
        while (talloc_array_length(fsp->blocked_smb1_lock_reqs) != 0) {
@@ -777,7 +887,6 @@ static NTSTATUS close_normal_file(struct smb_request *req, files_struct *fsp,
                DEBUG(10, ("%s disconnected durable handle for file %s\n",
                           conn->session_info->unix_info->unix_name,
                           fsp_str_dbg(fsp)));
-               file_free(req, fsp);
                return NT_STATUS_OK;
        }
 
@@ -788,18 +897,11 @@ static NTSTATUS close_normal_file(struct smb_request *req, files_struct *fsp,
                fsp->op->global->durable = false;
        }
 
-       if (fsp->print_file) {
-               /* FIXME: return spool errors */
-               print_spool_end(fsp, close_type);
-               file_free(req, fsp);
-               return NT_STATUS_OK;
-       }
-
        /* If this is an old DOS or FCB open and we have multiple opens on
           the same handle we only have one share mode. Ensure we only remove
           the share mode on the last close. */
 
-       if (fsp->fh->ref_count == 1) {
+       if (fh_get_refcount(fsp->fh) == 1) {
                /* Should we return on error here... ? */
                tmp = close_remove_share_mode(fsp, close_type);
                status = ntstatus_keeperror(status, tmp);
@@ -807,36 +909,34 @@ static NTSTATUS close_normal_file(struct smb_request *req, files_struct *fsp,
 
        locking_close_file(fsp, close_type);
 
-       tmp = fd_close(fsp);
-       status = ntstatus_keeperror(status, tmp);
-
-       /* check for magic scripts */
-       if (close_type == NORMAL_CLOSE) {
-               tmp = check_magic(fsp);
-               status = ntstatus_keeperror(status, tmp);
-       }
-
        /*
-        * Ensure pending modtime is set after close.
+        * Ensure pending modtime is set before closing underlying fd.
         */
 
        tmp = update_write_time_on_close(fsp);
        if (NT_STATUS_EQUAL(tmp, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
-               /* Someone renamed the file or a parent directory containing
-                * this file. We can't do anything about this, we don't have
-                * an "update timestamp by fd" call in POSIX. Eat the error. */
-
+               /*
+                * Someone renamed the file or a parent directory containing
+                * this file. We can't do anything about this, eat the error.
+                */
                tmp = NT_STATUS_OK;
        }
+       status = ntstatus_keeperror(status, tmp);
 
+       tmp = fd_close(fsp);
        status = ntstatus_keeperror(status, tmp);
 
+       /* check for magic scripts */
+       if (close_type == NORMAL_CLOSE) {
+               tmp = check_magic(fsp);
+               status = ntstatus_keeperror(status, tmp);
+       }
+
        DEBUG(2,("%s closed file %s (numopen=%d) %s\n",
                conn->session_info->unix_info->unix_name, fsp_str_dbg(fsp),
                conn->num_files_open - 1,
                nt_errstr(status) ));
 
-       file_free(req, fsp);
        return status;
 }
 /****************************************************************************
@@ -844,50 +944,50 @@ static NTSTATUS close_normal_file(struct smb_request *req, files_struct *fsp,
  tree recursively. Return True on ok, False on fail.
 ****************************************************************************/
 
-bool recursive_rmdir(TALLOC_CTX *ctx,
+NTSTATUS recursive_rmdir(TALLOC_CTX *ctx,
                     connection_struct *conn,
                     struct smb_filename *smb_dname)
 {
        const char *dname = NULL;
        char *talloced = NULL;
-       bool ret = True;
-       long offset = 0;
-       SMB_STRUCT_STAT st;
-       struct smb_Dir *dir_hnd;
+       struct smb_Dir *dir_hnd = NULL;
+       struct files_struct *dirfsp = NULL;
        int retval;
+       NTSTATUS status = NT_STATUS_OK;
 
        SMB_ASSERT(!is_ntfs_stream_smb_fname(smb_dname));
 
-       dir_hnd = OpenDir(talloc_tos(), conn, smb_dname, NULL, 0);
-       if(dir_hnd == NULL)
-               return False;
+       status = OpenDir(talloc_tos(),
+                        conn,
+                        smb_dname,
+                        NULL,
+                        0,
+                        &dir_hnd);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       dirfsp = dir_hnd_fetch_fsp(dir_hnd);
 
-       while((dname = ReadDirName(dir_hnd, &offset, &st, &talloced))) {
+       while ((dname = ReadDirName(dir_hnd, &talloced))) {
+               struct smb_filename *atname = NULL;
                struct smb_filename *smb_dname_full = NULL;
                char *fullname = NULL;
                bool do_break = true;
+               int unlink_flags = 0;
 
                if (ISDOT(dname) || ISDOTDOT(dname)) {
                        TALLOC_FREE(talloced);
                        continue;
                }
 
-               if (!is_visible_file(conn,
-                                       dir_hnd,
-                                       dname,
-                                       &st,
-                                       false)) {
-                       TALLOC_FREE(talloced);
-                       continue;
-               }
-
                /* Construct the full name. */
                fullname = talloc_asprintf(ctx,
                                "%s/%s",
                                smb_dname->base_name,
                                dname);
                if (!fullname) {
-                       errno = ENOMEM;
+                       status = NT_STATUS_NO_MEMORY;
                        goto err_break;
                }
 
@@ -898,33 +998,50 @@ bool recursive_rmdir(TALLOC_CTX *ctx,
                                                smb_dname->twrp,
                                                smb_dname->flags);
                if (smb_dname_full == NULL) {
-                       errno = ENOMEM;
+                       status = NT_STATUS_NO_MEMORY;
                        goto err_break;
                }
 
-               if(SMB_VFS_LSTAT(conn, smb_dname_full) != 0) {
+               if (SMB_VFS_LSTAT(conn, smb_dname_full) != 0) {
+                       status = map_nt_error_from_unix(errno);
                        goto err_break;
                }
 
-               if(smb_dname_full->st.st_ex_mode & S_IFDIR) {
-                       if(!recursive_rmdir(ctx, conn, smb_dname_full)) {
-                               goto err_break;
-                       }
-                       retval = SMB_VFS_UNLINKAT(conn,
-                                       conn->cwd_fsp,
-                                       smb_dname_full,
-                                       AT_REMOVEDIR);
-                       if (retval != 0) {
-                               goto err_break;
-                       }
-               } else {
-                       retval = SMB_VFS_UNLINKAT(conn,
-                                       conn->cwd_fsp,
-                                       smb_dname_full,
-                                       0);
-                       if (retval != 0) {
+               if (smb_dname_full->st.st_ex_mode & S_IFDIR) {
+                       status = recursive_rmdir(ctx, conn, smb_dname_full);
+                       if (!NT_STATUS_IS_OK(status)) {
                                goto err_break;
                        }
+                       unlink_flags = AT_REMOVEDIR;
+               }
+
+               status = synthetic_pathref(talloc_tos(),
+                                          dirfsp,
+                                          dname,
+                                          NULL,
+                                          &smb_dname_full->st,
+                                          smb_dname_full->twrp,
+                                          smb_dname_full->flags,
+                                          &atname);
+               if (!NT_STATUS_IS_OK(status)) {
+                       goto err_break;
+               }
+
+               if (!is_visible_fsp(atname->fsp)) {
+                       TALLOC_FREE(smb_dname_full);
+                       TALLOC_FREE(fullname);
+                       TALLOC_FREE(talloced);
+                       TALLOC_FREE(atname);
+                       continue;
+               }
+
+               retval = SMB_VFS_UNLINKAT(conn,
+                                         dirfsp,
+                                         atname,
+                                         unlink_flags);
+               if (retval != 0) {
+                       status = map_nt_error_from_unix(errno);
+                       goto err_break;
                }
 
                /* Successful iteration. */
@@ -934,229 +1051,436 @@ bool recursive_rmdir(TALLOC_CTX *ctx,
                TALLOC_FREE(smb_dname_full);
                TALLOC_FREE(fullname);
                TALLOC_FREE(talloced);
+               TALLOC_FREE(atname);
                if (do_break) {
-                       ret = false;
                        break;
                }
-       }
+        }
        TALLOC_FREE(dir_hnd);
-       return ret;
+       return status;
 }
 
 /****************************************************************************
  The internals of the rmdir code - called elsewhere.
 ****************************************************************************/
 
-static NTSTATUS rmdir_internals(TALLOC_CTX *ctx, files_struct *fsp)
+static NTSTATUS rmdir_internals(TALLOC_CTX *ctx, struct files_struct *fsp)
 {
-       connection_struct *conn = fsp->conn;
+       struct connection_struct *conn = fsp->conn;
        struct smb_filename *smb_dname = fsp->fsp_name;
-       const struct loadparm_substitution *lp_sub =
-               loadparm_s3_global_substitution();
+       struct smb_filename *parent_fname = NULL;
+       struct smb_filename *at_fname = NULL;
+       const char *dname = NULL;
+       char *talloced = NULL;
+       struct smb_Dir *dir_hnd = NULL;
+       struct files_struct *dirfsp = NULL;
+       int unlink_flags = 0;
+       NTSTATUS status;
        int ret;
 
        SMB_ASSERT(!is_ntfs_stream_smb_fname(smb_dname));
 
+       status = parent_pathref(talloc_tos(),
+                               conn->cwd_fsp,
+                               fsp->fsp_name,
+                               &parent_fname,
+                               &at_fname);
+       if (!NT_STATUS_IS_OK(status)) {
+               return status;
+       }
+
+       /*
+        * Todo: use SMB_VFS_STATX() once it's available.
+        */
+
        /* Might be a symlink. */
-       if(SMB_VFS_LSTAT(conn, smb_dname) != 0) {
+       ret = SMB_VFS_LSTAT(conn, smb_dname);
+       if (ret != 0) {
+               TALLOC_FREE(parent_fname);
                return map_nt_error_from_unix(errno);
        }
 
        if (S_ISLNK(smb_dname->st.st_ex_mode)) {
                /* Is what it points to a directory ? */
-               if(SMB_VFS_STAT(conn, smb_dname) != 0) {
+               ret = SMB_VFS_STAT(conn, smb_dname);
+               if (ret != 0) {
+                       TALLOC_FREE(parent_fname);
                        return map_nt_error_from_unix(errno);
                }
                if (!(S_ISDIR(smb_dname->st.st_ex_mode))) {
+                       TALLOC_FREE(parent_fname);
                        return NT_STATUS_NOT_A_DIRECTORY;
                }
-               ret = SMB_VFS_UNLINKAT(conn,
-                               conn->cwd_fsp,
-                               smb_dname,
-                               0);
        } else {
-               ret = SMB_VFS_UNLINKAT(conn,
-                               conn->cwd_fsp,
-                               smb_dname,
-                               AT_REMOVEDIR);
+               unlink_flags = AT_REMOVEDIR;
        }
+
+       ret = SMB_VFS_UNLINKAT(conn,
+                              parent_fname->fsp,
+                              at_fname,
+                              unlink_flags);
        if (ret == 0) {
+               TALLOC_FREE(parent_fname);
                notify_fname(conn, NOTIFY_ACTION_REMOVED,
                             FILE_NOTIFY_CHANGE_DIR_NAME,
                             smb_dname->base_name);
                return NT_STATUS_OK;
        }
 
-       if(((errno == ENOTEMPTY)||(errno == EEXIST)) && *lp_veto_files(talloc_tos(), lp_sub, SNUM(conn))) {
+       if (!((errno == ENOTEMPTY) || (errno == EEXIST))) {
+               DEBUG(3,("rmdir_internals: couldn't remove directory %s : "
+                        "%s\n", smb_fname_str_dbg(smb_dname),
+                        strerror(errno)));
+               TALLOC_FREE(parent_fname);
+               return map_nt_error_from_unix(errno);
+       }
+
+       /*
+        * Here we know the initial directory unlink failed with
+        * ENOTEMPTY or EEXIST so we know there are objects within.
+        * If we don't have permission to delete files non
+        * visible to the client just fail the directory delete.
+        */
+
+       if (!lp_delete_veto_files(SNUM(conn))) {
+               status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+               goto err;
+       }
+
+       /*
+        * Check to see if the only thing in this directory are
+        * files non-visible to the client. If not, fail the delete.
+        */
+
+       status = OpenDir(talloc_tos(),
+                        conn,
+                        smb_dname,
+                        NULL,
+                        0,
+                        &dir_hnd);
+       if (!NT_STATUS_IS_OK(status)) {
                /*
-                * Check to see if the only thing in this directory are
-                * vetoed files/directories. If so then delete them and
-                * retry. If we fail to delete any of them (and we *don't*
-                * do a recursive delete) then fail the rmdir.
+                * Note, we deliberately squash the error here
+                * to avoid leaking information about what we
+                * can't delete.
                 */
-               SMB_STRUCT_STAT st;
-               const char *dname = NULL;
-               char *talloced = NULL;
-               long dirpos = 0;
-               struct smb_Dir *dir_hnd = OpenDir(talloc_tos(), conn,
-                                                 smb_dname, NULL,
-                                                 0);
-
-               if(dir_hnd == NULL) {
-                       errno = ENOTEMPTY;
+               status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+               goto err;
+       }
+
+       dirfsp = dir_hnd_fetch_fsp(dir_hnd);
+
+       while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+               struct smb_filename *smb_dname_full = NULL;
+               struct smb_filename *direntry_fname = NULL;
+               char *fullname = NULL;
+               int retval;
+
+               if (ISDOT(dname) || ISDOTDOT(dname)) {
+                       TALLOC_FREE(talloced);
+                       continue;
+               }
+               if (IS_VETO_PATH(conn, dname)) {
+                       TALLOC_FREE(talloced);
+                       continue;
+               }
+
+               fullname = talloc_asprintf(talloc_tos(),
+                                          "%s/%s",
+                                          smb_dname->base_name,
+                                          dname);
+
+               if (fullname == NULL) {
+                       TALLOC_FREE(talloced);
+                       status = NT_STATUS_NO_MEMORY;
                        goto err;
                }
 
-               while ((dname = ReadDirName(dir_hnd, &dirpos, &st,
-                                           &talloced)) != NULL) {
-                       if((strcmp(dname, ".") == 0) || (strcmp(dname, "..")==0)) {
-                               TALLOC_FREE(talloced);
-                               continue;
-                       }
-                       if (!is_visible_file(conn,
-                                               dir_hnd,
+               smb_dname_full = synthetic_smb_fname(talloc_tos(),
+                                                    fullname,
+                                                    NULL,
+                                                    NULL,
+                                                    smb_dname->twrp,
+                                                    smb_dname->flags);
+               if (smb_dname_full == NULL) {
+                       TALLOC_FREE(talloced);
+                       TALLOC_FREE(fullname);
+                       status = NT_STATUS_NO_MEMORY;
+                       goto err;
+               }
+
+               retval = SMB_VFS_LSTAT(conn, smb_dname_full);
+               if (retval != 0) {
+                       status = map_nt_error_from_unix(errno);
+                       TALLOC_FREE(talloced);
+                       TALLOC_FREE(fullname);
+                       TALLOC_FREE(smb_dname_full);
+                       goto err;
+               }
+
+               if (S_ISLNK(smb_dname_full->st.st_ex_mode)) {
+                       /* Could it be an msdfs link ? */
+                       if (lp_host_msdfs() &&
+                           lp_msdfs_root(SNUM(conn))) {
+                               struct smb_filename *smb_atname;
+                               smb_atname = synthetic_smb_fname(talloc_tos(),
+                                                       dname,
+                                                       NULL,
+                                                       &smb_dname_full->st,
+                                                       fsp->fsp_name->twrp,
+                                                       fsp->fsp_name->flags);
+                               if (smb_atname == NULL) {
+                                       TALLOC_FREE(talloced);
+                                       TALLOC_FREE(fullname);
+                                       TALLOC_FREE(smb_dname_full);
+                                       status = NT_STATUS_NO_MEMORY;
+                                       goto err;
+                               }
+                               if (is_msdfs_link(fsp, smb_atname)) {
+                                       TALLOC_FREE(talloced);
+                                       TALLOC_FREE(fullname);
+                                       TALLOC_FREE(smb_dname_full);
+                                       TALLOC_FREE(smb_atname);
+                                       DBG_DEBUG("got msdfs link name %s "
+                                               "- can't delete directory %s\n",
                                                dname,
-                                               &st,
-                                               false)) {
-                               TALLOC_FREE(talloced);
-                               continue;
+                                               fsp_str_dbg(fsp));
+                                       status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+                                       goto err;
+                               }
+                               TALLOC_FREE(smb_atname);
                        }
-                       if(!IS_VETO_PATH(conn, dname)) {
-                               TALLOC_FREE(dir_hnd);
+
+                       /* Not a DFS link - could it be a dangling symlink ? */
+                       retval = SMB_VFS_STAT(conn, smb_dname_full);
+                       if (retval == -1 && (errno == ENOENT || errno == ELOOP)) {
+                               /*
+                                * Dangling symlink.
+                                * Allow delete as "delete veto files = yes"
+                                */
                                TALLOC_FREE(talloced);
-                               errno = ENOTEMPTY;
-                               goto err;
+                               TALLOC_FREE(fullname);
+                               TALLOC_FREE(smb_dname_full);
+                               continue;
                        }
+
+                       DBG_DEBUG("got symlink name %s - "
+                               "can't delete directory %s\n",
+                               dname,
+                               fsp_str_dbg(fsp));
                        TALLOC_FREE(talloced);
+                       TALLOC_FREE(fullname);
+                       TALLOC_FREE(smb_dname_full);
+                       status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+                       goto err;
                }
 
-               /* We only have veto files/directories.
-                * Are we allowed to delete them ? */
-
-               if(!lp_delete_veto_files(SNUM(conn))) {
-                       TALLOC_FREE(dir_hnd);
-                       errno = ENOTEMPTY;
+               /* Not a symlink, get a pathref. */
+               status = synthetic_pathref(talloc_tos(),
+                                          dirfsp,
+                                          dname,
+                                          NULL,
+                                          &smb_dname_full->st,
+                                          smb_dname->twrp,
+                                          smb_dname->flags,
+                                          &direntry_fname);
+               if (!NT_STATUS_IS_OK(status)) {
+                       TALLOC_FREE(talloced);
+                       TALLOC_FREE(fullname);
+                       TALLOC_FREE(smb_dname_full);
                        goto err;
                }
 
-               /* Do a recursive delete. */
-               RewindDir(dir_hnd,&dirpos);
-               while ((dname = ReadDirName(dir_hnd, &dirpos, &st,
-                                           &talloced)) != NULL) {
-                       struct smb_filename *smb_dname_full = NULL;
-                       char *fullname = NULL;
-                       bool do_break = true;
+               if (!is_visible_fsp(direntry_fname->fsp)) {
+                       TALLOC_FREE(talloced);
+                       TALLOC_FREE(fullname);
+                       TALLOC_FREE(smb_dname_full);
+                       TALLOC_FREE(direntry_fname);
+                       continue;
+               }
 
-                       if (ISDOT(dname) || ISDOTDOT(dname)) {
-                               TALLOC_FREE(talloced);
-                               continue;
-                       }
-                       if (!is_visible_file(conn,
-                                               dir_hnd,
-                                               dname,
-                                               &st,
-                                               false)) {
-                               TALLOC_FREE(talloced);
-                               continue;
-                       }
+               /*
+                * We found a client visible name.
+                * We cannot delete this directory.
+                */
+               DBG_DEBUG("got name %s - "
+                       "can't delete directory %s\n",
+                       dname,
+                       fsp_str_dbg(fsp));
+               TALLOC_FREE(talloced);
+               TALLOC_FREE(fullname);
+               TALLOC_FREE(smb_dname_full);
+               TALLOC_FREE(direntry_fname);
+               status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+               goto err;
+       }
 
-                       fullname = talloc_asprintf(ctx,
-                                       "%s/%s",
-                                       smb_dname->base_name,
-                                       dname);
+       /* Do a recursive delete. */
+       RewindDir(dir_hnd);
 
-                       if(!fullname) {
-                               errno = ENOMEM;
-                               goto err_break;
-                       }
+       while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+               struct smb_filename *direntry_fname = NULL;
+               struct smb_filename *smb_dname_full = NULL;
+               char *fullname = NULL;
+               bool do_break = true;
+               int retval;
 
-                       smb_dname_full = synthetic_smb_fname(talloc_tos(),
-                                                       fullname,
-                                                       NULL,
+               if (ISDOT(dname) || ISDOTDOT(dname)) {
+                       TALLOC_FREE(talloced);
+                       continue;
+               }
+
+               fullname = talloc_asprintf(ctx,
+                                          "%s/%s",
+                                          smb_dname->base_name,
+                                          dname);
+
+               if (fullname == NULL) {
+                       status = NT_STATUS_NO_MEMORY;
+                       goto err_break;
+               }
+
+               smb_dname_full = synthetic_smb_fname(talloc_tos(),
+                                                    fullname,
+                                                    NULL,
+                                                    NULL,
+                                                    smb_dname->twrp,
+                                                    smb_dname->flags);
+               if (smb_dname_full == NULL) {
+                       status = NT_STATUS_NO_MEMORY;
+                       goto err_break;
+               }
+
+               /*
+                * Todo: use SMB_VFS_STATX() once that's available.
+                */
+
+               retval = SMB_VFS_LSTAT(conn, smb_dname_full);
+               if (retval != 0) {
+                       status = map_nt_error_from_unix(errno);
+                       goto err_break;
+               }
+
+               /*
+                * We are only dealing with VETO'ed objects
+                * here. If it's a symlink, just delete the
+                * link without caring what it is pointing
+                * to.
+                */
+               if (S_ISLNK(smb_dname_full->st.st_ex_mode)) {
+                       direntry_fname = synthetic_smb_fname(talloc_tos(),
+                                                       dname,
                                                        NULL,
+                                                       &smb_dname_full->st,
                                                        smb_dname->twrp,
                                                        smb_dname->flags);
-                       if (smb_dname_full == NULL) {
-                               errno = ENOMEM;
+                       if (direntry_fname == NULL) {
+                               status = NT_STATUS_NO_MEMORY;
                                goto err_break;
                        }
-
-                       if(SMB_VFS_LSTAT(conn, smb_dname_full) != 0) {
+               } else {
+                       status = synthetic_pathref(talloc_tos(),
+                                                  dirfsp,
+                                                  dname,
+                                                  NULL,
+                                                  &smb_dname_full->st,
+                                                  smb_dname->twrp,
+                                                  smb_dname->flags,
+                                                  &direntry_fname);
+                       if (!NT_STATUS_IS_OK(status)) {
                                goto err_break;
                        }
-                       if(smb_dname_full->st.st_ex_mode & S_IFDIR) {
-                               int retval;
-                               if(!recursive_rmdir(ctx, conn,
-                                                   smb_dname_full)) {
-                                       goto err_break;
-                               }
-                               retval = SMB_VFS_UNLINKAT(conn,
-                                               conn->cwd_fsp,
-                                               smb_dname_full,
-                                               AT_REMOVEDIR);
-                               if(retval != 0) {
-                                       goto err_break;
-                               }
-                       } else {
-                               int retval = SMB_VFS_UNLINKAT(conn,
-                                               conn->cwd_fsp,
-                                               smb_dname_full,
-                                               0);
-                               if(retval != 0) {
-                                       goto err_break;
-                               }
+
+                       if (!is_visible_fsp(direntry_fname->fsp)) {
+                               TALLOC_FREE(fullname);
+                               TALLOC_FREE(smb_dname_full);
+                               TALLOC_FREE(talloced);
+                               TALLOC_FREE(direntry_fname);
+                               continue;
                        }
+               }
 
-                       /* Successful iteration. */
-                       do_break = false;
+               unlink_flags = 0;
 
-                err_break:
-                       TALLOC_FREE(fullname);
-                       TALLOC_FREE(smb_dname_full);
-                       TALLOC_FREE(talloced);
-                       if (do_break)
-                               break;
+               if (smb_dname_full->st.st_ex_mode & S_IFDIR) {
+                       status = recursive_rmdir(ctx, conn, smb_dname_full);
+                       if (!NT_STATUS_IS_OK(status)) {
+                               goto err_break;
+                       }
+                       unlink_flags = AT_REMOVEDIR;
+               }
+
+               retval = SMB_VFS_UNLINKAT(conn,
+                                         dirfsp,
+                                         direntry_fname,
+                                         unlink_flags);
+               if (retval != 0) {
+                       status = map_nt_error_from_unix(errno);
+                       goto err_break;
+               }
+
+               /* Successful iteration. */
+               do_break = false;
+
+       err_break:
+               TALLOC_FREE(fullname);
+               TALLOC_FREE(smb_dname_full);
+               TALLOC_FREE(talloced);
+               TALLOC_FREE(direntry_fname);
+               if (do_break) {
+                       break;
                }
-               TALLOC_FREE(dir_hnd);
-               /* Retry the rmdir */
-               ret = SMB_VFS_UNLINKAT(conn,
-                               conn->cwd_fsp,
-                               smb_dname,
-                               AT_REMOVEDIR);
        }
 
-  err:
+       /* If we get here, we know NT_STATUS_IS_OK(status) */
+       SMB_ASSERT(NT_STATUS_IS_OK(status));
 
+       /* Retry the rmdir */
+       ret = SMB_VFS_UNLINKAT(conn,
+                              parent_fname->fsp,
+                              at_fname,
+                              AT_REMOVEDIR);
        if (ret != 0) {
-               DEBUG(3,("rmdir_internals: couldn't remove directory %s : "
+               status = map_nt_error_from_unix(errno);
+       }
+
+  err:
+
+       TALLOC_FREE(dir_hnd);
+       TALLOC_FREE(parent_fname);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_NOTICE("couldn't remove directory %s : "
                         "%s\n", smb_fname_str_dbg(smb_dname),
-                        strerror(errno)));
-               return map_nt_error_from_unix(errno);
+                        nt_errstr(status));
+               return status;
        }
 
        notify_fname(conn, NOTIFY_ACTION_REMOVED,
                     FILE_NOTIFY_CHANGE_DIR_NAME,
                     smb_dname->base_name);
 
-       return NT_STATUS_OK;
+       return status;
 }
 
 /****************************************************************************
- Close a directory opened by an NT SMB call. 
+ Close a directory opened by an NT SMB call.
 ****************************************************************************/
-  
+
 static NTSTATUS close_directory(struct smb_request *req, files_struct *fsp,
                                enum file_close_type close_type)
 {
-       struct share_mode_lock *lck = NULL;
-       bool delete_dir = False;
+       connection_struct *conn = fsp->conn;
+       struct close_share_mode_lock_state lck_state = {};
+       bool changed_user = false;
        NTSTATUS status = NT_STATUS_OK;
        NTSTATUS status1 = NT_STATUS_OK;
-       const struct security_token *del_nt_token = NULL;
-       const struct security_unix_token *del_token = NULL;
        NTSTATUS notify_status;
+       NTSTATUS ulstatus;
+
+       SMB_ASSERT(fsp->fsp_flags.is_fsa);
 
-       if (fsp->conn->sconn->using_smb2) {
+       if (conn_using_smb2(fsp->conn->sconn)) {
                notify_status = NT_STATUS_NOTIFY_CLEANUP;
        } else {
                notify_status = NT_STATUS_OK;
@@ -1169,98 +1493,104 @@ static NTSTATUS close_directory(struct smb_request *req, files_struct *fsp,
         * reference to a directory also.
         */
 
-       lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
-       if (lck == NULL) {
-               DEBUG(0, ("close_directory: Could not get share mode lock for "
-                         "%s\n", fsp_str_dbg(fsp)));
-               file_free(req, fsp);
-               return NT_STATUS_INVALID_PARAMETER;
-       }
+       lck_state = (struct close_share_mode_lock_state) {
+               .fsp                    = fsp,
+               .object_type            = "directory",
+               .close_type             = close_type,
+       };
 
-       if (fsp->fsp_flags.initial_delete_on_close) {
-               struct auth_session_info *session_info = NULL;
+       status = share_mode_entry_prepare_lock_del(&lck_state.prepare_state,
+                                                  fsp->file_id,
+                                                  close_share_mode_lock_prepare,
+                                                  &lck_state);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_ERR("share_mode_entry_prepare_lock_del() failed for %s - %s\n",
+                       fsp_str_dbg(fsp), nt_errstr(status));
+               return status;
+       }
 
-               /* Initial delete on close was set - for
-                * directories we don't care if anyone else
-                * wrote a real delete on close. */
+       /*
+        * We don't have directory leases yet, so assert it in order
+        * to skip release_file_oplock().
+        */
+       SMB_ASSERT(fsp->oplock_type == NO_OPLOCK);
 
-               status = smbXsrv_session_info_lookup(fsp->conn->sconn->client,
-                                                    fsp->vuid,
-                                                    &session_info);
-               if (!NT_STATUS_IS_OK(status)) {
-                       return NT_STATUS_INTERNAL_ERROR;
-               }
+       /*
+        * NT can set delete_on_close of the last open
+        * reference to a file.
+        */
 
-               send_stat_cache_delete_message(fsp->conn->sconn->msg_ctx,
-                                              fsp->fsp_name->base_name);
-               set_delete_on_close_lck(fsp, lck,
-                                       session_info->security_token,
-                                       session_info->unix_token);
-               fsp->fsp_flags.delete_on_close = true;
+       if (!lck_state.delete_object) {
+               status = NT_STATUS_OK;
+               goto done;
        }
 
-       delete_dir = get_delete_on_close_token(
-               lck, fsp->name_hash, &del_nt_token, &del_token) &&
-               !has_other_nonposix_opens(lck, fsp);
+       /*
+        * Ok, we have to delete the directory
+        */
+       lck_state.cleanup_fn = close_share_mode_lock_cleanup;
 
-       if ((close_type == NORMAL_CLOSE || close_type == SHUTDOWN_CLOSE) &&
-                               delete_dir) {
-       
+       if (lck_state.got_tokens &&
+           !unix_token_equal(lck_state.del_token, get_current_utok(conn)))
+       {
                /* Become the user who requested the delete. */
 
+               DBG_INFO("dir %s. Change user to uid %u\n",
+                        fsp_str_dbg(fsp),
+                        (unsigned int)lck_state.del_token->uid);
+
                if (!push_sec_ctx()) {
                        smb_panic("close_directory: failed to push sec_ctx.\n");
                }
 
-               set_sec_ctx(del_token->uid,
-                               del_token->gid,
-                               del_token->ngroups,
-                               del_token->groups,
-                               del_nt_token);
+               set_sec_ctx(lck_state.del_token->uid,
+                           lck_state.del_token->gid,
+                           lck_state.del_token->ngroups,
+                           lck_state.del_token->groups,
+                           lck_state.del_nt_token);
 
-               if (!del_share_mode(lck, fsp)) {
-                       DEBUG(0, ("close_directory: Could not delete share entry for "
-                                 "%s\n", fsp_str_dbg(fsp)));
-               }
-
-               TALLOC_FREE(lck);
+               changed_user = true;
+       }
 
-               if ((fsp->conn->fs_capabilities & FILE_NAMED_STREAMS)
-                   && !is_ntfs_stream_smb_fname(fsp->fsp_name)) {
+       if ((fsp->conn->fs_capabilities & FILE_NAMED_STREAMS)
+           && !is_ntfs_stream_smb_fname(fsp->fsp_name)) {
 
-                       status = delete_all_streams(fsp->conn, fsp->fsp_name);
-                       if (!NT_STATUS_IS_OK(status)) {
-                               DEBUG(5, ("delete_all_streams failed: %s\n",
-                                         nt_errstr(status)));
-                               file_free(req, fsp);
-                               return status;
-                       }
+               status = delete_all_streams(fsp->conn, fsp->fsp_name);
+               if (!NT_STATUS_IS_OK(status)) {
+                       DEBUG(5, ("delete_all_streams failed: %s\n",
+                                 nt_errstr(status)));
+                       goto done;
                }
+       }
 
-               status = rmdir_internals(talloc_tos(), fsp);
+       status = rmdir_internals(talloc_tos(), fsp);
 
-               DEBUG(5,("close_directory: %s. Delete on close was set - "
-                        "deleting directory returned %s.\n",
-                        fsp_str_dbg(fsp), nt_errstr(status)));
+       DEBUG(5,("close_directory: %s. Delete on close was set - "
+                "deleting directory returned %s.\n",
+                fsp_str_dbg(fsp), nt_errstr(status)));
 
-               /* unbecome user. */
-               pop_sec_ctx();
+       /*
+        * Ensure we remove any change notify requests that would
+        * now fail as the directory has been deleted.
+        */
 
-               /*
-                * Ensure we remove any change notify requests that would
-                * now fail as the directory has been deleted.
-                */
+       if (NT_STATUS_IS_OK(status)) {
+               notify_status = NT_STATUS_DELETE_PENDING;
+       }
 
-               if (NT_STATUS_IS_OK(status)) {
-                       notify_status = NT_STATUS_DELETE_PENDING;
-               }
-       } else {
-               if (!del_share_mode(lck, fsp)) {
-                       DEBUG(0, ("close_directory: Could not delete share entry for "
-                                 "%s\n", fsp_str_dbg(fsp)));
-               }
+done:
+       if (changed_user) {
+               /* unbecome user. */
+               pop_sec_ctx();
+       }
 
-               TALLOC_FREE(lck);
+       ulstatus = share_mode_entry_prepare_unlock(&lck_state.prepare_state,
+                                                  lck_state.cleanup_fn,
+                                                  &lck_state);
+       if (!NT_STATUS_IS_OK(ulstatus)) {
+               DBG_ERR("share_mode_entry_prepare_unlock() failed for %s - %s\n",
+                       fsp_str_dbg(fsp), nt_errstr(ulstatus));
+               smb_panic("share_mode_entry_prepare_unlock() failed!");
        }
 
        remove_pending_change_notify_requests_by_fid(fsp, notify_status);
@@ -1269,15 +1599,10 @@ static NTSTATUS close_directory(struct smb_request *req, files_struct *fsp,
 
        if (!NT_STATUS_IS_OK(status1)) {
                DEBUG(0, ("Could not close dir! fname=%s, fd=%d, err=%d=%s\n",
-                         fsp_str_dbg(fsp), fsp->fh->fd, errno,
+                         fsp_str_dbg(fsp), fsp_get_pathref_fd(fsp), errno,
                          strerror(errno)));
        }
 
-       /*
-        * Do the code common to files and directories.
-        */
-       file_free(req, fsp);
-
        if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(status1)) {
                status = status1;
        }
@@ -1285,46 +1610,96 @@ static NTSTATUS close_directory(struct smb_request *req, files_struct *fsp,
 }
 
 /****************************************************************************
- Close a files_struct.
+ Rundown all SMB-related dependencies of a files struct
 ****************************************************************************/
-  
-NTSTATUS close_file(struct smb_request *req, files_struct *fsp,
-                   enum file_close_type close_type)
+
+NTSTATUS close_file_smb(struct smb_request *req,
+                       struct files_struct *fsp,
+                       enum file_close_type close_type)
 {
        NTSTATUS status;
-       struct files_struct *base_fsp = fsp->base_fsp;
 
-       if (fsp->fsp_flags.is_dirfsp) {
+       /*
+        * This fsp can never be an internal dirfsp. They must
+        * be explicitly closed by TALLOC_FREE of the dir handle.
+        */
+       SMB_ASSERT(!fsp->fsp_flags.is_dirfsp);
+
+       /*
+        * Never call directly on a base fsp
+        */
+       SMB_ASSERT(fsp->stream_fsp == NULL);
+
+       if (fsp->fake_file_handle != NULL) {
                /*
-                * The typical way to get here is via file_close_[conn|user]()
-                * and this is taken care of below.
+                * Named pipes are opened as fake files and
+                * can have pending aio requests. Ensure
+                * we clear out all pending aio on force
+                * shutdown of named pipes also.
+                * BUG: https://bugzilla.samba.org/show_bug.cgi?id=15423
                 */
-               return NT_STATUS_OK;
-       }
-
-       if (fsp->fsp_flags.is_directory) {
-               status = close_directory(req, fsp, close_type);
-       } else if (fsp->fake_file_handle != NULL) {
+               assert_no_pending_aio(fsp, close_type);
                status = close_fake_file(req, fsp);
+       } else if (fsp->print_file != NULL) {
+               /* FIXME: return spool errors */
+               print_spool_end(fsp, close_type);
+               fd_close(fsp);
+               status = NT_STATUS_OK;
+       } else if (!fsp->fsp_flags.is_fsa) {
+               if (close_type == NORMAL_CLOSE) {
+                       DBG_ERR("unexpected NORMAL_CLOSE for [%s] "
+                               "is_fsa[%u] is_pathref[%u] is_directory[%u]\n",
+                               fsp_str_dbg(fsp),
+                               fsp->fsp_flags.is_fsa,
+                               fsp->fsp_flags.is_pathref,
+                               fsp->fsp_flags.is_directory);
+               }
+               SMB_ASSERT(close_type != NORMAL_CLOSE);
+               fd_close(fsp);
+               status = NT_STATUS_OK;
+       } else if (fsp->fsp_flags.is_directory) {
+               status = close_directory(req, fsp, close_type);
        } else {
                status = close_normal_file(req, fsp, close_type);
        }
 
-       if ((base_fsp != NULL) && (close_type != SHUTDOWN_CLOSE)) {
+       if (fsp_is_alternate_stream(fsp)) {
+               /*
+                * fsp was a stream, its base_fsp can't be a stream
+                * as well
+                */
+               SMB_ASSERT(!fsp_is_alternate_stream(fsp->base_fsp));
+
+               /*
+                * There's a 1:1 relationship between fsp and a base_fsp
+                */
+               SMB_ASSERT(fsp->base_fsp->stream_fsp == fsp);
 
                /*
-                * fsp was a stream, the base fsp can't be a stream as well
-                *
-                * For SHUTDOWN_CLOSE this is not possible here, because
-                * SHUTDOWN_CLOSE only happens from files.c which walks the
-                * complete list of files. If we mess with more than one fsp
-                * those loops will become confused.
+                * Make base_fsp look standalone now
                 */
+               fsp->base_fsp->stream_fsp = NULL;
 
-               SMB_ASSERT(base_fsp->base_fsp == NULL);
-               close_file(req, base_fsp, close_type);
+               close_file_free(req, &fsp->base_fsp, close_type);
        }
 
+       fsp_unbind_smb(req, fsp);
+
+       return status;
+}
+
+NTSTATUS close_file_free(struct smb_request *req,
+                        struct files_struct **_fsp,
+                        enum file_close_type close_type)
+{
+       struct files_struct *fsp = *_fsp;
+       NTSTATUS status;
+
+       status = close_file_smb(req, fsp, close_type);
+
+       file_free(req, fsp);
+        *_fsp = NULL;
+
        return status;
 }
 
@@ -1363,5 +1738,5 @@ void msg_close_file(struct messaging_context *msg_ctx,
                DEBUG(10,("msg_close_file: failed to find file.\n"));
                return;
        }
-       close_file(NULL, fsp, NORMAL_CLOSE);
+       close_file_free(NULL, &fsp, NORMAL_CLOSE);
 }