build: Remove SMB_STRUCT_DIR define
[nivanova/samba-autobuild/.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 "smbd/smbd.h"
24 #include "onefs.h"
25 #include "onefs_config.h"
26
27 #include <ifs/ifs_syscalls.h>
28 #include <isi_util/isi_dir.h>
29
30 /* The OneFS filesystem provides a readdirplus() syscall, equivalent to the
31  * NFSv3 PDU, which retrieves bulk directory listings with stat information
32  * in a single syscall.
33  *
34  * This file hides this bulk interface underneath Samba's very POSIX like
35  * opendir/readdir/telldir VFS interface.  This is done to provide a
36  * significant performance improvement when listing the contents of large
37  * directories, which also require file meta information. ie a typical
38  * Windows Explorer request.
39  */
40
41 #define RDP_RESUME_KEY_START 0x1
42
43 #define RDP_BATCH_SIZE 128
44 #define RDP_DIRENTRIES_SIZE ((size_t)(RDP_BATCH_SIZE * sizeof(struct dirent)))
45
46 static char *rdp_direntries = NULL;
47 static struct stat *rdp_stats = NULL;
48 static uint64_t *rdp_cookies = NULL;
49
50 struct rdp_dir_state {
51         struct rdp_dir_state *next, *prev;
52         DIR *dirp;
53         char *direntries_cursor; /* cursor to last returned direntry in cache */
54         size_t stat_count;       /* number of entries stored in the cache */
55         size_t stat_cursor;      /* cursor to last returned stat in the cache */
56         uint64_t resume_cookie;  /* cookie from the last entry returned from the
57                                     cache */
58 };
59
60 static struct rdp_dir_state *dirstatelist = NULL;
61
62 DIR *rdp_last_dirp = NULL;
63
64 /**
65  * Given a DIR pointer, return our internal state.
66  *
67  * This function also tells us whether the given DIR is the same as we saw
68  * during the last call.  Because we use a single globally allocated buffer
69  * for readdirplus entries we must check every call into this API to see if
70  * it's for the same directory listing, or a new one. If it's the same we can
71  * maintain our current cached entries, otherwise we must go to the kernel.
72  *
73  * @return 0 on success, 1 on failure
74  */
75 static int
76 rdp_retrieve_dir_state(DIR *dirp, struct rdp_dir_state **dir_state,
77                        bool *same_as_last)
78 {
79         struct rdp_dir_state *dsp;
80
81         /* Is this directory the same as the last call */
82         *same_as_last = (dirp == rdp_last_dirp);
83
84         for(dsp = dirstatelist; dsp; dsp = dsp->next)
85                 if (dsp->dirp == dirp) {
86                         *dir_state = dsp;
87                         return 0;
88                 }
89
90         /* Couldn't find existing dir_state for the given directory
91          * pointer. */
92         return 1;
93 }
94
95 /**
96  * Initialize the global readdirplus buffers.
97  *
98  * These same buffers are used for all calls into readdirplus.
99  *
100  * @return 0 on success, errno value on failure
101  */
102 static int
103 rdp_init(struct rdp_dir_state *dsp)
104 {
105         /* Unfortunately, there is no good way to free these buffers.  If we
106          * allocated and freed for every DIR handle performance would be
107          * adversely affected.  For now these buffers will be leaked and only
108          * freed when the smbd process dies. */
109         if (!rdp_direntries) {
110                 rdp_direntries = SMB_MALLOC(RDP_DIRENTRIES_SIZE);
111                 if (!rdp_direntries)
112                         return ENOMEM;
113         }
114
115         if (!rdp_stats) {
116                 rdp_stats =
117                     SMB_MALLOC(RDP_BATCH_SIZE * sizeof(struct stat));
118                 if (!rdp_stats)
119                         return ENOMEM;
120         }
121
122         if (!rdp_cookies) {
123                 rdp_cookies = SMB_MALLOC(RDP_BATCH_SIZE * sizeof(uint64_t));
124                 if (!rdp_cookies)
125                         return ENOMEM;
126         }
127
128         dsp->direntries_cursor = rdp_direntries + RDP_DIRENTRIES_SIZE;
129         dsp->stat_count = RDP_BATCH_SIZE;
130         dsp->stat_cursor = RDP_BATCH_SIZE;
131         dsp->resume_cookie = RDP_RESUME_KEY_START;
132
133         return 0;
134 }
135
136 /**
137  * Call into readdirplus() to refill our global dirent cache.
138  *
139  * This function also resets all cursors back to the beginning of the cache.
140  * All stat buffers are retrieved by following symlinks.
141  *
142  * @return number of entries retrieved, -1 on error
143  */
144 static int
145 rdp_fill_cache(struct rdp_dir_state *dsp)
146 {
147         int nread, dirfd;
148
149         dirfd = dirfd(dsp->dirp);
150         if (dirfd < 0) {
151                 DEBUG(1, ("Could not retrieve fd for DIR\n"));
152                 return -1;
153         }
154
155         /* Resize the stat_count to grab as many entries as possible */
156         dsp->stat_count = RDP_BATCH_SIZE;
157
158         DEBUG(9, ("Calling readdirplus() with DIR %p, dirfd: %d, "
159                  "resume_cookie %#llx, size_to_read: %zu, "
160                  "direntries_size: %zu, stat_count: %u\n",
161                  dsp->dirp, dirfd, dsp->resume_cookie, RDP_BATCH_SIZE,
162                  RDP_DIRENTRIES_SIZE, dsp->stat_count));
163
164         nread = readdirplus(dirfd,
165                             RDP_FOLLOW,
166                             &dsp->resume_cookie,
167                             RDP_BATCH_SIZE,
168                             rdp_direntries,
169                             RDP_DIRENTRIES_SIZE,
170                             &dsp->stat_count,
171                             rdp_stats,
172                             rdp_cookies);
173         if (nread < 0) {
174                 DEBUG(1, ("Error calling readdirplus(): %s\n",
175                          strerror(errno)));
176                 return -1;
177         }
178
179         DEBUG(9, ("readdirplus() returned %u entries from DIR %p\n",
180                  dsp->stat_count, dsp->dirp));
181
182         dsp->direntries_cursor = rdp_direntries;
183         dsp->stat_cursor = 0;
184
185         return nread;
186 }
187
188 /**
189  * Create a dir_state to track an open directory that we're enumerating.
190  *
191  * This utility function is globally accessible for use by other parts of the
192  * onefs.so module to initialize a dir_state when a directory is opened through
193  * a path other than the VFS layer.
194  *
195  * @return 0 on success and errno on failure
196  *
197  * @note: Callers of this function MUST cleanup the dir_state through a proper
198  * call to VFS_CLOSEDIR().
199  */
200 int
201 onefs_rdp_add_dir_state(connection_struct *conn, DIR *dirp)
202 {
203         int ret = 0;
204         struct rdp_dir_state *dsp = NULL;
205
206         /* No-op if readdirplus is disabled */
207         if (!lp_parm_bool(SNUM(conn), PARM_ONEFS_TYPE,
208             PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
209         {
210                 return 0;
211         }
212
213         /* Create a struct dir_state */
214         dsp = SMB_MALLOC_P(struct rdp_dir_state);
215         if (!dsp) {
216                 DEBUG(0, ("Error allocating struct rdp_dir_state.\n"));
217                 return ENOMEM;
218         }
219
220         /* Initialize the dir_state structure and add it to the list */
221         ret = rdp_init(dsp);
222         if (ret) {
223                 DEBUG(0, ("Error initializing readdirplus() buffers: %s\n",
224                          strerror(ret)));
225                 return ret;
226         }
227
228         /* Set the DIR in the dsp */
229         dsp->dirp = dirp;
230
231         DLIST_ADD(dirstatelist, dsp);
232
233         return 0;
234 }
235
236 /**
237  * Open a directory for enumeration.
238  *
239  * Create a state struct to track the state of this directory for the life
240  * of this open.
241  *
242  * @param[in] handle vfs handle given in most VFS calls
243  * @param[in] fname filename of the directory to open
244  * @param[in] mask unused
245  * @param[in] attr unused
246  *
247  * @return DIR pointer, NULL if directory does not exist, NULL on error
248  */
249 DIR *
250 onefs_opendir(vfs_handle_struct *handle, const char *fname, const char *mask,
251               uint32 attr)
252 {
253         int ret = 0;
254         DIR *ret_dirp;
255
256         /* Fallback to default system routines if readdirplus is disabled */
257         if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
258             PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
259         {
260                 return SMB_VFS_NEXT_OPENDIR(handle, fname, mask, attr);
261         }
262
263         /* Open the directory */
264         ret_dirp = SMB_VFS_NEXT_OPENDIR(handle, fname, mask, attr);
265         if (!ret_dirp) {
266                 DEBUG(3, ("Unable to open directory: %s\n", fname));
267                 return NULL;
268         }
269
270         /* Create the dir_state struct and add it to the list */
271         ret = onefs_rdp_add_dir_state(handle->conn, ret_dirp);
272         if (ret) {
273                 DEBUG(0, ("Error adding dir_state to the list\n"));
274                 return NULL;
275         }
276
277         DEBUG(9, ("Opened handle on directory: \"%s\", DIR %p\n",
278                  fname, ret_dirp));
279
280         return ret_dirp;
281 }
282
283 /**
284  * Retrieve one direntry and optional stat buffer from our readdir cache.
285  *
286  * Increment the internal resume cookie, and refresh the cache from the
287  * kernel if necessary.
288  *
289  * The cache cursor tracks the last entry which was successfully returned
290  * to a caller of onefs_readdir().  When a new entry is requested, this
291  * function first increments the cursor, then returns that entry.
292  *
293  * @param[in] handle vfs handle given in most VFS calls
294  * @param[in] dirp system DIR handle to retrieve direntries from
295  * @param[in/out] sbuf optional stat buffer to fill, this can be NULL
296  *
297  * @return dirent structure, NULL if at the end of the directory, NULL on error
298  */
299 struct dirent *
300 onefs_readdir(vfs_handle_struct *handle, DIR *dirp,
301               SMB_STRUCT_STAT *sbuf)
302 {
303         struct rdp_dir_state *dsp = NULL;
304         struct dirent *ret_direntp;
305         bool same_as_last, filled_cache = false;
306         int ret = -1;
307
308         /* Set stat invalid in-case we error out */
309         if (sbuf)
310                 SET_STAT_INVALID(*sbuf);
311
312         /* Fallback to default system routines if readdirplus is disabled */
313         if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
314             PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
315         {
316                 return readdir(dirp);
317         }
318
319         /* Retrieve state based off DIR handle */
320         ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
321         if (ret) {
322                 DEBUG(1, ("Could not retrieve dir_state struct for "
323                          "DIR pointer.\n"));
324                 ret_direntp = NULL;
325                 goto end;
326         }
327
328         /* DIR is the same, current buffer and cursors are valid.
329          * Check if there are any entries left in our current cache. */
330         if (same_as_last) {
331                 if (dsp->stat_cursor == dsp->stat_count - 1) {
332                         /* Cache is empty, refill from kernel */
333                         ret = rdp_fill_cache(dsp);
334                         if (ret <= 0) {
335                                 ret_direntp = NULL;
336                                 goto end;
337                         }
338                         filled_cache = true;
339                 }
340         } else {
341                 /* DIR is different from last call, reset all buffers and
342                  * cursors, and refill the global cache from the new DIR */
343                 ret = rdp_fill_cache(dsp);
344                 if (ret <= 0) {
345                         ret_direntp = NULL;
346                         goto end;
347                 }
348                 filled_cache = true;
349                 DEBUG(8, ("Switched global rdp cache to new DIR entry.\n"));
350         }
351
352         /* If we just filled the cache we treat that action as the cursor
353          * increment as the resume cookie used belonged to the previous
354          * directory entry.  If the cache has not changed we first increment
355          * our cursor, then return the next entry */
356         if (!filled_cache) {
357                 dsp->direntries_cursor +=
358                     ((struct dirent *)dsp->direntries_cursor)->d_reclen;
359                 dsp->stat_cursor++;
360         }
361
362         /* The resume_cookie stored here purposely differs based on whether we
363          * just filled the cache. The resume cookie stored must always provide
364          * the next direntry, in case the cache is reloaded on every
365          * onefs_readdir() */
366         dsp->resume_cookie = rdp_cookies[dsp->stat_cursor];
367
368         /* Return an entry from cache */
369         ret_direntp = ((struct dirent *)dsp->direntries_cursor);
370         if (sbuf) {
371                 struct stat onefs_sbuf;
372
373                 onefs_sbuf = rdp_stats[dsp->stat_cursor];
374                 init_stat_ex_from_onefs_stat(sbuf, &onefs_sbuf);
375
376                 /* readdirplus() sets st_ino field to 0, if it was
377                  * unable to retrieve stat information for that
378                  * particular directory entry. */
379                 if (sbuf->st_ex_ino == 0)
380                         SET_STAT_INVALID(*sbuf);
381         }
382
383         DEBUG(9, ("Read from DIR %p, direntry: \"%s\", resume cookie: %#llx, "
384                  "cache cursor: %zu, cache count: %zu\n",
385                  dsp->dirp, ret_direntp->d_name, dsp->resume_cookie,
386                  dsp->stat_cursor, dsp->stat_count));
387
388         /* FALLTHROUGH */
389 end:
390         /* Set rdp_last_dirp at the end of every VFS call where the cache was
391          * reloaded */
392         rdp_last_dirp = dirp;
393         return ret_direntp;
394 }
395
396 /**
397  * Set the location of the next direntry to be read via onefs_readdir().
398  *
399  * This function should only pass in locations retrieved from onefs_telldir().
400  *
401  * @param[in] handle vfs handle given in most VFS calls
402  * @param[in] dirp system DIR handle to set offset on
403  * @param[in] offset into the directory to resume reading from
404  *
405  * @return no return value
406  */
407 void
408 onefs_seekdir(vfs_handle_struct *handle, DIR *dirp, long offset)
409 {
410         struct rdp_dir_state *dsp = NULL;
411         bool same_as_last;
412         uint64_t resume_cookie = 0;
413         int ret = -1;
414
415         /* Fallback to default system routines if readdirplus is disabled */
416         if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
417             PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
418         {
419                 return seekdir(dirp, offset);
420         }
421
422         /* Validate inputs */
423         if (offset < 0) {
424                 DEBUG(1, ("Invalid offset %ld passed.\n", offset));
425                 return;
426         }
427
428         /* Retrieve state based off DIR handle */
429         ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
430         if (ret) {
431                 DEBUG(1, ("Could not retrieve dir_state struct for "
432                          "DIR pointer.\n"));
433                 /* XXX: we can't return an error, should we ABORT rather than
434                  * return without actually seeking? */
435                 return;
436         }
437
438         /* Convert offset to resume_cookie */
439         resume_cookie = rdp_offset31_to_cookie63(offset);
440
441         DEBUG(9, ("Seek DIR %p, offset: %ld, resume_cookie: %#llx\n",
442                  dsp->dirp, offset, resume_cookie));
443
444         /* TODO: We could check if the resume_cookie is already in the cache
445          * through a linear search.  This would allow us to avoid the cost of
446          * flushing the cache.  Frequently, the seekdir offset will only be
447          * one entry before the current cache cursor.  However, usually
448          * VFS_SEEKDIR() is only called at the end of a TRAND2_FIND read and
449          * we'll flush the cache at the beginning of the next PDU anyway. Some
450          * analysis should be done to see if this enhancement would provide
451          * better performance. */
452
453         /* Set the resume cookie and indicate that the cache should be reloaded
454          * on next read */
455         dsp->resume_cookie = resume_cookie;
456         rdp_last_dirp = NULL;
457
458         return;
459 }
460
461 /**
462  * Returns the location of the next direntry to be read via onefs_readdir().
463  *
464  * This value can be passed into onefs_seekdir().
465  *
466  * @param[in] handle vfs handle given in most VFS calls
467  * @param[in] dirp system DIR handle to set offset on
468  *
469  * @return offset into the directory to resume reading from
470  */
471 long
472 onefs_telldir(vfs_handle_struct *handle,  DIR *dirp)
473 {
474         struct rdp_dir_state *dsp = NULL;
475         bool same_as_last;
476         long offset;
477         int ret = -1;
478
479         /* Fallback to default system routines if readdirplus is disabled */
480         if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
481             PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
482         {
483                 return telldir(dirp);
484         }
485
486         /* Retrieve state based off DIR handle */
487         ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
488         if (ret) {
489                 DEBUG(1, ("Could not retrieve dir_state struct for "
490                          "DIR pointer.\n"));
491                 return -1;
492         }
493
494         /* Convert resume_cookie to offset */
495         offset = rdp_cookie63_to_offset31(dsp->resume_cookie);
496         if (offset < 0) {
497                 DEBUG(1, ("Unable to convert resume_cookie: %#llx to a "
498                          "suitable 32-bit offset value. Error: %s\n",
499                          dsp->resume_cookie, strerror(errno)));
500                 return -1;
501         }
502
503         DEBUG(9, ("Seek DIR %p, offset: %ld, resume_cookie: %#llx\n",
504                  dsp->dirp, offset, dsp->resume_cookie));
505
506         return offset;
507 }
508
509 /**
510  * Set the next direntry to be read via onefs_readdir() to the beginning of the
511  * directory.
512  *
513  * @param[in] handle vfs handle given in most VFS calls
514  * @param[in] dirp system DIR handle to set offset on
515  *
516  * @return no return value
517  */
518 void
519 onefs_rewinddir(vfs_handle_struct *handle,  DIR *dirp)
520 {
521         struct rdp_dir_state *dsp = NULL;
522         bool same_as_last;
523         int ret = -1;
524
525         /* Fallback to default system routines if readdirplus is disabled */
526         if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
527             PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
528         {
529                 return rewinddir(dirp);
530         }
531
532         /* Retrieve state based off DIR handle */
533         ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
534         if (ret) {
535                 DEBUG(1, ("Could not retrieve dir_state struct for "
536                          "DIR pointer.\n"));
537                 return;
538         }
539
540         /* Reset location and resume key to beginning */
541         ret = rdp_init(dsp);
542         if (ret) {
543                 DEBUG(0, ("Error re-initializing rdp cursors: %s\n",
544                     strerror(ret)));
545                 return;
546         }
547
548         DEBUG(9, ("Rewind DIR: %p, to resume_cookie: %#llx\n", dsp->dirp,
549                  dsp->resume_cookie));
550
551         return;
552 }
553
554 /**
555  * Close DIR pointer and remove all state for that directory open.
556  *
557  * @param[in] handle vfs handle given in most VFS calls
558  * @param[in] dirp system DIR handle to set offset on
559  *
560  * @return -1 on failure, setting errno
561  */
562 int
563 onefs_closedir(vfs_handle_struct *handle,  DIR *dirp)
564 {
565         struct rdp_dir_state *dsp = NULL;
566         bool same_as_last;
567         int ret_val = -1;
568         int ret = -1;
569
570         /* Fallback to default system routines if readdirplus is disabled */
571         if (!lp_parm_bool(SNUM(handle->conn), PARM_ONEFS_TYPE,
572             PARM_USE_READDIRPLUS, PARM_USE_READDIRPLUS_DEFAULT))
573         {
574                 return SMB_VFS_NEXT_CLOSEDIR(handle, dirp);
575         }
576
577         /* Retrieve state based off DIR handle */
578         ret = rdp_retrieve_dir_state(dirp, &dsp, &same_as_last);
579         if (ret) {
580                 DEBUG(1, ("Could not retrieve dir_state struct for "
581                          "DIR pointer.\n"));
582                 errno = ENOENT;
583                 return -1;
584         }
585
586         /* Close DIR pointer */
587         ret_val = SMB_VFS_NEXT_CLOSEDIR(handle, dsp->dirp);
588
589         DEBUG(9, ("Closed handle on DIR %p\n", dsp->dirp));
590
591         /* Tear down state struct */
592         DLIST_REMOVE(dirstatelist, dsp);
593         SAFE_FREE(dsp);
594
595         /* Set lastp to NULL, as cache is no longer valid */
596         rdp_last_dirp = NULL;
597
598         return ret_val;
599 }
600
601 /**
602  * Initialize cache data at the beginning of every SMB search operation
603  *
604  * Since filesystem operations, such as delete files or meta data
605  * updates can occur to files in the directory we're searching
606  * between FIND_FIRST and FIND_NEXT calls we must refresh the cache
607  * from the kernel on every new search SMB.
608  *
609  * @param[in] handle vfs handle given in most VFS calls
610  * @param[in] dirp system DIR handle for the current search
611  *
612  * @return nothing
613  */
614 void
615 onefs_init_search_op(vfs_handle_struct *handle,  DIR *dirp)
616 {
617         /* Setting the rdp_last_dirp to NULL will cause the next readdir
618          * operation to refill the cache. */
619         rdp_last_dirp = NULL;
620
621         return;
622 }