r14157: - pass a struct ntvfs_request to the ntvfs layer
[jra/samba/.git] / source4 / 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 "includes.h"
24 #include "vfs_posix.h"
25 #include "system/time.h"
26 #include "librpc/gen_ndr/ndr_security.h"
27 #include "smbd/service_stream.h"
28 #include "lib/events/events.h"
29
30
31 /* the state of a search started with pvfs_search_first() */
32 struct pvfs_search_state {
33         struct pvfs_state *pvfs;
34         uint16_t handle;
35         uint_t current_index;
36         uint16_t search_attrib;
37         uint16_t must_attrib;
38         struct pvfs_dir *dir;
39         time_t last_used;
40         uint_t num_ea_names;
41         struct ea_name *ea_names;
42         struct timed_event *te;
43 };
44
45
46 /* place a reasonable limit on old-style searches as clients tend to
47    not send search close requests */
48 #define MAX_OLD_SEARCHES 2000
49
50 /*
51   destroy an open search
52 */
53 static int pvfs_search_destructor(void *ptr)
54 {
55         struct pvfs_search_state *search = ptr;
56         idr_remove(search->pvfs->idtree_search, search->handle);
57         return 0;
58 }
59
60 /*
61   called when a search timer goes off
62 */
63 static void pvfs_search_timer(struct event_context *ev, struct timed_event *te, 
64                                       struct timeval t, void *ptr)
65 {
66         struct pvfs_search_state *search = talloc_get_type(ptr, struct pvfs_search_state);
67         talloc_free(search);
68 }
69
70 /*
71   setup a timer to destroy a open search after a inactivity period
72 */
73 static void pvfs_search_setup_timer(struct pvfs_search_state *search)
74 {
75         struct event_context *ev = search->pvfs->tcon->smb_conn->connection->event.ctx;
76         talloc_free(search->te);
77         search->te = event_add_timed(ev, search, 
78                                      timeval_current_ofs(search->pvfs->search_inactivity_time, 0), 
79                                      pvfs_search_timer, search);
80 }
81
82 /*
83   fill in a single search result for a given info level
84 */
85 static NTSTATUS fill_search_info(struct pvfs_state *pvfs,
86                                  enum smb_search_level level,
87                                  const char *unix_path,
88                                  const char *fname, 
89                                  struct pvfs_search_state *search,
90                                  uint32_t dir_index,
91                                  union smb_search_data *file)
92 {
93         struct pvfs_filename *name;
94         NTSTATUS status;
95         const char *shortname;
96
97         status = pvfs_resolve_partial(pvfs, file, unix_path, fname, &name);
98         if (!NT_STATUS_IS_OK(status)) {
99                 return status;
100         }
101
102         status = pvfs_match_attrib(pvfs, name, search->search_attrib, search->must_attrib);
103         if (!NT_STATUS_IS_OK(status)) {
104                 return status;
105         }
106
107         switch (level) {
108         case RAW_SEARCH_SEARCH:
109         case RAW_SEARCH_FFIRST:
110         case RAW_SEARCH_FUNIQUE:
111                 shortname = pvfs_short_name(pvfs, name, name);
112                 file->search.attrib           = name->dos.attrib;
113                 file->search.write_time       = nt_time_to_unix(name->dos.write_time);
114                 file->search.size             = name->st.st_size;
115                 file->search.name             = shortname;
116                 file->search.id.reserved      = search->handle >> 8;
117                 memset(file->search.id.name, ' ', sizeof(file->search.id.name));
118                 memcpy(file->search.id.name, shortname, 
119                        MIN(strlen(shortname)+1, sizeof(file->search.id.name)));
120                 file->search.id.handle        = search->handle & 0xFF;
121                 file->search.id.server_cookie = dir_index;
122                 file->search.id.client_cookie = 0;
123                 return NT_STATUS_OK;
124
125         case RAW_SEARCH_STANDARD:
126                 file->standard.resume_key   = dir_index;
127                 file->standard.create_time  = nt_time_to_unix(name->dos.create_time);
128                 file->standard.access_time  = nt_time_to_unix(name->dos.access_time);
129                 file->standard.write_time   = nt_time_to_unix(name->dos.write_time);
130                 file->standard.size         = name->st.st_size;
131                 file->standard.alloc_size   = name->dos.alloc_size;
132                 file->standard.attrib       = name->dos.attrib;
133                 file->standard.name.s       = fname;
134                 return NT_STATUS_OK;
135
136         case RAW_SEARCH_EA_SIZE:
137                 file->ea_size.resume_key   = dir_index;
138                 file->ea_size.create_time  = nt_time_to_unix(name->dos.create_time);
139                 file->ea_size.access_time  = nt_time_to_unix(name->dos.access_time);
140                 file->ea_size.write_time   = nt_time_to_unix(name->dos.write_time);
141                 file->ea_size.size         = name->st.st_size;
142                 file->ea_size.alloc_size   = name->dos.alloc_size;
143                 file->ea_size.attrib       = name->dos.attrib;
144                 file->ea_size.ea_size      = name->dos.ea_size;
145                 file->ea_size.name.s       = fname;
146                 return NT_STATUS_OK;
147
148         case RAW_SEARCH_EA_LIST:
149                 file->ea_list.resume_key   = dir_index;
150                 file->ea_list.create_time  = nt_time_to_unix(name->dos.create_time);
151                 file->ea_list.access_time  = nt_time_to_unix(name->dos.access_time);
152                 file->ea_list.write_time   = nt_time_to_unix(name->dos.write_time);
153                 file->ea_list.size         = name->st.st_size;
154                 file->ea_list.alloc_size   = name->dos.alloc_size;
155                 file->ea_list.attrib       = name->dos.attrib;
156                 file->ea_list.name.s       = fname;
157                 return pvfs_query_ea_list(pvfs, file, name, -1, 
158                                           search->num_ea_names,
159                                           search->ea_names,
160                                           &file->ea_list.eas);
161
162         case RAW_SEARCH_DIRECTORY_INFO:
163                 file->directory_info.file_index   = dir_index;
164                 file->directory_info.create_time  = name->dos.create_time;
165                 file->directory_info.access_time  = name->dos.access_time;
166                 file->directory_info.write_time   = name->dos.write_time;
167                 file->directory_info.change_time  = name->dos.change_time;
168                 file->directory_info.size         = name->st.st_size;
169                 file->directory_info.alloc_size   = name->dos.alloc_size;
170                 file->directory_info.attrib       = name->dos.attrib;
171                 file->directory_info.name.s       = fname;
172                 return NT_STATUS_OK;
173
174         case RAW_SEARCH_FULL_DIRECTORY_INFO:
175                 file->full_directory_info.file_index   = dir_index;
176                 file->full_directory_info.create_time  = name->dos.create_time;
177                 file->full_directory_info.access_time  = name->dos.access_time;
178                 file->full_directory_info.write_time   = name->dos.write_time;
179                 file->full_directory_info.change_time  = name->dos.change_time;
180                 file->full_directory_info.size         = name->st.st_size;
181                 file->full_directory_info.alloc_size   = name->dos.alloc_size;
182                 file->full_directory_info.attrib       = name->dos.attrib;
183                 file->full_directory_info.ea_size      = name->dos.ea_size;
184                 file->full_directory_info.name.s       = fname;
185                 return NT_STATUS_OK;
186
187         case RAW_SEARCH_NAME_INFO:
188                 file->name_info.file_index   = dir_index;
189                 file->name_info.name.s       = fname;
190                 return NT_STATUS_OK;
191
192         case RAW_SEARCH_BOTH_DIRECTORY_INFO:
193                 file->both_directory_info.file_index   = dir_index;
194                 file->both_directory_info.create_time  = name->dos.create_time;
195                 file->both_directory_info.access_time  = name->dos.access_time;
196                 file->both_directory_info.write_time   = name->dos.write_time;
197                 file->both_directory_info.change_time  = name->dos.change_time;
198                 file->both_directory_info.size         = name->st.st_size;
199                 file->both_directory_info.alloc_size   = name->dos.alloc_size;
200                 file->both_directory_info.attrib       = name->dos.attrib;
201                 file->both_directory_info.ea_size      = name->dos.ea_size;
202                 file->both_directory_info.short_name.s = pvfs_short_name(pvfs, file, name);
203                 file->both_directory_info.name.s       = fname;
204                 return NT_STATUS_OK;
205
206         case RAW_SEARCH_ID_FULL_DIRECTORY_INFO:
207                 file->id_full_directory_info.file_index   = dir_index;
208                 file->id_full_directory_info.create_time  = name->dos.create_time;
209                 file->id_full_directory_info.access_time  = name->dos.access_time;
210                 file->id_full_directory_info.write_time   = name->dos.write_time;
211                 file->id_full_directory_info.change_time  = name->dos.change_time;
212                 file->id_full_directory_info.size         = name->st.st_size;
213                 file->id_full_directory_info.alloc_size   = name->dos.alloc_size;
214                 file->id_full_directory_info.attrib       = name->dos.attrib;
215                 file->id_full_directory_info.ea_size      = name->dos.ea_size;
216                 file->id_full_directory_info.file_id      = name->dos.file_id;
217                 file->id_full_directory_info.name.s       = fname;
218                 return NT_STATUS_OK;
219
220         case RAW_SEARCH_ID_BOTH_DIRECTORY_INFO:
221                 file->id_both_directory_info.file_index   = dir_index;
222                 file->id_both_directory_info.create_time  = name->dos.create_time;
223                 file->id_both_directory_info.access_time  = name->dos.access_time;
224                 file->id_both_directory_info.write_time   = name->dos.write_time;
225                 file->id_both_directory_info.change_time  = name->dos.change_time;
226                 file->id_both_directory_info.size         = name->st.st_size;
227                 file->id_both_directory_info.alloc_size   = name->dos.alloc_size;
228                 file->id_both_directory_info.attrib       = name->dos.attrib;
229                 file->id_both_directory_info.ea_size      = name->dos.ea_size;
230                 file->id_both_directory_info.file_id      = name->dos.file_id;
231                 file->id_both_directory_info.short_name.s = pvfs_short_name(pvfs, file, name);
232                 file->id_both_directory_info.name.s       = fname;
233                 return NT_STATUS_OK;
234
235         case RAW_SEARCH_GENERIC:
236                 break;
237         }
238
239         return NT_STATUS_INVALID_LEVEL;
240 }
241
242
243 /*
244   the search fill loop
245 */
246 static NTSTATUS pvfs_search_fill(struct pvfs_state *pvfs, TALLOC_CTX *mem_ctx, 
247                                  uint_t max_count, 
248                                  struct pvfs_search_state *search,
249                                  enum smb_search_level level,
250                                  uint_t *reply_count,
251                                  void *search_private, 
252                                  BOOL (*callback)(void *, union smb_search_data *))
253 {
254         struct pvfs_dir *dir = search->dir;
255         NTSTATUS status;
256
257         *reply_count = 0;
258
259         if (max_count == 0) {
260                 max_count = 1;
261         }
262
263         while ((*reply_count) < max_count) {
264                 union smb_search_data *file;
265                 const char *name;
266                 uint_t ofs = search->current_index;
267
268                 name = pvfs_list_next(dir, &search->current_index);
269                 if (name == NULL) break;
270
271                 file = talloc(mem_ctx, union smb_search_data);
272                 if (!file) {
273                         return NT_STATUS_NO_MEMORY;
274                 }
275
276                 status = fill_search_info(pvfs, level, 
277                                           pvfs_list_unix_path(dir), name, 
278                                           search, search->current_index, file);
279                 if (!NT_STATUS_IS_OK(status)) {
280                         talloc_free(file);
281                         continue;
282                 }
283
284                 if (!callback(search_private, file)) {
285                         talloc_free(file);
286                         search->current_index = ofs;
287                         break;
288                 }
289
290                 (*reply_count)++;
291                 talloc_free(file);
292         }
293
294         pvfs_search_setup_timer(search);
295
296         return NT_STATUS_OK;
297 }
298
299 /*
300   we've run out of search handles - cleanup those that the client forgot
301   to close
302 */
303 static void pvfs_search_cleanup(struct pvfs_state *pvfs)
304 {
305         int i;
306         time_t t = time(NULL);
307
308         for (i=0;i<MAX_OLD_SEARCHES;i++) {
309                 struct pvfs_search_state *search = idr_find(pvfs->idtree_search, i);
310                 if (search == NULL) return;
311                 if (pvfs_list_eos(search->dir, search->current_index) &&
312                     search->last_used != 0 &&
313                     t > search->last_used + 30) {
314                         /* its almost certainly been forgotten
315                          about */
316                         talloc_free(search);
317                 }
318         }
319 }
320
321
322 /* 
323    list files in a directory matching a wildcard pattern - old SMBsearch interface
324 */
325 static NTSTATUS pvfs_search_first_old(struct ntvfs_module_context *ntvfs,
326                                       struct ntvfs_request *req, union smb_search_first *io, 
327                                       void *search_private, 
328                                       BOOL (*callback)(void *, union smb_search_data *))
329 {
330         struct pvfs_dir *dir;
331         struct pvfs_state *pvfs = ntvfs->private_data;
332         struct pvfs_search_state *search;
333         uint_t reply_count;
334         uint16_t search_attrib;
335         const char *pattern;
336         NTSTATUS status;
337         struct pvfs_filename *name;
338         int id;
339
340         search_attrib = io->search_first.in.search_attrib;
341         pattern       = io->search_first.in.pattern;
342
343         /* resolve the cifs name to a posix name */
344         status = pvfs_resolve_name(pvfs, req, pattern, PVFS_RESOLVE_WILDCARD, &name);
345         if (!NT_STATUS_IS_OK(status)) {
346                 return status;
347         }
348
349         if (!name->has_wildcard && !name->exists) {
350                 return STATUS_NO_MORE_FILES;
351         }
352
353         status = pvfs_access_check_parent(pvfs, req, name, SEC_DIR_TRAVERSE | SEC_DIR_LIST);
354         if (!NT_STATUS_IS_OK(status)) {
355                 return status;
356         }
357
358         /* we initially make search a child of the request, then if we
359            need to keep it long term we steal it for the private
360            structure */
361         search = talloc(req, struct pvfs_search_state);
362         if (!search) {
363                 return NT_STATUS_NO_MEMORY;
364         }
365
366         /* do the actual directory listing */
367         status = pvfs_list_start(pvfs, name, search, &dir);
368         if (!NT_STATUS_IS_OK(status)) {
369                 return status;
370         }
371
372         /* we need to give a handle back to the client so it
373            can continue a search */
374         id = idr_get_new(pvfs->idtree_search, search, MAX_OLD_SEARCHES);
375         if (id == -1) {
376                 pvfs_search_cleanup(pvfs);
377                 id = idr_get_new(pvfs->idtree_search, search, MAX_OLD_SEARCHES);
378         }
379         if (id == -1) {
380                 return NT_STATUS_INSUFFICIENT_RESOURCES;
381         }
382
383         search->pvfs = pvfs;
384         search->handle = id;
385         search->dir = dir;
386         search->current_index = 0;
387         search->search_attrib = search_attrib & 0xFF;
388         search->must_attrib = (search_attrib>>8) & 0xFF;
389         search->last_used = time(NULL);
390         search->te = NULL;
391
392         talloc_set_destructor(search, pvfs_search_destructor);
393
394         status = pvfs_search_fill(pvfs, req, io->search_first.in.max_count, search, io->generic.level,
395                                   &reply_count, search_private, callback);
396         if (!NT_STATUS_IS_OK(status)) {
397                 return status;
398         }
399
400         io->search_first.out.count = reply_count;
401
402         /* not matching any entries is an error */
403         if (reply_count == 0) {
404                 return STATUS_NO_MORE_FILES;
405         }
406
407         talloc_steal(pvfs, search);
408
409         return NT_STATUS_OK;
410 }
411
412 /* continue a old style search */
413 static NTSTATUS pvfs_search_next_old(struct ntvfs_module_context *ntvfs,
414                                      struct ntvfs_request *req, union smb_search_next *io, 
415                                      void *search_private, 
416                                      BOOL (*callback)(void *, union smb_search_data *))
417 {
418         struct pvfs_state *pvfs = ntvfs->private_data;
419         struct pvfs_search_state *search;
420         struct pvfs_dir *dir;
421         uint_t reply_count, max_count;
422         uint16_t handle;
423         NTSTATUS status;
424
425         handle    = io->search_next.in.id.handle | (io->search_next.in.id.reserved<<8);
426         max_count = io->search_next.in.max_count;
427
428         search = idr_find(pvfs->idtree_search, handle);
429         if (search == NULL) {
430                 /* we didn't find the search handle */
431                 return NT_STATUS_INVALID_HANDLE;
432         }
433
434         search->current_index = io->search_next.in.id.server_cookie;
435         search->last_used = time(NULL);
436         dir = search->dir;
437
438         status = pvfs_search_fill(pvfs, req, max_count, search, io->generic.level,
439                                   &reply_count, search_private, callback);
440         if (!NT_STATUS_IS_OK(status)) {
441                 return status;
442         }
443
444         io->search_next.out.count = reply_count;
445
446         /* not matching any entries means end of search */
447         if (reply_count == 0) {
448                 talloc_free(search);
449         }
450
451         return NT_STATUS_OK;
452 }
453
454 /* 
455    list files in a directory matching a wildcard pattern
456 */
457 NTSTATUS pvfs_search_first(struct ntvfs_module_context *ntvfs,
458                            struct ntvfs_request *req, union smb_search_first *io, 
459                            void *search_private, 
460                            BOOL (*callback)(void *, union smb_search_data *))
461 {
462         struct pvfs_dir *dir;
463         struct pvfs_state *pvfs = ntvfs->private_data;
464         struct pvfs_search_state *search;
465         uint_t reply_count;
466         uint16_t search_attrib, max_count;
467         const char *pattern;
468         NTSTATUS status;
469         struct pvfs_filename *name;
470         int id;
471
472         if (io->generic.level >= RAW_SEARCH_SEARCH) {
473                 return pvfs_search_first_old(ntvfs, req, io, search_private, callback);
474         }
475
476         search_attrib = io->t2ffirst.in.search_attrib;
477         pattern       = io->t2ffirst.in.pattern;
478         max_count     = io->t2ffirst.in.max_count;
479
480         /* resolve the cifs name to a posix name */
481         status = pvfs_resolve_name(pvfs, req, pattern, PVFS_RESOLVE_WILDCARD, &name);
482         if (!NT_STATUS_IS_OK(status)) {
483                 return status;
484         }
485
486         if (!name->has_wildcard && !name->exists) {
487                 return NT_STATUS_NO_SUCH_FILE;
488         }
489
490         status = pvfs_access_check_parent(pvfs, req, name, SEC_DIR_TRAVERSE | SEC_DIR_LIST);
491         if (!NT_STATUS_IS_OK(status)) {
492                 return status;
493         }
494
495         /* we initially make search a child of the request, then if we
496            need to keep it long term we steal it for the private
497            structure */
498         search = talloc(req, struct pvfs_search_state);
499         if (!search) {
500                 return NT_STATUS_NO_MEMORY;
501         }
502
503         /* do the actual directory listing */
504         status = pvfs_list_start(pvfs, name, search, &dir);
505         if (!NT_STATUS_IS_OK(status)) {
506                 return status;
507         }
508
509         id = idr_get_new(pvfs->idtree_search, search, UINT16_MAX);
510         if (id == -1) {
511                 return NT_STATUS_INSUFFICIENT_RESOURCES;
512         }
513
514         search->pvfs = pvfs;
515         search->handle = id;
516         search->dir = dir;
517         search->current_index = 0;
518         search->search_attrib = search_attrib;
519         search->must_attrib = 0;
520         search->last_used = 0;
521         search->num_ea_names = io->t2ffirst.in.num_names;
522         search->ea_names = io->t2ffirst.in.ea_names;
523         search->te = NULL;
524
525         talloc_set_destructor(search, pvfs_search_destructor);
526
527         status = pvfs_search_fill(pvfs, req, max_count, search, io->generic.level,
528                                   &reply_count, search_private, callback);
529         if (!NT_STATUS_IS_OK(status)) {
530                 return status;
531         }
532
533         /* not matching any entries is an error */
534         if (reply_count == 0) {
535                 return NT_STATUS_NO_SUCH_FILE;
536         }
537
538         io->t2ffirst.out.count = reply_count;
539         io->t2ffirst.out.handle = search->handle;
540         io->t2ffirst.out.end_of_search = pvfs_list_eos(dir, search->current_index) ? 1 : 0;
541
542         /* work out if we are going to keep the search state
543            and allow for a search continue */
544         if ((io->t2ffirst.in.flags & FLAG_TRANS2_FIND_CLOSE) ||
545             ((io->t2ffirst.in.flags & FLAG_TRANS2_FIND_CLOSE_IF_END) && 
546              io->t2ffirst.out.end_of_search)) {
547                 talloc_free(search);
548         } else {
549                 talloc_steal(pvfs, search);
550         }
551
552         return NT_STATUS_OK;
553 }
554
555 /* continue a search */
556 NTSTATUS pvfs_search_next(struct ntvfs_module_context *ntvfs,
557                           struct ntvfs_request *req, union smb_search_next *io, 
558                           void *search_private, 
559                           BOOL (*callback)(void *, union smb_search_data *))
560 {
561         struct pvfs_state *pvfs = ntvfs->private_data;
562         struct pvfs_search_state *search;
563         struct pvfs_dir *dir;
564         uint_t reply_count;
565         uint16_t handle;
566         NTSTATUS status;
567
568         if (io->generic.level >= RAW_SEARCH_SEARCH) {
569                 return pvfs_search_next_old(ntvfs, req, io, search_private, callback);
570         }
571
572         handle = io->t2fnext.in.handle;
573
574         search = idr_find(pvfs->idtree_search, handle);
575         if (search == NULL) {
576                 /* we didn't find the search handle */
577                 return NT_STATUS_INVALID_HANDLE;
578         }
579
580         dir = search->dir;
581
582         /* work out what type of continuation is being used */
583         if (io->t2fnext.in.last_name && *io->t2fnext.in.last_name) {
584                 status = pvfs_list_seek(dir, io->t2fnext.in.last_name, &search->current_index);
585                 if (!NT_STATUS_IS_OK(status)) {
586                         if (io->t2fnext.in.resume_key) {
587                                 search->current_index = io->t2fnext.in.resume_key;
588                         } else {
589                                 return status;
590                         }
591                 }
592         } else if (io->t2fnext.in.flags & FLAG_TRANS2_FIND_CONTINUE) {
593                 /* plain continue - nothing to do */
594         } else {
595                 search->current_index = io->t2fnext.in.resume_key;
596         }
597
598         search->num_ea_names = io->t2fnext.in.num_names;
599         search->ea_names = io->t2fnext.in.ea_names;
600
601         status = pvfs_search_fill(pvfs, req, io->t2fnext.in.max_count, search, io->generic.level,
602                                   &reply_count, search_private, callback);
603         if (!NT_STATUS_IS_OK(status)) {
604                 return status;
605         }
606
607         io->t2fnext.out.count = reply_count;
608         io->t2fnext.out.end_of_search = pvfs_list_eos(dir, search->current_index) ? 1 : 0;
609
610         /* work out if we are going to keep the search state */
611         if ((io->t2fnext.in.flags & FLAG_TRANS2_FIND_CLOSE) ||
612             ((io->t2fnext.in.flags & FLAG_TRANS2_FIND_CLOSE_IF_END) && 
613              io->t2fnext.out.end_of_search)) {
614                 talloc_free(search);
615         }
616
617         return NT_STATUS_OK;
618 }
619
620 /* close a search */
621 NTSTATUS pvfs_search_close(struct ntvfs_module_context *ntvfs,
622                            struct ntvfs_request *req, union smb_search_close *io)
623 {
624         struct pvfs_state *pvfs = ntvfs->private_data;
625         struct pvfs_search_state *search;
626         uint16_t handle;
627
628         if (io->generic.level == RAW_FINDCLOSE_FCLOSE) {
629                 handle = io->fclose.in.id.handle;
630         } else {
631                 handle = io->findclose.in.handle;
632         }
633
634         search = idr_find(pvfs->idtree_search, handle);
635         if (search == NULL) {
636                 /* we didn't find the search handle */
637                 return NT_STATUS_INVALID_HANDLE;
638         }
639
640         talloc_free(search);
641
642         return NT_STATUS_OK;
643 }
644