1 /*
2  * VFS module to provide a sorted directory list.
3  *
4  * Copyright (C) Andy Kelk (andy@mopoke.co.uk), 2009
5  *
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "includes.h"
22 #include "smbd/smbd.h"
23 #include "system/filesys.h"
24 
compare_dirent(const struct dirent * da,const struct dirent * db)25 static int compare_dirent (const struct dirent *da, const struct dirent *db)
26 {
27 	return strcasecmp_m(da->d_name, db->d_name);
28 }
29 
30 struct dirsort_privates {
31 	struct dirsort_privates *prev, *next;
32 	long pos;
33 	struct dirent *directory_list;
34 	unsigned int number_of_entries;
35 	struct timespec mtime;
36 	DIR *source_directory;
37 	files_struct *fsp; /* If open via FDOPENDIR. */
38 	struct smb_filename *smb_fname; /* If open via OPENDIR */
39 };
40 
get_sorted_dir_mtime(vfs_handle_struct * handle,struct dirsort_privates * data,struct timespec * ret_mtime)41 static bool get_sorted_dir_mtime(vfs_handle_struct *handle,
42 				struct dirsort_privates *data,
43 				struct timespec *ret_mtime)
44 {
45 	int ret;
46 	struct timespec mtime;
47 	NTSTATUS status;
48 
49 	if (data->fsp) {
50 		status = vfs_stat_fsp(data->fsp);
51 		if (!NT_STATUS_IS_OK(status)) {
52 			return false;
53 		}
54 		mtime = data->fsp->fsp_name->st.st_ex_mtime;
55 	} else {
56 		ret = SMB_VFS_STAT(handle->conn, data->smb_fname);
57 		if (ret == -1) {
58 			return false;
59 		}
60 		mtime = data->smb_fname->st.st_ex_mtime;
61 	}
62 
63 	*ret_mtime = mtime;
64 
65 	return true;
66 }
67 
open_and_sort_dir(vfs_handle_struct * handle,struct dirsort_privates * data)68 static bool open_and_sort_dir(vfs_handle_struct *handle,
69 				struct dirsort_privates *data)
70 {
71 	uint32_t total_count = 0;
72 	/* This should be enough for most use cases */
73 	uint32_t dirent_allocated = 64;
74 	struct dirent *dp;
75 
76 	data->number_of_entries = 0;
77 
78 	if (get_sorted_dir_mtime(handle, data, &data->mtime) == false) {
79 		return false;
80 	}
81 
82 	dp = SMB_VFS_NEXT_READDIR(handle, data->source_directory, NULL);
83 	if (dp == NULL) {
84 		return false;
85 	}
86 
87 	/* Set up an array and read the directory entries into it */
88 	TALLOC_FREE(data->directory_list); /* destroy previous cache if needed */
89 	data->directory_list = talloc_zero_array(data,
90 						 struct dirent,
91 						 dirent_allocated);
92 	if (data->directory_list == NULL) {
93 		return false;
94 	}
95 
96 	do {
97 		if (total_count >= dirent_allocated) {
98 			struct dirent *dlist;
99 
100 			/*
101 			 * Be memory friendly.
102 			 *
103 			 * We should not double the amount of memory. With a lot
104 			 * of files we reach easily 50MB, and doubling will
105 			 * get much bigger just for a few files more.
106 			 *
107 			 * For 200k files this means 50 memory reallocations.
108 			 */
109 			dirent_allocated += 4096;
110 
111 			dlist = talloc_realloc(data,
112 					       data->directory_list,
113 					       struct dirent,
114 					       dirent_allocated);
115 			if (dlist == NULL) {
116 				break;
117 			}
118 			data->directory_list = dlist;
119 		}
120 		data->directory_list[total_count] = *dp;
121 
122 		total_count++;
123 		dp = SMB_VFS_NEXT_READDIR(handle, data->source_directory, NULL);
124 	} while (dp != NULL);
125 
126 	data->number_of_entries = total_count;
127 
128 	/* Sort the directory entries by name */
129 	TYPESAFE_QSORT(data->directory_list, data->number_of_entries, compare_dirent);
130 	return true;
131 }
132 
dirsort_opendir(vfs_handle_struct * handle,const struct smb_filename * smb_fname,const char * mask,uint32_t attr)133 static DIR *dirsort_opendir(vfs_handle_struct *handle,
134 				const struct smb_filename *smb_fname,
135 				const char *mask,
136 				uint32_t attr)
137 {
138 	struct dirsort_privates *list_head = NULL;
139 	struct dirsort_privates *data = NULL;
140 
141 	if (SMB_VFS_HANDLE_TEST_DATA(handle)) {
142 		/* Find the list head of all open directories. */
143 		SMB_VFS_HANDLE_GET_DATA(handle, list_head, struct dirsort_privates,
144 				return NULL);
145 	}
146 
147 	/* set up our private data about this directory */
148 	data = talloc_zero(handle->conn, struct dirsort_privates);
149 	if (!data) {
150 		return NULL;
151 	}
152 
153 	data->smb_fname = cp_smb_filename(data, smb_fname);
154 	if (data->smb_fname == NULL) {
155 		TALLOC_FREE(data);
156 		return NULL;
157 	}
158 
159 	if (ISDOT(data->smb_fname->base_name)) {
160 		struct smb_filename *cwd_fname = vfs_GetWd(data, handle->conn);
161 		if (cwd_fname == NULL) {
162 			TALLOC_FREE(data);
163 			return NULL;
164 		}
165 		TALLOC_FREE(data->smb_fname->base_name);
166 		data->smb_fname->base_name = talloc_move(data->smb_fname,
167 						&cwd_fname->base_name);
168 		TALLOC_FREE(cwd_fname);
169 	}
170 
171 	/* Open the underlying directory and count the number of entries */
172 	data->source_directory = SMB_VFS_NEXT_OPENDIR(handle, smb_fname, mask,
173 						      attr);
174 
175 	if (data->source_directory == NULL) {
176 		TALLOC_FREE(data);
177 		return NULL;
178 	}
179 
180 	if (!open_and_sort_dir(handle, data)) {
181 		SMB_VFS_NEXT_CLOSEDIR(handle,data->source_directory);
182 		TALLOC_FREE(data);
183 		return NULL;
184 	}
185 
186 	/* Add to the private list of all open directories. */
187 	DLIST_ADD(list_head, data);
188 	SMB_VFS_HANDLE_SET_DATA(handle, list_head, NULL,
189 				struct dirsort_privates, return NULL);
190 
191 	return data->source_directory;
192 }
193 
dirsort_fdopendir(vfs_handle_struct * handle,files_struct * fsp,const char * mask,uint32_t attr)194 static DIR *dirsort_fdopendir(vfs_handle_struct *handle,
195 					files_struct *fsp,
196 					const char *mask,
197 					uint32_t attr)
198 {
199 	struct dirsort_privates *list_head = NULL;
200 	struct dirsort_privates *data = NULL;
201 
202 	if (SMB_VFS_HANDLE_TEST_DATA(handle)) {
203 		/* Find the list head of all open directories. */
204 		SMB_VFS_HANDLE_GET_DATA(handle, list_head, struct dirsort_privates,
205 				return NULL);
206 	}
207 
208 	/* set up our private data about this directory */
209 	data = talloc_zero(handle->conn, struct dirsort_privates);
210 	if (!data) {
211 		return NULL;
212 	}
213 
214 	data->fsp = fsp;
215 
216 	/* Open the underlying directory and count the number of entries */
217 	data->source_directory = SMB_VFS_NEXT_FDOPENDIR(handle, fsp, mask,
218 						      attr);
219 
220 	if (data->source_directory == NULL) {
221 		TALLOC_FREE(data);
222 		return NULL;
223 	}
224 
225 	if (!open_and_sort_dir(handle, data)) {
226 		SMB_VFS_NEXT_CLOSEDIR(handle,data->source_directory);
227 		TALLOC_FREE(data);
228 		/* fd is now closed. */
229 		fsp->fh->fd = -1;
230 		return NULL;
231 	}
232 
233 	/* Add to the private list of all open directories. */
234 	DLIST_ADD(list_head, data);
235 	SMB_VFS_HANDLE_SET_DATA(handle, list_head, NULL,
236 				struct dirsort_privates, return NULL);
237 
238 	return data->source_directory;
239 }
240 
dirsort_readdir(vfs_handle_struct * handle,DIR * dirp,SMB_STRUCT_STAT * sbuf)241 static struct dirent *dirsort_readdir(vfs_handle_struct *handle,
242 					  DIR *dirp,
243 					  SMB_STRUCT_STAT *sbuf)
244 {
245 	struct dirsort_privates *data = NULL;
246 	struct timespec current_mtime;
247 
248 	SMB_VFS_HANDLE_GET_DATA(handle, data, struct dirsort_privates,
249 				return NULL);
250 
251 	while(data && (data->source_directory != dirp)) {
252 		data = data->next;
253 	}
254 	if (data == NULL) {
255 		return NULL;
256 	}
257 
258 	if (get_sorted_dir_mtime(handle, data, &current_mtime) == false) {
259 		return NULL;
260 	}
261 
262 	/* throw away cache and re-read the directory if we've changed */
263 	if (timespec_compare(&current_mtime, &data->mtime)) {
264 		SMB_VFS_NEXT_REWINDDIR(handle, data->source_directory);
265 		open_and_sort_dir(handle, data);
266 	}
267 
268 	if (data->pos >= data->number_of_entries) {
269 		return NULL;
270 	}
271 
272 	return &data->directory_list[data->pos++];
273 }
274 
dirsort_seekdir(vfs_handle_struct * handle,DIR * dirp,long offset)275 static void dirsort_seekdir(vfs_handle_struct *handle, DIR *dirp,
276 			    long offset)
277 {
278 	struct timespec current_mtime;
279 	struct dirsort_privates *data = NULL;
280 
281 	SMB_VFS_HANDLE_GET_DATA(handle, data, struct dirsort_privates, return);
282 
283 	/* Find the entry holding dirp. */
284 	while(data && (data->source_directory != dirp)) {
285 		data = data->next;
286 	}
287 	if (data == NULL) {
288 		return;
289 	}
290 	if (offset >= data->number_of_entries) {
291 		return;
292 	}
293 	data->pos = offset;
294 
295 	if (get_sorted_dir_mtime(handle, data, &current_mtime) == false) {
296 		return;
297 	}
298 
299 	if (timespec_compare(&current_mtime, &data->mtime)) {
300 		/* Directory changed. We must re-read the
301 		   cache and search for the name that was
302 		   previously stored at the offset being
303 		   requested, otherwise after the re-sort
304 		   we will point to the wrong entry. The
305 		   OS/2 incremental delete code relies on
306 		   this. */
307 		unsigned int i;
308 		char *wanted_name = talloc_strdup(handle->conn,
309 					data->directory_list[offset].d_name);
310 		if (wanted_name == NULL) {
311 			return;
312 		}
313 		SMB_VFS_NEXT_REWINDDIR(handle, data->source_directory);
314 		open_and_sort_dir(handle, data);
315 		/* Now search for where we were. */
316 		data->pos = 0;
317 		for (i = 0; i < data->number_of_entries; i++) {
318 			if(strcmp(wanted_name, data->directory_list[i].d_name) == 0) {
319 				data->pos = i;
320 				break;
321 			}
322 		}
323 		TALLOC_FREE(wanted_name);
324 	}
325 }
326 
dirsort_telldir(vfs_handle_struct * handle,DIR * dirp)327 static long dirsort_telldir(vfs_handle_struct *handle, DIR *dirp)
328 {
329 	struct dirsort_privates *data = NULL;
330 	SMB_VFS_HANDLE_GET_DATA(handle, data, struct dirsort_privates,
331 				return -1);
332 
333 	/* Find the entry holding dirp. */
334 	while(data && (data->source_directory != dirp)) {
335 		data = data->next;
336 	}
337 	if (data == NULL) {
338 		return -1;
339 	}
340 	return data->pos;
341 }
342 
dirsort_rewinddir(vfs_handle_struct * handle,DIR * dirp)343 static void dirsort_rewinddir(vfs_handle_struct *handle, DIR *dirp)
344 {
345 	struct dirsort_privates *data = NULL;
346 	SMB_VFS_HANDLE_GET_DATA(handle, data, struct dirsort_privates, return);
347 
348 	/* Find the entry holding dirp. */
349 	while(data && (data->source_directory != dirp)) {
350 		data = data->next;
351 	}
352 	if (data == NULL) {
353 		return;
354 	}
355 	data->pos = 0;
356 }
357 
dirsort_closedir(vfs_handle_struct * handle,DIR * dirp)358 static int dirsort_closedir(vfs_handle_struct *handle, DIR *dirp)
359 {
360 	struct dirsort_privates *list_head = NULL;
361 	struct dirsort_privates *data = NULL;
362 	int ret;
363 
364 	SMB_VFS_HANDLE_GET_DATA(handle, list_head, struct dirsort_privates, return -1);
365 	/* Find the entry holding dirp. */
366 	for(data = list_head; data && (data->source_directory != dirp); data = data->next) {
367 		;
368 	}
369 	if (data == NULL) {
370 		return -1;
371 	}
372 	/* Remove from the list and re-store the list head. */
373 	DLIST_REMOVE(list_head, data);
374 	SMB_VFS_HANDLE_SET_DATA(handle, list_head, NULL,
375 				struct dirsort_privates, return -1);
376 
377 	ret = SMB_VFS_NEXT_CLOSEDIR(handle, dirp);
378 	TALLOC_FREE(data);
379 	return ret;
380 }
381 
382 static struct vfs_fn_pointers vfs_dirsort_fns = {
383 	.opendir_fn = dirsort_opendir,
384 	.fdopendir_fn = dirsort_fdopendir,
385 	.readdir_fn = dirsort_readdir,
386 	.seekdir_fn = dirsort_seekdir,
387 	.telldir_fn = dirsort_telldir,
388 	.rewind_dir_fn = dirsort_rewinddir,
389 	.closedir_fn = dirsort_closedir,
390 };
391 
392 static_decl_vfs;
vfs_dirsort_init(TALLOC_CTX * ctx)393 NTSTATUS vfs_dirsort_init(TALLOC_CTX *ctx)
394 {
395 	return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "dirsort",
396 				&vfs_dirsort_fns);
397 }
398