changeset 13955:be2bd4e678d9

3223 SMB FindFirst/FindNext don't fill in Last Name Offset Reviewed by: Bayard Bell <bayard.bell@nexenta.com> Reviewed by: Dan McDonald <danmcd@nexenta.com> Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com> Reviewed by: Yakov Zaytsev <yakov@nexenta.com> Approved by: Richard Lowe <richlowe@richlowe.net>
author Gordon Ross <gwr@nexenta.com>
date Mon, 10 Sep 2012 22:06:18 -0400
parents d0de3e428c42
children ba0ec3e45034
files usr/src/uts/common/fs/smbsrv/smb_find.c usr/src/uts/common/fs/smbsrv/smb_odir.c usr/src/uts/common/fs/smbsrv/smb_trans2_find.c usr/src/uts/common/smbsrv/smb_kproto.h usr/src/uts/common/smbsrv/smb_ktypes.h
diffstat 5 files changed, 254 insertions(+), 130 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/uts/common/fs/smbsrv/smb_find.c	Mon Jan 21 12:21:50 2013 -0800
+++ b/usr/src/uts/common/fs/smbsrv/smb_find.c	Mon Sep 10 22:06:18 2012 -0400
@@ -20,6 +20,7 @@
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <smbsrv/smb_kproto.h>
@@ -230,7 +231,7 @@
 	smb_odir_t		*od;
 	smb_fileinfo_t		fileinfo;
 	smb_odir_resume_t	odir_resume;
-	boolean_t		eos;
+	uint16_t		eos;
 
 	to_upper = B_FALSE;
 	if ((sr->session->dialect <= LANMAN1_0) ||
@@ -328,7 +329,7 @@
 
 	while (count < maxcount) {
 		rc = smb_odir_read_fileinfo(sr, od, &fileinfo, &eos);
-		if ((rc != 0 || (eos == B_TRUE)))
+		if (rc != 0 || eos != 0)
 			break;
 
 		if (*fileinfo.fi_shortname == '\0') {
@@ -407,7 +408,7 @@
 	char			name83[SMB_SHORTNAMELEN];
 	smb_odir_t		*od;
 	smb_fileinfo_t		fileinfo;
-	boolean_t		eos;
+	uint16_t		eos;
 
 	smb_pathname_t		*pn;
 	unsigned char		resume_char;
@@ -474,7 +475,7 @@
 
 	while (count < maxcount) {
 		rc = smb_odir_read_fileinfo(sr, od, &fileinfo, &eos);
-		if ((rc != 0 || (eos == B_TRUE)))
+		if (rc != 0 || eos != 0)
 			break;
 
 		if (*fileinfo.fi_shortname == '\0') {
@@ -618,7 +619,7 @@
 	char			name83[SMB_SHORTNAMELEN];
 	smb_odir_t		*od;
 	smb_fileinfo_t		fileinfo;
-	boolean_t		eos;
+	uint16_t		eos;
 	smb_vdb_t		*vdb;
 
 	if (smbsr_decode_vwv(sr, "ww", &maxcount, &sattr) != 0)
@@ -660,7 +661,7 @@
 
 	while (count < maxcount) {
 		rc = smb_odir_read_fileinfo(sr, od, &fileinfo, &eos);
-		if ((rc != 0 || (eos == B_TRUE)))
+		if (rc != 0 || eos != 0)
 			break;
 
 		if (*fileinfo.fi_shortname == '\0') {
--- a/usr/src/uts/common/fs/smbsrv/smb_odir.c	Mon Jan 21 12:21:50 2013 -0800
+++ b/usr/src/uts/common/fs/smbsrv/smb_odir.c	Mon Sep 10 22:06:18 2012 -0400
@@ -559,7 +559,7 @@
  */
 int
 smb_odir_read_fileinfo(smb_request_t *sr, smb_odir_t *od,
-    smb_fileinfo_t *fileinfo, boolean_t *eof)
+    smb_fileinfo_t *fileinfo, uint16_t *eof)
 {
 	int		rc, errnum;
 	smb_odirent_t	*odirent;
@@ -584,7 +584,7 @@
 		return (-1);
 	}
 
-	if (!(od->d_flags & SMB_ODIR_FLAG_WILDCARDS)) {
+	if ((od->d_flags & SMB_ODIR_FLAG_WILDCARDS) == 0) {
 		if (od->d_eof)
 			rc = ENOENT;
 		else
@@ -617,10 +617,10 @@
 
 	switch (rc) {
 	case 0:
-		*eof = B_FALSE;
+		*eof = 0;
 		return (0);
 	case ENOENT:
-		*eof = B_TRUE;
+		*eof = 1;	/* per. FindFirst, FindNext spec. */
 		return (0);
 	default:
 		smbsr_errno(sr, rc);
@@ -743,6 +743,28 @@
 }
 
 /*
+ * smb_odir_save_fname
+ *
+ * Save a filename / offset pair, which are basically a
+ * one entry cache.  See smb_com_trans2_find_next2.
+ */
+void
+smb_odir_save_fname(smb_odir_t *od, uint32_t cookie, const char *fname)
+{
+	ASSERT(od);
+	ASSERT(od->d_magic == SMB_ODIR_MAGIC);
+
+	mutex_enter(&od->d_mutex);
+
+	od->d_last_cookie = cookie;
+	bzero(od->d_last_name, MAXNAMELEN);
+	if (fname != NULL)
+		(void) strlcpy(od->d_last_name, fname, MAXNAMELEN);
+
+	mutex_exit(&od->d_mutex);
+}
+
+/*
  * smb_odir_resume_at
  *
  * If SMB_ODIR_FLAG_WILDCARDS is not set the search is for a single
@@ -775,23 +797,45 @@
 	}
 
 	switch (resume->or_type) {
-		case SMB_ODIR_RESUME_IDX:
-			ASSERT(resume->or_idx >= 0);
-			ASSERT(resume->or_idx < SMB_MAX_SEARCH);
+
+	default:
+	case SMB_ODIR_RESUME_CONT:
+		/* Continue where we left off. */
+		break;
+
+	case SMB_ODIR_RESUME_IDX:
+		/*
+		 * This is used only by the (ancient) SMB_SEARCH.
+		 * Modern clients use trans2 FindFirst, FindNext.
+		 */
+		ASSERT(resume->or_idx >= 0);
+		ASSERT(resume->or_idx < SMB_MAX_SEARCH);
 
-			if ((resume->or_idx < 0) ||
-			    (resume->or_idx >= SMB_MAX_SEARCH)) {
-				resume->or_idx = 0;
-			}
-			od->d_offset = od->d_cookies[resume->or_idx];
-			break;
-		case SMB_ODIR_RESUME_COOKIE:
+		if ((resume->or_idx < 0) ||
+		    (resume->or_idx >= SMB_MAX_SEARCH)) {
+			resume->or_idx = 0;
+		}
+		od->d_offset = od->d_cookies[resume->or_idx];
+		break;
+
+	case SMB_ODIR_RESUME_COOKIE:
+		od->d_offset = resume->or_cookie;
+		break;
+
+	case SMB_ODIR_RESUME_FNAME:
+		/*
+		 * If the name matches the last one saved,
+		 * use the offset that was saved with it in
+		 * the odir.  Otherwise use the cookie value
+		 * in the resume data from the client.
+		 */
+		if (strcmp(resume->or_fname, od->d_last_name) &&
+		    od->d_last_cookie != 0) {
+			od->d_offset = od->d_last_cookie;
+		} else if (resume->or_cookie != 0) {
 			od->d_offset = resume->or_cookie;
-			break;
-		case SMB_ODIR_RESUME_FNAME:
-		default:
-			od->d_offset = od->d_cookies[0];
-			break;
+		} /* else continue where we left off */
+		break;
 	}
 
 	/* Force a vop_readdir to refresh d_buf */
--- a/usr/src/uts/common/fs/smbsrv/smb_trans2_find.c	Mon Jan 21 12:21:50 2013 -0800
+++ b/usr/src/uts/common/fs/smbsrv/smb_trans2_find.c	Mon Sep 10 22:06:18 2012 -0400
@@ -19,6 +19,7 @@
  * CDDL HEADER END
  */
 /*
+ * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
@@ -206,15 +207,23 @@
 #include <smbsrv/msgbuf.h>
 #include <smbsrv/smb_fsops.h>
 
+/*
+ * Args (and other state) that we carry around among the
+ * various functions involved in FindFirst, FindNext.
+ */
 typedef struct smb_find_args {
+	uint32_t fa_maxdata;
 	uint16_t fa_infolev;
 	uint16_t fa_maxcount;
 	uint16_t fa_fflag;
-	uint32_t fa_maxdata;
+	uint16_t fa_eos;	/* End Of Search */
+	uint16_t fa_lno;	/* Last Name Offset */
+	uint32_t fa_lastkey;	/* Last resume key */
+	char fa_lastname[MAXNAMELEN]; /* and name */
 } smb_find_args_t;
 
 static int smb_trans2_find_entries(smb_request_t *, smb_xa_t *,
-    smb_odir_t *, smb_find_args_t *, boolean_t *);
+    smb_odir_t *, smb_find_args_t *);
 static int smb_trans2_find_get_maxdata(smb_request_t *, uint16_t, uint16_t);
 static int smb_trans2_find_mbc_encode(smb_request_t *, smb_xa_t *,
     smb_fileinfo_t *, smb_find_args_t *);
@@ -276,7 +285,6 @@
 	smb_pathname_t	*pn;
 	smb_odir_t	*od;
 	smb_find_args_t	args;
-	boolean_t	eos;
 	uint32_t	odir_flags = 0;
 
 	bzero(&args, sizeof (smb_find_args_t));
@@ -327,7 +335,8 @@
 	od = smb_tree_lookup_odir(sr->tid_tree, odid);
 	if (od == NULL)
 		return (SDRC_ERROR);
-	count = smb_trans2_find_entries(sr, xa, od, &args, &eos);
+
+	count = smb_trans2_find_entries(sr, xa, od, &args);
 
 	if (count == -1) {
 		smb_odir_close(od);
@@ -343,14 +352,18 @@
 	}
 
 	if ((args.fa_fflag & SMB_FIND_CLOSE_AFTER_REQUEST) ||
-	    (eos && (args.fa_fflag & SMB_FIND_CLOSE_AT_EOS))) {
+	    (args.fa_eos && (args.fa_fflag & SMB_FIND_CLOSE_AT_EOS))) {
 		smb_odir_close(od);
 	} /* else leave odir open for trans2_find_next2 */
 
 	smb_odir_release(od);
 
 	(void) smb_mbc_encodef(&xa->rep_param_mb, "wwwww",
-	    odid, count, (eos) ? 1 : 0, 0, 0);
+	    odid,	/* Search ID */
+	    count,	/* Search Count */
+	    args.fa_eos, /* End Of Search */
+	    0,		/* EA Error Offset */
+	    args.fa_lno); /* Last Name Offset */
 
 	return (SDRC_SUCCESS);
 }
@@ -422,27 +435,24 @@
 {
 	int			count;
 	uint16_t		odid;
-	uint32_t		cookie;
 	smb_odir_t		*od;
 	smb_find_args_t		args;
-	boolean_t		eos;
 	smb_odir_resume_t	odir_resume;
 
-	bzero(&args, sizeof (smb_find_args_t));
+	bzero(&args, sizeof (args));
+	bzero(&odir_resume, sizeof (odir_resume));
 
-	if (smb_mbc_decodef(&xa->req_param_mb, "%wwwlw", sr, &odid,
-	    &args.fa_maxcount, &args.fa_infolev, &cookie, &args.fa_fflag)
-	    != 0) {
+	if (!STYPE_ISDSK(sr->tid_tree->t_res_type)) {
+		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
+		    ERRDOS, ERROR_ACCESS_DENIED);
 		return (SDRC_ERROR);
 	}
 
-	/* continuation by filename not supported */
-	if ((args.fa_fflag & SMB_FIND_CONTINUE_FROM_LAST) || (cookie == 0)) {
-		odir_resume.or_type = SMB_ODIR_RESUME_IDX;
-		odir_resume.or_idx = 0;
-	} else {
-		odir_resume.or_type = SMB_ODIR_RESUME_COOKIE;
-		odir_resume.or_cookie = cookie;
+	if (smb_mbc_decodef(&xa->req_param_mb, "%wwwlwu", sr,
+	    &odid, &args.fa_maxcount, &args.fa_infolev,
+	    &odir_resume.or_cookie, &args.fa_fflag,
+	    &odir_resume.or_fname) != 0) {
+		return (SDRC_ERROR);
 	}
 
 	if (args.fa_fflag & SMB_FIND_WITH_BACKUP_INTENT)
@@ -459,9 +469,34 @@
 		    ERRDOS, ERROR_INVALID_HANDLE);
 		return (SDRC_ERROR);
 	}
+
+	/*
+	 * Set the correct position in the directory.
+	 *
+	 * "Continue from last" is easy, but due to a history of
+	 * buggy server implementations, most clients don't use
+	 * that method.  The most widely used (and reliable) is
+	 * resume by file name.  Unfortunately, that can't really
+	 * be fully supported unless your file system stores all
+	 * directory entries in some sorted order (like NTFS).
+	 * We can partially support resume by name, where the only
+	 * name we're ever asked to resume on is the same as the
+	 * most recent we returned.  That's always what the client
+	 * gives us as the resume name, so we can simply remember
+	 * the last name/offset pair and use that to position on
+	 * the following FindNext call.  In the unlikely event
+	 * that the client asks to resume somewhere else, we'll
+	 * use the numeric resume key, and hope the client gives
+	 * correctly uses one of the resume keys we provided.
+	 */
+	if (args.fa_fflag & SMB_FIND_CONTINUE_FROM_LAST) {
+		odir_resume.or_type = SMB_ODIR_RESUME_CONT;
+	} else {
+		odir_resume.or_type = SMB_ODIR_RESUME_FNAME;
+	}
 	smb_odir_resume_at(od, &odir_resume);
-	count = smb_trans2_find_entries(sr, xa, od, &args, &eos);
 
+	count = smb_trans2_find_entries(sr, xa, od, &args);
 	if (count == -1) {
 		smb_odir_close(od);
 		smb_odir_release(od);
@@ -469,13 +504,17 @@
 	}
 
 	if ((args.fa_fflag & SMB_FIND_CLOSE_AFTER_REQUEST) ||
-	    (eos && (args.fa_fflag & SMB_FIND_CLOSE_AT_EOS))) {
+	    (args.fa_eos && (args.fa_fflag & SMB_FIND_CLOSE_AT_EOS))) {
 		smb_odir_close(od);
 	} /* else leave odir open for trans2_find_next2 */
 
 	smb_odir_release(od);
+
 	(void) smb_mbc_encodef(&xa->rep_param_mb, "wwww",
-	    count, (eos) ? 1 : 0, 0, 0);
+	    count,	/* Search Count */
+	    args.fa_eos, /* End Of Search */
+	    0,		/* EA Error Offset */
+	    args.fa_lno); /* Last Name Offset */
 
 	return (SDRC_SUCCESS);
 }
@@ -494,12 +533,12 @@
  */
 static int
 smb_trans2_find_entries(smb_request_t *sr, smb_xa_t *xa, smb_odir_t *od,
-    smb_find_args_t *args, boolean_t *eos)
+    smb_find_args_t *args)
 {
 	int		rc;
 	uint16_t	count, maxcount;
-	uint32_t	cookie;
 	smb_fileinfo_t	fileinfo;
+	smb_odir_resume_t odir_resume;
 
 	if ((maxcount = args->fa_maxcount) == 0)
 		maxcount = 1;
@@ -509,9 +548,10 @@
 
 	count = 0;
 	while (count < maxcount) {
-		if (smb_odir_read_fileinfo(sr, od, &fileinfo, eos) != 0)
+		if (smb_odir_read_fileinfo(sr, od, &fileinfo, &args->fa_eos)
+		    != 0)
 			return (-1);
-		if (*eos == B_TRUE)
+		if (args->fa_eos != 0)
 			break;
 
 		rc = smb_trans2_find_mbc_encode(sr, xa, &fileinfo, args);
@@ -520,21 +560,38 @@
 		if (rc == 1)
 			break;
 
-		cookie = fileinfo.fi_cookie;
+		/*
+		 * Save the info about the last file returned.
+		 */
+		args->fa_lastkey = fileinfo.fi_cookie;
+		bcopy(fileinfo.fi_name, args->fa_lastname, MAXNAMELEN);
+
 		++count;
 	}
 
 	/* save the last cookie returned to client */
 	if (count != 0)
-		smb_odir_save_cookie(od, 0, cookie);
+		smb_odir_save_fname(od, args->fa_lastkey, args->fa_lastname);
 
 	/*
 	 * If all retrieved entries have been successfully encoded
 	 * and eos has not already been detected, check if there are
 	 * any more entries. eos will be set if there are no more.
 	 */
-	if ((rc == 0) && (!*eos))
-		(void) smb_odir_read_fileinfo(sr, od, &fileinfo, eos);
+	if ((rc == 0) && (args->fa_eos == 0))
+		(void) smb_odir_read_fileinfo(sr, od, &fileinfo, &args->fa_eos);
+
+	/*
+	 * When the last entry we read from the directory did not
+	 * fit in the return buffer, we will have read one entry
+	 * that will not be returned in this call.  That, and the
+	 * check for EOS just above both can leave the directory
+	 * position incorrect for the next call.  Fix that now.
+	 */
+	bzero(&odir_resume, sizeof (odir_resume));
+	odir_resume.or_type = SMB_ODIR_RESUME_COOKIE;
+	odir_resume.or_cookie = args->fa_lastkey;
+	smb_odir_resume_at(od, &odir_resume);
 
 	return (count);
 }
@@ -608,6 +665,21 @@
 }
 
 /*
+ * This is an experimental feature that allows us to return zero
+ * for all numeric resume keys, to match Windows behavior with an
+ * NTFS share.  Setting this variable to zero does that.
+ *
+ * It's possible we could remove this variable and always set
+ * numeric resume keys to zero, but that would leave us unable
+ * to handle a FindNext call with an arbitrary start position.
+ * In practice we never see these, but in theory we could.
+ *
+ * See the long comment above smb_com_trans2_find_next2() for
+ * more details about resume key / resume name handling.
+ */
+int smbd_use_resume_keys = 1;
+
+/*
  * smb_trans2_mbc_encode
  *
  * This function encodes the mbc for one directory entry.
@@ -632,23 +704,18 @@
 smb_trans2_find_mbc_encode(smb_request_t *sr, smb_xa_t *xa,
     smb_fileinfo_t *fileinfo, smb_find_args_t *args)
 {
-	int		namelen, shortlen, buflen;
+	int		namelen, shortlen;
 	uint32_t	next_entry_offset;
 	uint32_t	dsize32, asize32;
 	uint32_t	mb_flags = 0;
+	uint32_t	resume_key;
 	char		buf83[26];
-	char		*tmpbuf;
 	smb_msgbuf_t	mb;
 
 	namelen = smb_ascii_or_unicode_strlen(sr, fileinfo->fi_name);
 	if (namelen == -1)
 		return (-1);
 
-	next_entry_offset = args->fa_maxdata + namelen;
-
-	if (MBC_ROOM_FOR(&xa->rep_data_mb, (args->fa_maxdata + namelen)) == 0)
-		return (1);
-
 	/*
 	 * If ascii the filename length returned to the client should
 	 * include the null terminator for levels except STANDARD and
@@ -660,52 +727,49 @@
 			namelen += 1;
 	}
 
+	next_entry_offset = args->fa_maxdata + namelen;
+
+	if (MBC_ROOM_FOR(&xa->rep_data_mb, (args->fa_maxdata + namelen)) == 0)
+		return (1);
+
 	mb_flags = (sr->smb_flg2 & SMB_FLAGS2_UNICODE) ? SMB_MSGBUF_UNICODE : 0;
 	dsize32 = (fileinfo->fi_size > UINT_MAX) ?
 	    UINT_MAX : (uint32_t)fileinfo->fi_size;
 	asize32 = (fileinfo->fi_alloc_size > UINT_MAX) ?
 	    UINT_MAX : (uint32_t)fileinfo->fi_alloc_size;
 
+	resume_key = fileinfo->fi_cookie;
+	if (smbd_use_resume_keys == 0)
+		resume_key = 0;
+
+	/*
+	 * This switch handles all the "information levels" (formats)
+	 * that we support.  Note that all formats have the file name
+	 * placed after some fixed-size data, and the code to write
+	 * the file name is factored out at the end of this switch.
+	 */
 	switch (args->fa_infolev) {
 	case SMB_INFO_STANDARD:
 		if (args->fa_fflag & SMB_FIND_RETURN_RESUME_KEYS)
 			(void) smb_mbc_encodef(&xa->rep_data_mb, "l",
-			    fileinfo->fi_cookie);
+			    resume_key);
 
-		(void) smb_mbc_encodef(&xa->rep_data_mb, "%yyyllwbu", sr,
+		(void) smb_mbc_encodef(&xa->rep_data_mb, "%yyyllwb", sr,
 		    smb_time_gmt_to_local(sr, fileinfo->fi_crtime.tv_sec),
 		    smb_time_gmt_to_local(sr, fileinfo->fi_atime.tv_sec),
 		    smb_time_gmt_to_local(sr, fileinfo->fi_mtime.tv_sec),
 		    dsize32,
 		    asize32,
 		    fileinfo->fi_dosattr,
-		    namelen,
-		    fileinfo->fi_name);
+		    namelen);
 		break;
 
 	case SMB_INFO_QUERY_EA_SIZE:
 		if (args->fa_fflag & SMB_FIND_RETURN_RESUME_KEYS)
 			(void) smb_mbc_encodef(&xa->rep_data_mb, "l",
-			    fileinfo->fi_cookie);
+			    resume_key);
 
-		/*
-		 * Unicode filename should NOT be aligned. Encode ('u')
-		 * into a temporary buffer, then encode buffer as a
-		 * byte stream ('#c').
-		 * Regardless of whether unicode or ascii, a single
-		 * termination byte is used.
-		 */
-		buflen = namelen + sizeof (smb_wchar_t);
-		tmpbuf = kmem_zalloc(buflen, KM_SLEEP);
-		smb_msgbuf_init(&mb, (uint8_t *)tmpbuf, buflen, mb_flags);
-		if (smb_msgbuf_encode(&mb, "u", fileinfo->fi_name) < 0) {
-			smb_msgbuf_term(&mb);
-			kmem_free(tmpbuf, buflen);
-			return (-1);
-		}
-		tmpbuf[namelen] = '\0';
-
-		(void) smb_mbc_encodef(&xa->rep_data_mb, "%yyyllwlb#c", sr,
+		(void) smb_mbc_encodef(&xa->rep_data_mb, "%yyyllwlb", sr,
 		    smb_time_gmt_to_local(sr, fileinfo->fi_crtime.tv_sec),
 		    smb_time_gmt_to_local(sr, fileinfo->fi_atime.tv_sec),
 		    smb_time_gmt_to_local(sr, fileinfo->fi_mtime.tv_sec),
@@ -713,18 +777,27 @@
 		    asize32,
 		    fileinfo->fi_dosattr,
 		    0L,		/* EA Size */
-		    namelen,
-		    namelen + 1,
-		    tmpbuf);
-
-		smb_msgbuf_term(&mb);
-		kmem_free(tmpbuf, buflen);
+		    namelen);
 		break;
 
 	case SMB_FIND_FILE_DIRECTORY_INFO:
-		(void) smb_mbc_encodef(&xa->rep_data_mb, "%llTTTTqqllu", sr,
+		(void) smb_mbc_encodef(&xa->rep_data_mb, "%llTTTTqqll", sr,
 		    next_entry_offset,
-		    fileinfo->fi_cookie,
+		    resume_key,
+		    &fileinfo->fi_crtime,
+		    &fileinfo->fi_atime,
+		    &fileinfo->fi_mtime,
+		    &fileinfo->fi_ctime,
+		    fileinfo->fi_size,
+		    fileinfo->fi_alloc_size,
+		    fileinfo->fi_dosattr,
+		    namelen);
+		break;
+
+	case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+		(void) smb_mbc_encodef(&xa->rep_data_mb, "%llTTTTqqlll", sr,
+		    next_entry_offset,
+		    resume_key,
 		    &fileinfo->fi_crtime,
 		    &fileinfo->fi_atime,
 		    &fileinfo->fi_mtime,
@@ -733,13 +806,13 @@
 		    fileinfo->fi_alloc_size,
 		    fileinfo->fi_dosattr,
 		    namelen,
-		    fileinfo->fi_name);
+		    0L);
 		break;
 
-	case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
-		(void) smb_mbc_encodef(&xa->rep_data_mb, "%llTTTTqqlllu", sr,
+	case SMB_FIND_FILE_ID_FULL_DIRECTORY_INFO:
+		(void) smb_mbc_encodef(&xa->rep_data_mb, "%llTTTTqqlll4.q", sr,
 		    next_entry_offset,
-		    fileinfo->fi_cookie,
+		    resume_key,
 		    &fileinfo->fi_crtime,
 		    &fileinfo->fi_atime,
 		    &fileinfo->fi_mtime,
@@ -749,24 +822,7 @@
 		    fileinfo->fi_dosattr,
 		    namelen,
 		    0L,
-		    fileinfo->fi_name);
-		break;
-
-	case SMB_FIND_FILE_ID_FULL_DIRECTORY_INFO:
-		(void) smb_mbc_encodef(&xa->rep_data_mb, "%llTTTTqqlll4.qu", sr,
-		    next_entry_offset,
-		    fileinfo->fi_cookie,
-		    &fileinfo->fi_crtime,
-		    &fileinfo->fi_atime,
-		    &fileinfo->fi_mtime,
-		    &fileinfo->fi_ctime,
-		    fileinfo->fi_size,
-		    fileinfo->fi_alloc_size,
-		    fileinfo->fi_dosattr,
-		    namelen,
-		    0L,
-		    fileinfo->fi_nodeid,
-		    fileinfo->fi_name);
+		    fileinfo->fi_nodeid);
 		break;
 
 	case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
@@ -779,10 +835,10 @@
 		}
 		shortlen = smb_wcequiv_strlen(fileinfo->fi_shortname);
 
-		(void) smb_mbc_encodef(&xa->rep_data_mb, "%llTTTTqqlllb.24cu",
+		(void) smb_mbc_encodef(&xa->rep_data_mb, "%llTTTTqqlllb.24c",
 		    sr,
 		    next_entry_offset,
-		    fileinfo->fi_cookie,
+		    resume_key,
 		    &fileinfo->fi_crtime,
 		    &fileinfo->fi_atime,
 		    &fileinfo->fi_mtime,
@@ -793,8 +849,7 @@
 		    namelen,
 		    0L,
 		    shortlen,
-		    buf83,
-		    fileinfo->fi_name);
+		    buf83);
 
 		smb_msgbuf_term(&mb);
 		break;
@@ -811,10 +866,10 @@
 		    fileinfo->fi_shortname);
 
 		(void) smb_mbc_encodef(&xa->rep_data_mb,
-		    "%llTTTTqqlllb.24c2.qu",
+		    "%llTTTTqqlllb.24c2.q",
 		    sr,
 		    next_entry_offset,
-		    fileinfo->fi_cookie,
+		    resume_key,
 		    &fileinfo->fi_crtime,
 		    &fileinfo->fi_atime,
 		    &fileinfo->fi_mtime,
@@ -826,21 +881,40 @@
 		    0L,
 		    shortlen,
 		    buf83,
-		    fileinfo->fi_nodeid,
-		    fileinfo->fi_name);
+		    fileinfo->fi_nodeid);
 
 		smb_msgbuf_term(&mb);
 		break;
 
 	case SMB_FIND_FILE_NAMES_INFO:
-		(void) smb_mbc_encodef(&xa->rep_data_mb, "%lllu", sr,
+		(void) smb_mbc_encodef(&xa->rep_data_mb, "%lll", sr,
 		    next_entry_offset,
-		    fileinfo->fi_cookie,
-		    namelen,
-		    fileinfo->fi_name);
+		    resume_key,
+		    namelen);
 		break;
+
+	default:
+		/* invalid info. level */
+		return (-1);
 	}
 
+	/*
+	 * At this point we have written all the fixed-size data
+	 * for the specified info. level, and we're about to put
+	 * the file name string in the message.  We may later
+	 * need the offset in the trans2 data where this string
+	 * is placed, so save the message position now.  Note:
+	 * We also need to account for the alignment padding
+	 * that may precede the unicode string.
+	 */
+	args->fa_lno = xa->rep_data_mb.chain_offset;
+	if ((sr->smb_flg2 & SMB_FLAGS2_UNICODE) != 0 &&
+	    (args->fa_lno & 1) != 0)
+		args->fa_lno++;
+
+	(void) smb_mbc_encodef(&xa->rep_data_mb, "%u", sr,
+	    fileinfo->fi_name);
+
 	return (0);
 }
 
--- a/usr/src/uts/common/smbsrv/smb_kproto.h	Mon Jan 21 12:21:50 2013 -0800
+++ b/usr/src/uts/common/smbsrv/smb_kproto.h	Mon Sep 10 22:06:18 2012 -0400
@@ -588,11 +588,13 @@
 int smb_odir_read(smb_request_t *, smb_odir_t *,
     smb_odirent_t *, boolean_t *);
 int smb_odir_read_fileinfo(smb_request_t *, smb_odir_t *,
-    smb_fileinfo_t *, boolean_t *);
+    smb_fileinfo_t *, uint16_t *);
 int smb_odir_read_streaminfo(smb_request_t *, smb_odir_t *,
     smb_streaminfo_t *, boolean_t *);
 
 void smb_odir_save_cookie(smb_odir_t *, int, uint32_t cookie);
+void smb_odir_save_fname(smb_odir_t *, uint32_t, const char *);
+
 void smb_odir_resume_at(smb_odir_t *, smb_odir_resume_t *);
 
 /*
--- a/usr/src/uts/common/smbsrv/smb_ktypes.h	Mon Jan 21 12:21:50 2013 -0800
+++ b/usr/src/uts/common/smbsrv/smb_ktypes.h	Mon Sep 10 22:06:18 2012 -0400
@@ -20,7 +20,7 @@
  */
 /*
  * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2012 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
@@ -1267,6 +1267,7 @@
 } smb_odir_state_t;
 
 typedef enum {
+	SMB_ODIR_RESUME_CONT,
 	SMB_ODIR_RESUME_IDX,
 	SMB_ODIR_RESUME_COOKIE,
 	SMB_ODIR_RESUME_FNAME
@@ -1306,9 +1307,11 @@
 		edirent_t	*u_edp;
 		dirent64_t	*u_dp;
 	} d_u;
+	uint32_t		d_last_cookie;
 	uint32_t		d_cookies[SMB_MAX_SEARCH];
 	char			d_pattern[MAXNAMELEN];
 	char			d_buf[SMB_ODIR_BUFSIZE];
+	char			d_last_name[MAXNAMELEN];
 } smb_odir_t;
 #define	d_bufptr	d_u.u_bufptr
 #define	d_edp		d_u.u_edp
@@ -1323,7 +1326,7 @@
 typedef struct smb_fileinfo {
 	char		fi_name[MAXNAMELEN];
 	char		fi_shortname[SMB_SHORTNAMELEN];
-	uint32_t	fi_cookie;
+	uint32_t	fi_cookie;	/* Dir offset (of next entry) */
 	uint32_t	fi_dosattr;	/* DOS attributes */
 	uint64_t	fi_nodeid;	/* file system node id */
 	uint64_t	fi_size;	/* file size in bytes */