07f2a0f127da980c5b5d934e117dde932095ce62
[jelmer/samba4-debian.git] / source / ntvfs / posix / pvfs_search.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    POSIX NTVFS backend - directory search functions
5
6    Copyright (C) Andrew Tridgell 2004
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 2 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, write to the Free Software
20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 */
22
23 #include "include/includes.h"
24 #include "vfs_posix.h"
25
26 /*
27   fill in a single search result for a given info level
28 */
29 static NTSTATUS fill_search_info(struct pvfs_state *pvfs,
30                                  enum smb_search_level level,
31                                  const char *unix_path,
32                                  const char *fname, 
33                                  struct pvfs_search_state *search,
34                                  uint32_t dir_index,
35                                  union smb_search_data *file)
36 {
37         struct pvfs_filename *name;
38         NTSTATUS status;
39         const char *shortname;
40
41         status = pvfs_resolve_partial(pvfs, file, unix_path, fname, &name);
42         if (!NT_STATUS_IS_OK(status)) {
43                 return status;
44         }
45
46         if (!pvfs_match_attrib(pvfs, name, search->search_attrib)) {
47                 return NT_STATUS_OBJECT_NAME_NOT_FOUND;
48         }
49
50         switch (level) {
51         case RAW_SEARCH_SEARCH:
52         case RAW_SEARCH_FFIRST:
53         case RAW_SEARCH_FUNIQUE:
54                 shortname = pvfs_short_name(pvfs, name, name);
55                 file->search.attrib           = name->dos.attrib;
56                 file->search.write_time       = nt_time_to_unix(name->dos.write_time);
57                 file->search.size             = name->st.st_size;
58                 file->search.name             = shortname;
59                 file->search.id.reserved      = 8;
60                 memset(file->search.id.name, ' ', sizeof(file->search.id.name));
61                 memcpy(file->search.id.name, shortname, 
62                        MIN(strlen(shortname)+1, sizeof(file->search.id.name)));
63                 file->search.id.handle        = search->handle;
64                 file->search.id.server_cookie = dir_index+1;
65                 file->search.id.client_cookie = 0;
66                 return NT_STATUS_OK;
67
68         case RAW_SEARCH_STANDARD:
69                 file->standard.resume_key   = dir_index+1;
70                 file->standard.create_time  = nt_time_to_unix(name->dos.create_time);
71                 file->standard.access_time  = nt_time_to_unix(name->dos.access_time);
72                 file->standard.write_time   = nt_time_to_unix(name->dos.write_time);
73                 file->standard.size         = name->st.st_size;
74                 file->standard.alloc_size   = name->dos.alloc_size;
75                 file->standard.attrib       = name->dos.attrib;
76                 file->standard.name.s       = fname;
77                 return NT_STATUS_OK;
78
79         case RAW_SEARCH_EA_SIZE:
80                 file->ea_size.resume_key   = dir_index+1;
81                 file->ea_size.create_time  = nt_time_to_unix(name->dos.create_time);
82                 file->ea_size.access_time  = nt_time_to_unix(name->dos.access_time);
83                 file->ea_size.write_time   = nt_time_to_unix(name->dos.write_time);
84                 file->ea_size.size         = name->st.st_size;
85                 file->ea_size.alloc_size   = name->dos.alloc_size;
86                 file->ea_size.attrib       = name->dos.attrib;
87                 file->ea_size.ea_size      = name->dos.ea_size;
88                 file->ea_size.name.s       = fname;
89                 return NT_STATUS_OK;
90
91         case RAW_SEARCH_DIRECTORY_INFO:
92                 file->directory_info.file_index   = dir_index+1;
93                 file->directory_info.create_time  = name->dos.create_time;
94                 file->directory_info.access_time  = name->dos.access_time;
95                 file->directory_info.write_time   = name->dos.write_time;
96                 file->directory_info.change_time  = name->dos.change_time;
97                 file->directory_info.size         = name->st.st_size;
98                 file->directory_info.alloc_size   = name->dos.alloc_size;
99                 file->directory_info.attrib       = name->dos.attrib;
100                 file->directory_info.name.s       = fname;
101                 return NT_STATUS_OK;
102
103         case RAW_SEARCH_FULL_DIRECTORY_INFO:
104                 file->full_directory_info.file_index   = dir_index+1;
105                 file->full_directory_info.create_time  = name->dos.create_time;
106                 file->full_directory_info.access_time  = name->dos.access_time;
107                 file->full_directory_info.write_time   = name->dos.write_time;
108                 file->full_directory_info.change_time  = name->dos.change_time;
109                 file->full_directory_info.size         = name->st.st_size;
110                 file->full_directory_info.alloc_size   = name->dos.alloc_size;
111                 file->full_directory_info.attrib       = name->dos.attrib;
112                 file->full_directory_info.ea_size      = name->dos.ea_size;
113                 file->full_directory_info.name.s       = fname;
114                 return NT_STATUS_OK;
115
116         case RAW_SEARCH_NAME_INFO:
117                 file->name_info.file_index   = dir_index;
118                 file->name_info.name.s       = fname;
119                 return NT_STATUS_OK;
120
121         case RAW_SEARCH_BOTH_DIRECTORY_INFO:
122                 file->both_directory_info.file_index   = dir_index+1;
123                 file->both_directory_info.create_time  = name->dos.create_time;
124                 file->both_directory_info.access_time  = name->dos.access_time;
125                 file->both_directory_info.write_time   = name->dos.write_time;
126                 file->both_directory_info.change_time  = name->dos.change_time;
127                 file->both_directory_info.size         = name->st.st_size;
128                 file->both_directory_info.alloc_size   = name->dos.alloc_size;
129                 file->both_directory_info.attrib       = name->dos.attrib;
130                 file->both_directory_info.ea_size      = name->dos.ea_size;
131                 file->both_directory_info.short_name.s = pvfs_short_name(pvfs, file, name);
132                 file->both_directory_info.name.s       = fname;
133                 return NT_STATUS_OK;
134
135         case RAW_SEARCH_ID_FULL_DIRECTORY_INFO:
136                 file->id_full_directory_info.file_index   = dir_index+1;
137                 file->id_full_directory_info.create_time  = name->dos.create_time;
138                 file->id_full_directory_info.access_time  = name->dos.access_time;
139                 file->id_full_directory_info.write_time   = name->dos.write_time;
140                 file->id_full_directory_info.change_time  = name->dos.change_time;
141                 file->id_full_directory_info.size         = name->st.st_size;
142                 file->id_full_directory_info.alloc_size   = name->dos.alloc_size;
143                 file->id_full_directory_info.attrib       = name->dos.attrib;
144                 file->id_full_directory_info.ea_size      = name->dos.ea_size;
145                 file->id_full_directory_info.file_id      = name->dos.file_id;
146                 file->id_full_directory_info.name.s       = fname;
147                 return NT_STATUS_OK;
148
149         case RAW_SEARCH_ID_BOTH_DIRECTORY_INFO:
150                 file->id_both_directory_info.file_index   = dir_index+1;
151                 file->id_both_directory_info.create_time  = name->dos.create_time;
152                 file->id_both_directory_info.access_time  = name->dos.access_time;
153                 file->id_both_directory_info.write_time   = name->dos.write_time;
154                 file->id_both_directory_info.change_time  = name->dos.change_time;
155                 file->id_both_directory_info.size         = name->st.st_size;
156                 file->id_both_directory_info.alloc_size   = name->dos.alloc_size;
157                 file->id_both_directory_info.attrib       = name->dos.attrib;
158                 file->id_both_directory_info.ea_size      = name->dos.ea_size;
159                 file->id_both_directory_info.file_id      = name->dos.file_id;
160                 file->id_both_directory_info.short_name.s = pvfs_short_name(pvfs, file, name);
161                 file->id_both_directory_info.name.s       = fname;
162                 return NT_STATUS_OK;
163
164         case RAW_SEARCH_GENERIC:
165                 break;
166         }
167
168         return NT_STATUS_INVALID_LEVEL;
169 }
170
171
172 /*
173   the search fill loop
174 */
175 static NTSTATUS pvfs_search_fill(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx, 
176                                  uint_t max_count, 
177                                  struct pvfs_search_state *search,
178                                  enum smb_search_level level,
179                                  uint_t *reply_count,
180                                  void *search_private, 
181                                  BOOL (*callback)(void *, union smb_search_data *))
182 {
183         int i;
184         struct pvfs_dir *dir = search->dir;
185         NTSTATUS status;
186
187         *reply_count = 0;
188
189         for (i = search->current_index; i < dir->count;i++) {
190                 union smb_search_data *file;
191
192                 file = talloc_p(mem_ctx, union smb_search_data);
193                 if (!file) {
194                         return NT_STATUS_NO_MEMORY;
195                 }
196
197                 status = fill_search_info(pvfs, level, dir->unix_path, dir->names[i], 
198                                           search, i, file);
199                 if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
200                         talloc_free(file);
201                         continue;
202                 }
203
204                 if (!NT_STATUS_IS_OK(status)) {
205                         talloc_free(file);
206                         search->current_index = i;
207                         return status;
208                 }
209
210                 if (!callback(search_private, file)) {
211                         talloc_free(file);
212                         break;
213                 }
214                 (*reply_count)++;
215                 talloc_free(file);
216
217                 /* note that this deliberately allows a reply_count of
218                    1 for a max_count of 0. w2k3 allows this too. */
219                 if (*reply_count >= max_count) break;
220         }
221
222         search->current_index = i;
223
224         return NT_STATUS_OK;
225 }
226
227 /*
228   return the next available search handle
229 */
230 static NTSTATUS pvfs_next_search_handle(struct pvfs_state *pvfs, uint16_t *handle, 
231                                         uint_t max_handles)
232 {
233         struct pvfs_search_state *search;
234
235         if (pvfs->search.num_active_searches >= max_handles) {
236                 return NT_STATUS_INSUFFICIENT_RESOURCES;
237         }
238
239         (*handle) = (pvfs->search.next_search_handle) & (max_handles-1);
240 again:
241         for (search=pvfs->search.open_searches;search;search=search->next) {
242                 if (*handle == search->handle) {
243                         *handle = ((*handle)+1) & (max_handles-1);
244                         goto again;
245                 } 
246         }
247         pvfs->search.next_search_handle = ((*handle)+1) & (max_handles-1);
248
249         return NT_STATUS_OK;
250 }
251
252
253 /* 
254    list files in a directory matching a wildcard pattern - old SMBsearch interface
255 */
256 static NTSTATUS pvfs_search_first_old(struct ntvfs_module_context *ntvfs,
257                                       struct smbsrv_request *req, union smb_search_first *io, 
258                                       void *search_private, 
259                                       BOOL (*callback)(void *, union smb_search_data *))
260 {
261         struct pvfs_dir *dir;
262         struct pvfs_state *pvfs = ntvfs->private_data;
263         struct pvfs_search_state *search;
264         uint_t reply_count;
265         uint16_t search_attrib;
266         const char *pattern;
267         NTSTATUS status;
268         struct pvfs_filename *name;
269
270         search_attrib = io->search_first.in.search_attrib;
271         pattern       = io->search_first.in.pattern;
272
273         /* resolve the cifs name to a posix name */
274         status = pvfs_resolve_name(pvfs, req, pattern, 0, &name);
275         if (!NT_STATUS_IS_OK(status)) {
276                 return status;
277         }
278
279         if (!name->has_wildcard && !name->exists) {
280                 return STATUS_NO_MORE_FILES;
281         }
282
283         /* we initially make search a child of the request, then if we
284            need to keep it long term we steal it for the private
285            structure */
286         search = talloc_p(req, struct pvfs_search_state);
287         if (!search) {
288                 return NT_STATUS_NO_MEMORY;
289         }
290
291         dir = talloc_p(search, struct pvfs_dir);
292         if (!dir) {
293                 return NT_STATUS_NO_MEMORY;
294         }
295
296         /* do the actual directory listing */
297         status = pvfs_list(pvfs, name, dir);
298         if (!NT_STATUS_IS_OK(status)) {
299                 return status;
300         }
301
302         /* we need to give a handle back to the client so it
303            can continue a search */
304         status = pvfs_next_search_handle(pvfs, &search->handle, 0x100);
305         if (!NT_STATUS_IS_OK(status)) {
306                 return status;
307         }
308         
309         search->dir = dir;
310         search->current_index = 0;
311         search->search_attrib = search_attrib;
312
313         status = pvfs_search_fill(pvfs, req, io->search_first.in.max_count, search, io->generic.level,
314                                   &reply_count, search_private, callback);
315         if (!NT_STATUS_IS_OK(status)) {
316                 return status;
317         }
318
319         io->search_first.out.count = reply_count;
320
321         /* not matching any entries is an error */
322         if (reply_count == 0) {
323                 return STATUS_NO_MORE_FILES;
324         }
325
326         pvfs->search.num_active_searches++;
327         talloc_steal(pvfs, search);
328         DLIST_ADD(pvfs->search.open_searches, search);
329
330         return NT_STATUS_OK;
331 }
332
333 /* continue a old style search */
334 static NTSTATUS pvfs_search_next_old(struct ntvfs_module_context *ntvfs,
335                                      struct smbsrv_request *req, union smb_search_next *io, 
336                                      void *search_private, 
337                                      BOOL (*callback)(void *, union smb_search_data *))
338 {
339         struct pvfs_state *pvfs = ntvfs->private_data;
340         struct pvfs_search_state *search;
341         struct pvfs_dir *dir;
342         uint_t reply_count, max_count;
343         uint16_t handle;
344         NTSTATUS status;
345
346         handle    = io->search_next.in.id.handle;
347         max_count = io->search_next.in.max_count;
348
349         for (search=pvfs->search.open_searches; search; search = search->next) {
350                 if (search->handle == handle) break;
351         }
352         
353         if (!search) {
354                 /* we didn't find the search handle */
355                 return NT_STATUS_INVALID_HANDLE;
356         }
357
358         search->current_index = io->search_next.in.id.server_cookie;
359
360         dir = search->dir;
361
362         status = pvfs_search_fill(pvfs, req, max_count, search, io->generic.level,
363                                   &reply_count, search_private, callback);
364         if (!NT_STATUS_IS_OK(status)) {
365                 return status;
366         }
367
368         io->search_next.out.count = reply_count;
369
370         /* not matching any entries means end of search */
371         if (reply_count == 0) {
372                 DLIST_REMOVE(pvfs->search.open_searches, search);
373                 talloc_free(search);
374         }
375
376         return NT_STATUS_OK;
377 }
378
379 /* 
380    list files in a directory matching a wildcard pattern
381 */
382 NTSTATUS pvfs_search_first(struct ntvfs_module_context *ntvfs,
383                            struct smbsrv_request *req, union smb_search_first *io, 
384                            void *search_private, 
385                            BOOL (*callback)(void *, union smb_search_data *))
386 {
387         struct pvfs_dir *dir;
388         struct pvfs_state *pvfs = ntvfs->private_data;
389         struct pvfs_search_state *search;
390         uint_t reply_count;
391         uint16_t search_attrib, max_count;
392         const char *pattern;
393         NTSTATUS status;
394         struct pvfs_filename *name;
395
396         if (io->generic.level >= RAW_SEARCH_SEARCH) {
397                 return pvfs_search_first_old(ntvfs, req, io, search_private, callback);
398         }
399
400         search_attrib = io->t2ffirst.in.search_attrib;
401         pattern       = io->t2ffirst.in.pattern;
402         max_count     = io->t2ffirst.in.max_count;
403
404         /* resolve the cifs name to a posix name */
405         status = pvfs_resolve_name(pvfs, req, pattern, 0, &name);
406         if (!NT_STATUS_IS_OK(status)) {
407                 return status;
408         }
409
410         if (!name->has_wildcard && !name->exists) {
411                 return NT_STATUS_NO_SUCH_FILE;
412         }
413
414         /* we initially make search a child of the request, then if we
415            need to keep it long term we steal it for the private
416            structure */
417         search = talloc_p(req, struct pvfs_search_state);
418         if (!search) {
419                 return NT_STATUS_NO_MEMORY;
420         }
421
422         dir = talloc_p(search, struct pvfs_dir);
423         if (!dir) {
424                 return NT_STATUS_NO_MEMORY;
425         }
426
427         /* do the actual directory listing */
428         status = pvfs_list(pvfs, name, dir);
429         if (!NT_STATUS_IS_OK(status)) {
430                 return status;
431         }
432
433         /* we need to give a handle back to the client so it
434            can continue a search */
435         status = pvfs_next_search_handle(pvfs, &search->handle, 0x10000);
436         if (!NT_STATUS_IS_OK(status)) {
437                 return status;
438         }
439         
440         search->dir = dir;
441         search->current_index = 0;
442         search->search_attrib = search_attrib;
443
444         status = pvfs_search_fill(pvfs, req, max_count, search, io->generic.level,
445                                   &reply_count, search_private, callback);
446         if (!NT_STATUS_IS_OK(status)) {
447                 return status;
448         }
449
450         /* not matching any entries is an error */
451         if (reply_count == 0) {
452                 return NT_STATUS_NO_SUCH_FILE;
453         }
454
455         io->t2ffirst.out.count = reply_count;
456         io->t2ffirst.out.handle = search->handle;
457         io->t2ffirst.out.end_of_search = (search->current_index == dir->count) ? 1 : 0;
458
459         /* work out if we are going to keep the search state
460            and allow for a search continue */
461         if ((io->t2ffirst.in.flags & FLAG_TRANS2_FIND_CLOSE) ||
462             ((io->t2ffirst.in.flags & FLAG_TRANS2_FIND_CLOSE_IF_END) && 
463              io->t2ffirst.out.end_of_search)) {
464                 talloc_free(search);
465         } else {
466                 pvfs->search.num_active_searches++;
467                 talloc_steal(pvfs, search);
468                 DLIST_ADD(pvfs->search.open_searches, search);
469         }
470
471         return NT_STATUS_OK;
472 }
473
474 /* continue a search */
475 NTSTATUS pvfs_search_next(struct ntvfs_module_context *ntvfs,
476                           struct smbsrv_request *req, union smb_search_next *io, 
477                           void *search_private, 
478                           BOOL (*callback)(void *, union smb_search_data *))
479 {
480         struct pvfs_state *pvfs = ntvfs->private_data;
481         struct pvfs_search_state *search;
482         struct pvfs_dir *dir;
483         uint_t reply_count;
484         uint16_t handle;
485         NTSTATUS status;
486         int i;
487
488         if (io->generic.level >= RAW_SEARCH_SEARCH) {
489                 return pvfs_search_next_old(ntvfs, req, io, search_private, callback);
490         }
491
492         handle = io->t2fnext.in.handle;
493
494         for (search=pvfs->search.open_searches; search; search = search->next) {
495                 if (search->handle == handle) break;
496         }
497         
498         if (!search) {
499                 /* we didn't find the search handle */
500                 return NT_STATUS_INVALID_HANDLE;
501         }
502
503         dir = search->dir;
504
505         /* work out what type of continuation is being used */
506         if (io->t2fnext.in.last_name && *io->t2fnext.in.last_name) {
507                 /* look backwards first */
508                 for (i=search->current_index; i > 0; i--) {
509                         if (strcmp(io->t2fnext.in.last_name, dir->names[i-1]) == 0) {
510                                 search->current_index = i;
511                                 goto found;
512                         }
513                 }
514
515                 /* then look forwards */
516                 for (i=search->current_index+1; i <= dir->count; i++) {
517                         if (strcmp(io->t2fnext.in.last_name, dir->names[i-1]) == 0) {
518                                 search->current_index = i;
519                                 goto found;
520                         }
521                 }
522         } else if (io->t2fnext.in.flags & FLAG_TRANS2_FIND_CONTINUE) {
523                 /* plain continue - nothing to do */
524         } else {
525                 search->current_index = io->t2fnext.in.resume_key;
526         }
527
528 found:  
529         status = pvfs_search_fill(pvfs, req, io->t2fnext.in.max_count, search, io->generic.level,
530                                   &reply_count, search_private, callback);
531         if (!NT_STATUS_IS_OK(status)) {
532                 return status;
533         }
534
535         /* not matching any entries is an error */
536         if (reply_count == 0) {
537                 return NT_STATUS_NO_MORE_ENTRIES;
538         }
539
540         io->t2fnext.out.count = reply_count;
541         io->t2fnext.out.end_of_search = (search->current_index == dir->count) ? 1 : 0;
542
543         /* work out if we are going to keep the search state */
544         if ((io->t2fnext.in.flags & FLAG_TRANS2_FIND_CLOSE) ||
545             ((io->t2fnext.in.flags & FLAG_TRANS2_FIND_CLOSE_IF_END) && 
546              io->t2fnext.out.end_of_search)) {
547                 DLIST_REMOVE(pvfs->search.open_searches, search);
548                 talloc_free(search);
549         }
550
551         return NT_STATUS_OK;
552 }
553
554 /* close a search */
555 NTSTATUS pvfs_search_close(struct ntvfs_module_context *ntvfs,
556                            struct smbsrv_request *req, union smb_search_close *io)
557 {
558         struct pvfs_state *pvfs = ntvfs->private_data;
559         struct pvfs_search_state *search;
560         uint16_t handle;
561
562         if (io->generic.level == RAW_FINDCLOSE_FCLOSE) {
563                 handle = io->fclose.in.id.handle;
564         } else {
565                 handle = io->findclose.in.handle;
566         }
567
568         for (search=pvfs->search.open_searches; search; search = search->next) {
569                 if (search->handle == handle) break;
570         }
571         
572         if (!search) {
573                 /* we didn't find the search handle */
574                 return NT_STATUS_INVALID_HANDLE;
575         }
576
577         DLIST_REMOVE(pvfs->search.open_searches, search);
578         talloc_free(search);
579
580         return NT_STATUS_OK;
581 }
582