/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * This module provides functions for TRANS2_FIND_FIRST2 and
 * TRANS2_FIND_NEXT2 requests. The requests allow the client to search
 * for the file(s) which match the file specification.  The search is
 * started with TRANS2_FIND_FIRST2 and can be continued if necessary with
 * TRANS2_FIND_NEXT2. There are numerous levels of information which may be
 * obtained for the returned files, the desired level is specified in the
 * InformationLevel field of the requests.
 *
 *  InformationLevel Name              Value
 *  =================================  ================
 *
 *  SMB_INFO_STANDARD                  1
 *  SMB_INFO_QUERY_EA_SIZE             2
 *  SMB_INFO_QUERY_EAS_FROM_LIST       3
 *  SMB_FIND_FILE_DIRECTORY_INFO       0x101
 *  SMB_FIND_FILE_FULL_DIRECTORY_INFO  0x102
 *  SMB_FIND_FILE_NAMES_INFO           0x103
 *  SMB_FIND_FILE_BOTH_DIRECTORY_INFO  0x104
 *
 * The following sections detail the data returned for each
 * InformationLevel. The requested information is placed in the Data
 * portion of the transaction response. Note: a client which does not
 * support long names can only request SMB_INFO_STANDARD.
 *
 * A four-byte resume key precedes each data item (described below) if bit
 * 2 in the Flags field is set, i.e. if the request indicates the server
 * should return resume keys. Note: it is not always the case. If the
 * data item already includes the resume key, the resume key should not be
 * added again.
 *
 * 4.3.4.1   SMB_INFO_STANDARD
 *
 *  Response Field                    Description
 *  ================================  ==================================
 *
 *  SMB_DATE CreationDate;            Date when file was created
 *  SMB_TIME CreationTime;            Time when file was created
 *  SMB_DATE LastAccessDate;          Date of last file access
 *  SMB_TIME LastAccessTime;          Time of last file access
 *  SMB_DATE LastWriteDate;           Date of last write to the file
 *  SMB_TIME LastWriteTime;           Time of last write to the file
 *  ULONG  DataSize;                  File Size
 *  ULONG AllocationSize;             Size of filesystem allocation unit
 *  USHORT Attributes;                File Attributes
 *  UCHAR FileNameLength;             Length of filename in bytes
 *  STRING FileName;                  Name of found file
 *
 * 4.3.4.2   SMB_INFO_QUERY_EA_SIZE
 *
 *  Response Field                     Description
 *  =================================  ==================================
 *
 *   SMB_DATE CreationDate;            Date when file was created
 *   SMB_TIME CreationTime;            Time when file was created
 *   SMB_DATE LastAccessDate;          Date of last file access
 *   SMB_TIME LastAccessTime;          Time of last file access
 *   SMB_DATE LastWriteDate;           Date of last write to the file
 *   SMB_TIME LastWriteTime;           Time of last write to the file
 *   ULONG DataSize;                   File Size
 *   ULONG AllocationSize;             Size of filesystem allocation unit
 *   USHORT Attributes;                File Attributes
 *   ULONG EaSize;                     Size of file's EA information
 *   UCHAR FileNameLength;             Length of filename in bytes
 *   STRING FileName;                  Name of found file
 *
 * 4.3.4.3   SMB_INFO_QUERY_EAS_FROM_LIST
 *
 * This request returns the same information as SMB_INFO_QUERY_EA_SIZE, but
 * only for files which have an EA list which match the EA information in
 * the Data part of the request.
 *
 * 4.3.4.4   SMB_FIND_FILE_DIRECTORY_INFO
 *
 *  Response Field                     Description
 *  =================================  ==================================
 *
 *  ULONG NextEntryOffset;             Offset from this structure to
 *					beginning of next one
 *  ULONG FileIndex;
 *  LARGE_INTEGER CreationTime;        file creation time
 *  LARGE_INTEGER LastAccessTime;      last access time
 *  LARGE_INTEGER LastWriteTime;       last write time
 *  LARGE_INTEGER ChangeTime;          last attribute change time
 *  LARGE_INTEGER EndOfFile;           file size
 *  LARGE_INTEGER AllocationSize;      size of filesystem allocation information
 *  ULONG ExtFileAttributes;           Extended file attributes
 *					(see section 3.11)
 *  ULONG FileNameLength;              Length of filename in bytes
 *  STRING FileName;                   Name of the file
 *
 * 4.3.4.5   SMB_FIND_FILE_FULL_DIRECTORY_INFO
 *
 *  Response Field                     Description
 *  =================================  ==================================
 *
 *  ULONG NextEntryOffset;             Offset from this structure to
 *					beginning of next one
 *  ULONG FileIndex;
 *  LARGE_INTEGER CreationTime;        file creation time
 *  LARGE_INTEGER LastAccessTime;      last access time
 *  LARGE_INTEGER LastWriteTime;       last write time
 *  LARGE_INTEGER ChangeTime;          last attribute change time
 *  LARGE_INTEGER EndOfFile;           file size
 *  LARGE_INTEGER AllocationSize;      size of filesystem allocation information
 *  ULONG ExtFileAttributes;           Extended file attributes
 *					(see section 3.11)
 *  ULONG FileNameLength;              Length of filename in bytes
 *  ULONG EaSize;                      Size of file's extended attributes
 *  STRING FileName;                   Name of the file
 *
 * 4.3.4.6   SMB_FIND_FILE_BOTH_DIRECTORY_INFO
 *
 *  Response Field                     Description
 *  =================================  ==================================
 *
 *  ULONG NextEntryOffset;             Offset from this structure to
 *					beginning of next one
 *  ULONG FileIndex;
 *  LARGE_INTEGER CreationTime;        file creation time
 *  LARGE_INTEGER LastAccessTime;      last access time
 *  LARGE_INTEGER LastWriteTime;       last write time
 *  LARGE_INTEGER ChangeTime;          last attribute change time
 *  LARGE_INTEGER EndOfFile;           file size
 *  LARGE_INTEGER AllocationSize;      size of filesystem allocation information
 *  ULONG ExtFileAttributes;           Extended file attributes
 *					(see section 3.11)
 *  ULONG FileNameLength;              Length of FileName in bytes
 *  ULONG EaSize;                      Size of file's extended attributes
 *  UCHAR ShortNameLength;             Length of file's short name in bytes
 *  UCHAR Reserved
 *  WCHAR ShortName[12];               File's 8.3 conformant name in Unicode
 *  STRING FileName;                   Files full length name
 *
 * 4.3.4.7   SMB_FIND_FILE_NAMES_INFO
 *
 *  Response Field                     Description
 *  =================================  ==================================
 *
 *  ULONG NextEntryOffset;             Offset from this structure to
 *                                     beginning of next one
 *  ULONG FileIndex;
 *  ULONG FileNameLength;              Length of FileName in bytes
 *  STRING FileName;                   Files full length name
 */

#include <smbsrv/smb_incl.h>
#include <smbsrv/msgbuf.h>
#include <smbsrv/smbtrans.h>
#include <smbsrv/smb_fsops.h>

static int smb_trans2_find_get_maxdata(smb_request_t *, uint16_t, uint16_t);

int smb_trans2_find_get_dents(smb_request_t *, smb_xa_t *,
    uint16_t, uint16_t, int, smb_node_t *,
    uint16_t, uint16_t, int, char *, uint32_t *, int *, int *);

int smb_gather_dents_info(char *, ino_t, int, char *, uint32_t, int32_t *,
    smb_attr_t *, smb_node_t *, char *, char *);

int smb_trans2_find_process_ients(smb_request_t *, smb_xa_t *,
    smb_dent_info_hdr_t *, uint16_t, uint16_t, int,
    smb_node_t *, int *, uint32_t *);

int smb_trans2_find_mbc_encode(smb_request_t *, smb_xa_t *,
    smb_dent_info_t *, int, uint16_t, uint16_t,
    uint32_t, smb_node_t *, smb_node_t *);

/*
 * The UNIX characters below are considered illegal in Windows file names.
 * The following character conversions are used to support sites in which
 * Catia v4 is in use on UNIX and Catia v5 is in use on Windows.
 *
 * ---------------------------
 * Unix-char	| Windows-char
 * ---------------------------
 *   "		| (0x00a8) Diaeresis
 *   *		| (0x00a4) Currency Sign
 *   :		| (0x00f7) Division Sign
 *   <		| (0x00ab) Left-Pointing Double Angle Quotation Mark
 *   >		| (0x00bb) Right-Pointing Double Angle Quotation Mark
 *   ?		| (0x00bf) Inverted Question mark
 *   \		| (0x00ff) Latin Small Letter Y with Diaeresis
 *   |		| (0x00a6) Broken Bar
 */
static int (*catia_callback)(uint8_t *, uint8_t *, int) = NULL;
void smb_register_catia_callback(
    int (*catia_v4tov5)(uint8_t *, uint8_t *, int));
void smb_unregister_catia_callback();

/*
 * Tunable parameter to limit the maximum
 * number of entries to be returned.
 */
uint16_t smb_trans2_find_max = 128;

/*
 * smb_register_catia_callback
 *
 * This function will be invoked by the catia module to register its
 * function that translates filename in version 4 to a format that is
 * compatible to version 5.
 */
void
smb_register_catia_callback(
    int (*catia_v4tov5)(uint8_t *, uint8_t *, int))
{
	catia_callback = catia_v4tov5;
}

/*
 * smb_unregister_catia_callback
 *
 * This function will unregister the catia callback prior to the catia
 * module gets unloaded.
 */
void
smb_unregister_catia_callback()
{
	catia_callback = 0;
}

/*
 * smb_com_trans2_find_first2
 *
 *  Client Request                Value
 *  ============================  ==================================
 *
 *  UCHAR  WordCount              15
 *  UCHAR  TotalDataCount         Total size of extended attribute list
 *  UCHAR  SetupCount             1
 *  UCHAR  Setup[0]               TRANS2_FIND_FIRST2
 *
 *  Parameter Block Encoding      Description
 *  ============================  ==================================
 *  USHORT SearchAttributes;
 *  USHORT SearchCount;           Maximum number of entries to return
 *  USHORT Flags;                 Additional information:
 *                                Bit 0 - close search after this request
 *                                Bit 1 - close search if end of search
 *                                reached
 *                                Bit 2 - return resume keys for each
 *                                entry found
 *                                Bit 3 - continue search from previous
 *                                ending place
 *                                Bit 4 - find with backup intent
 *  USHORT InformationLevel;      See below
 *  ULONG SearchStorageType;
 *  STRING FileName;              Pattern for the search
 *  UCHAR Data[ TotalDataCount ]  FEAList if InformationLevel is
 *                                QUERY_EAS_FROM_LIST
 *
 *  Response Parameter Block      Description
 *  ============================  ==================================
 *
 *  USHORT Sid;                   Search handle
 *  USHORT SearchCount;           Number of entries returned
 *  USHORT EndOfSearch;           Was last entry returned?
 *  USHORT EaErrorOffset;         Offset into EA list if EA error
 *  USHORT LastNameOffset;        Offset into data to file name of last
 *                                entry, if server needs it to resume
 *                                search; else 0
 *  UCHAR Data[ TotalDataCount ]  Level dependent info about the matches
 *                                found in the search
 */
smb_sdrc_t
smb_com_trans2_find_first2(smb_request_t *sr, smb_xa_t *xa)
{
	int		more = 0, rc;
	uint16_t	sattr, fflag, infolev;
	uint16_t	maxcount = 0;
	int		maxdata;
	int		count, wildcards;
	uint32_t	cookie;
	char		*path;
	smb_node_t	*dir_snode;
	char		*pattern;
	uint16_t	sid;

	if (!STYPE_ISDSK(sr->tid_tree->t_res_type)) {
		smbsr_error(sr, NT_STATUS_ACCESS_DENIED,
		    ERRDOS, ERROR_ACCESS_DENIED);
		return (SDRC_ERROR);
	}

	if (smb_decode_mbc(&xa->req_param_mb, "%wwww4.u", sr,
	    &sattr, &maxcount, &fflag, &infolev, &path) != 0) {
		return (SDRC_ERROR);
	}

	maxdata = smb_trans2_find_get_maxdata(sr, infolev, fflag);
	if (maxdata == 0) {
		smbsr_error(sr, NT_STATUS_INVALID_LEVEL,
		    ERRDOS, ERROR_INVALID_LEVEL);
		return (SDRC_ERROR);
	}

	/*
	 * When maxcount is zero Windows behaves as if it was 1.
	 */
	if (maxcount == 0)
		maxcount = 1;

	if ((smb_trans2_find_max != 0) && (maxcount > smb_trans2_find_max))
		maxcount = smb_trans2_find_max;

	if (sr->smb_flg2 & SMB_FLAGS2_UNICODE)
		(void) smb_convert_unicode_wildcards(path);

	if (smb_rdir_open(sr, path, sattr) != 0)
		return (SDRC_ERROR);

	/*
	 * Get a copy of information
	 */
	dir_snode = sr->sid_odir->d_dir_snode;
	pattern = kmem_alloc(MAXNAMELEN, KM_SLEEP);
	(void) strcpy(pattern, sr->sid_odir->d_pattern);

	if (strcmp(pattern, "*.*") == 0)
		(void) strncpy(pattern, "*", sizeof (pattern));

	wildcards = sr->sid_odir->d_wildcards;
	sattr = sr->sid_odir->d_sattr;
	cookie = 0;

	rc = smb_trans2_find_get_dents(sr, xa, fflag, infolev, maxdata,
	    dir_snode, sattr, maxcount, wildcards,
	    pattern, &cookie, &more, &count);

	if (!count)
		rc = ENOENT;

	if (rc) {
		smb_rdir_close(sr);
		kmem_free(pattern, MAXNAMELEN);
		smbsr_errno(sr, rc);
		return (SDRC_ERROR);
	}

	/*
	 * Save the sid here in case the search is closed below,
	 * which will invalidate sr->smb_sid.  We return the
	 * sid, even though the search has been closed, to be
	 * compatible with Windows.
	 */
	sid = sr->smb_sid;

	if (fflag & SMB_FIND_CLOSE_AFTER_REQUEST ||
	    (!more && fflag & SMB_FIND_CLOSE_AT_EOS)) {
		smb_rdir_close(sr);
	} else {
		mutex_enter(&sr->sid_odir->d_mutex);
		sr->sid_odir->d_cookie = cookie;
		mutex_exit(&sr->sid_odir->d_mutex);
	}

	(void) smb_encode_mbc(&xa->rep_param_mb, "wwwww",
	    sid, count, (more ? 0 : 1), 0, 0);

	kmem_free(pattern, MAXNAMELEN);
	return (SDRC_SUCCESS);
}

/*
 * smb_com_trans2_find_next2
 *
 *  Client Request                     Value
 *  ================================== =================================
 *
 *  WordCount                          15
 *  SetupCount                         1
 *  Setup[0]                           TRANS2_FIND_NEXT2
 *
 *  Parameter Block Encoding           Description
 *  ================================== =================================
 *
 *  USHORT Sid;                        Search handle
 *  USHORT SearchCount;                Maximum number of entries to
 *                                      return
 *  USHORT InformationLevel;           Levels described in
 *                                      TRANS2_FIND_FIRST2 request
 *  ULONG ResumeKey;                   Value returned by previous find2
 *                                      call
 *  USHORT Flags;                      Additional information: bit set-
 *                                      0 - close search after this
 *                                      request
 *                                      1 - close search if end of search
 *                                      reached
 *                                      2 - return resume keys for each
 *                                      entry found
 *                                      3 - resume/continue from previous
 *                                      ending place
 *                                      4 - find with backup intent
 *  STRING FileName;                   Resume file name
 *
 * Sid is the value returned by a previous successful TRANS2_FIND_FIRST2
 * call.  If Bit3 of Flags is set, then FileName may be the NULL string,
 * since the search is continued from the previous TRANS2_FIND request.
 * Otherwise, FileName must not be more than 256 characters long.
 *
 *  Response Field                     Description
 *  ================================== =================================
 *
 *  USHORT SearchCount;                Number of entries returned
 *  USHORT EndOfSearch;                Was last entry returned?
 *  USHORT EaErrorOffset;              Offset into EA list if EA error
 *  USHORT LastNameOffset;             Offset into data to file name of
 *                                      last entry, if server needs it to
 *                                      resume search; else 0
 *  UCHAR Data[TotalDataCount]         Level dependent info about the
 *                                      matches found in the search
 */
smb_sdrc_t
smb_com_trans2_find_next2(smb_request_t *sr, smb_xa_t *xa)
{
	uint16_t fflag, infolev;
	int	maxdata, count, wildcards, more = 0, rc;
	uint32_t cookie;
	uint16_t maxcount = 0;
	smb_node_t *dir_snode;
	char *pattern;
	uint16_t sattr;

	/*
	 * The last parameter in the request is a path, which is a
	 * null-terminated unicode string.
	 *
	 * smb_decode_mbc(&xa->req_param_mb, "%www lwu", sr,
	 *    &sr->smb_sid, &maxcount, &infolev, &cookie, &fflag, &path)
	 *
	 * We don't reference this parameter and it is not currently
	 * decoded because we a expect 2-byte null but Mac OS 10
	 * clients send a 1-byte null, which leads to a decode error.
	 */
	if (smb_decode_mbc(&xa->req_param_mb, "%www lw", sr,
	    &sr->smb_sid, &maxcount, &infolev, &cookie, &fflag) != 0) {
		return (SDRC_ERROR);
	}

	sr->sid_odir = smb_odir_lookup_by_sid(sr->tid_tree, sr->smb_sid);
	if (sr->sid_odir == NULL) {
		smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
		return (SDRC_ERROR);
	}

	maxdata = smb_trans2_find_get_maxdata(sr, infolev, fflag);
	if (maxdata == 0) {
		smb_rdir_close(sr);
		smbsr_error(sr, NT_STATUS_INVALID_LEVEL,
		    ERRDOS, ERROR_INVALID_LEVEL);
		return (SDRC_ERROR);
	}

	/*
	 * When maxcount is zero Windows behaves as if it was 1.
	 */
	if (maxcount == 0)
		maxcount = 1;

	if ((smb_trans2_find_max != 0) && (maxcount > smb_trans2_find_max))
		maxcount = smb_trans2_find_max;

	/*
	 * Get a copy of information
	 */
	dir_snode = sr->sid_odir->d_dir_snode;
	pattern = kmem_alloc(MAXNAMELEN, KM_SLEEP);
	(void) strcpy(pattern, sr->sid_odir->d_pattern);
	wildcards = sr->sid_odir->d_wildcards;
	sattr = sr->sid_odir->d_sattr;
	if (fflag & SMB_FIND_CONTINUE_FROM_LAST) {
		mutex_enter(&sr->sid_odir->d_mutex);
		cookie = sr->sid_odir->d_cookie;
		mutex_exit(&sr->sid_odir->d_mutex);
	}

	rc = smb_trans2_find_get_dents(sr, xa, fflag, infolev, maxdata,
	    dir_snode, sattr, maxcount, wildcards, pattern, &cookie,
	    &more, &count);

	if (rc) {
		smb_rdir_close(sr);
		kmem_free(pattern, MAXNAMELEN);
		smbsr_errno(sr, rc);
		return (SDRC_ERROR);
	}

	if (fflag & SMB_FIND_CLOSE_AFTER_REQUEST ||
	    (!more && fflag & SMB_FIND_CLOSE_AT_EOS))
		smb_rdir_close(sr);
	else {
		mutex_enter(&sr->sid_odir->d_mutex);
		sr->sid_odir->d_cookie = cookie;
		mutex_exit(&sr->sid_odir->d_mutex);
	}

	(void) smb_encode_mbc(&xa->rep_param_mb, "wwww",
	    count, (more ? 0 : 1), 0, 0);

	kmem_free(pattern, MAXNAMELEN);
	return (SDRC_SUCCESS);
}

/*
 * smb_trans2_find_get_maxdata
 *
 * Calculate the minimum response space required for the specified
 * information level.
 *
 * A non-zero return value provides the minimum space required.
 * A return value of zero indicates an unknown information level.
 */
static int
smb_trans2_find_get_maxdata(smb_request_t *sr, uint16_t infolev, uint16_t fflag)
{
	int maxdata;

	maxdata = smb_ascii_or_unicode_null_len(sr);

	switch (infolev) {
	case SMB_INFO_STANDARD :
		if (fflag & SMB_FIND_RETURN_RESUME_KEYS)
			maxdata += sizeof (int32_t);
		maxdata += 2 + 2 + 2 + 4 + 4 + 2 + 1;
		break;

	case SMB_INFO_QUERY_EA_SIZE:
		if (fflag & SMB_FIND_RETURN_RESUME_KEYS)
			maxdata += sizeof (int32_t);
		maxdata += 2 + 2 + 2 + 4 + 4 + 2 + 4 + 1;
		break;

	case SMB_FIND_FILE_DIRECTORY_INFO:
		maxdata += 4 + 4 + 8 + 8 + 8 + 8 + 8 + 8 + 4 + 4;
		break;

	case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
		maxdata += 4 + 4 + 8 + 8 + 8 + 8 + 8 + 8 + 4 + 4 + 4 + 2 + 24;
		break;

	case SMB_FIND_FILE_NAMES_INFO:
		maxdata += 4 + 4 + 4;
		break;

	case SMB_MAC_FIND_BOTH_HFS_INFO:
		maxdata += 4 + 4 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 4 + 1 + 1 + 2 +
		    4 + 32 + 4 + 1 + 1 + 24 + 4;
		break;

	default:
		maxdata = 0;
	}

	return (maxdata);
}

/*
 * smb_trans2_find_get_dents
 *
 * This function will get all the directory entry information and mbc
 * encode it in the xa. If there is an error, it will be returned;
 * otherwise, 0 is returned.
 *
 * The more field will be updated. If the value returned is one, it means
 * there are more entries; otherwise, the returned value will be zero. The
 * cookie will also be updated to indicate the next start point for the
 * search. The count value will also be updated to stores the total entries
 * encoded.
 */
int smb_trans2_find_get_dents(
    smb_request_t	*sr,
    smb_xa_t		*xa,
    uint16_t		fflag,
    uint16_t		infolev,
    int			maxdata,
    smb_node_t		*dir_snode,
    uint16_t		sattr,
    uint16_t		maxcount,
    int			wildcards,
    char		*pattern,
    uint32_t		*cookie,
    int			*more,
    int			*count)
{
	smb_dent_info_hdr_t	*ihdr;
	smb_dent_info_t		*ient;
	int			dent_buf_size;
	int			i;
	int			total;
	int			maxentries;
	int			rc;

	ihdr = kmem_zalloc(sizeof (smb_dent_info_hdr_t), KM_SLEEP);
	*count = 0;

	if (!wildcards)
		maxentries = maxcount = 1;
	else {
		maxentries = (xa->rep_data_mb.max_bytes -
		    xa->rep_data_mb.chain_offset) / maxdata;
		if (maxcount > SMB_MAX_DENTS_IOVEC)
			maxcount = SMB_MAX_DENTS_IOVEC;
		if (maxentries > maxcount)
			maxentries = maxcount;
	}

	/* Each entry will need to be aligned so add _POINTER_ALIGNMENT */
	dent_buf_size =
	    maxentries * (SMB_MAX_DENT_INFO_SIZE + _POINTER_ALIGNMENT);
	ihdr->iov->iov_base = kmem_alloc(dent_buf_size, KM_SLEEP);

	ihdr->sattr = sattr;
	ihdr->pattern = pattern;
	ihdr->sr = sr;

	ihdr->uio.uio_iovcnt = maxcount;
	ihdr->uio.uio_resid = dent_buf_size;
	ihdr->uio.uio_iov = ihdr->iov;
	ihdr->uio.uio_loffset = 0;

	rc = smb_get_dents(sr, cookie, dir_snode, wildcards, ihdr, more);
	if (rc != 0) {
		goto out;
	}

	if (ihdr->iov->iov_len == 0)
		*count = 0;
	else
		*count = smb_trans2_find_process_ients(sr, xa, ihdr, fflag,
		    infolev, maxdata, dir_snode, more, cookie);
	rc = 0;

out:

	total = maxcount - ihdr->uio.uio_iovcnt;
	ASSERT((total >= 0) && (total <= SMB_MAX_DENTS_IOVEC));
	for (i = 0; i < total; i++) {
		/*LINTED E_BAD_PTR_CAST_ALIGN*/
		ient = (smb_dent_info_t *)ihdr->iov[i].iov_base;
		ASSERT(ient);
		smb_node_release(ient->snode);
	}

	kmem_free(ihdr->iov->iov_base, dent_buf_size);
	kmem_free(ihdr, sizeof (smb_dent_info_hdr_t));
	return (0);
}



/*
 * smb_get_dents
 *
 * This function utilizes "smb_fsop_getdents()" to get dir entries.
 * The "smb_gather_dents_info()" is the call back function called
 * inside the file system. It is very important that the function
 * does not sleep or yield since it is processed inside a file
 * system transaction.
 *
 * The function returns 0 when successful and error code when failed.
 * If more is provided, the return value of 1 is returned indicating
 * more entries; otherwise, 0 is returned.
 */
int smb_get_dents(
    smb_request_t	*sr,
    uint32_t		*cookie,
    smb_node_t		*dir_snode,
    uint32_t		wildcards,
    smb_dent_info_hdr_t	*ihdr,
    int			*more)
{
	int		rc;
	char		*namebuf;
	smb_node_t	*snode;
	smb_attr_t	file_attr;
	uint32_t	maxcnt = ihdr->uio.uio_iovcnt;
	char		shortname[MANGLE_NAMELEN], name83[MANGLE_NAMELEN];
	fsvol_attr_t	vol_attr;

	namebuf = kmem_zalloc(MAXNAMELEN, KM_SLEEP);
	if (more)
		*more = 0;

	if ((rc = fsd_getattr(&sr->tid_tree->t_fsd, &vol_attr)) != 0) {
		kmem_free(namebuf, MAXNAMELEN);
		return (rc);
	}

	if (!wildcards) {
		/* Already found entry? */
		if (*cookie != 0)
			return (0);
		shortname[0] = '\0';

		rc = smb_fsop_lookup(sr, sr->user_cr, 0, sr->tid_tree->t_snode,
		    dir_snode, ihdr->pattern, &snode, &file_attr, shortname,
		    name83);

		if (rc) {
			kmem_free(namebuf, MAXNAMELEN);
			return (rc);
		}

		(void) strlcpy(namebuf, ihdr->pattern, MAXNAMELEN);

		/*
		 * It is not necessary to set the "force" flag (i.e. to
		 * take into account mangling for case-insensitive collisions)
		 */

		if (shortname[0] == '\0')
			(void) smb_mangle_name(snode->attr.sa_vattr.va_nodeid,
			    namebuf, shortname, name83, 0);
		(void) smb_gather_dents_info((char *)ihdr,
		    snode->attr.sa_vattr.va_nodeid,
		    strlen(namebuf), namebuf, -1, (int *)&maxcnt,
		    &snode->attr, snode, shortname, name83);
		kmem_free(namebuf, MAXNAMELEN);
		return (0);
	}

	if ((rc = smb_fsop_getdents(sr, sr->user_cr, dir_snode, cookie,
	    0, (int *)&maxcnt, (char *)ihdr, ihdr->pattern)) != 0) {
		if (rc == ENOENT) {
			kmem_free(namebuf, MAXNAMELEN);
			return (0);
		}
		kmem_free(namebuf, MAXNAMELEN);
		return (rc);
	}

	if (*cookie != 0x7FFFFFFF && more)
		*more = 1;

	kmem_free(namebuf, MAXNAMELEN);
	return (0);
}




/*
 * smb_gather_dents_info
 *
 * The function will accept information of each directory entry and put
 * the needed information in the buffer. It is passed as the call back
 * function for smb_fsop_getdents() to gather trans2 find info.
 *
 * If the buffer space is not enough, -1 will be returned. Regardless
 * of valid entry or not, 0 will be returned; however, only valid entry
 * will be stored in the buffer.
 */
int /*ARGSUSED*/
smb_gather_dents_info(
    char	*args,
    ino_t	fileid,
    int		namelen,
    char	*name,
    uint32_t	cookie,
    int32_t	*countp,
    smb_attr_t	*attr,
    smb_node_t	*snode,
    char	*shortname,
    char	*name83)
{
	/*LINTED E_BAD_PTR_CAST_ALIGN*/
	smb_dent_info_hdr_t	*ihdr = (smb_dent_info_hdr_t *)args;
	smb_dent_info_t		*ient;
	uint8_t			*v5_name = NULL;
	uint8_t			*np = (uint8_t *)name;
	int			reclen = sizeof (smb_dent_info_t) + namelen;

	v5_name = kmem_alloc(MAXNAMELEN-1, KM_SLEEP);

	if (!ihdr->uio.uio_iovcnt || ihdr->uio.uio_resid < reclen) {
		kmem_free(v5_name, MAXNAMELEN-1);
		smb_node_release(snode);
		return (-1);
	}

	if (!smb_sattr_check(attr, name, ihdr->sattr)) {
		kmem_free(v5_name, MAXNAMELEN-1);
		smb_node_release(snode);
		return (0);
	}

	if (catia_callback) {
		catia_callback(v5_name, (uint8_t *)name,  MAXNAMELEN-1);
		np = v5_name;
		reclen = sizeof (smb_dent_info_t) + strlen((char *)v5_name);
	}

	ASSERT(snode);
	ASSERT(snode->n_magic == SMB_NODE_MAGIC);
	ASSERT(snode->n_state != SMB_NODE_STATE_DESTROYING);

	/*
	 * Each entry needs to be properly aligned or we may get an alignment
	 * fault on sparc.
	 */
	ihdr->uio.uio_loffset = (offset_t)PTRALIGN(ihdr->uio.uio_loffset);
	/*LINTED E_BAD_PTR_CAST_ALIGN*/
	ient = (smb_dent_info_t *)&ihdr->iov->iov_base[ihdr->uio.uio_loffset];

	ient->cookie = cookie;
	ient->attr = *attr;
	ient->snode = snode;

	(void) strcpy(ient->name, (char *)np);
	(void) strcpy(ient->shortname, shortname);
	(void) strcpy(ient->name83, name83);
	ihdr->uio.uio_iov->iov_base = (char *)ient;
	ihdr->uio.uio_iov->iov_len = reclen;

	ihdr->uio.uio_iov++;
	ihdr->uio.uio_iovcnt--;
	ihdr->uio.uio_resid -= reclen;
	ihdr->uio.uio_loffset += reclen;

	kmem_free(v5_name, MAXNAMELEN-1);
	return (0);
}



/*
 * smb_trans2_find_process_ients
 *
 * This function encodes the directory entry information store in
 * the iov structure of the ihdr structure.
 *
 * The total entries encoded will be returned. If the entries encoded
 * is less than the total entries in the iov, the more field will
 * be updated to 1. Also, the next cookie wil be updated as well.
 */
int
smb_trans2_find_process_ients(
    smb_request_t	*sr,
    smb_xa_t		*xa,
    smb_dent_info_hdr_t	*ihdr,
    uint16_t		fflag,
    uint16_t		infolev,
    int			maxdata,
    smb_node_t		*dir_snode,
    int			*more,
    uint32_t		*cookie)
{
	int i, err = 0;
	smb_dent_info_t *ient;
	uint32_t mb_flags = (sr->smb_flg2 & SMB_FLAGS2_UNICODE)
	    ? SMB_MSGBUF_UNICODE : 0;

	for (i = 0; i < SMB_MAX_DENTS_IOVEC; i++) {
		/*LINTED E_BAD_PTR_CAST_ALIGN*/
		if ((ient = (smb_dent_info_t *)ihdr->iov[i].iov_base) == 0)
			break;

		/*
		 * Observed differences between our response and Windows
		 * response, which hasn't caused a problem yet!
		 *
		 * 1. The NextEntryOffset field for the last entry should
		 * be 0.  This code always calculate the record length
		 * and puts the result in the NextEntryOffset field.
		 *
		 * 2. The FileIndex field is always 0.  This code puts
		 * the cookie in the FileIndex field.
		 */
		err = smb_trans2_find_mbc_encode(sr, xa, ient, maxdata, infolev,
		    fflag, mb_flags, dir_snode, NULL);

		if (err)
			break;
	}

	/*
	 * Not enough space to store all the entries returned,
	 * which is indicated by setting more.
	 */
	if (more && err < 0) {
		*more = 1;

		/*
		 * Assume the space will be at least enough for 1 entry.
		 */
		/*LINTED E_BAD_PTR_CAST_ALIGN*/
		ient = (smb_dent_info_t *)ihdr->iov[i-1].iov_base;
		*cookie = ient->cookie;
	}
	return (i);
}

/*
 * smb_trans2_find_mbc_encode
 *
 * This function encodes the mbc for one directory entry.
 *
 * The function returns -1 when the max data requested by client
 * is reached. If the entry is valid and successful encoded, 0
 * will be returned; otherwise, 1 will be returned.
 */
int /*ARGSUSED*/
smb_trans2_find_mbc_encode(
    smb_request_t	*sr,
    smb_xa_t		*xa,
    smb_dent_info_t	*ient,
    int			maxdata,
    uint16_t		infolev,
    uint16_t		fflag,
    uint32_t		mb_flags,
    smb_node_t		*dir_snode,
    smb_node_t		*sd_snode)
{
	int uni_namelen;
	int shortlen;
	uint32_t next_entry_offset;
	char buf83[26];
	smb_msgbuf_t mb;
	uint32_t dattr = 0;
	uint32_t dsize32 = 0;
	uint32_t asize32 = 0;
	u_offset_t datasz = 0;
	u_offset_t allocsz = 0;
	smb_node_t *lnk_snode;
	smb_attr_t lnkattr;
	int rc;

	uni_namelen = smb_ascii_or_unicode_strlen(sr, ient->name);
	if (uni_namelen == -1)
		return (1);

	next_entry_offset = maxdata + uni_namelen;

	if (MBC_ROOM_FOR(&xa->rep_data_mb, (maxdata + uni_namelen)) == 0)
		return (-1);

	if (ient->attr.sa_vattr.va_type == VLNK) {
		rc = smb_fsop_lookup(sr, sr->user_cr, SMB_FOLLOW_LINKS,
		    sr->tid_tree->t_snode, dir_snode, ient->name, &lnk_snode,
		    &lnkattr, 0, 0);

		/*
		 * We normally want to resolve the object to which a symlink
		 * refers so that CIFS clients can access sub-directories and
		 * find the correct association for files. This causes a
		 * problem, however, if a symlink in a sub-directory points
		 * to a parent directory (some UNIX GUI's create a symlink in
		 * $HOME/.desktop that points to the user's home directory).
		 * Some Windows applications (i.e. virus scanning) loop/hang
		 * trying to follow this recursive path and there is little
		 * we can do because the path is constructed on the client.
		 * skc_dirsymlink_enable allows an end-user to disable
		 * symlinks to directories. Symlinks to other object types
		 * should be unaffected.
		 */
		if (rc == 0) {
			if (smb_dirsymlink_enable ||
			    (lnkattr.sa_vattr.va_type != VDIR)) {
				smb_node_release(ient->snode);
				ient->snode = lnk_snode;
				ient->attr = lnkattr;
			} else {
				smb_node_release(lnk_snode);
			}
		}
	}

	if (infolev != SMB_FIND_FILE_NAMES_INFO) {
		/* data size */
		datasz = smb_node_get_size(ient->snode, &ient->attr);
		dsize32 = (datasz > UINT_MAX) ? UINT_MAX : (uint32_t)datasz;

		/* allocation size */
		allocsz = ient->attr.sa_vattr.va_nblocks * DEV_BSIZE;
		asize32 = (allocsz > UINT_MAX) ? UINT_MAX : (uint32_t)allocsz;

		dattr = smb_mode_to_dos_attributes(&ient->attr);
	}

	switch (infolev) {
	case SMB_INFO_STANDARD:
		if (fflag & SMB_FIND_RETURN_RESUME_KEYS)
			(void) smb_encode_mbc(&xa->rep_data_mb, "l",
			    ient->cookie);

		(void) smb_encode_mbc(&xa->rep_data_mb, "%yyyllwbu", sr,
		    ient->attr.sa_crtime.tv_sec ? ient->attr.sa_crtime.tv_sec :
		    ient->attr.sa_vattr.va_mtime.tv_sec,
		    ient->attr.sa_vattr.va_atime.tv_sec,
		    ient->attr.sa_vattr.va_mtime.tv_sec,
		    dsize32,
		    asize32,
		    dattr,
		    uni_namelen,
		    ient->name);
		break;

	case SMB_INFO_QUERY_EA_SIZE:
		if (fflag & SMB_FIND_RETURN_RESUME_KEYS)
			(void) smb_encode_mbc(&xa->rep_data_mb, "l",
			    ient->cookie);

		(void) smb_encode_mbc(&xa->rep_data_mb, "%yyyllwlbu", sr,
		    ient->attr.sa_crtime.tv_sec ? ient->attr.sa_crtime.tv_sec :
		    ient->attr.sa_vattr.va_mtime.tv_sec,
		    ient->attr.sa_vattr.va_atime.tv_sec,
		    ient->attr.sa_vattr.va_mtime.tv_sec,
		    dsize32,
		    asize32,
		    dattr,
		    0L,		/* EA Size */
		    uni_namelen,
		    ient->name);
		break;

	case SMB_FIND_FILE_DIRECTORY_INFO:
		(void) smb_encode_mbc(&xa->rep_data_mb, "%llTTTTqqllu", sr,
		    next_entry_offset,
		    ient->cookie,
		    ient->attr.sa_crtime.tv_sec ? &ient->attr.sa_crtime :
		    &ient->attr.sa_vattr.va_mtime,
		    &ient->attr.sa_vattr.va_atime,
		    &ient->attr.sa_vattr.va_mtime,
		    &ient->attr.sa_vattr.va_ctime,
		    (uint64_t)datasz,
		    (uint64_t)allocsz,
		    dattr,
		    uni_namelen,
		    ient->name);
		break;

	case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
		bzero(buf83, sizeof (buf83));
		smb_msgbuf_init(&mb, (uint8_t *)buf83, sizeof (buf83),
		    mb_flags);
		if (smb_msgbuf_encode(&mb, "u", ient->shortname) < 0) {
			smb_msgbuf_term(&mb);
			return (-1);
		}
		shortlen = smb_ascii_or_unicode_strlen(sr, ient->shortname);

		(void) smb_encode_mbc(&xa->rep_data_mb, "%llTTTTqqlllb.24cu",
		    sr,
		    next_entry_offset,
		    ient->cookie,
		    ient->attr.sa_crtime.tv_sec ? &ient->attr.sa_crtime :
		    &ient->attr.sa_vattr.va_mtime,
		    &ient->attr.sa_vattr.va_atime,
		    &ient->attr.sa_vattr.va_mtime,
		    &ient->attr.sa_vattr.va_ctime,
		    (uint64_t)datasz,
		    (uint64_t)allocsz,
		    dattr,
		    uni_namelen,
		    0L,
		    shortlen,
		    buf83,
		    ient->name);

		smb_msgbuf_term(&mb);
		break;

	case SMB_FIND_FILE_NAMES_INFO:
		(void) smb_encode_mbc(&xa->rep_data_mb, "%lllu", sr,
		    next_entry_offset,
		    ient->cookie,
		    uni_namelen,
		    ient->name);
		break;
	}

	return (0);
}

/*
 * Close a search started by a Trans2FindFirst2 request.
 */
smb_sdrc_t
smb_pre_find_close2(smb_request_t *sr)
{
	int rc;

	rc = smbsr_decode_vwv(sr, "w", &sr->smb_sid);

	DTRACE_SMB_1(op__FindClose2__start, smb_request_t *, sr);
	return ((rc == 0) ? SDRC_SUCCESS : SDRC_ERROR);
}

void
smb_post_find_close2(smb_request_t *sr)
{
	DTRACE_SMB_1(op__FindClose2__done, smb_request_t *, sr);
}

smb_sdrc_t
smb_com_find_close2(smb_request_t *sr)
{
	sr->sid_odir = smb_odir_lookup_by_sid(sr->tid_tree, sr->smb_sid);
	if (sr->sid_odir == NULL) {
		smbsr_error(sr, NT_STATUS_INVALID_HANDLE, ERRDOS, ERRbadfid);
		return (SDRC_ERROR);
	}

	smb_rdir_close(sr);

	if (smbsr_encode_empty_result(sr))
		return (SDRC_ERROR);

	return (SDRC_SUCCESS);
}