root/usr/src/uts/common/fs/smbsrv/smb_trans2_find.c
/*
 * 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 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2017 Nexenta Systems, Inc.  All rights reserved.
 */


/*
 * 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
 *  SMB_FIND_FILE_ID_FULL_DIRECTORY_INFO  0x105
 *  SMB_FIND_FILE_ID_BOTH_DIRECTORY_INFO  0x106
 *
 * 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
 *
 *
 *  SMB_FIND_FILE_ID_FULL_DIRECTORY_INFO
 *
 *  This is the same as SMB_FIND_FILE_FULL_DIRECTORY_INFO but with
 *  FileId inserted after EaSize. FileId is preceded by a 4 byte
 *  alignment padding.
 *
 *  Response Field                     Description
 *  =================================  ==================================
 *  ...
 *  ULONG EaSize;                      Size of file's extended attributes
 *  UCHAR Reserved[4]
 *  LARGE_INTEGER FileId               Internal file system unique id.
 *  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
 *
 *
 *  SMB_FIND_FILE_ID_BOTH_DIRECTORY_INFO
 *
 *  This is the same as SMB_FIND_FILE_BOTH_DIRECTORY_INFO but with
 *  FileId inserted after ShortName. FileId is preceded by a 2 byte
 *  alignment pad.
 *
 *  Response Field                     Description
 *  =================================  ==================================
 *  ...
 *  WCHAR ShortName[12];               File's 8.3 conformant name in Unicode
 *  UCHAR Reserved[2]
 *  LARGE_INTEGER FileId               Internal file system unique id.
 *  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_kproto.h>
#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_fixedsize;
        uint16_t fa_infolev;
        uint16_t fa_maxcount;
        uint16_t fa_fflag;
        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 *);
static int smb_trans2_find_get_fixedsize(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 *);

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

/*
 * 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             count;
        uint16_t        sattr;
        smb_pathname_t  *pn;
        smb_odir_t      *od;
        smb_find_args_t args;
        uint32_t        status;
        uint32_t        odir_flags = 0;

        bzero(&args, sizeof (smb_find_args_t));

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

        pn = &sr->arg.dirop.fqi.fq_path;

        if (smb_mbc_decodef(&xa->req_param_mb, "%wwww4.u", sr, &sattr,
            &args.fa_maxcount, &args.fa_fflag, &args.fa_infolev,
            &pn->pn_path) != 0) {
                return (SDRC_ERROR);
        }

        smb_pathname_init(sr, pn, pn->pn_path);
        if (!smb_pathname_validate(sr, pn))
                return (-1);

        if (smb_is_stream_name(pn->pn_path)) {
                smbsr_error(sr, NT_STATUS_OBJECT_NAME_INVALID,
                    ERRDOS, ERROR_INVALID_NAME);
                return (SDRC_ERROR);
        }

        if (args.fa_fflag & SMB_FIND_WITH_BACKUP_INTENT) {
                sr->user_cr = smb_user_getprivcred(sr->uid_user);
                odir_flags = SMB_ODIR_OPENF_BACKUP_INTENT;
        }

        args.fa_fixedsize =
            smb_trans2_find_get_fixedsize(sr, args.fa_infolev, args.fa_fflag);
        if (args.fa_fixedsize == 0)
                return (SDRC_ERROR);

        status = smb_odir_openpath(sr, pn->pn_path, sattr, odir_flags, &od);
        if (status != 0) {
                smbsr_error(sr, status, 0, 0);
                return (SDRC_ERROR);
        }
        if (od == NULL)
                return (SDRC_ERROR);

        count = smb_trans2_find_entries(sr, xa, od, &args);

        if (count == -1) {
                smb_odir_close(od);
                smb_odir_release(od);
                return (SDRC_ERROR);
        }

        if (count == 0) {
                smb_odir_close(od);
                smb_odir_release(od);
                smbsr_status(sr, NT_STATUS_NO_SUCH_FILE,
                    ERRDOS, ERROR_FILE_NOT_FOUND);
                return (SDRC_ERROR);
        }

        if ((args.fa_fflag & SMB_FIND_CLOSE_AFTER_REQUEST) ||
            (args.fa_eos && (args.fa_fflag & SMB_FIND_CLOSE_AT_EOS))) {
                smb_odir_close(od);
        } /* else leave odir open for trans2_find_next2 */

        (void) smb_mbc_encodef(&xa->rep_param_mb, "wwwww",
            od->d_odid, /* Search ID */
            count,      /* Search Count */
            args.fa_eos, /* End Of Search */
            0,          /* EA Error Offset */
            args.fa_lno); /* Last Name Offset */

        smb_odir_release(od);

        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
 *
 *
 * The last parameter in the request is a filename, which is a
 * null-terminated unicode string.
 *
 * smb_mbc_decodef(&xa->req_param_mb, "%www lwu", sr,
 *    &odid, &fa_maxcount, &fa_infolev, &cookie, &fa_fflag, &fname)
 *
 * The filename parameter is not currently decoded because we
 * expect a 2-byte null but Mac OS 10 clients send a 1-byte null,
 * which leads to a decode error.
 * Thus, we do not support resume by filename.  We treat a request
 * to resume by filename as SMB_FIND_CONTINUE_FROM_LAST.
 */
smb_sdrc_t
smb_com_trans2_find_next2(smb_request_t *sr, smb_xa_t *xa)
{
        int                     count;
        uint16_t                odid;
        smb_odir_t              *od;
        smb_find_args_t         args;
        smb_odir_resume_t       odir_resume;

        bzero(&args, sizeof (args));
        bzero(&odir_resume, sizeof (odir_resume));

        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_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)
                sr->user_cr = smb_user_getprivcred(sr->uid_user);

        args.fa_fixedsize =
            smb_trans2_find_get_fixedsize(sr, args.fa_infolev, args.fa_fflag);
        if (args.fa_fixedsize == 0)
                return (SDRC_ERROR);

        od = smb_tree_lookup_odir(sr, odid);
        if (od == NULL) {
                smbsr_error(sr, NT_STATUS_INVALID_HANDLE,
                    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);
        if (count == -1) {
                smb_odir_close(od);
                smb_odir_release(od);
                return (SDRC_ERROR);
        }

        if ((args.fa_fflag & SMB_FIND_CLOSE_AFTER_REQUEST) ||
            (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,      /* Search Count */
            args.fa_eos, /* End Of Search */
            0,          /* EA Error Offset */
            args.fa_lno); /* Last Name Offset */

        return (SDRC_SUCCESS);
}


/*
 * smb_trans2_find_entries
 *
 * Find and encode up to args->fa_maxcount directory entries.
 * For compatibilty with Windows, if args->fa_maxcount is zero treat it as 1.
 *
 * Returns:
 *   count - count of entries encoded
 *           *eos = B_TRUE if no more directory entries
 *      -1 - error
 */
static int
smb_trans2_find_entries(smb_request_t *sr, smb_xa_t *xa, smb_odir_t *od,
    smb_find_args_t *args)
{
        smb_fileinfo_t  fileinfo;
        smb_odir_resume_t odir_resume;
        uint16_t        count, maxcount;
        int             rc = -1;
        int             LastEntryOffset = 0;
        boolean_t       need_rewind = B_FALSE;

        /*
         * EAs are not current supported, so a search for level
         * SMB_INFO_QUERY_EAS_FROM_LIST should always return an
         * empty list.  Returning zero for this case gives the
         * client an empty response, which is better than an
         * NT_STATUS_INVALID_LEVEL return (and test failures).
         *
         * If and when we do support EAs, this level will modify
         * the search here, and then return results just like
         * SMB_INFO_QUERY_EA_SIZE, but only including files
         * that have an EA in the provided list.
         */
        if (args->fa_infolev == SMB_INFO_QUERY_EAS_FROM_LIST)
                return (0);

        if ((maxcount = args->fa_maxcount) == 0)
                maxcount = 1;

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

        count = 0;
        while (count < maxcount) {
                rc = smb_odir_read_fileinfo(sr, od, &fileinfo, &args->fa_eos);
                if (rc != 0 || args->fa_eos != 0)
                        break;

                LastEntryOffset = xa->rep_data_mb.chain_offset;
                rc = smb_trans2_find_mbc_encode(sr, xa, &fileinfo, args);
                if (rc == -1)
                        return (-1); /* fatal encoding error */
                if (rc == 1) {
                        need_rewind = B_TRUE;
                        break; /* output space exhausted */
                }

                /*
                 * Save the info about the last file returned.
                 */
                args->fa_lastkey = fileinfo.fi_cookie;
                bcopy(fileinfo.fi_name, args->fa_lastname, MAXNAMELEN);

                ++count;
        }
        if (args->fa_eos != 0 && rc == ENOENT)
                rc = 0;

        /*
         * All but the ancient info levels start with NextEntryOffset.
         * That's supposed to be zero in the last entry returned.
         */
        if (args->fa_infolev >= SMB_FIND_FILE_DIRECTORY_INFO) {
                (void) smb_mbc_poke(&xa->rep_data_mb,
                    LastEntryOffset, "l", 0);
        }

        /* save the last cookie returned to client */
        if (count != 0)
                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) && (args->fa_eos == 0)) {
                rc = smb_odir_read_fileinfo(sr, od, &fileinfo, &args->fa_eos);
                /*
                 * If rc == ENOENT, we did not read any additional data.
                 * if rc != 0, there's no need to rewind.
                 */
                if (rc == 0)
                        need_rewind = B_TRUE;
        }

        /*
         * 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.
         */
        if (need_rewind) {
                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);
}

/*
 * smb_trans2_find_get_fixedsize
 *
 * Calculate the sizeof the fixed part of the response for the
 * specified information level.
 *
 * A non-zero return value provides the fixed size.
 * A return value of zero indicates an unknown information level.
 */
static int
smb_trans2_find_get_fixedsize(smb_request_t *sr, uint16_t infolev,
        uint16_t fflag)
{
        int maxdata = 0;

        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:
        case SMB_INFO_QUERY_EAS_FROM_LIST:
                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_FULL_DIRECTORY_INFO:
                maxdata += 4 + 4 + 8 + 8 + 8 + 8 + 8 + 8 + 4 + 4 + 4;
                break;

        case SMB_FIND_FILE_ID_FULL_DIRECTORY_INFO:
                maxdata += 4 + 4 + 8 + 8 + 8 + 8 + 8 + 8 + 4 + 4 + 4 + 4 + 8;
                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_ID_BOTH_DIRECTORY_INFO:
                maxdata += 4 + 4 + 8 + 8 + 8 + 8 + 8 + 8 + 4 + 4 + 4 + 2 + 24
                    + 2 + 8;
                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;
                smbsr_error(sr, NT_STATUS_INVALID_LEVEL,
                    ERRDOS, ERROR_INVALID_LEVEL);
        }

        return (maxdata);
}

/*
 * 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.
 *
 * 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.
 *
 * We always null terminate the filename. The space for the null
 * is included in the maxdata calculation and is therefore included
 * in the next_entry_offset. namelen is the unterminated length of
 * the filename. For levels except STANDARD and EA_SIZE, if the
 * filename is ascii the name length returned to the client should
 * include the null terminator. Otherwise the length returned to
 * the client should not include the terminator.
 *
 * Returns: 0 - data successfully encoded
 *          1 - client request's maxdata limit reached
 *         -1 - error
 */
static int
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;
        uint32_t        next_entry_offset;
        uint32_t        dsize32, asize32;
        uint32_t        mb_flags = 0;
        uint32_t        resume_key;
        char            buf83[26];
        smb_msgbuf_t    mb;
        int             pad = 0;

        namelen = smb_ascii_or_unicode_strlen(sr, fileinfo->fi_name);
        if (namelen == -1)
                return (-1);

        if (args->fa_infolev < SMB_FIND_FILE_DIRECTORY_INFO) {
                /*
                 * Ancient info levels don't have a NextEntryOffset
                 * field, so there's no padding for alignment.
                 * The client expects a null after the file name,
                 * and then the next entry.  The namelength field
                 * never includes the null for these old levels.
                 * Using the pad value to write the null because
                 * we don't want to add that to namelen.
                 * [MS-CIFS] sec. 2.8.1.{1-3}
                 */
                if ((sr->smb_flg2 & SMB_FLAGS2_UNICODE) != 0)
                        pad = 2; /* Unicode null */
                else
                        pad = 1; /* ascii null */
                next_entry_offset = args->fa_fixedsize + namelen + pad;
                if (!MBC_ROOM_FOR(&xa->rep_data_mb, next_entry_offset))
                        return (1);
        } else {
                /*
                 * Later info levels: The file name is written WITH
                 * null termination, and the size of that null _is_
                 * included in the namelen field.  There may also
                 * be padding, and we pad to align(4) like Windows.
                 * Don't include the padding in the "room for" test
                 * because we want to ignore any error writing the
                 * pad bytes after the last element.
                 */
                if ((sr->smb_flg2 & SMB_FLAGS2_UNICODE) != 0)
                        namelen += 2;
                else
                        namelen += 1;
                next_entry_offset = args->fa_fixedsize + namelen;
                if (!MBC_ROOM_FOR(&xa->rep_data_mb, next_entry_offset))
                        return (1);
                if ((next_entry_offset & 3) != 0) {
                        pad = 4 - (next_entry_offset & 3);
                        next_entry_offset += pad;
                }
        }

        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",
                            resume_key);

                (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);
                break;

        case SMB_INFO_QUERY_EA_SIZE:
        case SMB_INFO_QUERY_EAS_FROM_LIST:
                if (args->fa_fflag & SMB_FIND_RETURN_RESUME_KEYS)
                        (void) smb_mbc_encodef(&xa->rep_data_mb, "l",
                            resume_key);

                (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),
                    dsize32,
                    asize32,
                    fileinfo->fi_dosattr,
                    0L,         /* EA Size */
                    namelen);
                break;

        case SMB_FIND_FILE_DIRECTORY_INFO:
                (void) smb_mbc_encodef(&xa->rep_data_mb, "%llTTTTqqll", sr,
                    next_entry_offset,
                    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,
                    &fileinfo->fi_ctime,
                    fileinfo->fi_size,
                    fileinfo->fi_alloc_size,
                    fileinfo->fi_dosattr,
                    namelen,
                    0L);
                break;

        case SMB_FIND_FILE_ID_FULL_DIRECTORY_INFO:
                (void) smb_mbc_encodef(&xa->rep_data_mb, "%llTTTTqqlll4.q", sr,
                    next_entry_offset,
                    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,
                    0L,
                    fileinfo->fi_nodeid);
                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", fileinfo->fi_shortname) < 0) {
                        smb_msgbuf_term(&mb);
                        return (-1);
                }
                shortlen = smb_wcequiv_strlen(fileinfo->fi_shortname);

                (void) smb_mbc_encodef(&xa->rep_data_mb, "%llTTTTqqlllb.24c",
                    sr,
                    next_entry_offset,
                    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,
                    0L,
                    shortlen,
                    buf83);

                smb_msgbuf_term(&mb);
                break;

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

                (void) smb_mbc_encodef(&xa->rep_data_mb,
                    "%llTTTTqqlllb.24c2.q",
                    sr,
                    next_entry_offset,
                    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,
                    0L,
                    shortlen,
                    buf83,
                    fileinfo->fi_nodeid);

                smb_msgbuf_term(&mb);
                break;

        case SMB_FIND_FILE_NAMES_INFO:
                (void) smb_mbc_encodef(&xa->rep_data_mb, "%lll", sr,
                    next_entry_offset,
                    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,
            namelen, fileinfo->fi_name);

        if (pad)
                (void) smb_mbc_encodef(&xa->rep_data_mb, "#.", pad);

        return (0);
}

/*
 * Close a search started by a Trans2FindFirst2 request.
 */
smb_sdrc_t
smb_pre_find_close2(smb_request_t *sr)
{
        DTRACE_SMB_START(op__FindClose2, smb_request_t *, sr);
        return (SDRC_SUCCESS);
}

void
smb_post_find_close2(smb_request_t *sr)
{
        DTRACE_SMB_DONE(op__FindClose2, smb_request_t *, sr);
}

smb_sdrc_t
smb_com_find_close2(smb_request_t *sr)
{
        uint16_t        odid;
        smb_odir_t      *od;

        if (smbsr_decode_vwv(sr, "w", &odid) != 0)
                return (SDRC_ERROR);

        od = smb_tree_lookup_odir(sr, odid);
        if (od == NULL) {
                smbsr_error(sr, NT_STATUS_INVALID_HANDLE,
                    ERRDOS, ERROR_INVALID_HANDLE);
                return (SDRC_ERROR);
        }

        smb_odir_close(od);
        smb_odir_release(od);

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

        return (SDRC_SUCCESS);
}