/* [<][>][^][v][top][bottom][index][help] */
DEFINITIONS
This source file includes following definitions.
- rdp_retrieve_dir_state
- rdp_init
- rdp_fill_cache
- onefs_rdp_add_dir_state
- onefs_opendir
- onefs_readdir
- onefs_seekdir
- onefs_telldir
- onefs_rewinddir
- onefs_closedir
- onefs_init_search_op
1 /*
2 * Unix SMB/CIFS implementation.
3 *
4 * Support for OneFS bulk directory enumeration API
5 *
6 * Copyright (C) Steven Danneman, 2009
7 *
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.
12 *
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.
17 *
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/>.
20 */
21
22 #include "includes.h"
23 #include "onefs.h"
24 #include "onefs_config.h"
25
26 #include <ifs/ifs_syscalls.h>
27 #include <isi_util/isi_dir.h>
28
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.
32 *
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.
38 */
39
40 #define RDP_RESUME_KEY_START 0x1
41
42 #define RDP_BATCH_SIZE 128
43 #define RDP_DIRENTRIES_SIZE ((size_t)(RDP_BATCH_SIZE * sizeof(struct dirent)))
44
45 static char *rdp_direntries = NULL;
46 static SMB_STRUCT_STAT *rdp_stats = NULL;
47 static uint64_t *rdp_cookies = NULL;
48
49 struct rdp_dir_state {
50 struct rdp_dir_state *next, *prev;
51 SMB_STRUCT_DIR *dirp;
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
56 cache */
57 };
58
59 static struct rdp_dir_state *dirstatelist = NULL;
60
61 SMB_STRUCT_DIR *rdp_last_dirp = NULL;
62
63 /**
64 * Given a DIR pointer, return our internal state.
65 *
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.
71 *
72 * @return 0 on success, 1 on failure
73 */
74 static int
75 rdp_retrieve_dir_state(SMB_STRUCT_DIR *dirp, struct rdp_dir_state **dir_state,
/* [<][>][^][v][top][bottom][index][help] */
76 bool *same_as_last)
77 {
78 struct rdp_dir_state *dsp;
79
80 /* Is this directory the same as the last call */
81 *same_as_last = (dirp == rdp_last_dirp);
82
83 for(dsp = dirstatelist; dsp; dsp = dsp->next)
84 if (dsp->dirp == dirp) {
85 *dir_state = dsp;
86 return 0;
87 }
88
89 /* Couldn't find existing dir_state for the given directory
90 * pointer. */
91 return 1;
92 }
93
94 /**
95 * Initialize the global readdirplus buffers.
96 *
97 * These same buffers are used for all calls into readdirplus.
98 *
99 * @return 0 on success, errno value on failure
100 */
101 static int
102 rdp_init(struct rdp_dir_state *dsp)
/* [<][>][^][v][top][bottom][index][help] */
103 {
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);
110 if (!rdp_direntries)
111 return ENOMEM;
112 }
113
114 if (!rdp_stats) {
115 rdp_stats =
116 SMB_MALLOC(RDP_BATCH_SIZE * sizeof(SMB_STRUCT_STAT));
117 if (!rdp_stats)
118 return ENOMEM;
119 }
120
121 if (!rdp_cookies) {
122 rdp_cookies = SMB_MALLOC(RDP_BATCH_SIZE * sizeof(uint64_t));
123 if (!rdp_cookies)
124 return ENOMEM;
125 }
126
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;
131
132 return 0;
133 }
134
135 /**
136 * Call into readdirplus() to refill our global dirent cache.
137 *
138 * This function also resets all cursors back to the beginning of the cache.
139 * All stat buffers are retrieved by following symlinks.
140 *
141 * @return number of entries retrieved, -1 on error
142 */
143 static int
144 rdp_fill_cache(struct rdp_dir_state *dsp)
/* [<][>][^][v][top][bottom][index][help] */
145 {
146 int nread, dirfd;
147
148 dirfd = dirfd(dsp->dirp);
149 if (dirfd < 0) {
150 DEBUG(1, ("Could not retrieve fd for DIR\n"));
151 return -1;
152 }
153
154 /* Resize the stat_count to grab as many entries as possible */
155 dsp->stat_count = RDP_BATCH_SIZE;
156
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));
162
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;
176 }
177
178 DEBUG(9, ("readdirplus() returned %u entries from DIR %p\n",
179 dsp->stat_count, dsp->dirp));
180
181 dsp->direntries_cursor = rdp_direntries;
182 dsp->stat_cursor = 0;
183
184 return nread;
185 }
186
187 /**
188 * Create a dir_state to track an open directory that we're enumerating.
189 *
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.
193 *
194 * @return 0 on success and errno on failure
195 *
196 * @note: Callers of this function MUST cleanup the dir_state through a proper
197 * call to VFS_CLOSEDIR().
198 */
199 int
200 onefs_rdp_add_dir_state(connection_struct *conn, SMB_STRUCT_DIR *dirp)
/* [<][>][^][v][top][bottom][index][help] */
201 {
202 int ret = 0;
203 struct rdp_dir_state *dsp = NULL;
204
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))
208 {
209 return 0;
210 }
211
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;
217 }
218
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;
225 }
226
227 /* Set the SMB_STRUCT_DIR in the dsp */
228 dsp->dirp = dirp;
229
230 DLIST_ADD(dirstatelist, dsp);
231
232 return 0;
233 }
234
235 /**
236 * Open a directory for enumeration.
237 *
238 * Create a state struct to track the state of this directory for the life
239 * of this open.
240 *
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
245 *
246 * @return DIR pointer, NULL if directory does not exist, NULL on error
247 */
248 SMB_STRUCT_DIR *
249 onefs_opendir(vfs_handle_struct *handle, const char *fname, const char *mask,
/* [<][>][^][v][top][bottom][index][help] */
250 uint32 attr)
251 {
252 int ret = 0;
253 SMB_STRUCT_DIR *ret_dirp;
254
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))
258 {
259 return SMB_VFS_NEXT_OPENDIR(handle, fname, mask, attr);
260 }
261
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;
267 }
268
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;
274 }
275
276 DEBUG(9, ("Opened handle on directory: \"%s\", DIR %p\n",
277 fname, ret_dirp));
278
279 return ret_dirp;
280 }
281
282 /**
283 * Retrieve one direntry and optional stat buffer from our readdir cache.
284 *
285 * Increment the internal resume cookie, and refresh the cache from the
286 * kernel if necessary.
287 *
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.
291 *
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
295 *
296 * @return dirent structure, NULL if at the end of the directory, NULL on error
297 */
298 SMB_STRUCT_DIRENT *
299 onefs_readdir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp,
/* [<][>][^][v][top][bottom][index][help] */
300 SMB_STRUCT_STAT *sbuf)
301 {
302 struct rdp_dir_state *dsp = NULL;
303 SMB_STRUCT_DIRENT *ret_direntp;
304 bool same_as_last, filled_cache = false;
305 int ret = -1;
306
307 /* Set stat invalid in-case we error out */
308 if (sbuf)
309 SET_STAT_INVALID(*sbuf);
310
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))
314 {
315 return sys_readdir(dirp);
316 }
317
318 /* Retrieve state based off DIR handle */
319 ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
320 if (ret) {
321 DEBUG(1, ("Could not retrieve dir_state struct for "
322 "SMB_STRUCT_DIR pointer.\n"));
323 ret_direntp = NULL;
324 goto end;
325 }
326
327 /* DIR is the same, current buffer and cursors are valid.
328 * Check if there are any entries left in our current cache. */
329 if (same_as_last) {
330 if (dsp->stat_cursor == dsp->stat_count - 1) {
331 /* Cache is empty, refill from kernel */
332 ret = rdp_fill_cache(dsp);
333 if (ret <= 0) {
334 ret_direntp = NULL;
335 goto end;
336 }
337 filled_cache = true;
338 }
339 } else {
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);
343 if (ret <= 0) {
344 ret_direntp = NULL;
345 goto end;
346 }
347 filled_cache = true;
348 DEBUG(8, ("Switched global rdp cache to new DIR entry.\n"));
349 }
350
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 */
355 if (!filled_cache) {
356 dsp->direntries_cursor +=
357 ((SMB_STRUCT_DIRENT *)dsp->direntries_cursor)->d_reclen;
358 dsp->stat_cursor++;
359 }
360
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
364 * onefs_readdir() */
365 dsp->resume_cookie = rdp_cookies[dsp->stat_cursor];
366
367 /* Return an entry from cache */
368 ret_direntp = ((SMB_STRUCT_DIRENT *)dsp->direntries_cursor);
369 if (sbuf) {
370 *sbuf = rdp_stats[dsp->stat_cursor];
371 /* readdirplus() sets st_ino field to 0, if it was
372 * unable to retrieve stat information for that
373 * particular directory entry. */
374 if (sbuf->st_ino == 0)
375 SET_STAT_INVALID(*sbuf);
376 }
377
378 DEBUG(9, ("Read from DIR %p, direntry: \"%s\", resume cookie: %#llx, "
379 "cache cursor: %zu, cache count: %zu\n",
380 dsp->dirp, ret_direntp->d_name, dsp->resume_cookie,
381 dsp->stat_cursor, dsp->stat_count));
382
383 /* FALLTHROUGH */
384 end:
385 /* Set rdp_last_dirp at the end of every VFS call where the cache was
386 * reloaded */
387 rdp_last_dirp = dirp;
388 return ret_direntp;
389 }
390
391 /**
392 * Set the location of the next direntry to be read via onefs_readdir().
393 *
394 * This function should only pass in locations retrieved from onefs_telldir().
395 *
396 * @param[in] handle vfs handle given in most VFS calls
397 * @param[in] dirp system DIR handle to set offset on
398 * @param[in] offset into the directory to resume reading from
399 *
400 * @return no return value
401 */
402 void
403 onefs_seekdir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp, long offset)
/* [<][>][^][v][top][bottom][index][help] */
404 {
405 struct rdp_dir_state *dsp = NULL;
406 bool same_as_last;
407 uint64_t resume_cookie = 0;
408 int ret = -1;
409
410 /* Fallback to default system routines if readdirplus is disabled */
411 if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
412 PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
413 {
414 return sys_seekdir(dirp, offset);
415 }
416
417 /* Validate inputs */
418 if (offset < 0) {
419 DEBUG(1, ("Invalid offset %ld passed.\n", offset));
420 return;
421 }
422
423 /* Retrieve state based off DIR handle */
424 ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
425 if (ret) {
426 DEBUG(1, ("Could not retrieve dir_state struct for "
427 "SMB_STRUCT_DIR pointer.\n"));
428 /* XXX: we can't return an error, should we ABORT rather than
429 * return without actually seeking? */
430 return;
431 }
432
433 /* Convert offset to resume_cookie */
434 resume_cookie = rdp_offset31_to_cookie63(offset);
435
436 DEBUG(9, ("Seek DIR %p, offset: %ld, resume_cookie: %#llx\n",
437 dsp->dirp, offset, resume_cookie));
438
439 /* TODO: We could check if the resume_cookie is already in the cache
440 * through a linear search. This would allow us to avoid the cost of
441 * flushing the cache. Frequently, the seekdir offset will only be
442 * one entry before the current cache cursor. However, usually
443 * VFS_SEEKDIR() is only called at the end of a TRAND2_FIND read and
444 * we'll flush the cache at the beginning of the next PDU anyway. Some
445 * analysis should be done to see if this enhancement would provide
446 * better performance. */
447
448 /* Set the resume cookie and indicate that the cache should be reloaded
449 * on next read */
450 dsp->resume_cookie = resume_cookie;
451 rdp_last_dirp = NULL;
452
453 return;
454 }
455
456 /**
457 * Returns the location of the next direntry to be read via onefs_readdir().
458 *
459 * This value can be passed into onefs_seekdir().
460 *
461 * @param[in] handle vfs handle given in most VFS calls
462 * @param[in] dirp system DIR handle to set offset on
463 *
464 * @return offset into the directory to resume reading from
465 */
466 long
467 onefs_telldir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp)
/* [<][>][^][v][top][bottom][index][help] */
468 {
469 struct rdp_dir_state *dsp = NULL;
470 bool same_as_last;
471 long offset;
472 int ret = -1;
473
474 /* Fallback to default system routines if readdirplus is disabled */
475 if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
476 PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
477 {
478 return sys_telldir(dirp);
479 }
480
481 /* Retrieve state based off DIR handle */
482 ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
483 if (ret) {
484 DEBUG(1, ("Could not retrieve dir_state struct for "
485 "SMB_STRUCT_DIR pointer.\n"));
486 return -1;
487 }
488
489 /* Convert resume_cookie to offset */
490 offset = rdp_cookie63_to_offset31(dsp->resume_cookie);
491 if (offset < 0) {
492 DEBUG(1, ("Unable to convert resume_cookie: %#llx to a "
493 "suitable 32-bit offset value. Error: %s\n",
494 dsp->resume_cookie, strerror(errno)));
495 return -1;
496 }
497
498 DEBUG(9, ("Seek DIR %p, offset: %ld, resume_cookie: %#llx\n",
499 dsp->dirp, offset, dsp->resume_cookie));
500
501 return offset;
502 }
503
504 /**
505 * Set the next direntry to be read via onefs_readdir() to the beginning of the
506 * directory.
507 *
508 * @param[in] handle vfs handle given in most VFS calls
509 * @param[in] dirp system DIR handle to set offset on
510 *
511 * @return no return value
512 */
513 void
514 onefs_rewinddir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp)
/* [<][>][^][v][top][bottom][index][help] */
515 {
516 struct rdp_dir_state *dsp = NULL;
517 bool same_as_last;
518 int ret = -1;
519
520 /* Fallback to default system routines if readdirplus is disabled */
521 if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
522 PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
523 {
524 return sys_rewinddir(dirp);
525 }
526
527 /* Retrieve state based off DIR handle */
528 ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
529 if (ret) {
530 DEBUG(1, ("Could not retrieve dir_state struct for "
531 "SMB_STRUCT_DIR pointer.\n"));
532 return;
533 }
534
535 /* Reset location and resume key to beginning */
536 ret = rdp_init(dsp);
537 if (ret) {
538 DEBUG(0, ("Error re-initializing rdp cursors: %s\n",
539 strerror(ret)));
540 return;
541 }
542
543 DEBUG(9, ("Rewind DIR: %p, to resume_cookie: %#llx\n", dsp->dirp,
544 dsp->resume_cookie));
545
546 return;
547 }
548
549 /**
550 * Close DIR pointer and remove all state for that directory open.
551 *
552 * @param[in] handle vfs handle given in most VFS calls
553 * @param[in] dirp system DIR handle to set offset on
554 *
555 * @return -1 on failure, setting errno
556 */
557 int
558 onefs_closedir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp)
/* [<][>][^][v][top][bottom][index][help] */
559 {
560 struct rdp_dir_state *dsp = NULL;
561 bool same_as_last;
562 int ret_val = -1;
563 int ret = -1;
564
565 /* Fallback to default system routines if readdirplus is disabled */
566 if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
567 PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
568 {
569 return SMB_VFS_NEXT_CLOSEDIR(handle, dirp);
570 }
571
572 /* Retrieve state based off DIR handle */
573 ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
574 if (ret) {
575 DEBUG(1, ("Could not retrieve dir_state struct for "
576 "SMB_STRUCT_DIR pointer.\n"));
577 errno = ENOENT;
578 return -1;
579 }
580
581 /* Close DIR pointer */
582 ret_val = SMB_VFS_NEXT_CLOSEDIR(handle, dsp->dirp);
583
584 DEBUG(9, ("Closed handle on DIR %p\n", dsp->dirp));
585
586 /* Tear down state struct */
587 DLIST_REMOVE(dirstatelist, dsp);
588 SAFE_FREE(dsp);
589
590 /* Set lastp to NULL, as cache is no longer valid */
591 rdp_last_dirp = NULL;
592
593 return ret_val;
594 }
595
596 /**
597 * Initialize cache data at the beginning of every SMB search operation
598 *
599 * Since filesystem operations, such as delete files or meta data
600 * updates can occur to files in the directory we're searching
601 * between FIND_FIRST and FIND_NEXT calls we must refresh the cache
602 * from the kernel on every new search SMB.
603 *
604 * @param[in] handle vfs handle given in most VFS calls
605 * @param[in] dirp system DIR handle for the current search
606 *
607 * @return nothing
608 */
609 void
610 onefs_init_search_op(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp)
/* [<][>][^][v][top][bottom][index][help] */
611 {
612 /* Setting the rdp_last_dirp to NULL will cause the next readdir
613 * operation to refill the cache. */
614 rdp_last_dirp = NULL;
615
616 return;
617 }