root/fs/smb/client/smb1maperror.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *
 *   Copyright (c) International Business Machines  Corp., 2002,2008
 *   Author(s): Steve French (sfrench@us.ibm.com)
 *
 *   Error mapping routines from Samba libsmb/errormap.c
 *   Copyright (C) Andrew Tridgell 2001
 *   Copyright (C) Luke Kenneth Casson Leighton 1997-2001.
 */

#include <linux/bsearch.h>

#include <kunit/visibility.h>

#include "cifsproto.h"
#include "smb1proto.h"
#include "smberr.h"
#include "nterr.h"
#include "cifs_debug.h"

static __always_inline int smb1_posix_error_cmp(const void *_key, const void *_pivot)
{
        __u16 key = *(__u16 *)_key;
        const struct smb_to_posix_error *pivot = _pivot;

        if (key < pivot->smb_err)
                return -1;
        if (key > pivot->smb_err)
                return 1;
        return 0;
}

static const struct smb_to_posix_error mapping_table_ERRDOS[] = {
/*
 * Automatically generated by the `gen_smb1_mapping` script,
 * sorted by DOS error code (ascending).
 */
#include "smb1_err_dos_map.c"
};

static const struct smb_to_posix_error mapping_table_ERRSRV[] = {
/*
 * Automatically generated by the `gen_smb1_mapping` script,
 * sorted by SRV error code (ascending).
 */
#include "smb1_err_srv_map.c"
};

/*****************************************************************************
 *convert a NT status code to a dos class/code
 *****************************************************************************/

static __always_inline int ntstatus_to_dos_cmp(const void *_key, const void *_pivot)
{
        __u32 key = *(__u32 *)_key;
        const struct ntstatus_to_dos_err *pivot = _pivot;

        if (key < pivot->ntstatus)
                return -1;
        if (key > pivot->ntstatus)
                return 1;
        return 0;
}

/* NT status -> dos error map */
static const struct ntstatus_to_dos_err ntstatus_to_dos_map[] = {
/*
 * Automatically generated by the `gen_smb1_mapping` script,
 * sorted by NT status code (ascending).
 */
#include "smb1_mapping_table.c"
};

static const struct ntstatus_to_dos_err *
search_ntstatus_to_dos_map(__u32 ntstatus)
{
        return __inline_bsearch(&ntstatus, ntstatus_to_dos_map,
                                ARRAY_SIZE(ntstatus_to_dos_map),
                                sizeof(struct ntstatus_to_dos_err),
                                ntstatus_to_dos_cmp);
}

static const struct smb_to_posix_error *
search_mapping_table_ERRDOS(__u16 smb_err)
{
        return __inline_bsearch(&smb_err, mapping_table_ERRDOS,
                                ARRAY_SIZE(mapping_table_ERRDOS),
                                sizeof(struct smb_to_posix_error),
                                smb1_posix_error_cmp);
}

static const struct smb_to_posix_error *
search_mapping_table_ERRSRV(__u16 smb_err)
{
        return __inline_bsearch(&smb_err, mapping_table_ERRSRV,
                                ARRAY_SIZE(mapping_table_ERRSRV),
                                sizeof(struct smb_to_posix_error),
                                smb1_posix_error_cmp);
}

int
map_smb_to_linux_error(char *buf, bool logErr)
{
        struct smb_hdr *smb = (struct smb_hdr *)buf;
        int rc = -EIO;  /* if transport error smb error may not be set */
        __u8 smberrclass;
        __u16 smberrcode;
        const struct smb_to_posix_error *err_map = NULL;

        /* BB if NT Status codes - map NT BB */

        /* old style smb error codes */
        if (smb->Status.CifsError == 0)
                return 0;

        if (smb->Flags2 & SMBFLG2_ERR_STATUS) {
                /* translate the newer STATUS codes to old style SMB errors
                 * and then to POSIX errors */
                __u32 err = le32_to_cpu(smb->Status.CifsError);
                const struct ntstatus_to_dos_err *map = search_ntstatus_to_dos_map(err);

                if (map) {
                        if ((logErr && err != NT_STATUS_MORE_PROCESSING_REQUIRED) ||
                            (cifsFYI & CIFS_RC))
                                pr_notice("Status code returned 0x%08x %s\n",
                                          map->ntstatus, map->nt_errstr);

                        smberrclass = map->dos_class;
                        smberrcode = map->dos_code;
                } else {
                        smberrclass = ERRHRD;
                        smberrcode = ERRgeneral;
                }
        } else {
                smberrclass = smb->Status.DosError.ErrorClass;
                smberrcode = le16_to_cpu(smb->Status.DosError.Error);
        }

        /* old style errors */

        if (smberrclass == ERRDOS) {
                /* DOS class smb error codes - map DOS */
                /* 1 byte field no need to byte reverse */
                err_map = search_mapping_table_ERRDOS(smberrcode);
        } else if (smberrclass == ERRSRV) {
                /* server class of error codes */
                err_map = search_mapping_table_ERRSRV(smberrcode);
        }
        if (err_map)
                rc = err_map->posix_code;
        /* else ERRHRD class errors or junk  - return EIO */

        /* special cases for NT status codes which cannot be translated to DOS codes */
        if (smb->Flags2 & SMBFLG2_ERR_STATUS) {
                __u32 err = le32_to_cpu(smb->Status.CifsError);
                if (err == (NT_STATUS_NOT_A_REPARSE_POINT))
                        rc = -ENODATA;
                else if (err == (NT_STATUS_PRIVILEGE_NOT_HELD))
                        rc = -EPERM;
        }

        cifs_dbg(FYI, "Mapping smb error code 0x%x to POSIX err %d\n",
                 le32_to_cpu(smb->Status.CifsError), rc);

        /* generic corrective action e.g. reconnect SMB session on
         * ERRbaduid could be added */

        if (rc == -EIO)
                smb_EIO2(smb_eio_trace_smb1_received_error,
                         le32_to_cpu(smb->Status.CifsError),
                         le16_to_cpu(smb->Flags2));
        return rc;
}

int
map_and_check_smb_error(struct TCP_Server_Info *server,
                        struct mid_q_entry *mid, bool logErr)
{
        int rc;
        struct smb_hdr *smb = (struct smb_hdr *)mid->resp_buf;

        rc = map_smb_to_linux_error((char *)smb, logErr);
        if (rc == -EACCES && !(smb->Flags2 & SMBFLG2_ERR_STATUS)) {
                /* possible ERRBaduid */
                __u8 class = smb->Status.DosError.ErrorClass;
                __u16 code = le16_to_cpu(smb->Status.DosError.Error);

                /* switch can be used to handle different errors */
                if (class == ERRSRV && code == ERRbaduid) {
                        cifs_dbg(FYI, "Server returned 0x%x, reconnecting session...\n",
                                code);
                        cifs_signal_cifsd_for_reconnect(server, false);
                }
        }

        return rc;
}

#define DEFINE_CHECK_SORT_FUNC(__array, __field)                        \
static int __init __array ## _is_sorted(void)                           \
{                                                                       \
        unsigned int i;                                                 \
                                                                        \
        /* Check whether the array is sorted in ascending order */      \
        for (i = 1; i < ARRAY_SIZE(__array); i++) {                     \
                if (__array[i].__field >=                               \
                    __array[i - 1].__field)                             \
                        continue;                                       \
                                                                        \
                pr_err(#__array " array order is incorrect\n");         \
                return -EINVAL;                                         \
        }                                                               \
                                                                        \
        return 0;                                                       \
}

/* ntstatus_to_dos_map_is_sorted */
DEFINE_CHECK_SORT_FUNC(ntstatus_to_dos_map, ntstatus);
/* mapping_table_ERRDOS_is_sorted */
DEFINE_CHECK_SORT_FUNC(mapping_table_ERRDOS, smb_err);
/* mapping_table_ERRSRV_is_sorted */
DEFINE_CHECK_SORT_FUNC(mapping_table_ERRSRV, smb_err);

int __init smb1_init_maperror(void)
{
        int rc;

        rc = ntstatus_to_dos_map_is_sorted();
        if (rc)
                return rc;

        rc = mapping_table_ERRDOS_is_sorted();
        if (rc)
                return rc;

        return mapping_table_ERRSRV_is_sorted();
}

#if IS_ENABLED(CONFIG_SMB1_KUNIT_TESTS)
const struct ntstatus_to_dos_err *
search_ntstatus_to_dos_map_test(__u32 ntstatus)
{
        return search_ntstatus_to_dos_map(ntstatus);
}
EXPORT_SYMBOL_IF_KUNIT(search_ntstatus_to_dos_map_test);

const struct ntstatus_to_dos_err *
ntstatus_to_dos_map_test = ntstatus_to_dos_map;
EXPORT_SYMBOL_IF_KUNIT(ntstatus_to_dos_map_test);

unsigned int ntstatus_to_dos_num = ARRAY_SIZE(ntstatus_to_dos_map);
EXPORT_SYMBOL_IF_KUNIT(ntstatus_to_dos_num);

const struct smb_to_posix_error *
search_mapping_table_ERRDOS_test(__u16 smb_err)
{
        return search_mapping_table_ERRDOS(smb_err);
}
EXPORT_SYMBOL_IF_KUNIT(search_mapping_table_ERRDOS_test);

const struct smb_to_posix_error *
mapping_table_ERRDOS_test = mapping_table_ERRDOS;
EXPORT_SYMBOL_IF_KUNIT(mapping_table_ERRDOS_test);

unsigned int mapping_table_ERRDOS_num = ARRAY_SIZE(mapping_table_ERRDOS);
EXPORT_SYMBOL_IF_KUNIT(mapping_table_ERRDOS_num);

const struct smb_to_posix_error *
search_mapping_table_ERRSRV_test(__u16 smb_err)
{
        return search_mapping_table_ERRSRV(smb_err);
}
EXPORT_SYMBOL_IF_KUNIT(search_mapping_table_ERRSRV_test);

const struct smb_to_posix_error *
mapping_table_ERRSRV_test = mapping_table_ERRSRV;
EXPORT_SYMBOL_IF_KUNIT(mapping_table_ERRSRV_test);

unsigned int mapping_table_ERRSRV_num = ARRAY_SIZE(mapping_table_ERRSRV);
EXPORT_SYMBOL_IF_KUNIT(mapping_table_ERRSRV_num);
#endif