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 */
pvfs_list_no_wildcard(struct pvfs_state * pvfs,struct pvfs_filename * name,const char * pattern,struct pvfs_dir * dir)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 */
pvfs_dirlist_destructor(struct pvfs_dir * dir)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 */
pvfs_list_start(struct pvfs_state * pvfs,struct pvfs_filename * name,TALLOC_CTX * mem_ctx,struct pvfs_dir ** dirp)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 */
dcache_add(struct pvfs_dir * dir,const char * name)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 */
pvfs_list_next(struct pvfs_dir * dir,off_t * ofs)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 					false) == 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 					false) == 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 					false) != 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, false) != 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 */
pvfs_list_unix_path(struct pvfs_dir * dir)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 */
pvfs_list_eos(struct pvfs_dir * dir,off_t ofs)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 */
pvfs_list_seek(struct pvfs_dir * dir,const char * name,off_t * ofs)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 */
pvfs_list_seek_ofs(struct pvfs_dir * dir,uint32_t resume_key,off_t * ofs)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 */
pvfs_directory_empty(struct pvfs_state * pvfs,struct pvfs_filename * name)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 }
408