2 * Unix SMB/CIFS implementation.
4 * Support for OneFS bulk directory enumeration API
6 * Copyright (C) Steven Danneman, 2009
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, see <http://www.gnu.org/licenses/>.
24 #include "onefs_config.h"
26 #include <ifs/ifs_syscalls.h>
27 #include <isi_util/isi_dir.h>
29 /* The OneFS filesystem provides a readdirplus() syscall, equivalent to the
30 * NFSv3 PDU, which retrieves bulk directory listings with stat information
31 * in a single syscall.
33 * This file hides this bulk interface underneath Samba's very POSIX like
34 * opendir/readdir/telldir VFS interface. This is done to provide a
35 * significant performance improvement when listing the contents of large
36 * directories, which also require file meta information. ie a typical
37 * Windows Explorer request.
40 #define RDP_RESUME_KEY_START 0x1
42 #define RDP_BATCH_SIZE 128
43 #define RDP_DIRENTRIES_SIZE ((size_t)(RDP_BATCH_SIZE * sizeof(struct dirent)))
45 static char *rdp_direntries
= NULL
;
46 static struct stat
*rdp_stats
= NULL
;
47 static uint64_t *rdp_cookies
= NULL
;
49 struct rdp_dir_state
{
50 struct rdp_dir_state
*next
, *prev
;
52 char *direntries_cursor
; /* cursor to last returned direntry in cache */
53 size_t stat_count
; /* number of entries stored in the cache */
54 size_t stat_cursor
; /* cursor to last returned stat in the cache */
55 uint64_t resume_cookie
; /* cookie from the last entry returned from the
59 static struct rdp_dir_state
*dirstatelist
= NULL
;
61 SMB_STRUCT_DIR
*rdp_last_dirp
= NULL
;
64 * Given a DIR pointer, return our internal state.
66 * This function also tells us whether the given DIR is the same as we saw
67 * during the last call. Because we use a single globally allocated buffer
68 * for readdirplus entries we must check every call into this API to see if
69 * it's for the same directory listing, or a new one. If it's the same we can
70 * maintain our current cached entries, otherwise we must go to the kernel.
72 * @return 0 on success, 1 on failure
75 rdp_retrieve_dir_state(SMB_STRUCT_DIR
*dirp
, struct rdp_dir_state
**dir_state
,
78 struct rdp_dir_state
*dsp
;
80 /* Is this directory the same as the last call */
81 *same_as_last
= (dirp
== rdp_last_dirp
);
83 for(dsp
= dirstatelist
; dsp
; dsp
= dsp
->next
)
84 if (dsp
->dirp
== dirp
) {
89 /* Couldn't find existing dir_state for the given directory
95 * Initialize the global readdirplus buffers.
97 * These same buffers are used for all calls into readdirplus.
99 * @return 0 on success, errno value on failure
102 rdp_init(struct rdp_dir_state
*dsp
)
104 /* Unfortunately, there is no good way to free these buffers. If we
105 * allocated and freed for every DIR handle performance would be
106 * adversely affected. For now these buffers will be leaked and only
107 * freed when the smbd process dies. */
108 if (!rdp_direntries
) {
109 rdp_direntries
= SMB_MALLOC(RDP_DIRENTRIES_SIZE
);
116 SMB_MALLOC(RDP_BATCH_SIZE
* sizeof(struct stat
));
122 rdp_cookies
= SMB_MALLOC(RDP_BATCH_SIZE
* sizeof(uint64_t));
127 dsp
->direntries_cursor
= rdp_direntries
+ RDP_DIRENTRIES_SIZE
;
128 dsp
->stat_count
= RDP_BATCH_SIZE
;
129 dsp
->stat_cursor
= RDP_BATCH_SIZE
;
130 dsp
->resume_cookie
= RDP_RESUME_KEY_START
;
136 * Call into readdirplus() to refill our global dirent cache.
138 * This function also resets all cursors back to the beginning of the cache.
139 * All stat buffers are retrieved by following symlinks.
141 * @return number of entries retrieved, -1 on error
144 rdp_fill_cache(struct rdp_dir_state
*dsp
)
148 dirfd
= dirfd(dsp
->dirp
);
150 DEBUG(1, ("Could not retrieve fd for DIR\n"));
154 /* Resize the stat_count to grab as many entries as possible */
155 dsp
->stat_count
= RDP_BATCH_SIZE
;
157 DEBUG(9, ("Calling readdirplus() with DIR %p, dirfd: %d, "
158 "resume_cookie %#llx, size_to_read: %zu, "
159 "direntries_size: %zu, stat_count: %u\n",
160 dsp
->dirp
, dirfd
, dsp
->resume_cookie
, RDP_BATCH_SIZE
,
161 RDP_DIRENTRIES_SIZE
, dsp
->stat_count
));
163 nread
= readdirplus(dirfd
,
173 DEBUG(1, ("Error calling readdirplus(): %s\n",
178 DEBUG(9, ("readdirplus() returned %u entries from DIR %p\n",
179 dsp
->stat_count
, dsp
->dirp
));
181 dsp
->direntries_cursor
= rdp_direntries
;
182 dsp
->stat_cursor
= 0;
188 * Create a dir_state to track an open directory that we're enumerating.
190 * This utility function is globally accessible for use by other parts of the
191 * onefs.so module to initialize a dir_state when a directory is opened through
192 * a path other than the VFS layer.
194 * @return 0 on success and errno on failure
196 * @note: Callers of this function MUST cleanup the dir_state through a proper
197 * call to VFS_CLOSEDIR().
200 onefs_rdp_add_dir_state(connection_struct
*conn
, SMB_STRUCT_DIR
*dirp
)
203 struct rdp_dir_state
*dsp
= NULL
;
205 /* No-op if readdirplus is disabled */
206 if (!lp_parm_bool(SNUM(conn
), PARM_ONEFS_TYPE
,
207 PARM_USE_READDIRPLUS
, PARM_USE_READDIRPLUS_DEFAULT
))
212 /* Create a struct dir_state */
213 dsp
= SMB_MALLOC_P(struct rdp_dir_state
);
215 DEBUG(0, ("Error allocating struct rdp_dir_state.\n"));
219 /* Initialize the dir_state structure and add it to the list */
222 DEBUG(0, ("Error initializing readdirplus() buffers: %s\n",
227 /* Set the SMB_STRUCT_DIR in the dsp */
230 DLIST_ADD(dirstatelist
, dsp
);
236 * Open a directory for enumeration.
238 * Create a state struct to track the state of this directory for the life
241 * @param[in] handle vfs handle given in most VFS calls
242 * @param[in] fname filename of the directory to open
243 * @param[in] mask unused
244 * @param[in] attr unused
246 * @return DIR pointer, NULL if directory does not exist, NULL on error
249 onefs_opendir(vfs_handle_struct
*handle
, const char *fname
, const char *mask
,
253 SMB_STRUCT_DIR
*ret_dirp
;
255 /* Fallback to default system routines if readdirplus is disabled */
256 if (!lp_parm_bool(SNUM(handle
->conn
), PARM_ONEFS_TYPE
,
257 PARM_USE_READDIRPLUS
, PARM_USE_READDIRPLUS_DEFAULT
))
259 return SMB_VFS_NEXT_OPENDIR(handle
, fname
, mask
, attr
);
262 /* Open the directory */
263 ret_dirp
= SMB_VFS_NEXT_OPENDIR(handle
, fname
, mask
, attr
);
265 DEBUG(3, ("Unable to open directory: %s\n", fname
));
269 /* Create the dir_state struct and add it to the list */
270 ret
= onefs_rdp_add_dir_state(handle
->conn
, ret_dirp
);
272 DEBUG(0, ("Error adding dir_state to the list\n"));
276 DEBUG(9, ("Opened handle on directory: \"%s\", DIR %p\n",
283 * Retrieve one direntry and optional stat buffer from our readdir cache.
285 * Increment the internal resume cookie, and refresh the cache from the
286 * kernel if necessary.
288 * The cache cursor tracks the last entry which was successfully returned
289 * to a caller of onefs_readdir(). When a new entry is requested, this
290 * function first increments the cursor, then returns that entry.
292 * @param[in] handle vfs handle given in most VFS calls
293 * @param[in] dirp system DIR handle to retrieve direntries from
294 * @param[in/out] sbuf optional stat buffer to fill, this can be NULL
296 * @return dirent structure, NULL if at the end of the directory, NULL on error
299 onefs_readdir(vfs_handle_struct
*handle
, SMB_STRUCT_DIR
*dirp
,
300 SMB_STRUCT_STAT
*sbuf
)
302 struct rdp_dir_state
*dsp
= NULL
;
303 SMB_STRUCT_DIRENT
*ret_direntp
;
304 bool same_as_last
, filled_cache
= false;
307 /* Set stat invalid in-case we error out */
309 SET_STAT_INVALID(*sbuf
);
311 /* Fallback to default system routines if readdirplus is disabled */
312 if (!lp_parm_bool(SNUM(handle
->conn
), PARM_ONEFS_TYPE
,
313 PARM_USE_READDIRPLUS
, PARM_USE_READDIRPLUS_DEFAULT
))
315 return sys_readdir(dirp
);
318 /* Retrieve state based off DIR handle */
319 ret
= rdp_retrieve_dir_state(dirp
, &dsp
, &same_as_last
);
321 DEBUG(1, ("Could not retrieve dir_state struct for "
322 "SMB_STRUCT_DIR pointer.\n"));
327 /* DIR is the same, current buffer and cursors are valid.
328 * Check if there are any entries left in our current cache. */
330 if (dsp
->stat_cursor
== dsp
->stat_count
- 1) {
331 /* Cache is empty, refill from kernel */
332 ret
= rdp_fill_cache(dsp
);
340 /* DIR is different from last call, reset all buffers and
341 * cursors, and refill the global cache from the new DIR */
342 ret
= rdp_fill_cache(dsp
);
348 DEBUG(8, ("Switched global rdp cache to new DIR entry.\n"));
351 /* If we just filled the cache we treat that action as the cursor
352 * increment as the resume cookie used belonged to the previous
353 * directory entry. If the cache has not changed we first increment
354 * our cursor, then return the next entry */
356 dsp
->direntries_cursor
+=
357 ((SMB_STRUCT_DIRENT
*)dsp
->direntries_cursor
)->d_reclen
;
361 /* The resume_cookie stored here purposely differs based on whether we
362 * just filled the cache. The resume cookie stored must always provide
363 * the next direntry, in case the cache is reloaded on every
365 dsp
->resume_cookie
= rdp_cookies
[dsp
->stat_cursor
];
367 /* Return an entry from cache */
368 ret_direntp
= ((SMB_STRUCT_DIRENT
*)dsp
->direntries_cursor
);
370 struct stat onefs_sbuf
;
372 onefs_sbuf
= rdp_stats
[dsp
->stat_cursor
];
373 init_stat_ex_from_onefs_stat(sbuf
, &onefs_sbuf
);
375 /* readdirplus() sets st_ino field to 0, if it was
376 * unable to retrieve stat information for that
377 * particular directory entry. */
378 if (sbuf
->st_ex_ino
== 0)
379 SET_STAT_INVALID(*sbuf
);
382 DEBUG(9, ("Read from DIR %p, direntry: \"%s\", resume cookie: %#llx, "
383 "cache cursor: %zu, cache count: %zu\n",
384 dsp
->dirp
, ret_direntp
->d_name
, dsp
->resume_cookie
,
385 dsp
->stat_cursor
, dsp
->stat_count
));
389 /* Set rdp_last_dirp at the end of every VFS call where the cache was
391 rdp_last_dirp
= dirp
;
396 * Set the location of the next direntry to be read via onefs_readdir().
398 * This function should only pass in locations retrieved from onefs_telldir().
400 * @param[in] handle vfs handle given in most VFS calls
401 * @param[in] dirp system DIR handle to set offset on
402 * @param[in] offset into the directory to resume reading from
404 * @return no return value
407 onefs_seekdir(vfs_handle_struct
*handle
, SMB_STRUCT_DIR
*dirp
, long offset
)
409 struct rdp_dir_state
*dsp
= NULL
;
411 uint64_t resume_cookie
= 0;
414 /* Fallback to default system routines if readdirplus is disabled */
415 if (!lp_parm_bool(SNUM(handle
->conn
), PARM_ONEFS_TYPE
,
416 PARM_USE_READDIRPLUS
, PARM_USE_READDIRPLUS_DEFAULT
))
418 return sys_seekdir(dirp
, offset
);
421 /* Validate inputs */
423 DEBUG(1, ("Invalid offset %ld passed.\n", offset
));
427 /* Retrieve state based off DIR handle */
428 ret
= rdp_retrieve_dir_state(dirp
, &dsp
, &same_as_last
);
430 DEBUG(1, ("Could not retrieve dir_state struct for "
431 "SMB_STRUCT_DIR pointer.\n"));
432 /* XXX: we can't return an error, should we ABORT rather than
433 * return without actually seeking? */
437 /* Convert offset to resume_cookie */
438 resume_cookie
= rdp_offset31_to_cookie63(offset
);
440 DEBUG(9, ("Seek DIR %p, offset: %ld, resume_cookie: %#llx\n",
441 dsp
->dirp
, offset
, resume_cookie
));
443 /* TODO: We could check if the resume_cookie is already in the cache
444 * through a linear search. This would allow us to avoid the cost of
445 * flushing the cache. Frequently, the seekdir offset will only be
446 * one entry before the current cache cursor. However, usually
447 * VFS_SEEKDIR() is only called at the end of a TRAND2_FIND read and
448 * we'll flush the cache at the beginning of the next PDU anyway. Some
449 * analysis should be done to see if this enhancement would provide
450 * better performance. */
452 /* Set the resume cookie and indicate that the cache should be reloaded
454 dsp
->resume_cookie
= resume_cookie
;
455 rdp_last_dirp
= NULL
;
461 * Returns the location of the next direntry to be read via onefs_readdir().
463 * This value can be passed into onefs_seekdir().
465 * @param[in] handle vfs handle given in most VFS calls
466 * @param[in] dirp system DIR handle to set offset on
468 * @return offset into the directory to resume reading from
471 onefs_telldir(vfs_handle_struct
*handle
, SMB_STRUCT_DIR
*dirp
)
473 struct rdp_dir_state
*dsp
= NULL
;
478 /* Fallback to default system routines if readdirplus is disabled */
479 if (!lp_parm_bool(SNUM(handle
->conn
), PARM_ONEFS_TYPE
,
480 PARM_USE_READDIRPLUS
, PARM_USE_READDIRPLUS_DEFAULT
))
482 return sys_telldir(dirp
);
485 /* Retrieve state based off DIR handle */
486 ret
= rdp_retrieve_dir_state(dirp
, &dsp
, &same_as_last
);
488 DEBUG(1, ("Could not retrieve dir_state struct for "
489 "SMB_STRUCT_DIR pointer.\n"));
493 /* Convert resume_cookie to offset */
494 offset
= rdp_cookie63_to_offset31(dsp
->resume_cookie
);
496 DEBUG(1, ("Unable to convert resume_cookie: %#llx to a "
497 "suitable 32-bit offset value. Error: %s\n",
498 dsp
->resume_cookie
, strerror(errno
)));
502 DEBUG(9, ("Seek DIR %p, offset: %ld, resume_cookie: %#llx\n",
503 dsp
->dirp
, offset
, dsp
->resume_cookie
));
509 * Set the next direntry to be read via onefs_readdir() to the beginning of the
512 * @param[in] handle vfs handle given in most VFS calls
513 * @param[in] dirp system DIR handle to set offset on
515 * @return no return value
518 onefs_rewinddir(vfs_handle_struct
*handle
, SMB_STRUCT_DIR
*dirp
)
520 struct rdp_dir_state
*dsp
= NULL
;
524 /* Fallback to default system routines if readdirplus is disabled */
525 if (!lp_parm_bool(SNUM(handle
->conn
), PARM_ONEFS_TYPE
,
526 PARM_USE_READDIRPLUS
, PARM_USE_READDIRPLUS_DEFAULT
))
528 return sys_rewinddir(dirp
);
531 /* Retrieve state based off DIR handle */
532 ret
= rdp_retrieve_dir_state(dirp
, &dsp
, &same_as_last
);
534 DEBUG(1, ("Could not retrieve dir_state struct for "
535 "SMB_STRUCT_DIR pointer.\n"));
539 /* Reset location and resume key to beginning */
542 DEBUG(0, ("Error re-initializing rdp cursors: %s\n",
547 DEBUG(9, ("Rewind DIR: %p, to resume_cookie: %#llx\n", dsp
->dirp
,
548 dsp
->resume_cookie
));
554 * Close DIR pointer and remove all state for that directory open.
556 * @param[in] handle vfs handle given in most VFS calls
557 * @param[in] dirp system DIR handle to set offset on
559 * @return -1 on failure, setting errno
562 onefs_closedir(vfs_handle_struct
*handle
, SMB_STRUCT_DIR
*dirp
)
564 struct rdp_dir_state
*dsp
= NULL
;
569 /* Fallback to default system routines if readdirplus is disabled */
570 if (!lp_parm_bool(SNUM(handle
->conn
), PARM_ONEFS_TYPE
,
571 PARM_USE_READDIRPLUS
, PARM_USE_READDIRPLUS_DEFAULT
))
573 return SMB_VFS_NEXT_CLOSEDIR(handle
, dirp
);
576 /* Retrieve state based off DIR handle */
577 ret
= rdp_retrieve_dir_state(dirp
, &dsp
, &same_as_last
);
579 DEBUG(1, ("Could not retrieve dir_state struct for "
580 "SMB_STRUCT_DIR pointer.\n"));
585 /* Close DIR pointer */
586 ret_val
= SMB_VFS_NEXT_CLOSEDIR(handle
, dsp
->dirp
);
588 DEBUG(9, ("Closed handle on DIR %p\n", dsp
->dirp
));
590 /* Tear down state struct */
591 DLIST_REMOVE(dirstatelist
, dsp
);
594 /* Set lastp to NULL, as cache is no longer valid */
595 rdp_last_dirp
= NULL
;
601 * Initialize cache data at the beginning of every SMB search operation
603 * Since filesystem operations, such as delete files or meta data
604 * updates can occur to files in the directory we're searching
605 * between FIND_FIRST and FIND_NEXT calls we must refresh the cache
606 * from the kernel on every new search SMB.
608 * @param[in] handle vfs handle given in most VFS calls
609 * @param[in] dirp system DIR handle for the current search
614 onefs_init_search_op(vfs_handle_struct
*handle
, SMB_STRUCT_DIR
*dirp
)
616 /* Setting the rdp_last_dirp to NULL will cause the next readdir
617 * operation to refill the cache. */
618 rdp_last_dirp
= NULL
;