s3: OneFS bulk directory enumeration support
authorSteven Danneman <steven.danneman@isilon.com>
Tue, 27 Jan 2009 04:14:32 +0000 (20:14 -0800)
committerSteven Danneman <steven.danneman@isilon.com>
Tue, 10 Feb 2009 07:56:17 +0000 (23:56 -0800)
OneFS provides the bulk directory enumeration syscall readdirplus().  This
syscall has the same semantics as the NFSv3 READDIRPLUS command, returning
a batch of directory entries with prefetched stat information via one
syscall.

This commit wraps the readdirplus() call in the existing POSIX
readdir/seekdir VFS interface.  By default a batch of 128 directory entries
are optimistically read from the kernel into a global cache, and fed to
iterative calls of VFS_OP_READDIR.

The global buffers could be avoided in the future by hanging connection
specific buffers off the conn struct.

Added new parameter "onefs:use readdirplus" which toggles usage of this
code on or off.

source3/Makefile.in
source3/include/vfs.h
source3/modules/onefs.h
source3/modules/onefs_dir.c [new file with mode: 0644]
source3/modules/vfs_default.c
source3/modules/vfs_onefs.c

index 204995328487af8a9f4579a9872e9e26e47556d3..6e453c9a862ce5002261aaf42c173dae436765ac 100644 (file)
@@ -664,7 +664,7 @@ VFS_ACL_XATTR_OBJ = modules/vfs_acl_xattr.o
 VFS_ACL_TDB_OBJ = modules/vfs_acl_tdb.o
 VFS_SMB_TRAFFIC_ANALYZER_OBJ = modules/vfs_smb_traffic_analyzer.o
 VFS_ONEFS_OBJ = modules/vfs_onefs.o modules/onefs_acl.o modules/onefs_system.o \
 VFS_ACL_TDB_OBJ = modules/vfs_acl_tdb.o
 VFS_SMB_TRAFFIC_ANALYZER_OBJ = modules/vfs_smb_traffic_analyzer.o
 VFS_ONEFS_OBJ = modules/vfs_onefs.o modules/onefs_acl.o modules/onefs_system.o \
-               modules/onefs_open.o modules/onefs_streams.o
+               modules/onefs_open.o modules/onefs_streams.o modules/onefs_dir.c
 PERFCOUNT_ONEFS_OBJ = modules/perfcount_onefs.o
 
 PLAINTEXT_AUTH_OBJ = auth/pampass.o auth/pass_check.o
 PERFCOUNT_ONEFS_OBJ = modules/perfcount_onefs.o
 
 PLAINTEXT_AUTH_OBJ = auth/pampass.o auth/pass_check.o
index 99af30b1c5ed74f134bc2db6012b6e40a0faf7c4..f944c899c643c7f5b2fa6bc21d02481c65b80300 100644 (file)
@@ -252,7 +252,7 @@ typedef enum _vfs_op_type {
        SMB_VFS_OP_SYS_ACL_FREE_TEXT,
        SMB_VFS_OP_SYS_ACL_FREE_ACL,
        SMB_VFS_OP_SYS_ACL_FREE_QUALIFIER,
        SMB_VFS_OP_SYS_ACL_FREE_TEXT,
        SMB_VFS_OP_SYS_ACL_FREE_ACL,
        SMB_VFS_OP_SYS_ACL_FREE_QUALIFIER,
-       
+
        /* EA operations. */
        SMB_VFS_OP_GETXATTR,
        SMB_VFS_OP_LGETXATTR,
        /* EA operations. */
        SMB_VFS_OP_GETXATTR,
        SMB_VFS_OP_LGETXATTR,
index c8f19f4b31bafcb8683db715d9a29f2d006aae77..72be682c2126cd12c75ee09655ed579e882f3a57 100644 (file)
@@ -41,26 +41,28 @@ enum onefs_acl_wire_format
 #define PARM_ONEFS_TYPE "onefs"
 #define PARM_ACL_WIRE_FORMAT "acl wire format"
 #define PARM_ACL_WIRE_FORMAT_DEFAULT ACL_FORMAT_WINDOWS_SD
 #define PARM_ONEFS_TYPE "onefs"
 #define PARM_ACL_WIRE_FORMAT "acl wire format"
 #define PARM_ACL_WIRE_FORMAT_DEFAULT ACL_FORMAT_WINDOWS_SD
-#define PARM_SIMPLE_FILE_SHARING_COMPATIBILITY_MODE "simple file sharing compatibility mode"
-#define PARM_SIMPLE_FILE_SHARING_COMPATIBILITY_MODE_DEFAULT false
+#define PARM_ATIME_NOW         "atime now files"
+#define PARM_ATIME_STATIC      "atime static files"
+#define PARM_ATIME_SLOP                "atime now slop"
 #define PARM_CREATOR_OWNER_GETS_FULL_CONTROL "creator owner gets full control"
 #define PARM_CREATOR_OWNER_GETS_FULL_CONTROL_DEFAULT true
 #define PARM_CREATOR_OWNER_GETS_FULL_CONTROL "creator owner gets full control"
 #define PARM_CREATOR_OWNER_GETS_FULL_CONTROL_DEFAULT true
+#define PARM_CTIME_NOW         "ctime now files"
+#define PARM_CTIME_SLOP                "ctime now slop"
+#define PARM_IGNORE_SACL "ignore sacl"
+#define PARM_IGNORE_SACL_DEFAULT false
+#define PARM_MTIME_NOW         "mtime now files"
+#define PARM_MTIME_STATIC      "mtime static files"
+#define PARM_MTIME_SLOP                "mtime now slop"
+#define PARM_USE_READDIRPLUS "use readdirplus"
+#define PARM_USE_READDIRPLUS_DEFAULT true
+#define PARM_SIMPLE_FILE_SHARING_COMPATIBILITY_MODE "simple file sharing compatibility mode"
+#define PARM_SIMPLE_FILE_SHARING_COMPATIBILITY_MODE_DEFAULT false
 #define PARM_UNMAPPABLE_SIDS_DENY_EVERYONE "unmappable sids deny everyone"
 #define PARM_UNMAPPABLE_SIDS_DENY_EVERYONE_DEFAULT false
 #define PARM_UNMAPPABLE_SIDS_IGNORE "ignore unmappable sids"
 #define PARM_UNMAPPABLE_SIDS_IGNORE_DEFAULT false
 #define PARM_UNMAPPABLE_SIDS_IGNORE_LIST "unmappable sids ignore list"
 #define PARM_UNMAPPABLE_SIDS_IGNORE_LIST_DEFAULT NULL
 #define PARM_UNMAPPABLE_SIDS_DENY_EVERYONE "unmappable sids deny everyone"
 #define PARM_UNMAPPABLE_SIDS_DENY_EVERYONE_DEFAULT false
 #define PARM_UNMAPPABLE_SIDS_IGNORE "ignore unmappable sids"
 #define PARM_UNMAPPABLE_SIDS_IGNORE_DEFAULT false
 #define PARM_UNMAPPABLE_SIDS_IGNORE_LIST "unmappable sids ignore list"
 #define PARM_UNMAPPABLE_SIDS_IGNORE_LIST_DEFAULT NULL
-#define PARM_IGNORE_SACL "ignore sacl"
-#define PARM_IGNORE_SACL_DEFAULT false
-#define PARM_ATIME_NOW         "atime now files"
-#define PARM_ATIME_SLOP                "atime now slop"
-#define PARM_CTIME_NOW         "ctime now files"
-#define PARM_CTIME_SLOP                "ctime now slop"
-#define PARM_MTIME_NOW         "mtime now files"
-#define PARM_MTIME_SLOP                "mtime now slop"
-#define PARM_MTIME_STATIC      "mtime static files"
-#define PARM_ATIME_STATIC      "atime static files"
 
 #define IS_CTIME_NOW_PATH(conn,cfg,path)  ((conn) && is_in_path((path),\
        (cfg)->ctime_now_list,(conn)->case_sensitive))
 
 #define IS_CTIME_NOW_PATH(conn,cfg,path)  ((conn) && is_in_path((path),\
        (cfg)->ctime_now_list,(conn)->case_sensitive))
@@ -81,7 +83,6 @@ enum onefs_acl_wire_format
 
 #define ONEFS_VFS_CONFIG_FAKETIMESTAMPS        0x00000001
 
 
 #define ONEFS_VFS_CONFIG_FAKETIMESTAMPS        0x00000001
 
-
 struct onefs_vfs_config
 {
        int32 init_flags;
 struct onefs_vfs_config
 {
        int32 init_flags;
@@ -113,6 +114,25 @@ struct onefs_vfs_config
 /*
  * vfs interface handlers
  */
 /*
  * vfs interface handlers
  */
+SMB_STRUCT_DIR *onefs_opendir(struct vfs_handle_struct *handle,
+                             const char *fname, const char *mask,
+                             uint32 attributes);
+
+SMB_STRUCT_DIRENT *onefs_readdir(struct vfs_handle_struct *handle,
+                                SMB_STRUCT_DIR *dirp, SMB_STRUCT_STAT *sbuf);
+
+void onefs_seekdir(struct vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp,
+                  long offset);
+
+long onefs_telldir(struct vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp);
+
+void onefs_rewinddir(struct vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp);
+
+int onefs_closedir(struct vfs_handle_struct *handle, SMB_STRUCT_DIR *dir);
+
+void onefs_init_search_op(struct vfs_handle_struct *handle,
+                         SMB_STRUCT_DIR *dirp);
+
 NTSTATUS onefs_create_file(vfs_handle_struct *handle,
                           struct smb_request *req,
                           uint16_t root_dir_fid,
 NTSTATUS onefs_create_file(vfs_handle_struct *handle,
                           struct smb_request *req,
                           uint16_t root_dir_fid,
diff --git a/source3/modules/onefs_dir.c b/source3/modules/onefs_dir.c
new file mode 100644 (file)
index 0000000..3c1a836
--- /dev/null
@@ -0,0 +1,636 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Support for OneFS bulk directory enumeration API
+ *
+ * Copyright (C) Steven Danneman, 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "onefs.h"
+
+#include <ifs/ifs_syscalls.h>
+
+/* The OneFS filesystem provides a readdirplus() syscall, equivalent to the
+ * NFSv3 PDU, which retrieves bulk directory listings with stat information
+ * in a single syscall.
+ *
+ * This file hides this bulk interface underneath Samba's very POSIX like
+ * opendir/readdir/telldir VFS interface.  This is done to provide a
+ * significant performance improvement when listing the contents of large
+ * directories, which also require file meta information. ie a typical
+ * Windows Explorer request.
+ */
+
+#define RDP_RESUME_KEY_START 0x1
+
+#define RDP_BATCH_SIZE 128
+#define RDP_DIRENTRIES_SIZE ((size_t)(RDP_BATCH_SIZE * sizeof(struct dirent)))
+
+static char *rdp_direntries = NULL;
+static SMB_STRUCT_STAT *rdp_stats = NULL;
+static uint64_t *rdp_cookies = NULL;
+
+struct rdp_dir_state {
+       struct rdp_dir_state *next, *prev;
+       SMB_STRUCT_DIR *dirp;
+       char *direntries_cursor; /* cursor to current direntry in the cache */
+       size_t stat_count;       /* number of entries stored in the cache */
+       size_t stat_cursor;      /* cursor to current stat in the cache */
+       uint64_t resume_cookie;  /* last cookie returned from the cache */
+       long location;           /* absolute location of direnty in DIR */
+};
+
+static struct rdp_dir_state *dirstatelist = NULL;
+
+SMB_STRUCT_DIR *rdp_last_dirp = NULL;
+
+/**
+ * Given a DIR pointer, return our internal state.
+ *
+ * This function also tells us whether the given DIR is the same as we saw
+ * during the last call.  Because we use a single globally allocated buffer
+ * for readdirplus entries we must check every call into this API to see if
+ * it's for the same directory listing, or a new one. If it's the same we can
+ * maintain our current cached entries, otherwise we must go to the kernel.
+ *
+ * @return 0 on success, 1 on failure
+ */
+static int
+rdp_retrieve_dir_state(SMB_STRUCT_DIR *dirp, struct rdp_dir_state **dir_state,
+                      bool *same_as_last)
+{
+       struct rdp_dir_state *dsp;
+
+       /* Is this directory the same as the last call */
+       *same_as_last = (dirp == rdp_last_dirp);
+
+       for(dsp = dirstatelist; dsp; dsp = dsp->next)
+               if (dsp->dirp == dirp) {
+                       *dir_state = dsp;
+                       return 0;
+               }
+
+       /* Couldn't find existing dir_state for the given directory
+        * pointer. */
+       return 1;
+}
+
+/**
+ * Initialize the global readdirplus buffers.
+ *
+ * These same buffers are used for all calls into readdirplus.
+ *
+ * @return 0 on success, errno value on failure
+ */
+static int
+rdp_init(struct rdp_dir_state *dsp)
+{
+       /* Unfortunately, there is no good way to free these buffers.  If we
+        * allocated and freed for every DIR handle performance would be
+        * adversely affected.  For now these buffers will be leaked and only
+        * freed when the smbd process dies. */
+       if (!rdp_direntries) {
+               rdp_direntries = SMB_MALLOC(RDP_DIRENTRIES_SIZE);
+               if (!rdp_direntries)
+                       return ENOMEM;
+       }
+
+       if (!rdp_stats) {
+               rdp_stats =
+                   SMB_MALLOC(RDP_BATCH_SIZE * sizeof(SMB_STRUCT_STAT));
+               if (!rdp_stats)
+                       return ENOMEM;
+       }
+
+       if (!rdp_cookies) {
+               rdp_cookies = SMB_MALLOC(RDP_BATCH_SIZE * sizeof(uint64_t));
+               if (!rdp_cookies)
+                       return ENOMEM;
+       }
+
+       dsp->direntries_cursor = rdp_direntries + RDP_DIRENTRIES_SIZE;
+       dsp->stat_count = RDP_BATCH_SIZE;
+       dsp->stat_cursor = RDP_BATCH_SIZE;
+       dsp->resume_cookie = RDP_RESUME_KEY_START;
+       dsp->location = 0;
+
+       return 0;
+}
+
+/**
+ * Call into readdirplus() to refill our global dirent cache.
+ *
+ * This function also resets all cursors back to the beginning of the cache.
+ * All stat buffers are retrieved by following symlinks.
+ *
+ * @return number of entries retrieved, -1 on error
+ */
+static int
+rdp_fill_cache(struct rdp_dir_state *dsp)
+{
+       int nread, dirfd;
+
+       dirfd = dirfd(dsp->dirp);
+       if (dirfd < 0) {
+               DEBUG(1, ("Could not retrieve fd for DIR\n"));
+               return -1;
+       }
+
+       /* Resize the stat_count to grab as many entries as possible */
+       dsp->stat_count = RDP_BATCH_SIZE;
+
+       DEBUG(9, ("Calling readdirplus() with DIR %p, dirfd: %d, "
+                "resume_cookie 0x%llx, location %u, size_to_read: %zu, "
+                "direntries_size: %zu, stat_count: %u\n",
+                dsp->dirp, dirfd, dsp->resume_cookie, dsp->location,
+                RDP_BATCH_SIZE, RDP_DIRENTRIES_SIZE, dsp->stat_count));
+
+       nread = readdirplus(dirfd,
+                           RDP_FOLLOW,
+                           &dsp->resume_cookie,
+                           RDP_BATCH_SIZE,
+                           rdp_direntries,
+                           RDP_DIRENTRIES_SIZE,
+                           &dsp->stat_count,
+                           rdp_stats,
+                           rdp_cookies);
+       if (nread < 0) {
+               DEBUG(1, ("Error calling readdirplus(): %s\n",
+                        strerror(errno)));
+               return -1;
+       }
+
+       DEBUG(9, ("readdirplus() returned %u entries from DIR %p\n",
+                dsp->stat_count, dsp->dirp));
+
+       dsp->direntries_cursor = rdp_direntries;
+       dsp->stat_cursor = 0;
+
+       return nread;
+}
+
+/**
+ * Open a directory for enumeration.
+ *
+ * Create a state struct to track the state of this directory for the life
+ * of this open.
+ *
+ * @param[in] handle vfs handle given in most VFS calls
+ * @param[in] fname filename of the directory to open
+ * @param[in] mask unused
+ * @param[in] attr unused
+ *
+ * @return DIR pointer, NULL if directory does not exist, NULL on error
+ */
+SMB_STRUCT_DIR *
+onefs_opendir(vfs_handle_struct *handle, const char *fname, const char *mask,
+             uint32 attr)
+{
+       int ret = 0;
+       SMB_STRUCT_DIR *ret_dirp;
+       struct rdp_dir_state *dsp = NULL;
+
+       /* Fallback to default system routines if readdirplus is disabled */
+       if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
+           PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
+       {
+               return SMB_VFS_NEXT_OPENDIR(handle, fname, mask, attr);
+       }
+
+       /* Create a struct dir_state */
+       dsp = SMB_MALLOC_P(struct rdp_dir_state);
+       if (!dsp) {
+               DEBUG(0, ("Error allocating struct rdp_dir_state.\n"));
+               return NULL;
+       }
+
+       /* Open the directory */
+       ret_dirp = SMB_VFS_NEXT_OPENDIR(handle, fname, mask, attr);
+       if (!ret_dirp) {
+               DEBUG(3, ("Unable to open directory: %s\n", fname));
+               return NULL;
+       }
+
+       /* Initialize the dir_state structure and add it to the list */
+       ret = rdp_init(dsp);
+       if (ret) {
+               DEBUG(0, ("Error initializing readdirplus() buffers: %s\n",
+                   strerror(ret)));
+               return NULL;
+       }
+
+       /* Set the SMB_STRUCT_DIR in the dsp */
+       dsp->dirp = ret_dirp;
+
+       DLIST_ADD(dirstatelist, dsp);
+
+       DEBUG(9, ("Opened handle on directory: \"%s\", DIR %p\n",
+                fname, dsp->dirp));
+
+       return ret_dirp;
+}
+
+/**
+ * Retrieve one direntry and optional stat buffer from our readdir cache.
+ *
+ * Increment the internal resume cookie, and refresh the cache from the
+ * kernel if necessary.
+ *
+ * @param[in] handle vfs handle given in most VFS calls
+ * @param[in] dirp system DIR handle to retrieve direntries from
+ * @param[in/out] sbuf optional stat buffer to fill, this can be NULL
+ *
+ * @return dirent structure, NULL if at the end of the directory, NULL on error
+ */
+SMB_STRUCT_DIRENT *
+onefs_readdir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp,
+             SMB_STRUCT_STAT *sbuf)
+{
+       struct rdp_dir_state *dsp = NULL;
+       SMB_STRUCT_DIRENT *ret_direntp;
+       bool same_as_last;
+       int ret = -1;
+
+       /* Set stat invalid in-case we error out */
+       if (sbuf)
+               SET_STAT_INVALID(*sbuf);
+
+       /* Fallback to default system routines if readdirplus is disabled */
+       if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
+           PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
+       {
+               return sys_readdir(dirp);
+       }
+
+       /* Retrieve state based off DIR handle */
+       ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
+       if (ret) {
+               DEBUG(1, ("Could not retrieve dir_state struct for "
+                        "SMB_STRUCT_DIR pointer.\n"));
+               ret_direntp = NULL;
+               goto end;
+       }
+
+       /* DIR is the same, current buffer and cursors are valid.
+        * Grab the next direntry from our cache. */
+       if (same_as_last) {
+               if ((dsp->direntries_cursor >=
+                   rdp_direntries + RDP_DIRENTRIES_SIZE) ||
+                   (dsp->stat_cursor == dsp->stat_count))
+               {
+                       /* Cache is empty, refill from kernel */
+                       ret = rdp_fill_cache(dsp);
+                       if (ret <= 0) {
+                               ret_direntp = NULL;
+                               goto end;
+                       }
+               }
+       } else {
+               /* DIR is different from last call, reset all buffers and
+                * cursors, and refill the global cache from the new DIR */
+               ret = rdp_fill_cache(dsp);
+               if (ret <= 0) {
+                       ret_direntp = NULL;
+                       goto end;
+               }
+               DEBUG(8, ("Switched global rdp cache to new DIR entry.\n"));
+       }
+
+       /* Return next entry from cache */
+       ret_direntp = ((SMB_STRUCT_DIRENT *)dsp->direntries_cursor);
+       dsp->direntries_cursor +=
+           ((SMB_STRUCT_DIRENT *)dsp->direntries_cursor)->d_reclen;
+       if (sbuf) {
+               *sbuf = rdp_stats[dsp->stat_cursor];
+               /* readdirplus() sets st_ino field to 0, if it was
+                * unable to retrieve stat information for that
+                * particular directory entry. */
+               if (sbuf->st_ino == 0)
+                       SET_STAT_INVALID(*sbuf);
+       }
+
+       DEBUG(9, ("Read from DIR %p, direntry: \"%s\", location: %ld, "
+                "resume cookie: 0x%llx, cache cursor: %zu, cache count: %zu\n",
+                dsp->dirp, ret_direntp->d_name, dsp->location,
+                dsp->resume_cookie, dsp->stat_cursor, dsp->stat_count));
+
+       dsp->resume_cookie = rdp_cookies[dsp->stat_cursor];
+       dsp->stat_cursor++;
+       dsp->location++;
+
+       /* FALLTHROUGH */
+end:
+       /* Set rdp_last_dirp at the end of every VFS call where the cache was
+        * reloaded */
+       rdp_last_dirp = dirp;
+       return ret_direntp;
+}
+
+/**
+ * Set the location of the next direntry to be read via onefs_readdir().
+ *
+ * This function should only pass in locations retrieved from onefs_telldir().
+ *
+ * Ideally the seek point will still be in the readdirplus cache, and we'll
+ * just update our cursors.  If the seek location is outside of the current
+ * cache we must do an expensive re-enumeration of the entire directory up
+ * to the offset.
+ *
+ * @param[in] handle vfs handle given in most VFS calls
+ * @param[in] dirp system DIR handle to set offset on
+ * @param[in] offset from the start of the directory where the next read
+ *           will take place
+ *
+ * @return no return value
+ */
+void
+onefs_seekdir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp, long offset)
+{
+       struct rdp_dir_state *dsp = NULL;
+       bool same_as_last;
+       bool outside_cache = false;
+       int ret = -1, i;
+
+       /* Fallback to default system routines if readdirplus is disabled */
+       if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
+           PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
+       {
+               return sys_seekdir(dirp, offset);
+       }
+
+       /* Validate inputs */
+       if (offset < 0) {
+               DEBUG(1, ("Invalid offset %ld passed.\n", offset));
+               return;
+       }
+
+       /* Retrieve state based off DIR handle */
+       ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
+       if (ret) {
+               DEBUG(1, ("Could not retrieve dir_state struct for "
+                        "SMB_STRUCT_DIR pointer.\n"));
+               /* XXX: we can't return an error, should we ABORT rather than
+                * return without actually seeking? */
+               return;
+       }
+
+       /* Short cut if no work needs to be done */
+       if (offset == dsp->location)
+               return;
+
+       /* If DIR is different from last call, reset all buffers and cursors,
+        * and refill the global cache from the new DIR */
+       if (!same_as_last) {
+               ret = rdp_fill_cache(dsp);
+               if (ret <= 0)
+                       goto out;
+               DEBUG(8, ("Switched global rdp cache to new DIR entry.\n"));
+       }
+
+       /* Check if location is outside the currently cached entries */
+       if (offset < dsp->location - dsp->stat_cursor) {
+               /* offset is before the current cache */
+               /* reset to the beginning of the directory */
+               ret = rdp_init(dsp);
+               if (ret) {
+                       DEBUG(0, ("Error initializing readdirplus() buffers: "
+                                "%s\n", strerror(ret)));
+                       goto out;
+               }
+               outside_cache = true;
+       } else if (offset >
+           dsp->location + (dsp->stat_count - 1 - dsp->stat_cursor))
+       {
+               /* offset is after the current cache
+                * advance the cookie to the end of the cache */
+               dsp->resume_cookie = rdp_cookies[dsp->stat_count - 1];
+               outside_cache = true;
+       }
+
+       if (outside_cache) {
+               /* start reading from the directory, until we have the
+                * specified offset in our cache */
+               do {
+                       dsp->location += dsp->stat_count - dsp->stat_cursor;
+                       ret = rdp_fill_cache(dsp);
+                       if (ret <= 0) {
+                               DEBUG(1, ("Error seeking to offset outside the "
+                                        "cached directory entries. Offset "
+                                        "%ld \n", dsp->location));
+                               goto out;
+                       }
+                       dsp->resume_cookie = rdp_cookies[dsp->stat_count - 1];
+               } while (offset >= dsp->location + dsp->stat_count);
+       }
+
+       /* Location should be within the currently cached entries */
+       if (offset < dsp->location &&
+           offset >= dsp->location - dsp->stat_cursor)
+       {
+               /* offset is within the current cache, before the cursor.
+                * update cursors to the new location */
+               int new_cursor = dsp->stat_cursor - (dsp->location - offset);
+
+               dsp->direntries_cursor = rdp_direntries;
+               for (i=0; i < new_cursor; i++) {
+                       dsp->direntries_cursor +=
+                           ((SMB_STRUCT_DIRENT *)
+                            dsp->direntries_cursor)->d_reclen;
+               }
+               dsp->stat_cursor = new_cursor;
+               dsp->resume_cookie = rdp_cookies[dsp->stat_cursor];
+               dsp->location = offset;
+       } else if (offset >= dsp->location &&
+          offset <= dsp->location + (dsp->stat_count - 1 - dsp->stat_cursor))
+       {
+               /* offset is within the current cache, at or after the cursor.
+                * update cursors to the new location */
+               int add_to_cursor = offset - dsp->location - 1;
+
+               for (i=0; i < add_to_cursor; i++) {
+                       dsp->direntries_cursor +=
+                           ((SMB_STRUCT_DIRENT *)
+                            dsp->direntries_cursor)->d_reclen;
+               }
+               dsp->stat_cursor += add_to_cursor;
+               dsp->resume_cookie = rdp_cookies[dsp->stat_cursor];
+               dsp->location = offset;
+       }
+
+       DEBUG(9, ("Seek DIR %p, location: %ld, cache cursor: %zu\n",
+                dsp->dirp, dsp->location, dsp->stat_cursor));
+
+       /* FALLTHROUGH */
+out:
+       /* Set rdp_last_dirp at the end of every VFS call where the cache was
+        * reloaded */
+       rdp_last_dirp = dirp;
+       return;
+}
+
+/**
+ * Returns the location of the next direntry to be read via onefs_readdir().
+ *
+ * This value can be passed into onefs_seekdir().
+ *
+ * @param[in] handle vfs handle given in most VFS calls
+ * @param[in] dirp system DIR handle to set offset on
+ *
+ * @return offset from the start of the directory where the next read
+ *        will take place
+ */
+long
+onefs_telldir(vfs_handle_struct *handle,  SMB_STRUCT_DIR *dirp)
+{
+       struct rdp_dir_state *dsp = NULL;
+       bool same_as_last;
+       int ret = -1;
+
+       /* Fallback to default system routines if readdirplus is disabled */
+       if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
+           PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
+       {
+               return sys_telldir(dirp);
+       }
+
+       /* Retrieve state based off DIR handle */
+       ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
+       if (ret) {
+               DEBUG(1, ("Could not retrieve dir_state struct for "
+                        "SMB_STRUCT_DIR pointer.\n"));
+               return -1;
+       }
+
+       DEBUG(9, ("Tell DIR %p, location: %ld, cache cursor: %zu\n",
+                dsp->dirp, dsp->location, dsp->stat_cursor));
+
+       return dsp->location;
+}
+
+/**
+ * Set the next direntry to be read via onefs_readdir() to the beginning of the
+ * directory.
+ *
+ * @param[in] handle vfs handle given in most VFS calls
+ * @param[in] dirp system DIR handle to set offset on
+ *
+ * @return no return value
+ */
+void
+onefs_rewinddir(vfs_handle_struct *handle,  SMB_STRUCT_DIR *dirp)
+{
+       struct rdp_dir_state *dsp = NULL;
+       bool same_as_last;
+       int ret = -1;
+
+       /* Fallback to default system routines if readdirplus is disabled */
+       if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
+           PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
+       {
+               return sys_rewinddir(dirp);
+       }
+
+       /* Retrieve state based off DIR handle */
+       ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
+       if (ret) {
+               DEBUG(1, ("Could not retrieve dir_state struct for "
+                        "SMB_STRUCT_DIR pointer.\n"));
+               return;
+       }
+
+       /* Reset location and resume key to beginning */
+       ret = rdp_init(dsp);
+       if (ret) {
+               DEBUG(0, ("Error re-initializing rdp cursors: %s\n",
+                   strerror(ret)));
+               return;
+       }
+
+       DEBUG(9, ("Rewind DIR: %p, to location: %ld\n", dsp->dirp,
+                dsp->location));
+
+       return;
+}
+
+/**
+ * Close DIR pointer and remove all state for that directory open.
+ *
+ * @param[in] handle vfs handle given in most VFS calls
+ * @param[in] dirp system DIR handle to set offset on
+ *
+ * @return -1 on failure, setting errno
+ */
+int
+onefs_closedir(vfs_handle_struct *handle,  SMB_STRUCT_DIR *dirp)
+{
+       struct rdp_dir_state *dsp = NULL;
+       bool same_as_last;
+       int ret_val = -1;
+       int ret = -1;
+
+       /* Fallback to default system routines if readdirplus is disabled */
+       if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
+           PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
+       {
+               return SMB_VFS_NEXT_CLOSEDIR(handle, dirp);
+       }
+
+       /* Retrieve state based off DIR handle */
+       ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
+       if (ret) {
+               DEBUG(1, ("Could not retrieve dir_state struct for "
+                        "SMB_STRUCT_DIR pointer.\n"));
+               errno = ENOENT;
+               return -1;
+       }
+
+       /* Close DIR pointer */
+       ret_val = SMB_VFS_NEXT_CLOSEDIR(handle, dsp->dirp);
+
+       DEBUG(9, ("Closed handle on DIR %p\n", dsp->dirp));
+
+       /* Tear down state struct */
+       DLIST_REMOVE(dirstatelist, dsp);
+       SAFE_FREE(dsp);
+
+       /* Set lastp to NULL, as cache is no longer valid */
+       rdp_last_dirp = NULL;
+
+       return ret_val;
+}
+
+/**
+ * Initialize cache data at the beginning of every SMB search operation
+ *
+ * Since filesystem operations, such as delete files or meta data
+ * updates can occur to files in the directory we're searching
+ * between FIND_FIRST and FIND_NEXT calls we must refresh the cache
+ * from the kernel on every new search SMB.
+ *
+ * @param[in] handle vfs handle given in most VFS calls
+ * @param[in] dirp system DIR handle for the current search
+ *
+ * @return nothing
+ */
+void
+onefs_init_search_op(vfs_handle_struct *handle,  SMB_STRUCT_DIR *dirp)
+{
+       /* Setting the rdp_last_dirp to NULL will cause the next readdir operation
+        * to refill the cache. */
+       rdp_last_dirp = NULL;
+
+       return;
+}
index cb56690dbb8f2c9108d2d5dace3e91bb3e68d753..4b123ab03ed56b858a40935fd189dd8c7d484d43 100644 (file)
@@ -210,7 +210,6 @@ static void vfswrap_init_search_op(vfs_handle_struct *handle,
                                   SMB_STRUCT_DIR *dirp)
 {
        /* Default behavior is a NOOP */
                                   SMB_STRUCT_DIR *dirp)
 {
        /* Default behavior is a NOOP */
-       return;
 }
 
 /* File operations */
 }
 
 /* File operations */
@@ -1456,7 +1455,7 @@ static vfs_op_tuple vfs_default_ops[] = {
         SMB_VFS_LAYER_OPAQUE},
        {SMB_VFS_OP(vfswrap_closedir),  SMB_VFS_OP_CLOSEDIR,
         SMB_VFS_LAYER_OPAQUE},
         SMB_VFS_LAYER_OPAQUE},
        {SMB_VFS_OP(vfswrap_closedir),  SMB_VFS_OP_CLOSEDIR,
         SMB_VFS_LAYER_OPAQUE},
-       {SMB_VFS_OP(vfswrap_init_search_op),    SMB_VFS_OP_INIT_SEARCH_OP,
+       {SMB_VFS_OP(vfswrap_init_search_op), SMB_VFS_OP_INIT_SEARCH_OP,
         SMB_VFS_LAYER_OPAQUE},
 
        /* File operations */
         SMB_VFS_LAYER_OPAQUE},
 
        /* File operations */
index 1f11f691a49a4e4d7531845d144bd2fcd421ee6a..af52c713a835856751ccc08d0d67b8679077e2de 100644 (file)
@@ -91,8 +91,9 @@ static int onefs_load_config(struct vfs_handle_struct *handle)
                if (share_count <= ONEFS_DATA_FASTBUF)
                        pshare_config = share_config;
                else {
                if (share_count <= ONEFS_DATA_FASTBUF)
                        pshare_config = share_config;
                else {
-                       pshare_config = SMB_MALLOC_ARRAY(struct onefs_vfs_config,
-                                                        share_count);
+                       pshare_config =
+                           SMB_MALLOC_ARRAY(struct onefs_vfs_config,
+                                            share_count);
                        if (!pshare_config) {
                                errno = ENOMEM;
                                return -1;
                        if (!pshare_config) {
                                errno = ENOMEM;
                                return -1;
@@ -152,8 +153,9 @@ static int onefs_open(vfs_handle_struct *handle, const char *fname,
        return SMB_VFS_NEXT_OPEN(handle, fname, fsp, flags, mode);
 }
 
        return SMB_VFS_NEXT_OPEN(handle, fname, fsp, flags, mode);
 }
 
-static uint64_t onefs_get_alloc_size(struct vfs_handle_struct *handle,  files_struct *fsp,
-                               const SMB_STRUCT_STAT *sbuf)
+static uint64_t onefs_get_alloc_size(struct vfs_handle_struct *handle,
+                                    files_struct *fsp,
+                                    const SMB_STRUCT_STAT *sbuf)
 {
        uint64_t result;
 
 {
        uint64_t result;
 
@@ -246,8 +248,22 @@ static vfs_op_tuple onefs_ops[] = {
         SMB_VFS_LAYER_TRANSPARENT},
        {SMB_VFS_OP(onefs_fs_capabilities), SMB_VFS_OP_FS_CAPABILITIES,
         SMB_VFS_LAYER_TRANSPARENT},
         SMB_VFS_LAYER_TRANSPARENT},
        {SMB_VFS_OP(onefs_fs_capabilities), SMB_VFS_OP_FS_CAPABILITIES,
         SMB_VFS_LAYER_TRANSPARENT},
+       {SMB_VFS_OP(onefs_opendir), SMB_VFS_OP_OPENDIR,
+        SMB_VFS_LAYER_TRANSPARENT},
+       {SMB_VFS_OP(onefs_readdir), SMB_VFS_OP_READDIR,
+        SMB_VFS_LAYER_OPAQUE},
+       {SMB_VFS_OP(onefs_seekdir), SMB_VFS_OP_SEEKDIR,
+        SMB_VFS_LAYER_OPAQUE},
+       {SMB_VFS_OP(onefs_telldir), SMB_VFS_OP_TELLDIR,
+        SMB_VFS_LAYER_OPAQUE},
+       {SMB_VFS_OP(onefs_rewinddir), SMB_VFS_OP_REWINDDIR,
+        SMB_VFS_LAYER_OPAQUE},
        {SMB_VFS_OP(onefs_mkdir), SMB_VFS_OP_MKDIR,
         SMB_VFS_LAYER_OPAQUE},
        {SMB_VFS_OP(onefs_mkdir), SMB_VFS_OP_MKDIR,
         SMB_VFS_LAYER_OPAQUE},
+       {SMB_VFS_OP(onefs_closedir), SMB_VFS_OP_CLOSEDIR,
+        SMB_VFS_LAYER_TRANSPARENT},
+       {SMB_VFS_OP(onefs_init_search_op), SMB_VFS_OP_INIT_SEARCH_OP,
+        SMB_VFS_LAYER_OPAQUE},
        {SMB_VFS_OP(onefs_open), SMB_VFS_OP_OPEN,
         SMB_VFS_LAYER_OPAQUE},
        {SMB_VFS_OP(onefs_create_file), SMB_VFS_OP_CREATE_FILE,
        {SMB_VFS_OP(onefs_open), SMB_VFS_OP_OPEN,
         SMB_VFS_LAYER_OPAQUE},
        {SMB_VFS_OP(onefs_create_file), SMB_VFS_OP_CREATE_FILE,