* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
+#include "includes.h"
+#include "smbd/smbd.h"
#include "onefs.h"
+#include "onefs_config.h"
#include <ifs/ifs_syscalls.h>
+#include <isi_util/isi_dir.h>
/* The OneFS filesystem provides a readdirplus() syscall, equivalent to the
* NFSv3 PDU, which retrieves bulk directory listings with stat information
#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 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 */
+ DIR *dirp;
+ char *direntries_cursor; /* cursor to last returned direntry in 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 */
+ size_t stat_cursor; /* cursor to last returned stat in the cache */
+ uint64_t resume_cookie; /* cookie from the last entry returned from the
+ cache */
};
static struct rdp_dir_state *dirstatelist = NULL;
-SMB_STRUCT_DIR *rdp_last_dirp = NULL;
+DIR *rdp_last_dirp = NULL;
/**
* Given a DIR pointer, return our internal state.
* @return 0 on success, 1 on failure
*/
static int
-rdp_retrieve_dir_state(SMB_STRUCT_DIR *dirp, struct rdp_dir_state **dir_state,
+rdp_retrieve_dir_state(DIR *dirp, struct rdp_dir_state **dir_state,
bool *same_as_last)
{
struct rdp_dir_state *dsp;
if (!rdp_stats) {
rdp_stats =
- SMB_MALLOC(RDP_BATCH_SIZE * sizeof(SMB_STRUCT_STAT));
+ SMB_MALLOC(RDP_BATCH_SIZE * sizeof(struct stat));
if (!rdp_stats)
return ENOMEM;
}
dsp->stat_count = RDP_BATCH_SIZE;
dsp->stat_cursor = RDP_BATCH_SIZE;
dsp->resume_cookie = RDP_RESUME_KEY_START;
- dsp->location = 0;
return 0;
}
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, "
+ "resume_cookie %#llx, 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));
+ dsp->dirp, dirfd, dsp->resume_cookie, RDP_BATCH_SIZE,
+ RDP_DIRENTRIES_SIZE, dsp->stat_count));
nread = readdirplus(dirfd,
RDP_FOLLOW,
return nread;
}
+/**
+ * Create a dir_state to track an open directory that we're enumerating.
+ *
+ * This utility function is globally accessible for use by other parts of the
+ * onefs.so module to initialize a dir_state when a directory is opened through
+ * a path other than the VFS layer.
+ *
+ * @return 0 on success and errno on failure
+ *
+ * @note: Callers of this function MUST cleanup the dir_state through a proper
+ * call to VFS_CLOSEDIR().
+ */
+int
+onefs_rdp_add_dir_state(connection_struct *conn, DIR *dirp)
+{
+ int ret = 0;
+ struct rdp_dir_state *dsp = NULL;
+
+ /* No-op if readdirplus is disabled */
+ if (!lp_parm_bool(SNUM(conn), PARM_ONEFS_TYPE,
+ PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
+ {
+ return 0;
+ }
+
+ /* 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 ENOMEM;
+ }
+
+ /* 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 ret;
+ }
+
+ /* Set the DIR in the dsp */
+ dsp->dirp = dirp;
+
+ DLIST_ADD(dirstatelist, dsp);
+
+ return 0;
+}
+
/**
* Open a directory for enumeration.
*
*
* @return DIR pointer, NULL if directory does not exist, NULL on error
*/
-SMB_STRUCT_DIR *
+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;
+ DIR *ret_dirp;
/* Fallback to default system routines if readdirplus is disabled */
if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
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) {
return NULL;
}
- /* Initialize the dir_state structure and add it to the list */
- ret = rdp_init(dsp);
+ /* Create the dir_state struct and add it to the list */
+ ret = onefs_rdp_add_dir_state(handle->conn, ret_dirp);
if (ret) {
- DEBUG(0, ("Error initializing readdirplus() buffers: %s\n",
- strerror(ret)));
+ DEBUG(0, ("Error adding dir_state to the list\n"));
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));
+ fname, ret_dirp));
return ret_dirp;
}
* Increment the internal resume cookie, and refresh the cache from the
* kernel if necessary.
*
+ * The cache cursor tracks the last entry which was successfully returned
+ * to a caller of onefs_readdir(). When a new entry is requested, this
+ * function first increments the cursor, then returns that entry.
+ *
* @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,
+struct dirent *
+onefs_readdir(vfs_handle_struct *handle, DIR *dirp,
SMB_STRUCT_STAT *sbuf)
{
struct rdp_dir_state *dsp = NULL;
- SMB_STRUCT_DIRENT *ret_direntp;
- bool same_as_last;
+ struct dirent *ret_direntp;
+ bool same_as_last, filled_cache = false;
int ret = -1;
/* Set stat invalid in-case we error out */
if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
{
- return sys_readdir(dirp);
+ return 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"));
+ "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. */
+ * Check if there are any entries left in our current cache. */
if (same_as_last) {
- if ((dsp->direntries_cursor >=
- rdp_direntries + RDP_DIRENTRIES_SIZE) ||
- (dsp->stat_cursor == dsp->stat_count))
- {
+ if (dsp->stat_cursor == dsp->stat_count - 1) {
/* Cache is empty, refill from kernel */
ret = rdp_fill_cache(dsp);
if (ret <= 0) {
ret_direntp = NULL;
goto end;
}
+ filled_cache = true;
}
} else {
/* DIR is different from last call, reset all buffers and
ret_direntp = NULL;
goto end;
}
+ filled_cache = true;
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 we just filled the cache we treat that action as the cursor
+ * increment as the resume cookie used belonged to the previous
+ * directory entry. If the cache has not changed we first increment
+ * our cursor, then return the next entry */
+ if (!filled_cache) {
+ dsp->direntries_cursor +=
+ ((struct dirent *)dsp->direntries_cursor)->d_reclen;
+ dsp->stat_cursor++;
+ }
+
+ /* The resume_cookie stored here purposely differs based on whether we
+ * just filled the cache. The resume cookie stored must always provide
+ * the next direntry, in case the cache is reloaded on every
+ * onefs_readdir() */
+ dsp->resume_cookie = rdp_cookies[dsp->stat_cursor];
+
+ /* Return an entry from cache */
+ ret_direntp = ((struct dirent *)dsp->direntries_cursor);
if (sbuf) {
- *sbuf = rdp_stats[dsp->stat_cursor];
+ struct stat onefs_sbuf;
+
+ onefs_sbuf = rdp_stats[dsp->stat_cursor];
+ init_stat_ex_from_onefs_stat(sbuf, &onefs_sbuf);
+
/* 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)
+ if (sbuf->st_ex_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++;
+ DEBUG(9, ("Read from DIR %p, direntry: \"%s\", resume cookie: %#llx, "
+ "cache cursor: %zu, cache count: %zu\n",
+ dsp->dirp, ret_direntp->d_name, dsp->resume_cookie,
+ dsp->stat_cursor, dsp->stat_count));
/* FALLTHROUGH */
end:
*
* 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
+ * @param[in] offset into the directory to resume reading from
*
* @return no return value
*/
void
-onefs_seekdir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp, long offset)
+onefs_seekdir(vfs_handle_struct *handle, DIR *dirp, long offset)
{
struct rdp_dir_state *dsp = NULL;
bool same_as_last;
- bool outside_cache = false;
- int ret = -1, i;
+ uint64_t resume_cookie = 0;
+ 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_seekdir(dirp, offset);
+ return seekdir(dirp, offset);
}
/* Validate inputs */
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"));
+ "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;
+ /* Convert offset to resume_cookie */
+ resume_cookie = rdp_offset31_to_cookie63(offset);
- /* 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"));
- }
+ DEBUG(9, ("Seek DIR %p, offset: %ld, resume_cookie: %#llx\n",
+ dsp->dirp, offset, resume_cookie));
- /* 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);
- }
+ /* TODO: We could check if the resume_cookie is already in the cache
+ * through a linear search. This would allow us to avoid the cost of
+ * flushing the cache. Frequently, the seekdir offset will only be
+ * one entry before the current cache cursor. However, usually
+ * VFS_SEEKDIR() is only called at the end of a TRAND2_FIND read and
+ * we'll flush the cache at the beginning of the next PDU anyway. Some
+ * analysis should be done to see if this enhancement would provide
+ * better performance. */
- /* 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));
+ /* Set the resume cookie and indicate that the cache should be reloaded
+ * on next read */
+ dsp->resume_cookie = resume_cookie;
+ rdp_last_dirp = NULL;
- /* FALLTHROUGH */
-out:
- /* Set rdp_last_dirp at the end of every VFS call where the cache was
- * reloaded */
- rdp_last_dirp = dirp;
return;
}
* @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
+ * @return offset into the directory to resume reading from
*/
long
-onefs_telldir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp)
+onefs_telldir(vfs_handle_struct *handle, DIR *dirp)
{
struct rdp_dir_state *dsp = NULL;
bool same_as_last;
+ long offset;
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);
+ return 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"));
+ "DIR pointer.\n"));
+ return -1;
+ }
+
+ /* Convert resume_cookie to offset */
+ offset = rdp_cookie63_to_offset31(dsp->resume_cookie);
+ if (offset < 0) {
+ DEBUG(1, ("Unable to convert resume_cookie: %#llx to a "
+ "suitable 32-bit offset value. Error: %s\n",
+ dsp->resume_cookie, strerror(errno)));
return -1;
}
- DEBUG(9, ("Tell DIR %p, location: %ld, cache cursor: %zu\n",
- dsp->dirp, dsp->location, dsp->stat_cursor));
+ DEBUG(9, ("Seek DIR %p, offset: %ld, resume_cookie: %#llx\n",
+ dsp->dirp, offset, dsp->resume_cookie));
- return dsp->location;
+ return offset;
}
/**
* @return no return value
*/
void
-onefs_rewinddir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp)
+onefs_rewinddir(vfs_handle_struct *handle, DIR *dirp)
{
struct rdp_dir_state *dsp = NULL;
bool same_as_last;
if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
{
- return sys_rewinddir(dirp);
+ return 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"));
+ "DIR pointer.\n"));
return;
}
return;
}
- DEBUG(9, ("Rewind DIR: %p, to location: %ld\n", dsp->dirp,
- dsp->location));
+ DEBUG(9, ("Rewind DIR: %p, to resume_cookie: %#llx\n", dsp->dirp,
+ dsp->resume_cookie));
return;
}
* @return -1 on failure, setting errno
*/
int
-onefs_closedir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp)
+onefs_closedir(vfs_handle_struct *handle, DIR *dirp)
{
struct rdp_dir_state *dsp = NULL;
bool same_as_last;
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"));
+ "DIR pointer.\n"));
errno = ENOENT;
return -1;
}
* @return nothing
*/
void
-onefs_init_search_op(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp)
+onefs_init_search_op(vfs_handle_struct *handle, DIR *dirp)
{
- /* Setting the rdp_last_dirp to NULL will cause the next readdir operation
- * to refill the cache. */
+ /* Setting the rdp_last_dirp to NULL will cause the next readdir
+ * operation to refill the cache. */
rdp_last_dirp = NULL;
return;