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