smbd: Remove unused [push_pull]_file_id_24
[samba.git] / source3 / modules / onefs_dir.c
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 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,
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)
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(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)
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)
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,
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,
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                 struct stat onefs_sbuf;
371
372                 onefs_sbuf = rdp_stats[dsp->stat_cursor];
373                 init_stat_ex_from_onefs_stat(sbuf, &onefs_sbuf);
374
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);
380         }
381
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));
386
387         /* FALLTHROUGH */
388 end:
389         /* Set rdp_last_dirp at the end of every VFS call where the cache was
390          * reloaded */
391         rdp_last_dirp = dirp;
392         return ret_direntp;
393 }
394
395 /**
396  * Set the location of the next direntry to be read via onefs_readdir().
397  *
398  * This function should only pass in locations retrieved from onefs_telldir().
399  *
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
403  *
404  * @return no return value
405  */
406 void
407 onefs_seekdir(vfs_handle_struct *handle, SMB_STRUCT_DIR *dirp, long offset)
408 {
409         struct rdp_dir_state *dsp = NULL;
410         bool same_as_last;
411         uint64_t resume_cookie = 0;
412         int ret = -1;
413
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))
417         {
418                 return sys_seekdir(dirp, offset);
419         }
420
421         /* Validate inputs */
422         if (offset < 0) {
423                 DEBUG(1, ("Invalid offset %ld passed.\n", offset));
424                 return;
425         }
426
427         /* Retrieve state based off DIR handle */
428         ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
429         if (ret) {
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? */
434                 return;
435         }
436
437         /* Convert offset to resume_cookie */
438         resume_cookie = rdp_offset31_to_cookie63(offset);
439
440         DEBUG(9, ("Seek DIR %p, offset: %ld, resume_cookie: %#llx\n",
441                  dsp->dirp, offset, resume_cookie));
442
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. */
451
452         /* Set the resume cookie and indicate that the cache should be reloaded
453          * on next read */
454         dsp->resume_cookie = resume_cookie;
455         rdp_last_dirp = NULL;
456
457         return;
458 }
459
460 /**
461  * Returns the location of the next direntry to be read via onefs_readdir().
462  *
463  * This value can be passed into onefs_seekdir().
464  *
465  * @param[in] handle vfs handle given in most VFS calls
466  * @param[in] dirp system DIR handle to set offset on
467  *
468  * @return offset into the directory to resume reading from
469  */
470 long
471 onefs_telldir(vfs_handle_struct *handle,  SMB_STRUCT_DIR *dirp)
472 {
473         struct rdp_dir_state *dsp = NULL;
474         bool same_as_last;
475         long offset;
476         int ret = -1;
477
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))
481         {
482                 return sys_telldir(dirp);
483         }
484
485         /* Retrieve state based off DIR handle */
486         ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
487         if (ret) {
488                 DEBUG(1, ("Could not retrieve dir_state struct for "
489                          "SMB_STRUCT_DIR pointer.\n"));
490                 return -1;
491         }
492
493         /* Convert resume_cookie to offset */
494         offset = rdp_cookie63_to_offset31(dsp->resume_cookie);
495         if (offset < 0) {
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)));
499                 return -1;
500         }
501
502         DEBUG(9, ("Seek DIR %p, offset: %ld, resume_cookie: %#llx\n",
503                  dsp->dirp, offset, dsp->resume_cookie));
504
505         return offset;
506 }
507
508 /**
509  * Set the next direntry to be read via onefs_readdir() to the beginning of the
510  * directory.
511  *
512  * @param[in] handle vfs handle given in most VFS calls
513  * @param[in] dirp system DIR handle to set offset on
514  *
515  * @return no return value
516  */
517 void
518 onefs_rewinddir(vfs_handle_struct *handle,  SMB_STRUCT_DIR *dirp)
519 {
520         struct rdp_dir_state *dsp = NULL;
521         bool same_as_last;
522         int ret = -1;
523
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))
527         {
528                 return sys_rewinddir(dirp);
529         }
530
531         /* Retrieve state based off DIR handle */
532         ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
533         if (ret) {
534                 DEBUG(1, ("Could not retrieve dir_state struct for "
535                          "SMB_STRUCT_DIR pointer.\n"));
536                 return;
537         }
538
539         /* Reset location and resume key to beginning */
540         ret = rdp_init(dsp);
541         if (ret) {
542                 DEBUG(0, ("Error re-initializing rdp cursors: %s\n",
543                     strerror(ret)));
544                 return;
545         }
546
547         DEBUG(9, ("Rewind DIR: %p, to resume_cookie: %#llx\n", dsp->dirp,
548                  dsp->resume_cookie));
549
550         return;
551 }
552
553 /**
554  * Close DIR pointer and remove all state for that directory open.
555  *
556  * @param[in] handle vfs handle given in most VFS calls
557  * @param[in] dirp system DIR handle to set offset on
558  *
559  * @return -1 on failure, setting errno
560  */
561 int
562 onefs_closedir(vfs_handle_struct *handle,  SMB_STRUCT_DIR *dirp)
563 {
564         struct rdp_dir_state *dsp = NULL;
565         bool same_as_last;
566         int ret_val = -1;
567         int ret = -1;
568
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))
572         {
573                 return SMB_VFS_NEXT_CLOSEDIR(handle, dirp);
574         }
575
576         /* Retrieve state based off DIR handle */
577         ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
578         if (ret) {
579                 DEBUG(1, ("Could not retrieve dir_state struct for "
580                          "SMB_STRUCT_DIR pointer.\n"));
581                 errno = ENOENT;
582                 return -1;
583         }
584
585         /* Close DIR pointer */
586         ret_val = SMB_VFS_NEXT_CLOSEDIR(handle, dsp->dirp);
587
588         DEBUG(9, ("Closed handle on DIR %p\n", dsp->dirp));
589
590         /* Tear down state struct */
591         DLIST_REMOVE(dirstatelist, dsp);
592         SAFE_FREE(dsp);
593
594         /* Set lastp to NULL, as cache is no longer valid */
595         rdp_last_dirp = NULL;
596
597         return ret_val;
598 }
599
600 /**
601  * Initialize cache data at the beginning of every SMB search operation
602  *
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.
607  *
608  * @param[in] handle vfs handle given in most VFS calls
609  * @param[in] dirp system DIR handle for the current search
610  *
611  * @return nothing
612  */
613 void
614 onefs_init_search_op(vfs_handle_struct *handle,  SMB_STRUCT_DIR *dirp)
615 {
616         /* Setting the rdp_last_dirp to NULL will cause the next readdir
617          * operation to refill the cache. */
618         rdp_last_dirp = NULL;
619
620         return;
621 }