s3:smbd: pass (raw) ev to fetch_dos_mode_send instead of smb_vfs_ev_glue
[samba.git] / source3 / smbd / smb2_query_directory.c
index 81f2e1772c53344be5af273f046f7b1336990ce2..1a3447c3a828ed06bbd941db5a822f5a42f754c4 100644 (file)
 #include "../libcli/smb/smb_common.h"
 #include "trans2.h"
 #include "../lib/util/tevent_ntstatus.h"
+#include "system/filesys.h"
+#include "lib/pthreadpool/pthreadpool_tevent.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
 
 static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
                                              struct tevent_context *ev,
@@ -120,7 +125,7 @@ NTSTATUS smbd_smb2_request_process_query_directory(struct smbd_smb2_request *req
                return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
        }
 
-       subreq = smbd_smb2_query_directory_send(req, req->sconn->ev_ctx,
+       subreq = smbd_smb2_query_directory_send(req, req->ev_ctx,
                                     req, in_fsp,
                                     in_file_info_class,
                                     in_flags,
@@ -192,11 +197,58 @@ static void smbd_smb2_request_find_done(struct tevent_req *subreq)
        }
 }
 
+static struct tevent_req *fetch_write_time_send(TALLOC_CTX *mem_ctx,
+                                               struct tevent_context *ev,
+                                               connection_struct *conn,
+                                               struct file_id id,
+                                               int info_level,
+                                               char *entry_marshall_buf,
+                                               bool *stop);
+static NTSTATUS fetch_write_time_recv(struct tevent_req *req);
+
+static struct tevent_req *fetch_dos_mode_send(
+       TALLOC_CTX *mem_ctx,
+       struct tevent_context *ev,
+       struct files_struct *dir_fsp,
+       struct smb_filename **smb_fname,
+       uint32_t info_level,
+       uint8_t *entry_marshall_buf);
+
+static NTSTATUS fetch_dos_mode_recv(struct tevent_req *req);
+
 struct smbd_smb2_query_directory_state {
+       struct tevent_context *ev;
        struct smbd_smb2_request *smb2req;
+       uint64_t async_sharemode_count;
+       uint32_t find_async_delay_usec;
        DATA_BLOB out_output_buffer;
+       struct smb_request *smbreq;
+       int in_output_buffer_length;
+       struct files_struct *fsp;
+       const char *in_file_name;
+       NTSTATUS empty_status;
+       uint32_t info_level;
+       uint32_t max_count;
+       char *pdata;
+       char *base_data;
+       char *end_data;
+       uint32_t num;
+       uint32_t dirtype;
+       bool dont_descend;
+       bool ask_sharemode;
+       bool async_dosmode;
+       bool async_ask_sharemode;
+       int last_entry_off;
+       size_t max_async_dosmode_active;
+       uint32_t async_dosmode_active;
+       bool done;
 };
 
+static bool smb2_query_directory_next_entry(struct tevent_req *req);
+static void smb2_query_directory_fetch_write_time_done(struct tevent_req *subreq);
+static void smb2_query_directory_dos_mode_done(struct tevent_req *subreq);
+static void smb2_query_directory_waited(struct tevent_req *subreq);
+
 static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
                                              struct tevent_context *ev,
                                              struct smbd_smb2_request *smb2req,
@@ -210,38 +262,32 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
        struct smbXsrv_connection *xconn = smb2req->xconn;
        struct tevent_req *req;
        struct smbd_smb2_query_directory_state *state;
-       struct smb_request *smbreq;
        connection_struct *conn = smb2req->tcon->compat;
        NTSTATUS status;
-       NTSTATUS empty_status;
-       uint32_t info_level;
-       uint32_t max_count;
-       char *pdata;
-       char *base_data;
-       char *end_data;
-       int last_entry_off = 0;
-       int off = 0;
-       uint32_t num = 0;
-       uint32_t dirtype = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY;
-       bool dont_descend = false;
-       bool ask_sharemode = true;
-       bool wcard_has_wild;
+       bool wcard_has_wild = false;
        struct tm tm;
        char *p;
+       bool stop = false;
+       bool ok;
 
        req = tevent_req_create(mem_ctx, &state,
                                struct smbd_smb2_query_directory_state);
        if (req == NULL) {
                return NULL;
        }
+       state->ev = fsp->conn->sconn->raw_ev_ctx;
+       state->fsp = fsp;
        state->smb2req = smb2req;
+       state->in_output_buffer_length = in_output_buffer_length;
+       state->in_file_name = in_file_name;
        state->out_output_buffer = data_blob_null;
+       state->dirtype = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY;
 
        DEBUG(10,("smbd_smb2_query_directory_send: %s - %s\n",
                  fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
 
-       smbreq = smbd_smb2_fake_smb_request(smb2req);
-       if (tevent_req_nomem(smbreq, req)) {
+       state->smbreq = smbd_smb2_fake_smb_request(smb2req);
+       if (tevent_req_nomem(state->smbreq, req)) {
                return tevent_req_post(req, ev);
        }
 
@@ -250,20 +296,20 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
                return tevent_req_post(req, ev);
        }
 
-       if (strcmp(in_file_name, "") == 0) {
+       if (strcmp(state->in_file_name, "") == 0) {
                tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_INVALID);
                return tevent_req_post(req, ev);
        }
-       if (strchr_m(in_file_name, '\\') != NULL) {
+       if (strchr_m(state->in_file_name, '\\') != NULL) {
                tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_INVALID);
                return tevent_req_post(req, ev);
        }
-       if (strchr_m(in_file_name, '/') != NULL) {
+       if (strchr_m(state->in_file_name, '/') != NULL) {
                tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_INVALID);
                return tevent_req_post(req, ev);
        }
 
-       p = strptime(in_file_name, GMT_FORMAT, &tm);
+       p = strptime(state->in_file_name, GMT_FORMAT, &tm);
        if ((p != NULL) && (*p =='\0')) {
                /*
                 * Bogus find that asks for a shadow copy timestamp as a
@@ -293,27 +339,27 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
 
        switch (in_file_info_class) {
        case SMB2_FIND_DIRECTORY_INFO:
-               info_level = SMB_FIND_FILE_DIRECTORY_INFO;
+               state->info_level = SMB_FIND_FILE_DIRECTORY_INFO;
                break;
 
        case SMB2_FIND_FULL_DIRECTORY_INFO:
-               info_level = SMB_FIND_FILE_FULL_DIRECTORY_INFO;
+               state->info_level = SMB_FIND_FILE_FULL_DIRECTORY_INFO;
                break;
 
        case SMB2_FIND_BOTH_DIRECTORY_INFO:
-               info_level = SMB_FIND_FILE_BOTH_DIRECTORY_INFO;
+               state->info_level = SMB_FIND_FILE_BOTH_DIRECTORY_INFO;
                break;
 
        case SMB2_FIND_NAME_INFO:
-               info_level = SMB_FIND_FILE_NAMES_INFO;
+               state->info_level = SMB_FIND_FILE_NAMES_INFO;
                break;
 
        case SMB2_FIND_ID_BOTH_DIRECTORY_INFO:
-               info_level = SMB_FIND_ID_BOTH_DIRECTORY_INFO;
+               state->info_level = SMB_FIND_ID_BOTH_DIRECTORY_INFO;
                break;
 
        case SMB2_FIND_ID_FULL_DIRECTORY_INFO:
-               info_level = SMB_FIND_ID_FULL_DIRECTORY_INFO;
+               state->info_level = SMB_FIND_ID_FULL_DIRECTORY_INFO;
                break;
 
        default:
@@ -322,10 +368,31 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
        }
 
        if (in_flags & SMB2_CONTINUE_FLAG_REOPEN) {
-               dptr_CloseDir(fsp);
+               int flags;
+
+               status = fd_close(fsp);
+               if (tevent_req_nterror(req, status)) {
+                       return tevent_req_post(req, ev);
+               }
+
+               /*
+                * fd_close() will close and invalidate the fsp's file
+                * descriptor. So we have to reopen it.
+                */
+
+               flags = O_RDONLY;
+#ifdef O_DIRECTORY
+               flags |= O_DIRECTORY;
+#endif
+               status = fd_open(conn, fsp, flags, 0);
+               if (tevent_req_nterror(req, status)) {
+                       return tevent_req_post(req, ev);
+               }
        }
 
-       wcard_has_wild = ms_has_wild(in_file_name);
+       if (!state->smbreq->posix_pathnames) {
+               wcard_has_wild = ms_has_wild(state->in_file_name);
+       }
 
        /* Ensure we've canonicalized any search path if not a wildcard. */
        if (!wcard_has_wild) {
@@ -333,15 +400,19 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
                const char *fullpath;
                char tmpbuf[PATH_MAX];
                char *to_free = NULL;
+               uint32_t ucf_flags = UCF_SAVE_LCOMP |
+                                    UCF_ALWAYS_ALLOW_WCARD_LCOMP |
+                                    (state->smbreq->posix_pathnames ?
+                                       UCF_POSIX_PATHNAMES : 0);
 
                if (ISDOT(fsp->fsp_name->base_name)) {
-                       fullpath = in_file_name;
+                       fullpath = state->in_file_name;
                } else {
                        size_t len;
                        char *tmp;
 
                        len = full_path_tos(
-                               fsp->fsp_name->base_name, in_file_name,
+                               fsp->fsp_name->base_name, state->in_file_name,
                                tmpbuf, sizeof(tmpbuf), &tmp, &to_free);
                        if (len == -1) {
                                tevent_req_oom(req);
@@ -351,9 +422,9 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
                }
                status = filename_convert(state,
                                conn,
-                               false, /* Not a DFS path. */
                                fullpath,
-                               UCF_SAVE_LCOMP | UCF_ALWAYS_ALLOW_WCARD_LCOMP,
+                               ucf_flags,
+                               NULL,
                                &wcard_has_wild,
                                &smb_fname);
 
@@ -363,29 +434,29 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
                        return tevent_req_post(req, ev);
                }
 
-               in_file_name = smb_fname->original_lcomp;
+               state->in_file_name = smb_fname->original_lcomp;
        }
 
        if (fsp->dptr == NULL) {
                status = dptr_create(conn,
                                     NULL, /* req */
                                     fsp,
-                                    fsp->fsp_name->base_name,
+                                    fsp->fsp_name,
                                     false, /* old_handle */
                                     false, /* expect_close */
                                     0, /* spid */
-                                    in_file_name, /* wcard */
+                                    state->in_file_name, /* wcard */
                                     wcard_has_wild,
-                                    dirtype,
+                                    state->dirtype,
                                     &fsp->dptr);
                if (!NT_STATUS_IS_OK(status)) {
                        tevent_req_nterror(req, status);
                        return tevent_req_post(req, ev);
                }
 
-               empty_status = NT_STATUS_NO_SUCH_FILE;
+               state->empty_status = NT_STATUS_NO_SUCH_FILE;
        } else {
-               empty_status = STATUS_NO_MORE_FILES;
+               state->empty_status = STATUS_NO_MORE_FILES;
        }
 
        if (in_flags & SMB2_CONTINUE_FLAG_RESTART) {
@@ -393,9 +464,9 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
        }
 
        if (in_flags & SMB2_CONTINUE_FLAG_SINGLE) {
-               max_count = 1;
+               state->max_count = 1;
        } else {
-               max_count = UINT16_MAX;
+               state->max_count = UINT16_MAX;
        }
 
 #define DIR_ENTRY_SAFETY_MARGIN 4096
@@ -407,16 +478,13 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
        }
 
        state->out_output_buffer.length = 0;
-       pdata = (char *)state->out_output_buffer.data;
-       base_data = pdata;
+       state->pdata = (char *)state->out_output_buffer.data;
+       state->base_data = state->pdata;
        /*
         * end_data must include the safety margin as it's what is
         * used to determine if pushed strings have been truncated.
         */
-       end_data = pdata + in_output_buffer_length + DIR_ENTRY_SAFETY_MARGIN - 1;
-       last_entry_off = 0;
-       off = 0;
-       num = 0;
+       state->end_data = state->pdata + in_output_buffer_length + DIR_ENTRY_SAFETY_MARGIN - 1;
 
        DEBUG(8,("smbd_smb2_query_directory_send: dirpath=<%s> dontdescend=<%s>, "
                "in_output_buffer_length = %u\n",
@@ -424,75 +492,329 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
                (unsigned int)in_output_buffer_length ));
        if (in_list(fsp->fsp_name->base_name,lp_dont_descend(talloc_tos(), SNUM(conn)),
                        conn->case_sensitive)) {
-               dont_descend = true;
-       }
-
-       ask_sharemode = lp_parm_bool(SNUM(conn),
-                                    "smbd", "search ask sharemode",
-                                    true);
-
-       while (true) {
-               bool got_exact_match = false;
-               int space_remaining = in_output_buffer_length - off;
-
-               SMB_ASSERT(space_remaining >= 0);
-
-               status = smbd_dirptr_lanman2_entry(state,
-                                              conn,
-                                              fsp->dptr,
-                                              smbreq->flags2,
-                                              in_file_name,
-                                              dirtype,
-                                              info_level,
-                                              false, /* requires_resume_key */
-                                              dont_descend,
-                                              ask_sharemode,
-                                              8, /* align to 8 bytes */
-                                              false, /* no padding */
-                                              &pdata,
-                                              base_data,
-                                              end_data,
-                                              space_remaining,
-                                              &got_exact_match,
-                                              &last_entry_off,
-                                              NULL);
-
-               off = (int)PTR_DIFF(pdata, base_data);
+               state->dont_descend = true;
+       }
 
-               if (!NT_STATUS_IS_OK(status)) {
-                       if (NT_STATUS_EQUAL(status, NT_STATUS_ILLEGAL_CHARACTER)) {
-                               /*
-                                * Bad character conversion on name. Ignore this
-                                * entry.
-                                */
-                               continue;
-                       } else if (num > 0) {
-                               SIVAL(state->out_output_buffer.data, last_entry_off, 0);
-                               tevent_req_done(req);
-                               return tevent_req_post(req, ev);
-                       } else if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
-                               tevent_req_nterror(req, NT_STATUS_INFO_LENGTH_MISMATCH);
-                               return tevent_req_post(req, ev);
-                       } else {
-                               tevent_req_nterror(req, empty_status);
-                               return tevent_req_post(req, ev);
-                       }
-               }
+       /*
+        * SMB_FIND_FILE_NAMES_INFO doesn't need stat information
+        *
+        * This may change when we try to improve the delete on close
+        * handling in future.
+        */
+       if (state->info_level != SMB_FIND_FILE_NAMES_INFO) {
+               state->ask_sharemode = lp_smbd_search_ask_sharemode(SNUM(conn));
+
+               state->async_dosmode = lp_smbd_async_dosmode(SNUM(conn));
+       }
+
+       if (state->ask_sharemode && lp_clustering()) {
+               state->ask_sharemode = false;
+               state->async_ask_sharemode = true;
+       }
+
+       if (state->async_dosmode) {
+               size_t max_threads;
 
-               num++;
-               state->out_output_buffer.length = off;
+               max_threads = pthreadpool_tevent_max_threads(conn->sconn->raw_thread_pool);
 
-               if (num < max_count) {
-                       continue;
+               state->max_async_dosmode_active = lp_smbd_max_async_dosmode(
+                                                       SNUM(conn));
+               if (state->max_async_dosmode_active == 0) {
+                       state->max_async_dosmode_active = max_threads * 2;
                }
+       }
 
-               SIVAL(state->out_output_buffer.data, last_entry_off, 0);
-               tevent_req_done(req);
+       if (state->async_dosmode || state->async_ask_sharemode) {
+               /*
+                * Should we only set async_internal
+                * if we're not the last request in
+                * a compound chain?
+                */
+               smb2_request_set_async_internal(smb2req, true);
+       }
+
+       /*
+        * This gets set in autobuild for some tests
+        */
+       state->find_async_delay_usec = lp_parm_ulong(SNUM(conn), "smbd",
+                                                    "find async delay usec",
+                                                    0);
+
+       while (!stop) {
+               stop = smb2_query_directory_next_entry(req);
+       }
+
+       if (!tevent_req_is_in_progress(req)) {
+               return tevent_req_post(req, ev);
+       }
+
+       ok = aio_add_req_to_fsp(fsp, req);
+       if (!ok) {
+               DBG_ERR("Could not add req to fsp\n");
+               tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
                return tevent_req_post(req, ev);
        }
 
-       tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
-       return tevent_req_post(req, ev);
+       return req;
+}
+
+static bool smb2_query_directory_next_entry(struct tevent_req *req)
+{
+       struct smbd_smb2_query_directory_state *state = tevent_req_data(
+               req, struct smbd_smb2_query_directory_state);
+       struct smb_filename *smb_fname = NULL; /* relative to fsp !! */
+       bool got_exact_match = false;
+       int off = state->out_output_buffer.length;
+       int space_remaining = state->in_output_buffer_length - off;
+       struct file_id file_id;
+       NTSTATUS status;
+       bool get_dosmode = !state->async_dosmode;
+       bool stop = false;
+
+       SMB_ASSERT(space_remaining >= 0);
+
+       status = smbd_dirptr_lanman2_entry(state,
+                                          state->fsp->conn,
+                                          state->fsp->dptr,
+                                          state->smbreq->flags2,
+                                          state->in_file_name,
+                                          state->dirtype,
+                                          state->info_level,
+                                          false, /* requires_resume_key */
+                                          state->dont_descend,
+                                          state->ask_sharemode,
+                                          get_dosmode,
+                                          8, /* align to 8 bytes */
+                                          false, /* no padding */
+                                          &state->pdata,
+                                          state->base_data,
+                                          state->end_data,
+                                          space_remaining,
+                                          &smb_fname,
+                                          &got_exact_match,
+                                          &state->last_entry_off,
+                                          NULL,
+                                          &file_id);
+
+       off = (int)PTR_DIFF(state->pdata, state->base_data);
+
+       if (!NT_STATUS_IS_OK(status)) {
+               if (NT_STATUS_EQUAL(status, NT_STATUS_ILLEGAL_CHARACTER)) {
+                       /*
+                        * Bad character conversion on name. Ignore this
+                        * entry.
+                        */
+                       return false;
+               } else if (state->num > 0) {
+                       goto last_entry_done;
+               } else if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+                       tevent_req_nterror(req, NT_STATUS_INFO_LENGTH_MISMATCH);
+                       return true;
+               } else {
+                       tevent_req_nterror(req, state->empty_status);
+                       return true;
+               }
+       }
+
+       if (state->async_ask_sharemode) {
+               struct tevent_req *subreq = NULL;
+               char *buf = state->base_data + state->last_entry_off;
+
+               subreq = fetch_write_time_send(state,
+                                              state->ev,
+                                              state->fsp->conn,
+                                              file_id,
+                                              state->info_level,
+                                              buf,
+                                              &stop);
+               if (tevent_req_nomem(subreq, req)) {
+                       return true;
+               }
+               tevent_req_set_callback(
+                       subreq,
+                       smb2_query_directory_fetch_write_time_done,
+                       req);
+               state->async_sharemode_count++;
+       }
+
+       if (state->async_dosmode) {
+               struct tevent_req *subreq = NULL;
+               uint8_t *buf = NULL;
+               size_t outstanding_aio;
+
+               buf = (uint8_t *)state->base_data + state->last_entry_off;
+
+               subreq = fetch_dos_mode_send(state,
+                                            state->ev,
+                                            state->fsp,
+                                            &smb_fname,
+                                            state->info_level,
+                                            buf);
+               if (tevent_req_nomem(subreq, req)) {
+                       return true;
+               }
+               tevent_req_set_callback(subreq,
+                                       smb2_query_directory_dos_mode_done,
+                                       req);
+
+               state->async_dosmode_active++;
+
+               outstanding_aio = pthreadpool_tevent_queued_jobs(
+                                       state->fsp->conn->sconn->raw_thread_pool);
+
+               if (outstanding_aio > state->max_async_dosmode_active) {
+                       stop = true;
+               }
+       }
+
+       TALLOC_FREE(smb_fname);
+
+       state->num++;
+       state->out_output_buffer.length = off;
+
+       if (!state->done && state->num < state->max_count) {
+               return stop;
+       }
+
+last_entry_done:
+       SIVAL(state->out_output_buffer.data, state->last_entry_off, 0);
+
+       state->done = true;
+
+       if (state->async_sharemode_count > 0) {
+               DBG_DEBUG("Stopping after %"PRIu64" async mtime "
+                         "updates\n", state->async_sharemode_count);
+               return true;
+       }
+
+       if (state->async_dosmode_active > 0) {
+               return true;
+       }
+
+       if (state->find_async_delay_usec > 0) {
+               struct timeval tv;
+               struct tevent_req *subreq = NULL;
+
+               /*
+                * Should we only set async_internal
+                * if we're not the last request in
+                * a compound chain?
+                */
+               smb2_request_set_async_internal(state->smb2req, true);
+
+               tv = timeval_current_ofs(0, state->find_async_delay_usec);
+
+               subreq = tevent_wakeup_send(state, state->ev, tv);
+               if (tevent_req_nomem(subreq, req)) {
+                       return true;
+               }
+               tevent_req_set_callback(subreq,
+                                       smb2_query_directory_waited,
+                                       req);
+               return true;
+       }
+
+       tevent_req_done(req);
+       return true;
+}
+
+static void smb2_query_directory_check_next_entry(struct tevent_req *req);
+
+static void smb2_query_directory_fetch_write_time_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct smbd_smb2_query_directory_state *state = tevent_req_data(
+               req, struct smbd_smb2_query_directory_state);
+       NTSTATUS status;
+
+       state->async_sharemode_count--;
+
+       status = fetch_write_time_recv(subreq);
+       TALLOC_FREE(subreq);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+
+       smb2_query_directory_check_next_entry(req);
+       return;
+}
+
+static void smb2_query_directory_dos_mode_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req =
+               tevent_req_callback_data(subreq,
+               struct tevent_req);
+       struct smbd_smb2_query_directory_state *state =
+               tevent_req_data(req,
+               struct smbd_smb2_query_directory_state);
+       NTSTATUS status;
+
+       status = fetch_dos_mode_recv(subreq);
+       TALLOC_FREE(subreq);
+       if (tevent_req_nterror(req, status)) {
+               return;
+       }
+
+       state->async_dosmode_active--;
+
+       smb2_query_directory_check_next_entry(req);
+       return;
+}
+
+static void smb2_query_directory_check_next_entry(struct tevent_req *req)
+{
+       struct smbd_smb2_query_directory_state *state = tevent_req_data(
+               req, struct smbd_smb2_query_directory_state);
+       bool stop = false;
+
+       if (!state->done) {
+               while (!stop) {
+                       stop = smb2_query_directory_next_entry(req);
+               }
+               return;
+       }
+
+       if (state->async_sharemode_count > 0 ||
+           state->async_dosmode_active > 0)
+       {
+               return;
+       }
+
+       if (state->find_async_delay_usec > 0) {
+               struct timeval tv;
+               struct tevent_req *subreq = NULL;
+
+               tv = timeval_current_ofs(0, state->find_async_delay_usec);
+
+               subreq = tevent_wakeup_send(state, state->ev, tv);
+               if (tevent_req_nomem(subreq, req)) {
+                       tevent_req_post(req, state->ev);
+                       return;
+               }
+               tevent_req_set_callback(subreq,
+                                       smb2_query_directory_waited,
+                                       req);
+               return;
+       }
+
+       tevent_req_done(req);
+       return;
+}
+
+static void smb2_query_directory_waited(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       bool ok;
+
+       ok = tevent_wakeup_recv(subreq);
+       TALLOC_FREE(subreq);
+       if (!ok) {
+               tevent_req_oom(req);
+               return;
+       }
+       tevent_req_done(req);
 }
 
 static NTSTATUS smbd_smb2_query_directory_recv(struct tevent_req *req,
@@ -514,3 +836,240 @@ static NTSTATUS smbd_smb2_query_directory_recv(struct tevent_req *req,
        tevent_req_received(req);
        return NT_STATUS_OK;
 }
+
+struct fetch_write_time_state {
+       connection_struct *conn;
+       struct file_id id;
+       int info_level;
+       char *entry_marshall_buf;
+};
+
+static void fetch_write_time_done(struct tevent_req *subreq);
+
+static struct tevent_req *fetch_write_time_send(TALLOC_CTX *mem_ctx,
+                                               struct tevent_context *ev,
+                                               connection_struct *conn,
+                                               struct file_id id,
+                                               int info_level,
+                                               char *entry_marshall_buf,
+                                               bool *stop)
+{
+       struct tevent_req *req = NULL;
+       struct fetch_write_time_state *state = NULL;
+       struct tevent_req *subreq = NULL;
+       bool req_queued;
+
+       *stop = false;
+
+       req = tevent_req_create(mem_ctx, &state, struct fetch_write_time_state);
+       if (req == NULL) {
+               return NULL;
+       }
+
+       *state = (struct fetch_write_time_state) {
+               .conn = conn,
+               .id = id,
+               .info_level = info_level,
+               .entry_marshall_buf = entry_marshall_buf,
+       };
+
+       subreq = fetch_share_mode_send(state, ev, id, &req_queued);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, fetch_write_time_done, req);
+
+       if (req_queued) {
+               *stop = true;
+       }
+       return req;
+}
+
+static void fetch_write_time_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct fetch_write_time_state *state = tevent_req_data(
+               req, struct fetch_write_time_state);
+       struct timespec write_time;
+       struct share_mode_lock *lck = NULL;
+       NTSTATUS status;
+       size_t off;
+
+       status = fetch_share_mode_recv(subreq, state, &lck);
+       TALLOC_FREE(subreq);
+       if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+               tevent_req_done(req);
+               return;
+       }
+       if (!NT_STATUS_IS_OK(status)) {
+               tevent_req_nterror(req, status);
+               return;
+       }
+
+       write_time = get_share_mode_write_time(lck);
+       TALLOC_FREE(lck);
+
+       if (null_timespec(write_time)) {
+               tevent_req_done(req);
+               return;
+       }
+
+       switch (state->info_level) {
+       case SMB_FIND_FILE_DIRECTORY_INFO:
+       case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+       case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+       case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+       case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+               off = 24;
+               break;
+
+       default:
+               DBG_ERR("Unsupported info_level [%d]\n", state->info_level);
+               tevent_req_nterror(req, NT_STATUS_INVALID_LEVEL);
+               return;
+       }
+
+       put_long_date_timespec(state->conn->ts_res,
+                              state->entry_marshall_buf + off,
+                              write_time);
+
+       tevent_req_done(req);
+       return;
+}
+
+static NTSTATUS fetch_write_time_recv(struct tevent_req *req)
+{
+       NTSTATUS status;
+
+       if (tevent_req_is_nterror(req, &status)) {
+               tevent_req_received(req);
+               return status;
+       }
+
+       tevent_req_received(req);
+       return NT_STATUS_OK;
+}
+
+struct fetch_dos_mode_state {
+       struct files_struct *dir_fsp;
+       struct smb_filename *smb_fname;
+       uint32_t info_level;
+       uint8_t *entry_marshall_buf;
+};
+
+static void fetch_dos_mode_done(struct tevent_req *subreq);
+
+static struct tevent_req *fetch_dos_mode_send(
+                       TALLOC_CTX *mem_ctx,
+                       struct tevent_context *ev,
+                       struct files_struct *dir_fsp,
+                       struct smb_filename **smb_fname,
+                       uint32_t info_level,
+                       uint8_t *entry_marshall_buf)
+{
+       struct tevent_req *req = NULL;
+       struct fetch_dos_mode_state *state = NULL;
+       struct tevent_req *subreq = NULL;
+
+       req = tevent_req_create(mem_ctx, &state, struct fetch_dos_mode_state);
+       if (req == NULL) {
+               return NULL;
+       }
+       *state = (struct fetch_dos_mode_state) {
+               .dir_fsp = dir_fsp,
+               .info_level = info_level,
+               .entry_marshall_buf = entry_marshall_buf,
+       };
+
+       state->smb_fname = talloc_move(state, smb_fname);
+
+       subreq = dos_mode_at_send(state, ev, dir_fsp, state->smb_fname);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, fetch_dos_mode_done, req);
+
+       return req;
+}
+
+static void fetch_dos_mode_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req =
+               tevent_req_callback_data(subreq,
+               struct tevent_req);
+       struct fetch_dos_mode_state *state =
+               tevent_req_data(req,
+               struct fetch_dos_mode_state);
+       uint32_t dfs_dosmode;
+       uint32_t dosmode;
+       struct timespec btime_ts = {0};
+       off_t dosmode_off;
+       off_t btime_off;
+       NTSTATUS status;
+
+       status = dos_mode_at_recv(subreq, &dosmode);
+       TALLOC_FREE(subreq);
+       if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+               tevent_req_done(req);
+               return;
+       }
+       if (!NT_STATUS_IS_OK(status)) {
+               tevent_req_nterror(req, status);
+               return;
+       }
+
+       switch (state->info_level) {
+       case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+       case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+       case SMB_FIND_FILE_DIRECTORY_INFO:
+       case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+       case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+               btime_off = 8;
+               dosmode_off = 56;
+               break;
+
+       default:
+               DBG_ERR("Unsupported info_level [%u]\n", state->info_level);
+               tevent_req_nterror(req, NT_STATUS_INVALID_LEVEL);
+               return;
+       }
+
+
+       dfs_dosmode = IVAL(state->entry_marshall_buf, dosmode_off);
+       if (dfs_dosmode == 0) {
+               /*
+                * DOS mode for a DFS link, only overwrite if still set to 0 and
+                * not already populated by the lower layer for a DFS link in
+                * smbd_dirptr_lanman2_mode_fn().
+                */
+               SIVAL(state->entry_marshall_buf, dosmode_off, dosmode);
+       }
+
+       btime_ts = get_create_timespec(state->dir_fsp->conn,
+                                      NULL,
+                                      state->smb_fname);
+       if (lp_dos_filetime_resolution(SNUM(state->dir_fsp->conn))) {
+               dos_filetime_timespec(&btime_ts);
+       }
+
+       put_long_date_timespec(state->dir_fsp->conn->ts_res,
+                              (char *)state->entry_marshall_buf + btime_off,
+                              btime_ts);
+
+       tevent_req_done(req);
+       return;
+}
+
+static NTSTATUS fetch_dos_mode_recv(struct tevent_req *req)
+{
+       NTSTATUS status;
+
+       if (tevent_req_is_nterror(req, &status)) {
+               tevent_req_received(req);
+               return status;
+       }
+
+       tevent_req_received(req);
+       return NT_STATUS_OK;
+}