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