2 Unix SMB/CIFS implementation.
4 POSIX NTVFS backend - directory search functions
6 Copyright (C) Andrew Tridgell 2004
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.
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.
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.
24 #include "vfs_posix.h"
25 #include "system/time.h"
26 #include "librpc/gen_ndr/ndr_security.h"
29 /* the state of a search started with pvfs_search_first() */
30 struct pvfs_search_state
{
31 struct pvfs_state
*pvfs
;
34 uint16_t search_attrib
;
39 struct ea_name
*ea_names
;
43 /* place a reasonable limit on old-style searches as clients tend to
44 not send search close requests */
45 #define MAX_OLD_SEARCHES 2000
48 destroy an open search
50 static int pvfs_search_destructor(void *ptr
)
52 struct pvfs_search_state
*search
= ptr
;
53 idr_remove(search
->pvfs
->idtree_search
, search
->handle
);
58 fill in a single search result for a given info level
60 static NTSTATUS
fill_search_info(struct pvfs_state
*pvfs
,
61 enum smb_search_level level
,
62 const char *unix_path
,
64 struct pvfs_search_state
*search
,
66 union smb_search_data
*file
)
68 struct pvfs_filename
*name
;
70 const char *shortname
;
72 status
= pvfs_resolve_partial(pvfs
, file
, unix_path
, fname
, &name
);
73 if (!NT_STATUS_IS_OK(status
)) {
77 status
= pvfs_match_attrib(pvfs
, name
, search
->search_attrib
, search
->must_attrib
);
78 if (!NT_STATUS_IS_OK(status
)) {
83 case RAW_SEARCH_SEARCH
:
84 case RAW_SEARCH_FFIRST
:
85 case RAW_SEARCH_FUNIQUE
:
86 shortname
= pvfs_short_name(pvfs
, name
, name
);
87 file
->search
.attrib
= name
->dos
.attrib
;
88 file
->search
.write_time
= nt_time_to_unix(name
->dos
.write_time
);
89 file
->search
.size
= name
->st
.st_size
;
90 file
->search
.name
= shortname
;
91 file
->search
.id
.reserved
= search
->handle
>> 8;
92 memset(file
->search
.id
.name
, ' ', sizeof(file
->search
.id
.name
));
93 memcpy(file
->search
.id
.name
, shortname
,
94 MIN(strlen(shortname
)+1, sizeof(file
->search
.id
.name
)));
95 file
->search
.id
.handle
= search
->handle
& 0xFF;
96 file
->search
.id
.server_cookie
= dir_index
;
97 file
->search
.id
.client_cookie
= 0;
100 case RAW_SEARCH_STANDARD
:
101 file
->standard
.resume_key
= dir_index
;
102 file
->standard
.create_time
= nt_time_to_unix(name
->dos
.create_time
);
103 file
->standard
.access_time
= nt_time_to_unix(name
->dos
.access_time
);
104 file
->standard
.write_time
= nt_time_to_unix(name
->dos
.write_time
);
105 file
->standard
.size
= name
->st
.st_size
;
106 file
->standard
.alloc_size
= name
->dos
.alloc_size
;
107 file
->standard
.attrib
= name
->dos
.attrib
;
108 file
->standard
.name
.s
= fname
;
111 case RAW_SEARCH_EA_SIZE
:
112 file
->ea_size
.resume_key
= dir_index
;
113 file
->ea_size
.create_time
= nt_time_to_unix(name
->dos
.create_time
);
114 file
->ea_size
.access_time
= nt_time_to_unix(name
->dos
.access_time
);
115 file
->ea_size
.write_time
= nt_time_to_unix(name
->dos
.write_time
);
116 file
->ea_size
.size
= name
->st
.st_size
;
117 file
->ea_size
.alloc_size
= name
->dos
.alloc_size
;
118 file
->ea_size
.attrib
= name
->dos
.attrib
;
119 file
->ea_size
.ea_size
= name
->dos
.ea_size
;
120 file
->ea_size
.name
.s
= fname
;
123 case RAW_SEARCH_EA_LIST
:
124 file
->ea_list
.resume_key
= dir_index
;
125 file
->ea_list
.create_time
= nt_time_to_unix(name
->dos
.create_time
);
126 file
->ea_list
.access_time
= nt_time_to_unix(name
->dos
.access_time
);
127 file
->ea_list
.write_time
= nt_time_to_unix(name
->dos
.write_time
);
128 file
->ea_list
.size
= name
->st
.st_size
;
129 file
->ea_list
.alloc_size
= name
->dos
.alloc_size
;
130 file
->ea_list
.attrib
= name
->dos
.attrib
;
131 file
->ea_list
.name
.s
= fname
;
132 return pvfs_query_ea_list(pvfs
, file
, name
, -1,
133 search
->num_ea_names
,
137 case RAW_SEARCH_DIRECTORY_INFO
:
138 file
->directory_info
.file_index
= dir_index
;
139 file
->directory_info
.create_time
= name
->dos
.create_time
;
140 file
->directory_info
.access_time
= name
->dos
.access_time
;
141 file
->directory_info
.write_time
= name
->dos
.write_time
;
142 file
->directory_info
.change_time
= name
->dos
.change_time
;
143 file
->directory_info
.size
= name
->st
.st_size
;
144 file
->directory_info
.alloc_size
= name
->dos
.alloc_size
;
145 file
->directory_info
.attrib
= name
->dos
.attrib
;
146 file
->directory_info
.name
.s
= fname
;
149 case RAW_SEARCH_FULL_DIRECTORY_INFO
:
150 file
->full_directory_info
.file_index
= dir_index
;
151 file
->full_directory_info
.create_time
= name
->dos
.create_time
;
152 file
->full_directory_info
.access_time
= name
->dos
.access_time
;
153 file
->full_directory_info
.write_time
= name
->dos
.write_time
;
154 file
->full_directory_info
.change_time
= name
->dos
.change_time
;
155 file
->full_directory_info
.size
= name
->st
.st_size
;
156 file
->full_directory_info
.alloc_size
= name
->dos
.alloc_size
;
157 file
->full_directory_info
.attrib
= name
->dos
.attrib
;
158 file
->full_directory_info
.ea_size
= name
->dos
.ea_size
;
159 file
->full_directory_info
.name
.s
= fname
;
162 case RAW_SEARCH_NAME_INFO
:
163 file
->name_info
.file_index
= dir_index
;
164 file
->name_info
.name
.s
= fname
;
167 case RAW_SEARCH_BOTH_DIRECTORY_INFO
:
168 file
->both_directory_info
.file_index
= dir_index
;
169 file
->both_directory_info
.create_time
= name
->dos
.create_time
;
170 file
->both_directory_info
.access_time
= name
->dos
.access_time
;
171 file
->both_directory_info
.write_time
= name
->dos
.write_time
;
172 file
->both_directory_info
.change_time
= name
->dos
.change_time
;
173 file
->both_directory_info
.size
= name
->st
.st_size
;
174 file
->both_directory_info
.alloc_size
= name
->dos
.alloc_size
;
175 file
->both_directory_info
.attrib
= name
->dos
.attrib
;
176 file
->both_directory_info
.ea_size
= name
->dos
.ea_size
;
177 file
->both_directory_info
.short_name
.s
= pvfs_short_name(pvfs
, file
, name
);
178 file
->both_directory_info
.name
.s
= fname
;
181 case RAW_SEARCH_ID_FULL_DIRECTORY_INFO
:
182 file
->id_full_directory_info
.file_index
= dir_index
;
183 file
->id_full_directory_info
.create_time
= name
->dos
.create_time
;
184 file
->id_full_directory_info
.access_time
= name
->dos
.access_time
;
185 file
->id_full_directory_info
.write_time
= name
->dos
.write_time
;
186 file
->id_full_directory_info
.change_time
= name
->dos
.change_time
;
187 file
->id_full_directory_info
.size
= name
->st
.st_size
;
188 file
->id_full_directory_info
.alloc_size
= name
->dos
.alloc_size
;
189 file
->id_full_directory_info
.attrib
= name
->dos
.attrib
;
190 file
->id_full_directory_info
.ea_size
= name
->dos
.ea_size
;
191 file
->id_full_directory_info
.file_id
= name
->dos
.file_id
;
192 file
->id_full_directory_info
.name
.s
= fname
;
195 case RAW_SEARCH_ID_BOTH_DIRECTORY_INFO
:
196 file
->id_both_directory_info
.file_index
= dir_index
;
197 file
->id_both_directory_info
.create_time
= name
->dos
.create_time
;
198 file
->id_both_directory_info
.access_time
= name
->dos
.access_time
;
199 file
->id_both_directory_info
.write_time
= name
->dos
.write_time
;
200 file
->id_both_directory_info
.change_time
= name
->dos
.change_time
;
201 file
->id_both_directory_info
.size
= name
->st
.st_size
;
202 file
->id_both_directory_info
.alloc_size
= name
->dos
.alloc_size
;
203 file
->id_both_directory_info
.attrib
= name
->dos
.attrib
;
204 file
->id_both_directory_info
.ea_size
= name
->dos
.ea_size
;
205 file
->id_both_directory_info
.file_id
= name
->dos
.file_id
;
206 file
->id_both_directory_info
.short_name
.s
= pvfs_short_name(pvfs
, file
, name
);
207 file
->id_both_directory_info
.name
.s
= fname
;
210 case RAW_SEARCH_GENERIC
:
214 return NT_STATUS_INVALID_LEVEL
;
221 static NTSTATUS
pvfs_search_fill(struct pvfs_state
*pvfs
, TALLOC_CTX
*mem_ctx
,
223 struct pvfs_search_state
*search
,
224 enum smb_search_level level
,
226 void *search_private
,
227 BOOL (*callback
)(void *, union smb_search_data
*))
229 struct pvfs_dir
*dir
= search
->dir
;
234 if (max_count
== 0) {
238 while ((*reply_count
) < max_count
) {
239 union smb_search_data
*file
;
241 uint_t ofs
= search
->current_index
;
243 name
= pvfs_list_next(dir
, &search
->current_index
);
244 if (name
== NULL
) break;
246 file
= talloc(mem_ctx
, union smb_search_data
);
248 return NT_STATUS_NO_MEMORY
;
251 status
= fill_search_info(pvfs
, level
,
252 pvfs_list_unix_path(dir
), name
,
253 search
, search
->current_index
, file
);
254 if (!NT_STATUS_IS_OK(status
)) {
259 if (!callback(search_private
, file
)) {
261 search
->current_index
= ofs
;
269 pvfs_list_hibernate(dir
);
275 we've run out of search handles - cleanup those that the client forgot
278 static void pvfs_search_cleanup(struct pvfs_state
*pvfs
)
281 time_t t
= time(NULL
);
283 for (i
=0;i
<MAX_OLD_SEARCHES
;i
++) {
284 struct pvfs_search_state
*search
= idr_find(pvfs
->idtree_search
, i
);
285 if (search
== NULL
) return;
286 if (pvfs_list_eos(search
->dir
, search
->current_index
) &&
287 search
->last_used
!= 0 &&
288 t
> search
->last_used
+ 30) {
289 /* its almost certainly been forgotten
298 list files in a directory matching a wildcard pattern - old SMBsearch interface
300 static NTSTATUS
pvfs_search_first_old(struct ntvfs_module_context
*ntvfs
,
301 struct smbsrv_request
*req
, union smb_search_first
*io
,
302 void *search_private
,
303 BOOL (*callback
)(void *, union smb_search_data
*))
305 struct pvfs_dir
*dir
;
306 struct pvfs_state
*pvfs
= ntvfs
->private_data
;
307 struct pvfs_search_state
*search
;
309 uint16_t search_attrib
;
312 struct pvfs_filename
*name
;
315 search_attrib
= io
->search_first
.in
.search_attrib
;
316 pattern
= io
->search_first
.in
.pattern
;
318 /* resolve the cifs name to a posix name */
319 status
= pvfs_resolve_name(pvfs
, req
, pattern
, PVFS_RESOLVE_WILDCARD
, &name
);
320 if (!NT_STATUS_IS_OK(status
)) {
324 if (!name
->has_wildcard
&& !name
->exists
) {
325 return STATUS_NO_MORE_FILES
;
328 status
= pvfs_access_check_parent(pvfs
, req
, name
, SEC_DIR_TRAVERSE
| SEC_DIR_LIST
);
329 if (!NT_STATUS_IS_OK(status
)) {
333 /* we initially make search a child of the request, then if we
334 need to keep it long term we steal it for the private
336 search
= talloc(req
, struct pvfs_search_state
);
338 return NT_STATUS_NO_MEMORY
;
341 /* do the actual directory listing */
342 status
= pvfs_list_start(pvfs
, name
, search
, &dir
);
343 if (!NT_STATUS_IS_OK(status
)) {
347 /* we need to give a handle back to the client so it
348 can continue a search */
349 id
= idr_get_new(pvfs
->idtree_search
, search
, MAX_OLD_SEARCHES
);
351 pvfs_search_cleanup(pvfs
);
352 id
= idr_get_new(pvfs
->idtree_search
, search
, MAX_OLD_SEARCHES
);
355 return NT_STATUS_INSUFFICIENT_RESOURCES
;
361 search
->current_index
= 0;
362 search
->search_attrib
= search_attrib
& 0xFF;
363 search
->must_attrib
= (search_attrib
>>8) & 0xFF;
364 search
->last_used
= time(NULL
);
366 talloc_set_destructor(search
, pvfs_search_destructor
);
368 status
= pvfs_search_fill(pvfs
, req
, io
->search_first
.in
.max_count
, search
, io
->generic
.level
,
369 &reply_count
, search_private
, callback
);
370 if (!NT_STATUS_IS_OK(status
)) {
374 io
->search_first
.out
.count
= reply_count
;
376 /* not matching any entries is an error */
377 if (reply_count
== 0) {
378 return STATUS_NO_MORE_FILES
;
381 talloc_steal(pvfs
, search
);
386 /* continue a old style search */
387 static NTSTATUS
pvfs_search_next_old(struct ntvfs_module_context
*ntvfs
,
388 struct smbsrv_request
*req
, union smb_search_next
*io
,
389 void *search_private
,
390 BOOL (*callback
)(void *, union smb_search_data
*))
392 struct pvfs_state
*pvfs
= ntvfs
->private_data
;
393 struct pvfs_search_state
*search
;
394 struct pvfs_dir
*dir
;
395 uint_t reply_count
, max_count
;
399 handle
= io
->search_next
.in
.id
.handle
| (io
->search_next
.in
.id
.reserved
<<8);
400 max_count
= io
->search_next
.in
.max_count
;
402 search
= idr_find(pvfs
->idtree_search
, handle
);
403 if (search
== NULL
) {
404 /* we didn't find the search handle */
405 return NT_STATUS_INVALID_HANDLE
;
408 search
->current_index
= io
->search_next
.in
.id
.server_cookie
;
409 search
->last_used
= time(NULL
);
412 status
= pvfs_list_wakeup(dir
, &search
->current_index
);
413 if (!NT_STATUS_IS_OK(status
)) {
417 status
= pvfs_search_fill(pvfs
, req
, max_count
, search
, io
->generic
.level
,
418 &reply_count
, search_private
, callback
);
419 if (!NT_STATUS_IS_OK(status
)) {
423 io
->search_next
.out
.count
= reply_count
;
425 /* not matching any entries means end of search */
426 if (reply_count
== 0) {
434 list files in a directory matching a wildcard pattern
436 NTSTATUS
pvfs_search_first(struct ntvfs_module_context
*ntvfs
,
437 struct smbsrv_request
*req
, union smb_search_first
*io
,
438 void *search_private
,
439 BOOL (*callback
)(void *, union smb_search_data
*))
441 struct pvfs_dir
*dir
;
442 struct pvfs_state
*pvfs
= ntvfs
->private_data
;
443 struct pvfs_search_state
*search
;
445 uint16_t search_attrib
, max_count
;
448 struct pvfs_filename
*name
;
451 if (io
->generic
.level
>= RAW_SEARCH_SEARCH
) {
452 return pvfs_search_first_old(ntvfs
, req
, io
, search_private
, callback
);
455 search_attrib
= io
->t2ffirst
.in
.search_attrib
;
456 pattern
= io
->t2ffirst
.in
.pattern
;
457 max_count
= io
->t2ffirst
.in
.max_count
;
459 /* resolve the cifs name to a posix name */
460 status
= pvfs_resolve_name(pvfs
, req
, pattern
, PVFS_RESOLVE_WILDCARD
, &name
);
461 if (!NT_STATUS_IS_OK(status
)) {
465 if (!name
->has_wildcard
&& !name
->exists
) {
466 return NT_STATUS_NO_SUCH_FILE
;
469 status
= pvfs_access_check_parent(pvfs
, req
, name
, SEC_DIR_TRAVERSE
| SEC_DIR_LIST
);
470 if (!NT_STATUS_IS_OK(status
)) {
474 /* we initially make search a child of the request, then if we
475 need to keep it long term we steal it for the private
477 search
= talloc(req
, struct pvfs_search_state
);
479 return NT_STATUS_NO_MEMORY
;
482 /* do the actual directory listing */
483 status
= pvfs_list_start(pvfs
, name
, search
, &dir
);
484 if (!NT_STATUS_IS_OK(status
)) {
488 id
= idr_get_new(pvfs
->idtree_search
, search
, UINT16_MAX
);
490 return NT_STATUS_INSUFFICIENT_RESOURCES
;
496 search
->current_index
= 0;
497 search
->search_attrib
= search_attrib
;
498 search
->must_attrib
= 0;
499 search
->last_used
= 0;
500 search
->num_ea_names
= io
->t2ffirst
.in
.num_names
;
501 search
->ea_names
= io
->t2ffirst
.in
.ea_names
;
503 talloc_set_destructor(search
, pvfs_search_destructor
);
505 status
= pvfs_search_fill(pvfs
, req
, max_count
, search
, io
->generic
.level
,
506 &reply_count
, search_private
, callback
);
507 if (!NT_STATUS_IS_OK(status
)) {
511 /* not matching any entries is an error */
512 if (reply_count
== 0) {
513 return NT_STATUS_NO_SUCH_FILE
;
516 io
->t2ffirst
.out
.count
= reply_count
;
517 io
->t2ffirst
.out
.handle
= search
->handle
;
518 io
->t2ffirst
.out
.end_of_search
= pvfs_list_eos(dir
, search
->current_index
) ? 1 : 0;
520 /* work out if we are going to keep the search state
521 and allow for a search continue */
522 if ((io
->t2ffirst
.in
.flags
& FLAG_TRANS2_FIND_CLOSE
) ||
523 ((io
->t2ffirst
.in
.flags
& FLAG_TRANS2_FIND_CLOSE_IF_END
) &&
524 io
->t2ffirst
.out
.end_of_search
)) {
527 talloc_steal(pvfs
, search
);
533 /* continue a search */
534 NTSTATUS
pvfs_search_next(struct ntvfs_module_context
*ntvfs
,
535 struct smbsrv_request
*req
, union smb_search_next
*io
,
536 void *search_private
,
537 BOOL (*callback
)(void *, union smb_search_data
*))
539 struct pvfs_state
*pvfs
= ntvfs
->private_data
;
540 struct pvfs_search_state
*search
;
541 struct pvfs_dir
*dir
;
546 if (io
->generic
.level
>= RAW_SEARCH_SEARCH
) {
547 return pvfs_search_next_old(ntvfs
, req
, io
, search_private
, callback
);
550 handle
= io
->t2fnext
.in
.handle
;
552 search
= idr_find(pvfs
->idtree_search
, handle
);
553 if (search
== NULL
) {
554 /* we didn't find the search handle */
555 return NT_STATUS_INVALID_HANDLE
;
560 /* work out what type of continuation is being used */
561 if (io
->t2fnext
.in
.last_name
&& *io
->t2fnext
.in
.last_name
) {
562 status
= pvfs_list_seek(dir
, io
->t2fnext
.in
.last_name
, &search
->current_index
);
563 if (!NT_STATUS_IS_OK(status
)) {
564 if (io
->t2fnext
.in
.resume_key
) {
565 search
->current_index
= io
->t2fnext
.in
.resume_key
;
570 } else if (io
->t2fnext
.in
.flags
& FLAG_TRANS2_FIND_CONTINUE
) {
571 /* plain continue - nothing to do */
573 search
->current_index
= io
->t2fnext
.in
.resume_key
;
576 status
= pvfs_list_wakeup(dir
, &search
->current_index
);
577 if (!NT_STATUS_IS_OK(status
)) {
581 search
->num_ea_names
= io
->t2fnext
.in
.num_names
;
582 search
->ea_names
= io
->t2fnext
.in
.ea_names
;
584 status
= pvfs_search_fill(pvfs
, req
, io
->t2fnext
.in
.max_count
, search
, io
->generic
.level
,
585 &reply_count
, search_private
, callback
);
586 if (!NT_STATUS_IS_OK(status
)) {
590 io
->t2fnext
.out
.count
= reply_count
;
591 io
->t2fnext
.out
.end_of_search
= pvfs_list_eos(dir
, search
->current_index
) ? 1 : 0;
593 /* work out if we are going to keep the search state */
594 if ((io
->t2fnext
.in
.flags
& FLAG_TRANS2_FIND_CLOSE
) ||
595 ((io
->t2fnext
.in
.flags
& FLAG_TRANS2_FIND_CLOSE_IF_END
) &&
596 io
->t2fnext
.out
.end_of_search
)) {
604 NTSTATUS
pvfs_search_close(struct ntvfs_module_context
*ntvfs
,
605 struct smbsrv_request
*req
, union smb_search_close
*io
)
607 struct pvfs_state
*pvfs
= ntvfs
->private_data
;
608 struct pvfs_search_state
*search
;
611 if (io
->generic
.level
== RAW_FINDCLOSE_FCLOSE
) {
612 handle
= io
->fclose
.in
.id
.handle
;
614 handle
= io
->findclose
.in
.handle
;
617 search
= idr_find(pvfs
->idtree_search
, handle
);
618 if (search
== NULL
) {
619 /* we didn't find the search handle */
620 return NT_STATUS_INVALID_HANDLE
;