s3-net: use rpccli_spoolss_enumprinterdataex.
[Samba.git] / source3 / modules / onefs_dir.c
blob68a58b3bb256a9d32f9ad1d8596150ab15c874d3
1 /*
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/>.
22 #include "includes.h"
23 #include "onefs.h"
24 #include "onefs_config.h"
26 #include <ifs/ifs_syscalls.h>
28 /* The OneFS filesystem provides a readdirplus() syscall, equivalent to the
29 * NFSv3 PDU, which retrieves bulk directory listings with stat information
30 * in a single syscall.
32 * This file hides this bulk interface underneath Samba's very POSIX like
33 * opendir/readdir/telldir VFS interface. This is done to provide a
34 * significant performance improvement when listing the contents of large
35 * directories, which also require file meta information. ie a typical
36 * Windows Explorer request.
39 #define RDP_RESUME_KEY_START 0x1
41 #define RDP_BATCH_SIZE 128
42 #define RDP_DIRENTRIES_SIZE ((size_t)(RDP_BATCH_SIZE * sizeof(struct dirent)))
44 static char *rdp_direntries = NULL;
45 static SMB_STRUCT_STAT *rdp_stats = NULL;
46 static uint64_t *rdp_cookies = NULL;
48 struct rdp_dir_state {
49 struct rdp_dir_state *next, *prev;
50 SMB_STRUCT_DIR *dirp;
51 char *direntries_cursor; /* cursor to current direntry in the cache */
52 size_t stat_count; /* number of entries stored in the cache */
53 size_t stat_cursor; /* cursor to current stat in the cache */
54 uint64_t resume_cookie; /* last cookie returned from the cache */
55 long location; /* absolute location of direnty in DIR */
58 static struct rdp_dir_state *dirstatelist = NULL;
60 SMB_STRUCT_DIR *rdp_last_dirp = NULL;
62 /**
63 * Given a DIR pointer, return our internal state.
65 * This function also tells us whether the given DIR is the same as we saw
66 * during the last call. Because we use a single globally allocated buffer
67 * for readdirplus entries we must check every call into this API to see if
68 * it's for the same directory listing, or a new one. If it's the same we can
69 * maintain our current cached entries, otherwise we must go to the kernel.
71 * @return 0 on success, 1 on failure
73 static int
74 rdp_retrieve_dir_state(SMB_STRUCT_DIR *dirp, struct rdp_dir_state **dir_state,
75 bool *same_as_last)
77 struct rdp_dir_state *dsp;
79 /* Is this directory the same as the last call */
80 *same_as_last = (dirp == rdp_last_dirp);
82 for(dsp = dirstatelist; dsp; dsp = dsp->next)
83 if (dsp->dirp == dirp) {
84 *dir_state = dsp;
85 return 0;
88 /* Couldn't find existing dir_state for the given directory
89 * pointer. */
90 return 1;
93 /**
94 * Initialize the global readdirplus buffers.
96 * These same buffers are used for all calls into readdirplus.
98 * @return 0 on success, errno value on failure
100 static int
101 rdp_init(struct rdp_dir_state *dsp)
103 /* Unfortunately, there is no good way to free these buffers. If we
104 * allocated and freed for every DIR handle performance would be
105 * adversely affected. For now these buffers will be leaked and only
106 * freed when the smbd process dies. */
107 if (!rdp_direntries) {
108 rdp_direntries = SMB_MALLOC(RDP_DIRENTRIES_SIZE);
109 if (!rdp_direntries)
110 return ENOMEM;
113 if (!rdp_stats) {
114 rdp_stats =
115 SMB_MALLOC(RDP_BATCH_SIZE * sizeof(SMB_STRUCT_STAT));
116 if (!rdp_stats)
117 return ENOMEM;
120 if (!rdp_cookies) {
121 rdp_cookies = SMB_MALLOC(RDP_BATCH_SIZE * sizeof(uint64_t));
122 if (!rdp_cookies)
123 return ENOMEM;
126 dsp->direntries_cursor = rdp_direntries + RDP_DIRENTRIES_SIZE;
127 dsp->stat_count = RDP_BATCH_SIZE;
128 dsp->stat_cursor = RDP_BATCH_SIZE;
129 dsp->resume_cookie = RDP_RESUME_KEY_START;
130 dsp->location = 0;
132 return 0;
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
143 static int
144 rdp_fill_cache(struct rdp_dir_state *dsp)
146 int nread, dirfd;
148 dirfd = dirfd(dsp->dirp);
149 if (dirfd < 0) {
150 DEBUG(1, ("Could not retrieve fd for DIR\n"));
151 return -1;
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 0x%llx, location %u, size_to_read: %zu, "
159 "direntries_size: %zu, stat_count: %u\n",
160 dsp->dirp, dirfd, dsp->resume_cookie, dsp->location,
161 RDP_BATCH_SIZE, RDP_DIRENTRIES_SIZE, dsp->stat_count));
163 nread = readdirplus(dirfd,
164 RDP_FOLLOW,
165 &dsp->resume_cookie,
166 RDP_BATCH_SIZE,
167 rdp_direntries,
168 RDP_DIRENTRIES_SIZE,
169 &dsp->stat_count,
170 rdp_stats,
171 rdp_cookies);
172 if (nread < 0) {
173 DEBUG(1, ("Error calling readdirplus(): %s\n",
174 strerror(errno)));
175 return -1;
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;
184 return nread;
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)
202 int ret = 0;
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))
209 return 0;
212 /* Create a struct dir_state */
213 dsp = SMB_MALLOC_P(struct rdp_dir_state);
214 if (!dsp) {
215 DEBUG(0, ("Error allocating struct rdp_dir_state.\n"));
216 return ENOMEM;
219 /* Initialize the dir_state structure and add it to the list */
220 ret = rdp_init(dsp);
221 if (ret) {
222 DEBUG(0, ("Error initializing readdirplus() buffers: %s\n",
223 strerror(ret)));
224 return ret;
227 /* Set the SMB_STRUCT_DIR in the dsp */
228 dsp->dirp = dirp;
230 DLIST_ADD(dirstatelist, dsp);
232 return 0;
236 * Open a directory for enumeration.
238 * Create a state struct to track the state of this directory for the life
239 * of this open.
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
248 SMB_STRUCT_DIR *
249 onefs_opendir(vfs_handle_struct *handle, const char *fname, const char *mask,
250 uint32 attr)
252 int ret = 0;
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);
264 if (!ret_dirp) {
265 DEBUG(3, ("Unable to open directory: %s\n", fname));
266 return NULL;
269 /* Create the dir_state struct and add it to the list */
270 ret = onefs_rdp_add_dir_state(handle->conn, ret_dirp);
271 if (ret) {
272 DEBUG(0, ("Error adding dir_state to the list\n"));
273 return NULL;
276 DEBUG(9, ("Opened handle on directory: \"%s\", DIR %p\n",
277 fname, ret_dirp));
279 return ret_dirp;
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 * @param[in] handle vfs handle given in most VFS calls
289 * @param[in] dirp system DIR handle to retrieve direntries from
290 * @param[in/out] sbuf optional stat buffer to fill, this can be NULL
292 * @return dirent structure, NULL if at the end of the directory, NULL on error
294 SMB_STRUCT_DIRENT *
295 onefs_readdir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp,
296 SMB_STRUCT_STAT *sbuf)
298 struct rdp_dir_state *dsp = NULL;
299 SMB_STRUCT_DIRENT *ret_direntp;
300 bool same_as_last;
301 int ret = -1;
303 /* Set stat invalid in-case we error out */
304 if (sbuf)
305 SET_STAT_INVALID(*sbuf);
307 /* Fallback to default system routines if readdirplus is disabled */
308 if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
309 PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
311 return sys_readdir(dirp);
314 /* Retrieve state based off DIR handle */
315 ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
316 if (ret) {
317 DEBUG(1, ("Could not retrieve dir_state struct for "
318 "SMB_STRUCT_DIR pointer.\n"));
319 ret_direntp = NULL;
320 goto end;
323 /* DIR is the same, current buffer and cursors are valid.
324 * Grab the next direntry from our cache. */
325 if (same_as_last) {
326 if ((dsp->direntries_cursor >=
327 rdp_direntries + RDP_DIRENTRIES_SIZE) ||
328 (dsp->stat_cursor == dsp->stat_count))
330 /* Cache is empty, refill from kernel */
331 ret = rdp_fill_cache(dsp);
332 if (ret <= 0) {
333 ret_direntp = NULL;
334 goto end;
337 } else {
338 /* DIR is different from last call, reset all buffers and
339 * cursors, and refill the global cache from the new DIR */
340 ret = rdp_fill_cache(dsp);
341 if (ret <= 0) {
342 ret_direntp = NULL;
343 goto end;
345 DEBUG(8, ("Switched global rdp cache to new DIR entry.\n"));
348 /* Return next entry from cache */
349 ret_direntp = ((SMB_STRUCT_DIRENT *)dsp->direntries_cursor);
350 dsp->direntries_cursor +=
351 ((SMB_STRUCT_DIRENT *)dsp->direntries_cursor)->d_reclen;
352 if (sbuf) {
353 *sbuf = rdp_stats[dsp->stat_cursor];
354 /* readdirplus() sets st_ino field to 0, if it was
355 * unable to retrieve stat information for that
356 * particular directory entry. */
357 if (sbuf->st_ino == 0)
358 SET_STAT_INVALID(*sbuf);
361 DEBUG(9, ("Read from DIR %p, direntry: \"%s\", location: %ld, "
362 "resume cookie: 0x%llx, cache cursor: %zu, cache count: %zu\n",
363 dsp->dirp, ret_direntp->d_name, dsp->location,
364 dsp->resume_cookie, dsp->stat_cursor, dsp->stat_count));
366 dsp->resume_cookie = rdp_cookies[dsp->stat_cursor];
367 dsp->stat_cursor++;
368 dsp->location++;
370 /* FALLTHROUGH */
371 end:
372 /* Set rdp_last_dirp at the end of every VFS call where the cache was
373 * reloaded */
374 rdp_last_dirp = dirp;
375 return ret_direntp;
379 * Set the location of the next direntry to be read via onefs_readdir().
381 * This function should only pass in locations retrieved from onefs_telldir().
383 * Ideally the seek point will still be in the readdirplus cache, and we'll
384 * just update our cursors. If the seek location is outside of the current
385 * cache we must do an expensive re-enumeration of the entire directory up
386 * to the offset.
388 * @param[in] handle vfs handle given in most VFS calls
389 * @param[in] dirp system DIR handle to set offset on
390 * @param[in] offset from the start of the directory where the next read
391 * will take place
393 * @return no return value
395 void
396 onefs_seekdir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp, long offset)
398 struct rdp_dir_state *dsp = NULL;
399 bool same_as_last;
400 bool outside_cache = false;
401 int ret = -1, i;
403 /* Fallback to default system routines if readdirplus is disabled */
404 if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
405 PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
407 return sys_seekdir(dirp, offset);
410 /* Validate inputs */
411 if (offset < 0) {
412 DEBUG(1, ("Invalid offset %ld passed.\n", offset));
413 return;
416 /* Retrieve state based off DIR handle */
417 ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
418 if (ret) {
419 DEBUG(1, ("Could not retrieve dir_state struct for "
420 "SMB_STRUCT_DIR pointer.\n"));
421 /* XXX: we can't return an error, should we ABORT rather than
422 * return without actually seeking? */
423 return;
426 /* Short cut if no work needs to be done */
427 if (offset == dsp->location)
428 return;
430 /* If DIR is different from last call, reset all buffers and cursors,
431 * and refill the global cache from the new DIR */
432 if (!same_as_last) {
433 ret = rdp_fill_cache(dsp);
434 if (ret <= 0)
435 goto out;
436 DEBUG(8, ("Switched global rdp cache to new DIR entry.\n"));
439 /* Check if location is outside the currently cached entries */
440 if (offset < dsp->location - dsp->stat_cursor) {
441 /* offset is before the current cache */
442 /* reset to the beginning of the directory */
443 ret = rdp_init(dsp);
444 if (ret) {
445 DEBUG(0, ("Error initializing readdirplus() buffers: "
446 "%s\n", strerror(ret)));
447 goto out;
449 outside_cache = true;
450 } else if (offset >
451 dsp->location + (dsp->stat_count - 1 - dsp->stat_cursor))
453 /* offset is after the current cache
454 * advance the cookie to the end of the cache */
455 dsp->resume_cookie = rdp_cookies[dsp->stat_count - 1];
456 outside_cache = true;
459 if (outside_cache) {
460 /* start reading from the directory, until we have the
461 * specified offset in our cache */
462 do {
463 dsp->location += dsp->stat_count - dsp->stat_cursor;
464 ret = rdp_fill_cache(dsp);
465 if (ret <= 0) {
466 DEBUG(1, ("Error seeking to offset outside the "
467 "cached directory entries. Offset "
468 "%ld \n", dsp->location));
469 goto out;
471 dsp->resume_cookie = rdp_cookies[dsp->stat_count - 1];
472 } while (offset >= dsp->location + dsp->stat_count);
475 /* Location should be within the currently cached entries */
476 if (offset < dsp->location &&
477 offset >= dsp->location - dsp->stat_cursor)
479 /* offset is within the current cache, before the cursor.
480 * update cursors to the new location */
481 int new_cursor = dsp->stat_cursor - (dsp->location - offset);
483 dsp->direntries_cursor = rdp_direntries;
484 for (i=0; i < new_cursor; i++) {
485 dsp->direntries_cursor +=
486 ((SMB_STRUCT_DIRENT *)
487 dsp->direntries_cursor)->d_reclen;
489 dsp->stat_cursor = new_cursor;
490 dsp->resume_cookie = rdp_cookies[dsp->stat_cursor];
491 dsp->location = offset;
492 } else if (offset >= dsp->location &&
493 offset <= dsp->location + (dsp->stat_count - 1 - dsp->stat_cursor))
495 /* offset is within the current cache, at or after the cursor.
496 * update cursors to the new location */
497 int add_to_cursor = offset - dsp->location - 1;
499 for (i=0; i < add_to_cursor; i++) {
500 dsp->direntries_cursor +=
501 ((SMB_STRUCT_DIRENT *)
502 dsp->direntries_cursor)->d_reclen;
504 dsp->stat_cursor += add_to_cursor;
505 dsp->resume_cookie = rdp_cookies[dsp->stat_cursor];
506 dsp->location = offset;
509 DEBUG(9, ("Seek DIR %p, location: %ld, cache cursor: %zu\n",
510 dsp->dirp, dsp->location, dsp->stat_cursor));
512 /* FALLTHROUGH */
513 out:
514 /* Set rdp_last_dirp at the end of every VFS call where the cache was
515 * reloaded */
516 rdp_last_dirp = dirp;
517 return;
521 * Returns the location of the next direntry to be read via onefs_readdir().
523 * This value can be passed into onefs_seekdir().
525 * @param[in] handle vfs handle given in most VFS calls
526 * @param[in] dirp system DIR handle to set offset on
528 * @return offset from the start of the directory where the next read
529 * will take place
531 long
532 onefs_telldir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp)
534 struct rdp_dir_state *dsp = NULL;
535 bool same_as_last;
536 int ret = -1;
538 /* Fallback to default system routines if readdirplus is disabled */
539 if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
540 PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
542 return sys_telldir(dirp);
545 /* Retrieve state based off DIR handle */
546 ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
547 if (ret) {
548 DEBUG(1, ("Could not retrieve dir_state struct for "
549 "SMB_STRUCT_DIR pointer.\n"));
550 return -1;
553 DEBUG(9, ("Tell DIR %p, location: %ld, cache cursor: %zu\n",
554 dsp->dirp, dsp->location, dsp->stat_cursor));
556 return dsp->location;
560 * Set the next direntry to be read via onefs_readdir() to the beginning of the
561 * directory.
563 * @param[in] handle vfs handle given in most VFS calls
564 * @param[in] dirp system DIR handle to set offset on
566 * @return no return value
568 void
569 onefs_rewinddir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp)
571 struct rdp_dir_state *dsp = NULL;
572 bool same_as_last;
573 int ret = -1;
575 /* Fallback to default system routines if readdirplus is disabled */
576 if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
577 PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
579 return sys_rewinddir(dirp);
582 /* Retrieve state based off DIR handle */
583 ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
584 if (ret) {
585 DEBUG(1, ("Could not retrieve dir_state struct for "
586 "SMB_STRUCT_DIR pointer.\n"));
587 return;
590 /* Reset location and resume key to beginning */
591 ret = rdp_init(dsp);
592 if (ret) {
593 DEBUG(0, ("Error re-initializing rdp cursors: %s\n",
594 strerror(ret)));
595 return;
598 DEBUG(9, ("Rewind DIR: %p, to location: %ld\n", dsp->dirp,
599 dsp->location));
601 return;
605 * Close DIR pointer and remove all state for that directory open.
607 * @param[in] handle vfs handle given in most VFS calls
608 * @param[in] dirp system DIR handle to set offset on
610 * @return -1 on failure, setting errno
613 onefs_closedir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp)
615 struct rdp_dir_state *dsp = NULL;
616 bool same_as_last;
617 int ret_val = -1;
618 int ret = -1;
620 /* Fallback to default system routines if readdirplus is disabled */
621 if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
622 PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
624 return SMB_VFS_NEXT_CLOSEDIR(handle, dirp);
627 /* Retrieve state based off DIR handle */
628 ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
629 if (ret) {
630 DEBUG(1, ("Could not retrieve dir_state struct for "
631 "SMB_STRUCT_DIR pointer.\n"));
632 errno = ENOENT;
633 return -1;
636 /* Close DIR pointer */
637 ret_val = SMB_VFS_NEXT_CLOSEDIR(handle, dsp->dirp);
639 DEBUG(9, ("Closed handle on DIR %p\n", dsp->dirp));
641 /* Tear down state struct */
642 DLIST_REMOVE(dirstatelist, dsp);
643 SAFE_FREE(dsp);
645 /* Set lastp to NULL, as cache is no longer valid */
646 rdp_last_dirp = NULL;
648 return ret_val;
652 * Initialize cache data at the beginning of every SMB search operation
654 * Since filesystem operations, such as delete files or meta data
655 * updates can occur to files in the directory we're searching
656 * between FIND_FIRST and FIND_NEXT calls we must refresh the cache
657 * from the kernel on every new search SMB.
659 * @param[in] handle vfs handle given in most VFS calls
660 * @param[in] dirp system DIR handle for the current search
662 * @return nothing
664 void
665 onefs_init_search_op(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp)
667 /* Setting the rdp_last_dirp to NULL will cause the next readdir operation
668 * to refill the cache. */
669 rdp_last_dirp = NULL;
671 return;