2 Unix SMB/CIFS implementation.
3 client directory list routines
4 Copyright (C) Andrew Tridgell 1994-1998
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "async_smb.h"
23 /****************************************************************************
24 Calculate a safe next_entry_offset.
25 ****************************************************************************/
27 static size_t calc_next_entry_offset(const char *base, const char *pdata_end)
29 size_t next_entry_offset = (size_t)IVAL(base,0);
31 if (next_entry_offset == 0 ||
32 base + next_entry_offset < base ||
33 base + next_entry_offset > pdata_end) {
34 next_entry_offset = pdata_end - base;
36 return next_entry_offset;
39 /****************************************************************************
40 Interpret a long filename structure - this is mostly guesses at the moment.
41 The length of the structure is returned
42 The structure of a long filename depends on the info level.
43 SMB_FIND_FILE_BOTH_DIRECTORY_INFO is used
44 by NT and SMB_FIND_EA_SIZE is used by OS/2
45 ****************************************************************************/
47 static size_t interpret_long_filename(TALLOC_CTX *ctx,
48 struct cli_state *cli,
53 const char *pdata_end,
54 struct file_info *finfo,
56 DATA_BLOB *p_last_name_raw)
62 data_blob_free(p_last_name_raw);
70 case SMB_FIND_INFO_STANDARD: /* OS/2 understands this */
71 /* these dates are converted to GMT by
73 if (pdata_end - base < 27) {
74 return pdata_end - base;
76 finfo->ctime_ts = convert_time_t_to_timespec(
77 make_unix_date2(p+4, cli->serverzone));
78 finfo->atime_ts = convert_time_t_to_timespec(
79 make_unix_date2(p+8, cli->serverzone));
80 finfo->mtime_ts = convert_time_t_to_timespec(
81 make_unix_date2(p+12, cli->serverzone));
82 finfo->size = IVAL(p,16);
83 finfo->mode = CVAL(p,24);
86 p += align_string(base_ptr, p, 0);
88 /* We can safely use len here (which is required by OS/2)
89 * and the NAS-BASIC server instead of +2 or +1 as the
90 * STR_TERMINATE flag below is
91 * actually used as the length calculation.
92 * The len is merely an upper bound.
93 * Due to the explicit 2 byte null termination
94 * in cli_receive_trans/cli_receive_nt_trans
95 * we know this is safe. JRA + kukks
98 if (p + len > pdata_end) {
99 return pdata_end - base;
102 /* the len+2 below looks strange but it is
103 important to cope with the differences
104 between win2000 and win9x for this call
106 ret = clistr_pull_talloc(ctx,
113 if (ret == (size_t)-1) {
114 return pdata_end - base;
117 return PTR_DIFF(p, base);
119 case SMB_FIND_EA_SIZE: /* this is what OS/2 uses mostly */
120 /* these dates are converted to GMT by
122 if (pdata_end - base < 31) {
123 return pdata_end - base;
125 finfo->ctime_ts = convert_time_t_to_timespec(
126 make_unix_date2(p+4, cli->serverzone));
127 finfo->atime_ts = convert_time_t_to_timespec(
128 make_unix_date2(p+8, cli->serverzone));
129 finfo->mtime_ts = convert_time_t_to_timespec(
130 make_unix_date2(p+12, cli->serverzone));
131 finfo->size = IVAL(p,16);
132 finfo->mode = CVAL(p,24);
135 /* check for unisys! */
136 if (p + len + 1 > pdata_end) {
137 return pdata_end - base;
139 ret = clistr_pull_talloc(ctx,
146 if (ret == (size_t)-1) {
147 return pdata_end - base;
150 return PTR_DIFF(p, base) + 1;
152 case SMB_FIND_FILE_BOTH_DIRECTORY_INFO: /* NT uses this, but also accepts 2 */
154 size_t namelen, slen;
156 if (pdata_end - base < 94) {
157 return pdata_end - base;
160 p += 4; /* next entry offset */
163 *p_resume_key = IVAL(p,0);
165 p += 4; /* fileindex */
167 /* Offset zero is "create time", not "change time". */
169 finfo->atime_ts = interpret_long_date(p);
171 finfo->mtime_ts = interpret_long_date(p);
173 finfo->ctime_ts = interpret_long_date(p);
175 finfo->size = IVAL2_TO_SMB_BIG_UINT(p,0);
177 p += 8; /* alloc size */
178 finfo->mode = CVAL(p,0);
182 p += 4; /* EA size */
185 /* Bad short name length. */
186 return pdata_end - base;
190 /* stupid NT bugs. grr */
192 if (p[1] == 0 && namelen > 1) flags |= STR_UNICODE;
193 clistr_pull(base_ptr, finfo->short_name, p,
194 sizeof(finfo->short_name),
197 p += 24; /* short name? */
198 if (p + namelen < p || p + namelen > pdata_end) {
199 return pdata_end - base;
201 ret = clistr_pull_talloc(ctx,
208 if (ret == (size_t)-1) {
209 return pdata_end - base;
212 /* To be robust in the face of unicode conversion failures
213 we need to copy the raw bytes of the last name seen here.
214 Namelen doesn't include the terminating unicode null, so
217 if (p_last_name_raw) {
218 *p_last_name_raw = data_blob(NULL, namelen+2);
219 memcpy(p_last_name_raw->data, p, namelen);
220 SSVAL(p_last_name_raw->data, namelen, 0);
222 return calc_next_entry_offset(base, pdata_end);
226 DEBUG(1,("Unknown long filename format %d\n",level));
227 return calc_next_entry_offset(base, pdata_end);
230 /****************************************************************************
231 Interpret a short filename structure.
232 The length of the structure is returned.
233 ****************************************************************************/
235 static bool interpret_short_filename(TALLOC_CTX *ctx,
236 struct cli_state *cli,
238 struct file_info *finfo)
243 finfo->mode = CVAL(p,21);
245 /* this date is converted to GMT by make_unix_date */
246 finfo->ctime_ts.tv_sec = make_unix_date(p+22, cli->serverzone);
247 finfo->ctime_ts.tv_nsec = 0;
248 finfo->mtime_ts.tv_sec = finfo->atime_ts.tv_sec = finfo->ctime_ts.tv_sec;
249 finfo->mtime_ts.tv_nsec = finfo->atime_ts.tv_nsec = 0;
250 finfo->size = IVAL(p,26);
251 ret = clistr_pull_talloc(ctx,
253 SVAL(cli->inbuf, smb_flg2),
258 if (ret == (size_t)-1) {
263 strlcpy(finfo->short_name,
265 sizeof(finfo->short_name));
270 struct cli_list_old_state {
271 struct tevent_context *ev;
272 struct cli_state *cli;
277 uint8_t search_status[23];
283 static void cli_list_old_done(struct tevent_req *subreq);
285 static struct tevent_req *cli_list_old_send(TALLOC_CTX *mem_ctx,
286 struct tevent_context *ev,
287 struct cli_state *cli,
291 struct tevent_req *req, *subreq;
292 struct cli_list_old_state *state;
294 static const uint16_t zero = 0;
296 req = tevent_req_create(mem_ctx, &state, struct cli_list_old_state);
302 state->attribute = attribute;
304 state->mask = talloc_strdup(state, mask);
305 if (tevent_req_nomem(state->mask, req)) {
306 return tevent_req_post(req, ev);
308 state->num_asked = (cli->max_xmit - 100) / DIR_STRUCT_SIZE;
310 SSVAL(state->vwv + 0, 0, state->num_asked);
311 SSVAL(state->vwv + 1, 0, state->attribute);
313 bytes = talloc_array(state, uint8_t, 1);
314 if (tevent_req_nomem(bytes, req)) {
315 return tevent_req_post(req, ev);
318 bytes = smb_bytes_push_str(bytes, cli_ucs2(cli), mask,
319 strlen(mask)+1, NULL);
321 bytes = smb_bytes_push_bytes(bytes, 5, (uint8_t *)&zero, 2);
322 if (tevent_req_nomem(bytes, req)) {
323 return tevent_req_post(req, ev);
326 subreq = cli_smb_send(state, state->ev, state->cli, SMBsearch,
327 0, 2, state->vwv, talloc_get_size(bytes), bytes);
328 if (tevent_req_nomem(subreq, req)) {
329 return tevent_req_post(req, ev);
331 tevent_req_set_callback(subreq, cli_list_old_done, req);
335 static void cli_list_old_done(struct tevent_req *subreq)
337 struct tevent_req *req = tevent_req_callback_data(
338 subreq, struct tevent_req);
339 struct cli_list_old_state *state = tevent_req_data(
340 req, struct cli_list_old_state);
351 status = cli_smb_recv(subreq, state, NULL, 0, &wct, &vwv, &num_bytes,
353 if (!NT_STATUS_IS_OK(status)
354 && !NT_STATUS_EQUAL(status, NT_STATUS_DOS(ERRDOS, ERRnofiles))
355 && !NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) {
357 tevent_req_nterror(req, status);
360 if (NT_STATUS_EQUAL(status, NT_STATUS_DOS(ERRDOS, ERRnofiles))
361 || NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) {
367 req, NT_STATUS_INVALID_NETWORK_RESPONSE);
370 received = SVAL(vwv + 0, 0);
375 * I don't think this can wrap. received is
376 * initialized from a 16-bit value.
378 if (num_bytes < (received * DIR_STRUCT_SIZE + 3)) {
381 req, NT_STATUS_INVALID_NETWORK_RESPONSE);
385 dirlist_len = talloc_get_size(state->dirlist);
387 tmp = TALLOC_REALLOC_ARRAY(
388 state, state->dirlist, uint8_t,
389 dirlist_len + received * DIR_STRUCT_SIZE);
390 if (tevent_req_nomem(tmp, req)) {
393 state->dirlist = tmp;
394 memcpy(state->dirlist + dirlist_len, bytes + 3,
395 received * DIR_STRUCT_SIZE);
397 SSVAL(state->search_status, 0, 21);
398 memcpy(state->search_status + 2,
399 bytes + 3 + (received-1)*DIR_STRUCT_SIZE, 21);
402 if (state->first || state->done) {
403 tevent_req_done(req);
407 state->num_asked = 0;
412 state->first = false;
414 SSVAL(state->vwv + 0, 0, state->num_asked);
415 SSVAL(state->vwv + 1, 0, state->attribute);
417 bytes = talloc_array(state, uint8_t, 1);
418 if (tevent_req_nomem(bytes, req)) {
422 bytes = smb_bytes_push_str(bytes, cli_ucs2(state->cli), "",
424 bytes = smb_bytes_push_bytes(bytes, 5, state->search_status,
425 sizeof(state->search_status));
426 if (tevent_req_nomem(bytes, req)) {
429 subreq = cli_smb_send(state, state->ev, state->cli, cmd, 0,
430 2, state->vwv, talloc_get_size(bytes), bytes);
431 if (tevent_req_nomem(subreq, req)) {
434 tevent_req_set_callback(subreq, cli_list_old_done, req);
437 static NTSTATUS cli_list_old_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
438 struct file_info **pfinfo)
440 struct cli_list_old_state *state = tevent_req_data(
441 req, struct cli_list_old_state);
443 size_t i, num_received;
444 struct file_info *finfo;
446 if (tevent_req_is_nterror(req, &status)) {
450 num_received = talloc_array_length(state->dirlist) / DIR_STRUCT_SIZE;
452 finfo = TALLOC_ARRAY(mem_ctx, struct file_info, num_received);
454 return NT_STATUS_NO_MEMORY;
457 for (i=0; i<num_received; i++) {
458 if (!interpret_short_filename(
460 (char *)state->dirlist + i * DIR_STRUCT_SIZE,
463 return NT_STATUS_NO_MEMORY;
470 NTSTATUS cli_list_old(struct cli_state *cli, const char *mask,
472 void (*fn)(const char *, struct file_info *,
473 const char *, void *), void *state)
475 TALLOC_CTX *frame = talloc_stackframe();
476 struct event_context *ev;
477 struct tevent_req *req;
478 NTSTATUS status = NT_STATUS_NO_MEMORY;
479 struct file_info *finfo;
482 if (cli_has_async_calls(cli)) {
484 * Can't use sync call while an async call is in flight
486 status = NT_STATUS_INVALID_PARAMETER;
489 ev = event_context_init(frame);
493 req = cli_list_old_send(frame, ev, cli, mask, attribute);
497 if (!tevent_req_poll(req, ev)) {
498 status = map_nt_error_from_unix(errno);
501 status = cli_list_old_recv(req, frame, &finfo);
502 if (!NT_STATUS_IS_OK(status)) {
505 num_finfo = talloc_array_length(finfo);
506 for (i=0; i<num_finfo; i++) {
507 fn(cli->dfs_mountpoint, &finfo[i], mask, state);
511 if (!NT_STATUS_IS_OK(status)) {
512 cli_set_error(cli, status);
517 struct cli_list_trans_state {
518 struct tevent_context *ev;
519 struct cli_state *cli;
526 uint16_t max_matches;
535 struct file_info *finfo;
538 static void cli_list_trans_done(struct tevent_req *subreq);
540 static struct tevent_req *cli_list_trans_send(TALLOC_CTX *mem_ctx,
541 struct tevent_context *ev,
542 struct cli_state *cli,
547 struct tevent_req *req, *subreq;
548 struct cli_list_trans_state *state;
549 size_t nlen, param_len;
552 req = tevent_req_create(mem_ctx, &state,
553 struct cli_list_trans_state);
559 state->mask = talloc_strdup(state, mask);
560 if (tevent_req_nomem(state->mask, req)) {
561 return tevent_req_post(req, ev);
563 state->attribute = attribute;
564 state->info_level = info_level;
565 state->loop_count = 0;
568 state->max_matches = 1366; /* Match W2k */
570 state->setup[0] = TRANSACT2_FINDFIRST;
572 nlen = 2*(strlen(mask)+1);
573 state->param = TALLOC_ARRAY(state, uint8_t, 12+nlen+2);
574 if (tevent_req_nomem(state->param, req)) {
575 return tevent_req_post(req, ev);
578 SSVAL(state->param, 0, state->attribute);
579 SSVAL(state->param, 2, state->max_matches);
580 SSVAL(state->param, 4,
581 FLAG_TRANS2_FIND_REQUIRE_RESUME
582 |FLAG_TRANS2_FIND_CLOSE_IF_END);
583 SSVAL(state->param, 6, state->info_level);
584 SIVAL(state->param, 8, 0);
586 p = ((char *)state->param)+12;
587 p += clistr_push(state->cli, p, state->mask, nlen,
589 param_len = PTR_DIFF(p, state->param);
591 subreq = cli_trans_send(state, state->ev, state->cli,
592 SMBtrans2, NULL, -1, 0, 0,
594 state->param, param_len, 10,
595 NULL, 0, cli->max_xmit);
596 if (tevent_req_nomem(subreq, req)) {
597 return tevent_req_post(req, ev);
599 tevent_req_set_callback(subreq, cli_list_trans_done, req);
603 static void cli_list_trans_done(struct tevent_req *subreq)
605 struct tevent_req *req = tevent_req_callback_data(
606 subreq, struct tevent_req);
607 struct cli_list_trans_state *state = tevent_req_data(
608 req, struct cli_list_trans_state);
616 struct file_info *tmp;
617 size_t old_num_finfo;
618 uint16_t recv_flags2;
622 uint32_t resume_key = 0;
624 DATA_BLOB last_name_raw;
625 struct file_info *finfo = NULL;
626 size_t nlen, param_len;
628 min_param = (state->first ? 6 : 4);
630 status = cli_trans_recv(subreq, talloc_tos(), &recv_flags2,
632 ¶m, min_param, &num_param,
633 &data, 0, &num_data);
635 if (!NT_STATUS_IS_OK(status)) {
637 * TODO: retry, OS/2 nofiles
639 tevent_req_nterror(req, status);
644 state->ff_dir_handle = SVAL(param, 0);
645 ff_searchcount = SVAL(param, 2);
646 ff_eos = SVAL(param, 4) != 0;
648 ff_searchcount = SVAL(param, 0);
649 ff_eos = SVAL(param, 2) != 0;
652 old_num_finfo = talloc_array_length(state->finfo);
654 tmp = TALLOC_REALLOC_ARRAY(state, state->finfo, struct file_info,
655 old_num_finfo + ff_searchcount);
656 if (tevent_req_nomem(tmp, req)) {
661 p2 = p = (char *)data;
662 data_end = (char *)data + num_data;
663 last_name_raw = data_blob_null;
665 for (i=0; i<ff_searchcount; i++) {
666 if (p2 >= data_end) {
670 if ((state->info_level == SMB_FIND_FILE_BOTH_DIRECTORY_INFO)
671 && (i == ff_searchcount-1)) {
672 /* Last entry - fixup the last offset length. */
673 SIVAL(p2, 0, PTR_DIFF((data + num_data), p2));
676 data_blob_free(&last_name_raw);
678 finfo = &state->finfo[old_num_finfo + i];
680 p2 += interpret_long_filename(
681 state->finfo, /* Stick fname to the array as such */
682 state->cli, state->info_level,
683 (char *)data, recv_flags2, p2,
684 data_end, finfo, &resume_key, &last_name_raw);
686 if (finfo->name == NULL) {
687 DEBUG(1, ("cli_list: Error: unable to parse name from "
688 "info level %d\n", state->info_level));
692 if (!state->first && (state->mask[0] != '\0') &&
693 strcsequal(finfo->name, state->mask)) {
694 DEBUG(1, ("Error: Looping in FIND_NEXT as name %s has "
695 "already been seen?\n", finfo->name));
701 if (ff_searchcount == 0) {
709 * Shrink state->finfo to the real length we received
711 tmp = TALLOC_REALLOC_ARRAY(state, state->finfo, struct file_info,
713 if (tevent_req_nomem(tmp, req)) {
718 state->first = false;
721 data_blob_free(&last_name_raw);
722 tevent_req_done(req);
726 TALLOC_FREE(state->mask);
727 state->mask = talloc_strdup(state, finfo->name);
728 if (tevent_req_nomem(state->mask, req)) {
732 state->setup[0] = TRANSACT2_FINDNEXT;
734 nlen = 2*(strlen(state->mask) + 1);
736 param = TALLOC_REALLOC_ARRAY(state, state->param, uint8_t,
737 12 + nlen + last_name_raw.length + 2);
738 if (tevent_req_nomem(param, req)) {
741 state->param = param;
743 SSVAL(param, 0, state->ff_dir_handle);
744 SSVAL(param, 2, state->max_matches); /* max count */
745 SSVAL(param, 4, state->info_level);
747 * For W2K servers serving out FAT filesystems we *must* set
748 * the resume key. If it's not FAT then it's returned as zero.
750 SIVAL(param, 6, resume_key); /* ff_resume_key */
752 * NB. *DON'T* use continue here. If you do it seems that W2K
753 * and bretheren can miss filenames. Use last filename
754 * continue instead. JRA
756 SSVAL(param, 10, (FLAG_TRANS2_FIND_REQUIRE_RESUME
757 |FLAG_TRANS2_FIND_CLOSE_IF_END));
758 p = ((char *)param)+12;
759 if (last_name_raw.length) {
760 memcpy(p, last_name_raw.data, last_name_raw.length);
761 p += last_name_raw.length;
762 data_blob_free(&last_name_raw);
764 p += clistr_push(state->cli, p, state->mask, nlen,
768 param_len = PTR_DIFF(p, param);
770 subreq = cli_trans_send(state, state->ev, state->cli,
771 SMBtrans2, NULL, -1, 0, 0,
773 state->param, param_len, 10,
774 NULL, 0, state->cli->max_xmit);
775 if (tevent_req_nomem(subreq, req)) {
778 tevent_req_set_callback(subreq, cli_list_trans_done, req);
781 static NTSTATUS cli_list_trans_recv(struct tevent_req *req,
783 struct file_info **finfo)
785 struct cli_list_trans_state *state = tevent_req_data(
786 req, struct cli_list_trans_state);
789 if (tevent_req_is_nterror(req, &status)) {
792 *finfo = talloc_move(mem_ctx, &state->finfo);
796 NTSTATUS cli_list_trans(struct cli_state *cli, const char *mask,
797 uint16_t attribute, int info_level,
798 void (*fn)(const char *mnt, struct file_info *finfo,
799 const char *mask, void *private_data),
802 TALLOC_CTX *frame = talloc_stackframe();
803 struct event_context *ev;
804 struct tevent_req *req;
806 struct file_info *finfo = NULL;
807 NTSTATUS status = NT_STATUS_NO_MEMORY;
809 if (cli_has_async_calls(cli)) {
811 * Can't use sync call while an async call is in flight
813 status = NT_STATUS_INVALID_PARAMETER;
816 ev = event_context_init(frame);
820 req = cli_list_trans_send(frame, ev, cli, mask, attribute, info_level);
824 if (!tevent_req_poll_ntstatus(req, ev, &status)) {
827 status = cli_list_trans_recv(req, frame, &finfo);
828 if (!NT_STATUS_IS_OK(status)) {
831 num_finfo = talloc_array_length(finfo);
832 for (i=0; i<num_finfo; i++) {
833 fn(cli->dfs_mountpoint, &finfo[i], mask, private_data);
837 if (!NT_STATUS_IS_OK(status)) {
838 cli_set_error(cli, status);
843 struct cli_list_state {
844 NTSTATUS (*recv_fn)(struct tevent_req *req, TALLOC_CTX *mem_ctx,
845 struct file_info **finfo);
846 struct file_info *finfo;
849 static void cli_list_done(struct tevent_req *subreq);
851 struct tevent_req *cli_list_send(TALLOC_CTX *mem_ctx,
852 struct tevent_context *ev,
853 struct cli_state *cli,
858 struct tevent_req *req, *subreq;
859 struct cli_list_state *state;
861 req = tevent_req_create(mem_ctx, &state, struct cli_list_state);
866 if (cli->protocol <= PROTOCOL_LANMAN1) {
867 subreq = cli_list_old_send(state, ev, cli, mask, attribute);
868 state->recv_fn = cli_list_old_recv;
870 subreq = cli_list_trans_send(state, ev, cli, mask, attribute,
872 state->recv_fn = cli_list_trans_recv;
874 if (tevent_req_nomem(subreq, req)) {
875 return tevent_req_post(req, ev);
877 tevent_req_set_callback(subreq, cli_list_done, req);
881 static void cli_list_done(struct tevent_req *subreq)
883 struct tevent_req *req = tevent_req_callback_data(
884 subreq, struct tevent_req);
885 struct cli_list_state *state = tevent_req_data(
886 req, struct cli_list_state);
889 status = state->recv_fn(subreq, state, &state->finfo);
891 if (!NT_STATUS_IS_OK(status)) {
892 tevent_req_nterror(req, status);
895 tevent_req_done(req);
898 NTSTATUS cli_list_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
899 struct file_info **finfo, size_t *num_finfo)
901 struct cli_list_state *state = tevent_req_data(
902 req, struct cli_list_state);
905 if (tevent_req_is_nterror(req, &status)) {
908 *num_finfo = talloc_array_length(state->finfo);
909 *finfo = talloc_move(mem_ctx, &state->finfo);
913 NTSTATUS cli_list(struct cli_state *cli, const char *mask, uint16 attribute,
914 void (*fn)(const char *, struct file_info *, const char *,
915 void *), void *state)
917 TALLOC_CTX *frame = talloc_stackframe();
918 struct event_context *ev;
919 struct tevent_req *req;
920 NTSTATUS status = NT_STATUS_NO_MEMORY;
921 struct file_info *finfo;
925 if (cli_has_async_calls(cli)) {
927 * Can't use sync call while an async call is in flight
929 status = NT_STATUS_INVALID_PARAMETER;
932 ev = event_context_init(frame);
937 info_level = (cli->capabilities & CAP_NT_SMBS)
938 ? SMB_FIND_FILE_BOTH_DIRECTORY_INFO : SMB_FIND_INFO_STANDARD;
940 req = cli_list_send(frame, ev, cli, mask, attribute, info_level);
944 if (!tevent_req_poll(req, ev)) {
945 status = map_nt_error_from_unix(errno);
949 status = cli_list_recv(req, frame, &finfo, &num_finfo);
950 if (!NT_STATUS_IS_OK(status)) {
954 for (i=0; i<num_finfo; i++) {
955 fn(cli->dfs_mountpoint, &finfo[i], mask, state);
959 if (!NT_STATUS_IS_OK(status)) {
960 cli_set_error(cli, status);