xref: /illumos-gate/usr/src/uts/common/fs/smbsrv/smb2_query_dir.c (revision 1d0ec46fafb49266ae79840a692bb48af60ade70)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
26  * Copyright 2022-2023 RackTop Systems, Inc.
27  */
28 
29 /*
30  * Dispatch function for SMB2_QUERY_DIRECTORY
31  * MS-SMB2 sec. 3.3.5.18
32  * and MS-FSA sec. 2.1.15
33  *
34  * Similar to smb_trans2_find.c (from SMB1)
35  */
36 
37 #include <smbsrv/smb2_kproto.h>
38 #include <smbsrv/smb2_aapl.h>
39 
40 /*
41  * Internally defined info. level for MacOS support.
42  * Make sure this does not conflict with real values in
43  * FILE_INFORMATION_CLASS, and that it fits in 8-bits.
44  */
45 #define	FileIdMacOsDirectoryInformation (FileMaximumInformation + 10)
46 
47 /*
48  * Args (and other state) that we carry around among the
49  * various functions involved in SMB2 Query Directory.
50  */
51 typedef struct smb2_find_args {
52 	uint32_t fa_maxdata;
53 	uint8_t fa_infoclass;
54 	uint8_t fa_fflags;
55 	uint16_t fa_maxcount;
56 	uint16_t fa_eos;	/* End Of Search */
57 	uint16_t fa_fixedsize;	/* size of fixed part of a returned entry */
58 	uint32_t fa_lastkey;	/* Last resume key */
59 	int fa_last_entry;	/* offset of last entry */
60 
61 	/* Normal info, per dir. entry */
62 	smb_fileinfo_t fa_fi;
63 
64 	/* MacOS AAPL extension stuff. */
65 	smb_macinfo_t fa_mi;
66 } smb2_find_args_t;
67 
68 static uint32_t smb2_find_entries(smb_request_t *,
69     smb_odir_t *, smb2_find_args_t *);
70 static uint32_t smb2_find_mbc_encode(smb_request_t *, smb2_find_args_t *);
71 
72 /*
73  * Tunable parameter to limit the maximum
74  * number of entries to be returned.
75  */
76 uint16_t smb2_find_max = 1024;
77 
78 smb_sdrc_t
79 smb2_query_dir(smb_request_t *sr)
80 {
81 	smb2_find_args_t args;
82 	smb_odir_resume_t odir_resume;
83 	smb_ofile_t *of = NULL;
84 	smb_odir_t *od = NULL;
85 	char *pattern = NULL;
86 	uint16_t StructSize;
87 	uint32_t FileIndex;
88 	uint16_t NameOffset;
89 	uint16_t NameLength;
90 	smb2fid_t smb2fid;
91 	uint16_t sattr = SMB_SEARCH_ATTRIBUTES;
92 	uint16_t DataOff;
93 	uint32_t DataLen;
94 	uint32_t status;
95 	int skip, rc = 0;
96 
97 	bzero(&args, sizeof (args));
98 	bzero(&odir_resume, sizeof (odir_resume));
99 
100 	/*
101 	 * SMB2 Query Directory request
102 	 */
103 	rc = smb_mbc_decodef(
104 	    &sr->smb_data, "wbblqqwwl",
105 	    &StructSize,		/* w */
106 	    &args.fa_infoclass,		/* b */
107 	    &args.fa_fflags,		/* b */
108 	    &FileIndex,			/* l */
109 	    &smb2fid.persistent,	/* q */
110 	    &smb2fid.temporal,		/* q */
111 	    &NameOffset,		/* w */
112 	    &NameLength,		/* w */
113 	    &args.fa_maxdata);		/* l */
114 	if (rc || StructSize != 33)
115 		return (SDRC_ERROR);
116 
117 	status = smb2sr_lookup_fid(sr, &smb2fid);
118 	of = sr->fid_ofile;
119 
120 	DTRACE_SMB2_START(op__QueryDirectory, smb_request_t *, sr);
121 
122 	if (status)
123 		goto errout;
124 
125 	/*
126 	 * If there's an input buffer (search pattern), decode it.
127 	 * Two times MAXNAMELEN because it represents the UNICODE string
128 	 * length in bytes.
129 	 */
130 	if (NameLength >= (2 * MAXNAMELEN)) {
131 		status = NT_STATUS_OBJECT_PATH_INVALID;
132 		goto errout;
133 	}
134 	if (NameLength != 0) {
135 		/*
136 		 * We're normally positioned at the pattern now,
137 		 * but there could be some padding before it.
138 		 */
139 		skip = (sr->smb2_cmd_hdr + NameOffset) -
140 		    sr->smb_data.chain_offset;
141 		if (skip < 0) {
142 			status = NT_STATUS_OBJECT_PATH_INVALID;
143 			goto errout;
144 		}
145 		if (skip > 0)
146 			(void) smb_mbc_decodef(&sr->smb_data, "#.", skip);
147 		rc = smb_mbc_decodef(&sr->smb_data, "%#U", sr,
148 		    NameLength, &pattern);
149 		if (rc || pattern == NULL) {
150 			status = NT_STATUS_OBJECT_PATH_INVALID;
151 			goto errout;
152 		}
153 	} else
154 		pattern = "*";
155 
156 	/*
157 	 * Setup the output buffer.
158 	 */
159 	if (args.fa_maxdata > smb2_max_trans)
160 		args.fa_maxdata = smb2_max_trans;
161 	sr->raw_data.max_bytes = args.fa_maxdata;
162 
163 	/*
164 	 * Get the fixed size of entries we will return, which
165 	 * lets us estimate the number of entries we'll need.
166 	 *
167 	 * Also use this opportunity to validate fa_infoclass.
168 	 */
169 
170 	switch (args.fa_infoclass) {
171 	case FileDirectoryInformation:		/* 1 */
172 		args.fa_fixedsize = 64;
173 		break;
174 	case FileFullDirectoryInformation:	/* 2 */
175 		args.fa_fixedsize = 68;
176 		break;
177 	case FileBothDirectoryInformation:	/* 3 */
178 		args.fa_fixedsize = 94;
179 		break;
180 	case FileNamesInformation:		/* 12 */
181 		args.fa_fixedsize = 12;
182 		break;
183 	case FileIdBothDirectoryInformation:	/* 37 */
184 		args.fa_fixedsize = 96;
185 		break;
186 	case FileIdFullDirectoryInformation:	/* 38 */
187 		args.fa_fixedsize = 84;
188 		break;
189 	default:
190 		status = NT_STATUS_INVALID_INFO_CLASS;
191 		goto errout;
192 	}
193 
194 	/*
195 	 * MacOS, when using the AAPL CreateContext extensions
196 	 * and the "read dir attr" feature, uses a non-standard
197 	 * information format for directory entries.  Internally
198 	 * we'll use a fake info level to represent this case.
199 	 * (Wish they had just defined a new info level.)
200 	 */
201 	if ((sr->session->s_flags & SMB_SSN_AAPL_READDIR) != 0 &&
202 	    args.fa_infoclass == FileIdBothDirectoryInformation) {
203 		args.fa_infoclass = FileIdMacOsDirectoryInformation;
204 		args.fa_fixedsize = 96; /* yes, same size */
205 	}
206 
207 	args.fa_maxcount = args.fa_maxdata / (args.fa_fixedsize + 4);
208 	if (args.fa_maxcount == 0)
209 		args.fa_maxcount = 1;
210 	if ((smb2_find_max != 0) && (args.fa_maxcount > smb2_find_max))
211 		args.fa_maxcount = smb2_find_max;
212 	if (args.fa_fflags & SMB2_QDIR_FLAG_SINGLE)
213 		args.fa_maxcount = 1;
214 
215 	/*
216 	 * If this ofile does not have an odir yet, get one.
217 	 */
218 	mutex_enter(&of->f_mutex);
219 	if ((od = of->f_odir) == NULL) {
220 		status = smb_odir_openfh(sr, pattern, sattr, &od);
221 		of->f_odir = od;
222 	}
223 	mutex_exit(&of->f_mutex);
224 	if (od == NULL) {
225 		if (status == 0)
226 			status = NT_STATUS_INTERNAL_ERROR;
227 		goto errout;
228 	}
229 
230 	/*
231 	 * "Reopen" sets a new pattern and restart.
232 	 */
233 	if (args.fa_fflags & SMB2_QDIR_FLAG_REOPEN) {
234 		smb_odir_reopen(od, pattern, sattr);
235 	}
236 
237 	/*
238 	 * Set the correct position in the directory.
239 	 */
240 	if (args.fa_fflags & SMB2_QDIR_FLAG_RESTART) {
241 		odir_resume.or_type = SMB_ODIR_RESUME_COOKIE;
242 		odir_resume.or_cookie = 0;
243 	} else if (args.fa_fflags & SMB2_QDIR_FLAG_INDEX) {
244 		odir_resume.or_type = SMB_ODIR_RESUME_COOKIE;
245 		odir_resume.or_cookie = FileIndex;
246 	} else {
247 		odir_resume.or_type = SMB_ODIR_RESUME_CONT;
248 	}
249 	smb_odir_resume_at(od, &odir_resume);
250 	of->f_seek_pos = od->d_offset;
251 
252 	/*
253 	 * The real work of readdir and format conversion.
254 	 */
255 	status = smb2_find_entries(sr, od, &args);
256 
257 	of->f_seek_pos = od->d_offset;
258 
259 	if ((args.fa_fflags & SMB2_QDIR_FLAG_SINGLE) &&
260 	    status == NT_STATUS_NO_MORE_FILES) {
261 		status = NT_STATUS_NO_SUCH_FILE;
262 	}
263 
264 errout:
265 	sr->smb2_status = status;
266 	DTRACE_SMB2_DONE(op__QueryDirectory, smb_request_t *, sr);
267 	if (status != 0) {
268 		smb2sr_put_error(sr, status);
269 		return (SDRC_SUCCESS);
270 	}
271 
272 	/*
273 	 * SMB2 Query Directory reply
274 	 */
275 	StructSize = 9;
276 	DataOff = SMB2_HDR_SIZE + 8;
277 	DataLen = MBC_LENGTH(&sr->raw_data);
278 	ASSERT(DataLen != 0);
279 	rc = smb_mbc_encodef(
280 	    &sr->reply, "wwlC",
281 	    StructSize,		/* w */
282 	    DataOff,		/* w */
283 	    DataLen,		/* l */
284 	    &sr->raw_data);	/* C */
285 	if (rc)
286 		sr->smb2_status = NT_STATUS_INTERNAL_ERROR;
287 
288 	return (SDRC_SUCCESS);
289 }
290 
291 /*
292  * smb2_find_entries
293  *
294  * Find and encode up to args->fa_maxcount directory entries.
295  *
296  * Returns:
297  *   NT status
298  */
299 static uint32_t
300 smb2_find_entries(smb_request_t *sr, smb_odir_t *od, smb2_find_args_t *args)
301 {
302 	smb_odir_resume_t odir_resume;
303 	char		*tbuf = NULL;
304 	size_t		tbuflen = 0;
305 	uint16_t	count;
306 	uint16_t	minsize;
307 	uint32_t	status = 0;
308 	int		rc = -1;
309 
310 	/*
311 	 * Let's stop when the remaining space will not hold a
312 	 * minimum size entry.  That's the fixed part plus the
313 	 * storage size of a 1 char unicode string.
314 	 */
315 	minsize = args->fa_fixedsize + 2;
316 
317 	/*
318 	 * FileIdMacOsDirectoryInformation needs some buffer space
319 	 * for composing directory entry + stream name for lookup.
320 	 * Get the buffer now to avoid alloc/free per entry.
321 	 */
322 	if (args->fa_infoclass == FileIdMacOsDirectoryInformation) {
323 		tbuflen = 2 * MAXNAMELEN;
324 		tbuf = kmem_alloc(tbuflen, KM_SLEEP);
325 	}
326 
327 	count = 0;
328 	while (count < args->fa_maxcount) {
329 
330 		if (!MBC_ROOM_FOR(&sr->raw_data, minsize)) {
331 			status = NT_STATUS_BUFFER_OVERFLOW;
332 			break;
333 		}
334 
335 		rc = smb_odir_read_fileinfo(sr, od,
336 		    &args->fa_fi, &args->fa_eos);
337 		if (rc == ENOENT) {
338 			status = NT_STATUS_NO_MORE_FILES;
339 			break;
340 		}
341 		if (rc != 0) {
342 			status = smb_errno2status(rc);
343 			break;
344 		}
345 		if (args->fa_eos != 0) {
346 			/* The readdir call hit the end. */
347 			status = NT_STATUS_NO_MORE_FILES;
348 			break;
349 		}
350 
351 		if (args->fa_infoclass == FileIdMacOsDirectoryInformation)
352 			(void) smb2_aapl_get_macinfo(sr, od,
353 			    &args->fa_fi, &args->fa_mi, tbuf, tbuflen);
354 
355 		if (smb2_aapl_use_file_ids == 0 &&
356 		    (sr->session->s_flags & SMB_SSN_AAPL_CCEXT) != 0)
357 			args->fa_fi.fi_nodeid = 0;
358 
359 		status = smb2_find_mbc_encode(sr, args);
360 		if (status) {
361 			/*
362 			 * We read a directory entry but failed to
363 			 * copy it into the output buffer.  Rewind
364 			 * the directory pointer so this will be
365 			 * the first entry read next time.
366 			 */
367 			bzero(&odir_resume, sizeof (odir_resume));
368 			odir_resume.or_type = SMB_ODIR_RESUME_COOKIE;
369 			odir_resume.or_cookie = args->fa_lastkey;
370 			smb_odir_resume_at(od, &odir_resume);
371 			break;
372 		}
373 
374 		/*
375 		 * Save the offset of the next entry we'll read.
376 		 * If we fail copying, we'll need this offset.
377 		 */
378 		args->fa_lastkey = args->fa_fi.fi_cookie;
379 		++count;
380 	}
381 
382 	if (count == 0) {
383 		ASSERT(status != 0);
384 	} else {
385 		/*
386 		 * We copied some directory entries, but stopped for
387 		 * NT_STATUS_NO_MORE_FILES, or something.
388 		 *
389 		 * Per [MS-FSCC] sec. 2.4, the last entry in the
390 		 * enumeration MUST have its NextEntryOffset value
391 		 * set to zero.  Overwrite that in the last entry.
392 		 */
393 		(void) smb_mbc_poke(&sr->raw_data,
394 		    args->fa_last_entry, "l", 0);
395 		status = 0;
396 	}
397 
398 	if (tbuf != NULL)
399 		kmem_free(tbuf, tbuflen);
400 
401 	return (status);
402 }
403 
404 /*
405  * smb2_mbc_encode
406  *
407  * This function encodes the mbc for one directory entry.
408  *
409  * The function returns -1 when the max data requested by client
410  * is reached. If the entry is valid and successful encoded, 0
411  * will be returned; otherwise, 1 will be returned.
412  *
413  * We always null terminate the filename. The space for the null
414  * is included in the maxdata calculation and is therefore included
415  * in the next_entry_offset. namelen is the unterminated length of
416  * the filename. For levels except STANDARD and EA_SIZE, if the
417  * filename is ascii the name length returned to the client should
418  * include the null terminator. Otherwise the length returned to
419  * the client should not include the terminator.
420  *
421  * Returns: 0 - data successfully encoded
422  *      NT status
423  */
424 static uint32_t
425 smb2_find_mbc_encode(smb_request_t *sr, smb2_find_args_t *args)
426 {
427 	smb_fileinfo_t	*fileinfo = &args->fa_fi;
428 	smb_macinfo_t	*macinfo = &args->fa_mi;
429 	uint8_t		buf83[26];
430 	smb_msgbuf_t	mb;
431 	int		namelen;
432 	int		shortlen = 0;
433 	int		rc, starting_offset;
434 	uint32_t	next_entry_offset;
435 	uint32_t	mb_flags = SMB_MSGBUF_UNICODE | SMB_MSGBUF_NOTERM;
436 	uint32_t	resume_key;
437 
438 	namelen = smb_wcequiv_strlen(fileinfo->fi_name);
439 	if (namelen == -1)
440 		return (NT_STATUS_INTERNAL_ERROR);
441 
442 	/*
443 	 * Keep track of where the last entry starts so we can
444 	 * come back and poke the NextEntryOffset field.  Also,
445 	 * after enumeration finishes, the caller uses this to
446 	 * poke the last entry again with zero to mark it as
447 	 * the end of the enumeration.
448 	 */
449 	starting_offset = sr->raw_data.chain_offset;
450 
451 	/*
452 	 * Technically (per MS-SMB2) resume keys are optional.
453 	 * Windows doesn't need them, but MacOS does.
454 	 */
455 	resume_key = fileinfo->fi_cookie;
456 
457 	/*
458 	 * This switch handles all the "information levels" (formats)
459 	 * that we support.  Note that all formats have the file name
460 	 * placed after some fixed-size data, and the code to write
461 	 * the file name is factored out at the end of this switch.
462 	 */
463 	switch (args->fa_infoclass) {
464 
465 	/* See also: SMB_FIND_FILE_DIRECTORY_INFO */
466 	case FileDirectoryInformation:		/* 1 */
467 		rc = smb_mbc_encodef(
468 		    &sr->raw_data, "llTTTTqqll",
469 		    0,	/* NextEntryOffset (set later) */
470 		    resume_key,
471 		    &fileinfo->fi_crtime,
472 		    &fileinfo->fi_atime,
473 		    &fileinfo->fi_mtime,
474 		    &fileinfo->fi_ctime,
475 		    fileinfo->fi_size,
476 		    fileinfo->fi_alloc_size,
477 		    fileinfo->fi_dosattr,
478 		    namelen);
479 		break;
480 
481 	/* See also: SMB_FIND_FILE_FULL_DIRECTORY_INFO */
482 	case FileFullDirectoryInformation:	/* 2 */
483 		rc = smb_mbc_encodef(
484 		    &sr->raw_data, "llTTTTqqlll",
485 		    0,	/* NextEntryOffset (set later) */
486 		    resume_key,
487 		    &fileinfo->fi_crtime,
488 		    &fileinfo->fi_atime,
489 		    &fileinfo->fi_mtime,
490 		    &fileinfo->fi_ctime,
491 		    fileinfo->fi_size,
492 		    fileinfo->fi_alloc_size,
493 		    fileinfo->fi_dosattr,
494 		    namelen,
495 		    0L);	/* EaSize */
496 		break;
497 
498 	/* See also: SMB_FIND_FILE_ID_FULL_DIRECTORY_INFO */
499 	case FileIdFullDirectoryInformation:	/* 38 */
500 		rc = smb_mbc_encodef(
501 		    &sr->raw_data, "llTTTTqqllllq",
502 		    0,	/* NextEntryOffset (set later) */
503 		    resume_key,
504 		    &fileinfo->fi_crtime,
505 		    &fileinfo->fi_atime,
506 		    &fileinfo->fi_mtime,
507 		    &fileinfo->fi_ctime,
508 		    fileinfo->fi_size,
509 		    fileinfo->fi_alloc_size,
510 		    fileinfo->fi_dosattr,
511 		    namelen,
512 		    0L,		/* EaSize */
513 		    0L,		/* reserved */
514 		    fileinfo->fi_nodeid);
515 		break;
516 
517 	/* See also: SMB_FIND_FILE_BOTH_DIRECTORY_INFO */
518 	case FileBothDirectoryInformation:	/* 3 */
519 		bzero(buf83, sizeof (buf83));
520 		smb_msgbuf_init(&mb, buf83, sizeof (buf83), mb_flags);
521 		shortlen = smb_msgbuf_encode(&mb, "U", fileinfo->fi_shortname);
522 		if (shortlen < 0) {
523 			shortlen = 0;
524 			bzero(buf83, sizeof (buf83));
525 		}
526 
527 		rc = smb_mbc_encodef(
528 		    &sr->raw_data, "llTTTTqqlllb.24c",
529 		    0,	/* NextEntryOffset (set later) */
530 		    resume_key,
531 		    &fileinfo->fi_crtime,
532 		    &fileinfo->fi_atime,
533 		    &fileinfo->fi_mtime,
534 		    &fileinfo->fi_ctime,
535 		    fileinfo->fi_size,
536 		    fileinfo->fi_alloc_size,
537 		    fileinfo->fi_dosattr,
538 		    namelen,
539 		    0L,		/* EaSize */
540 		    shortlen,
541 		    buf83);
542 
543 		smb_msgbuf_term(&mb);
544 		break;
545 
546 	/* See also: SMB_FIND_FILE_ID_BOTH_DIRECTORY_INFO */
547 	case FileIdBothDirectoryInformation:	/* 37 */
548 		bzero(buf83, sizeof (buf83));
549 		smb_msgbuf_init(&mb, buf83, sizeof (buf83), mb_flags);
550 		shortlen = smb_msgbuf_encode(&mb, "U", fileinfo->fi_shortname);
551 		if (shortlen < 0) {
552 			shortlen = 0;
553 			bzero(buf83, sizeof (buf83));
554 		}
555 
556 		rc = smb_mbc_encodef(
557 		    &sr->raw_data, "llTTTTqqlllb.24c..q",
558 		    0,	/* NextEntryOffset (set later) */
559 		    resume_key,
560 		    &fileinfo->fi_crtime,
561 		    &fileinfo->fi_atime,
562 		    &fileinfo->fi_mtime,
563 		    &fileinfo->fi_ctime,
564 		    fileinfo->fi_size,		/* q */
565 		    fileinfo->fi_alloc_size,	/* q */
566 		    fileinfo->fi_dosattr,	/* l */
567 		    namelen,			/* l */
568 		    0L,		/* EaSize	   l */
569 		    shortlen,			/* b. */
570 		    buf83,			/* 24c */
571 		    /* reserved			   .. */
572 		    fileinfo->fi_nodeid);	/* q */
573 
574 		smb_msgbuf_term(&mb);
575 		break;
576 
577 	/*
578 	 * MacOS, when using the AAPL extensions (see smb2_create)
579 	 * uses modified directory listing responses where the
580 	 * "EA size" field is replaced with "maximum access".
581 	 * This avoids the need for MacOS Finder to come back
582 	 * N times to get the maximum access for every file.
583 	 */
584 	case FileIdMacOsDirectoryInformation:
585 		rc = smb_mbc_encodef(
586 		    &sr->raw_data, "llTTTTqqll",
587 		    0,	/* NextEntryOffset (set later) */
588 		    resume_key,		/* a.k.a. file index */
589 		    &fileinfo->fi_crtime,
590 		    &fileinfo->fi_atime,
591 		    &fileinfo->fi_mtime,
592 		    &fileinfo->fi_ctime,
593 		    fileinfo->fi_size,		/* q */
594 		    fileinfo->fi_alloc_size,	/* q */
595 		    fileinfo->fi_dosattr,	/* l */
596 		    namelen);			/* l */
597 		if (rc != 0)
598 			break;
599 		/*
600 		 * This where FileIdMacOsDirectoryInformation
601 		 * differs from FileIdBothDirectoryInformation
602 		 * Instead of: EaSize, ShortNameLen, ShortName;
603 		 * MacOS wants: MaxAccess, ResourceForkSize, and
604 		 * 16 bytes of "compressed finder info".
605 		 * mi_rforksize + mi_finderinfo falls where
606 		 * the 24 byte shortname would normally be.
607 		 */
608 		rc = smb_mbc_encodef(
609 		    &sr->raw_data, "l..q16cwq",
610 		    macinfo->mi_maxaccess,	/* l */
611 		    /* short_name_len, reserved  (..) */
612 		    macinfo->mi_rforksize,	/* q */
613 		    macinfo->mi_finderinfo,	/* 16c */
614 		    macinfo->mi_unixmode,	/* w */
615 		    fileinfo->fi_nodeid);	/* q */
616 		break;
617 
618 	/* See also: SMB_FIND_FILE_NAMES_INFO */
619 	case FileNamesInformation:		/* 12 */
620 		rc = smb_mbc_encodef(
621 		    &sr->raw_data, "lll",
622 		    0,	/* NextEntryOffset (set later) */
623 		    resume_key,
624 		    namelen);
625 		break;
626 
627 	default:
628 		return (NT_STATUS_INVALID_INFO_CLASS);
629 	}
630 	if (rc)	/* smb_mbc_encodef failed */
631 		return (NT_STATUS_BUFFER_OVERFLOW);
632 
633 	/*
634 	 * At this point we have written all the fixed-size data
635 	 * for the specified info. class.  Now put the name and
636 	 * alignment padding, and then patch the NextEntryOffset.
637 	 * Also store this offset for the caller so they can
638 	 * patch this (again) to zero on the very last entry.
639 	 */
640 	rc = smb_mbc_encodef(
641 	    &sr->raw_data, "U",
642 	    fileinfo->fi_name);
643 	if (rc)
644 		return (NT_STATUS_BUFFER_OVERFLOW);
645 
646 	/* Next entry needs to be 8-byte aligned. */
647 	(void) smb_mbc_put_align(&sr->raw_data, 8);
648 
649 	next_entry_offset = sr->raw_data.chain_offset -	starting_offset;
650 	(void) smb_mbc_poke(&sr->raw_data, starting_offset, "l",
651 	    next_entry_offset);
652 	args->fa_last_entry = starting_offset;
653 
654 	return (0);
655 }
656