s3:smbd: don't use async dosmode if per-thread cwd is not available
[garming/samba-autobuild/.git] / source3 / smbd / smb2_query_directory.c
index fc9ce5223970e840764cd17457297666fc468363..fdb87188f93386018ec2eefe920d220ad596bf3e 100644 (file)
 #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,
@@ -202,14 +206,48 @@ static struct tevent_req *fetch_write_time_send(TALLOC_CTX *mem_ctx,
                                                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_count;
+       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,
@@ -224,39 +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 = false;
-       bool async_ask_sharemode = false;
        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 = ev;
+       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);
        }
 
@@ -265,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
@@ -308,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:
@@ -339,11 +370,14 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
        if (in_flags & SMB2_CONTINUE_FLAG_REOPEN) {
                int flags;
 
-               dptr_CloseDir(fsp);
+               status = fd_close(fsp);
+               if (tevent_req_nterror(req, status)) {
+                       return tevent_req_post(req, ev);
+               }
 
                /*
-                * dptr_CloseDir() will close and invalidate the fsp's file
-                * descriptor, we have to reopen it.
+                * fd_close() will close and invalidate the fsp's file
+                * descriptor. So we have to reopen it.
                 */
 
                flags = O_RDONLY;
@@ -356,8 +390,8 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
                }
        }
 
-       if (!smbreq->posix_pathnames) {
-               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. */
@@ -368,17 +402,17 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
                char *to_free = NULL;
                uint32_t ucf_flags = UCF_SAVE_LCOMP |
                                     UCF_ALWAYS_ALLOW_WCARD_LCOMP |
-                                    (smbreq->posix_pathnames ?
+                                    (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);
@@ -388,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_flags,
+                               NULL,
                                &wcard_has_wild,
                                &smb_fname);
 
@@ -400,7 +434,7 @@ 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) {
@@ -411,18 +445,18 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
                                     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) {
@@ -430,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
@@ -444,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",
@@ -461,7 +492,7 @@ 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;
+               state->dont_descend = true;
        }
 
        /*
@@ -470,16 +501,33 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
         * This may change when we try to improve the delete on close
         * handling in future.
         */
-       if (info_level != SMB_FIND_FILE_NAMES_INFO) {
-               ask_sharemode = lp_parm_bool(SNUM(conn),
-                                            "smbd", "search ask sharemode",
-                                            true);
+       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 (ask_sharemode && lp_clustering()) {
-               ask_sharemode = false;
-               async_ask_sharemode = true;
+       if (state->async_dosmode) {
+               size_t max_threads;
+
+               max_threads = pthreadpool_tevent_max_threads(conn->sconn->pool);
+               if (max_threads == 0 || !per_thread_cwd_supported()) {
+                       state->async_dosmode = false;
+               }
+
+               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;
+               }
+       }
 
+       if (state->async_dosmode || state->async_ask_sharemode) {
                /*
                 * Should we only set async_internal
                 * if we're not the last request in
@@ -488,103 +536,193 @@ static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
                smb2_request_set_async_internal(smb2req, true);
        }
 
-       while (true) {
-               bool got_exact_match = false;
-               int space_remaining = in_output_buffer_length - off;
-               int cur_off = off;
-               struct file_id file_id;
-               bool stop = false;
-
-               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,
-                                              &file_id);
-
-               off = (int)PTR_DIFF(pdata, base_data);
+       /*
+        * This gets set in autobuild for some tests
+        */
+       state->find_async_delay_usec = lp_parm_ulong(SNUM(conn), "smbd",
+                                                    "find async delay usec",
+                                                    0);
 
-               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) {
-                               goto last_entry_done;
-                       } 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);
-                       }
+       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);
+       }
+
+       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 (async_ask_sharemode) {
-                       struct tevent_req *subreq = NULL;
-
-                       subreq = fetch_write_time_send(req,
-                                                      ev,
-                                                      conn,
-                                                      file_id,
-                                                      info_level,
-                                                      base_data + cur_off,
-                                                      &stop);
-                       if (tevent_req_nomem(subreq, req)) {
-                               return tevent_req_post(req, ev);
-                       }
-                       tevent_req_set_callback(
-                               subreq,
-                               smb2_query_directory_fetch_write_time_done,
-                               req);
+       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++;
+       }
 
-                       state->async_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++;
 
-               num++;
-               state->out_output_buffer.length = off;
+               outstanding_aio = pthreadpool_tevent_queued_jobs(
+                                       state->fsp->conn->sconn->pool);
 
-               if (num >= max_count) {
+               if (outstanding_aio > state->max_async_dosmode_active) {
                        stop = true;
                }
+       }
 
-               if (!stop) {
-                       continue;
-               }
+       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, last_entry_off, 0);
-               if (state->async_count > 0) {
-                       DBG_DEBUG("Stopping after %zu async mtime updates\n",
-                                 state->async_count);
-                       return req;
+       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_done(req);
-               return tevent_req_post(req, ev);
+               tevent_req_set_callback(subreq,
+                                       smb2_query_directory_waited,
+                                       req);
+               return true;
        }
 
-       tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
-       return tevent_req_post(req, ev);
+       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(
@@ -593,7 +731,7 @@ static void smb2_query_directory_fetch_write_time_done(struct tevent_req *subreq
                req, struct smbd_smb2_query_directory_state);
        NTSTATUS status;
 
-       state->async_count--;
+       state->async_sharemode_count--;
 
        status = fetch_write_time_recv(subreq);
        TALLOC_FREE(subreq);
@@ -601,7 +739,65 @@ static void smb2_query_directory_fetch_write_time_done(struct tevent_req *subreq
                return;
        }
 
-       if (state->async_count > 0) {
+       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;
        }
 
@@ -609,6 +805,21 @@ static void smb2_query_directory_fetch_write_time_done(struct tevent_req *subreq
        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,
                                    TALLOC_CTX *mem_ctx,
                                    DATA_BLOB *out_output_buffer)
@@ -742,3 +953,126 @@ static NTSTATUS fetch_write_time_recv(struct tevent_req *req)
        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;
+}