d86fce4e85252b02b8323b7463a65e40f5b810cb
[nivanova/samba-autobuild/.git] / source4 / ntvfs / posix / pvfs_dirlist.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    Copyright (C) Andrew Tridgell 2004
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10    
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15    
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 /*
20   directory listing functions for posix backend
21 */
22
23 #include "includes.h"
24 #include "vfs_posix.h"
25 #include "system/dir.h"
26
27 #define NAME_CACHE_SIZE 100
28
29 struct name_cache_entry {
30         char *name;
31         off_t offset;
32 };
33
34 struct pvfs_dir {
35         struct pvfs_state *pvfs;
36         bool no_wildcard;
37         char *single_name;
38         const char *pattern;
39         off_t offset;
40         DIR *dir;
41         const char *unix_path;
42         bool end_of_search;
43         struct name_cache_entry *name_cache;
44         uint32_t name_cache_index;
45 };
46
47 /* these three numbers are chosen to minimise the chances of a bad
48    interaction with the OS value for 'end of directory'. On IRIX
49    telldir() returns 0xFFFFFFFF at the end of a directory, and that
50    caused an infinite loop with the original values of 0,1,2
51
52    On XFS on linux telldir returns 0x7FFFFFFF at the end of a
53    directory. Thus the change from 0x80000002, as otherwise
54    0x7FFFFFFF+0x80000002==1==DIR_OFFSET_DOTDOT
55 */
56 #define DIR_OFFSET_DOT    0
57 #define DIR_OFFSET_DOTDOT 1
58 #define DIR_OFFSET_BASE   0x80000022
59
60 /*
61   a special directory listing case where the pattern has no wildcard. We can just do a single stat()
62   thus avoiding the more expensive directory scan
63 */
64 static NTSTATUS pvfs_list_no_wildcard(struct pvfs_state *pvfs, struct pvfs_filename *name, 
65                                       const char *pattern, struct pvfs_dir *dir)
66 {
67         if (!name->exists) {
68                 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
69         }
70
71         dir->pvfs = pvfs;
72         dir->no_wildcard = true;
73         dir->end_of_search = false;
74         dir->unix_path = talloc_strdup(dir, name->full_name);
75         if (!dir->unix_path) {
76                 return NT_STATUS_NO_MEMORY;
77         }
78
79         dir->single_name = talloc_strdup(dir, pattern);
80         if (!dir->single_name) {
81                 return NT_STATUS_NO_MEMORY;
82         }
83
84         dir->dir = NULL;
85         dir->offset = 0;
86         dir->pattern = NULL;
87
88         return NT_STATUS_OK;
89 }
90
91 /*
92   destroy an open search
93 */
94 static int pvfs_dirlist_destructor(struct pvfs_dir *dir)
95 {
96         if (dir->dir) closedir(dir->dir);
97         return 0;
98 }
99
100 /*
101   start to read a directory 
102
103   if the pattern matches no files then we return NT_STATUS_OK, with dir->count = 0
104 */
105 NTSTATUS pvfs_list_start(struct pvfs_state *pvfs, struct pvfs_filename *name, 
106                          TALLOC_CTX *mem_ctx, struct pvfs_dir **dirp)
107 {
108         char *pattern;
109         struct pvfs_dir *dir;
110
111         (*dirp) = talloc_zero(mem_ctx, struct pvfs_dir);
112         if (*dirp == NULL) {
113                 return NT_STATUS_NO_MEMORY;
114         }
115         
116         dir = *dirp;
117
118         /* split the unix path into a directory + pattern */
119         pattern = strrchr(name->full_name, '/');
120         if (!pattern) {
121                 /* this should not happen, as pvfs_unix_path is supposed to 
122                    return an absolute path */
123                 return NT_STATUS_UNSUCCESSFUL;
124         }
125
126         *pattern++ = 0;
127
128         if (!name->has_wildcard) {
129                 return pvfs_list_no_wildcard(pvfs, name, pattern, dir);
130         }
131
132         dir->unix_path = talloc_strdup(dir, name->full_name);
133         if (!dir->unix_path) {
134                 return NT_STATUS_NO_MEMORY;
135         }
136
137         dir->pattern = talloc_strdup(dir, pattern);
138         if (dir->pattern == NULL) {
139                 return NT_STATUS_NO_MEMORY;
140         }
141         
142         dir->dir = opendir(name->full_name);
143         if (!dir->dir) { 
144                 return pvfs_map_errno(pvfs, errno); 
145         }
146
147         dir->pvfs = pvfs;
148         dir->no_wildcard = false;
149         dir->end_of_search = false;
150         dir->offset = DIR_OFFSET_DOT;
151         dir->name_cache = talloc_zero_array(dir, 
152                                             struct name_cache_entry, 
153                                             NAME_CACHE_SIZE);
154         if (dir->name_cache == NULL) {
155                 talloc_free(dir);
156                 return NT_STATUS_NO_MEMORY;
157         }
158
159         talloc_set_destructor(dir, pvfs_dirlist_destructor);
160
161         return NT_STATUS_OK;
162 }
163
164 /*
165   add an entry to the local cache
166 */
167 static void dcache_add(struct pvfs_dir *dir, const char *name)
168 {
169         struct name_cache_entry *e;
170
171         dir->name_cache_index = (dir->name_cache_index+1) % NAME_CACHE_SIZE;
172         e = &dir->name_cache[dir->name_cache_index];
173
174         if (e->name) talloc_free(e->name);
175
176         e->name = talloc_strdup(dir->name_cache, name);
177         e->offset = dir->offset;
178 }
179
180 /* 
181    return the next entry
182 */
183 const char *pvfs_list_next(struct pvfs_dir *dir, off_t *ofs)
184 {
185         struct dirent *de;
186         enum protocol_types protocol = dir->pvfs->ntvfs->ctx->protocol;
187
188         /* non-wildcard searches are easy */
189         if (dir->no_wildcard) {
190                 dir->end_of_search = true;
191                 if (*ofs != 0) return NULL;
192                 (*ofs)++;
193                 return dir->single_name;
194         }
195
196         /* . and .. are handled separately as some unix systems will
197            not return them first in a directory, but windows client
198            may assume that these entries always appear first */
199         if (*ofs == DIR_OFFSET_DOT) {
200                 (*ofs) = DIR_OFFSET_DOTDOT;
201                 dir->offset = *ofs;
202                 if (ms_fnmatch_protocol(dir->pattern, ".", protocol,
203                                         true) == 0) {
204                         dcache_add(dir, ".");
205                         return ".";
206                 }
207         }
208
209         if (*ofs == DIR_OFFSET_DOTDOT) {
210                 (*ofs) = DIR_OFFSET_BASE;
211                 dir->offset = *ofs;
212                 if (ms_fnmatch_protocol(dir->pattern, "..", protocol,
213                                         true) == 0) {
214                         dcache_add(dir, "..");
215                         return "..";
216                 }
217         }
218
219         if (*ofs == DIR_OFFSET_BASE) {
220                 rewinddir(dir->dir);
221         } else if (*ofs != dir->offset) {
222                 seekdir(dir->dir, (*ofs) - DIR_OFFSET_BASE);
223         }
224         dir->offset = *ofs;
225         
226         while ((de = readdir(dir->dir))) {
227                 const char *dname = de->d_name;
228
229                 if (ISDOT(dname) || ISDOTDOT(dname)) {
230                         continue;
231                 }
232
233                 if (ms_fnmatch_protocol(dir->pattern, dname, protocol,
234                                         true) != 0) {
235                         char *short_name = pvfs_short_name_component(dir->pvfs, dname);
236                         if (short_name == NULL ||
237                             ms_fnmatch_protocol(dir->pattern, short_name,
238                                                 protocol, true) != 0) {
239                                 talloc_free(short_name);
240                                 continue;
241                         }
242                         talloc_free(short_name);
243                 }
244
245                 dir->offset = telldir(dir->dir) + DIR_OFFSET_BASE;
246                 (*ofs) = dir->offset;
247
248                 dcache_add(dir, dname);
249
250                 return dname;
251         }
252
253         dir->end_of_search = true;
254         return NULL;
255 }
256
257 /*
258   return unix directory of an open search
259 */
260 const char *pvfs_list_unix_path(struct pvfs_dir *dir)
261 {
262         return dir->unix_path;
263 }
264
265 /*
266   return true if end of search has been reached
267 */
268 bool pvfs_list_eos(struct pvfs_dir *dir, off_t ofs)
269 {
270         return dir->end_of_search;
271 }
272
273 /*
274   seek to the given name
275 */
276 NTSTATUS pvfs_list_seek(struct pvfs_dir *dir, const char *name, off_t *ofs)
277 {
278         struct dirent *de;
279         int i;
280
281         dir->end_of_search = false;
282
283         if (ISDOT(name)) {
284                 dir->offset = DIR_OFFSET_DOTDOT;
285                 *ofs = dir->offset;
286                 return NT_STATUS_OK;
287         }
288
289         if (ISDOTDOT(name)) {
290                 dir->offset = DIR_OFFSET_BASE;
291                 *ofs = dir->offset;
292                 return NT_STATUS_OK;
293         }
294
295         for (i=dir->name_cache_index;i>=0;i--) {
296                 struct name_cache_entry *e = &dir->name_cache[i];
297                 if (e->name && strcasecmp_m(name, e->name) == 0) {
298                         *ofs = e->offset;
299                         return NT_STATUS_OK;
300                 }
301         }
302         for (i=NAME_CACHE_SIZE-1;i>dir->name_cache_index;i--) {
303                 struct name_cache_entry *e = &dir->name_cache[i];
304                 if (e->name && strcasecmp_m(name, e->name) == 0) {
305                         *ofs = e->offset;
306                         return NT_STATUS_OK;
307                 }
308         }
309
310         rewinddir(dir->dir);
311
312         while ((de = readdir(dir->dir))) {
313                 if (strcasecmp_m(name, de->d_name) == 0) {
314                         dir->offset = telldir(dir->dir) + DIR_OFFSET_BASE;
315                         *ofs = dir->offset;
316                         return NT_STATUS_OK;
317                 }
318         }
319
320         dir->end_of_search = true;
321
322         return NT_STATUS_OBJECT_NAME_NOT_FOUND;
323 }
324
325 /*
326   seek to the given offset
327 */
328 NTSTATUS pvfs_list_seek_ofs(struct pvfs_dir *dir, uint32_t resume_key, off_t *ofs)
329 {
330         struct dirent *de;
331         int i;
332
333         dir->end_of_search = false;
334
335         if (resume_key == DIR_OFFSET_DOT) {
336                 *ofs = DIR_OFFSET_DOTDOT;
337                 return NT_STATUS_OK;
338         }
339
340         if (resume_key == DIR_OFFSET_DOTDOT) {
341                 *ofs = DIR_OFFSET_BASE;
342                 return NT_STATUS_OK;
343         }
344
345         if (resume_key == DIR_OFFSET_BASE) {
346                 rewinddir(dir->dir);
347                 if ((de=readdir(dir->dir)) == NULL) {
348                         dir->end_of_search = true;
349                         return NT_STATUS_OBJECT_NAME_NOT_FOUND;
350                 }
351                 *ofs = telldir(dir->dir) + DIR_OFFSET_BASE;
352                 dir->offset = *ofs;
353                 return NT_STATUS_OK;
354         }
355
356         for (i=dir->name_cache_index;i>=0;i--) {
357                 struct name_cache_entry *e = &dir->name_cache[i];
358                 if (resume_key == (uint32_t)e->offset) {
359                         *ofs = e->offset;
360                         return NT_STATUS_OK;
361                 }
362         }
363         for (i=NAME_CACHE_SIZE-1;i>dir->name_cache_index;i--) {
364                 struct name_cache_entry *e = &dir->name_cache[i];
365                 if (resume_key == (uint32_t)e->offset) {
366                         *ofs = e->offset;
367                         return NT_STATUS_OK;
368                 }
369         }
370
371         rewinddir(dir->dir);
372
373         while ((de = readdir(dir->dir))) {
374                 dir->offset = telldir(dir->dir) + DIR_OFFSET_BASE;
375                 if (resume_key == (uint32_t)dir->offset) {
376                         *ofs = dir->offset;
377                         return NT_STATUS_OK;
378                 }
379         }
380
381         dir->end_of_search = true;
382
383         return NT_STATUS_OBJECT_NAME_NOT_FOUND;
384 }
385
386
387 /*
388   see if a directory is empty
389 */
390 bool pvfs_directory_empty(struct pvfs_state *pvfs, struct pvfs_filename *name)
391 {
392         struct dirent *de;
393         DIR *dir = opendir(name->full_name);
394         if (dir == NULL) {
395                 return true;
396         }
397
398         while ((de = readdir(dir))) {
399                 if (!ISDOT(de->d_name) && !ISDOTDOT(de->d_name)) {
400                         closedir(dir);
401                         return false;
402                 }
403         }
404
405         closedir(dir);
406         return true;
407 }