/*
 * sg_rdac
 *
 * Retrieve / set RDAC options.
 *
 * Copyright (C) 2006-2023 Hannes Reinecke <hare@suse.de>
 *
 * Based on sg_modes.c and sg_emc_trespass.c; credits from there apply.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "sg_lib.h"
#include "sg_cmds_basic.h"
#include "sg_unaligned.h"
#include "sg_pr2serr.h"


static const char * version_str = "1.18 20230622";

static const uint8_t mode6_hdr[] = {
    0x75, /* Length */
    0, /* medium */
    0, /* params */
    8, /* Block descriptor length */
};

static const uint8_t mode10_hdr[] = {
    0x01, 0x18, /* Length */
    0, /* medium */
    0, /* params */
    0, 0, /* reserved */
    0, 0, /* block descriptor length */
};

static const uint8_t block_descriptor[] = {
    0, /* Density code */
    0, 0, 0, /* Number of blocks */
    0, /* Reserved */
    0, 0x02, 0, /* 512 byte blocks */
};

struct rdac_page_common {
    uint8_t  current_serial[16];
    uint8_t  alternate_serial[16];
    uint8_t  current_mode_msb;
    uint8_t  current_mode_lsb;
    uint8_t  alternate_mode_msb;
    uint8_t  alternate_mode_lsb;
    uint8_t  quiescence;
    uint8_t  options;
};

struct rdac_legacy_page {
    uint8_t  page_code;
    uint8_t  page_length;
    struct rdac_page_common attr;
    uint8_t  lun_table[32];
    uint8_t  lun_table_exp[32];
    unsigned short reserved;
};

struct rdac_expanded_page {
    uint8_t  page_code;
    uint8_t  subpage_code;
    uint8_t  page_length[2];
    struct rdac_page_common attr;
    uint8_t  lun_table[256];
    uint8_t  reserved[2];
};

static int do_verbose = 0;


static void dump_mode_page( uint8_t *page, int len )
{
        int i, k;

        for (k = 0; k < len; k += 16) {

                printf("%x:",k / 16);
                for (i = 0; i < 16; i++) {
                        printf(" %02x", page[k + i]);
                        if (k + i >= len) {
                                printf("\n");
                                break;
                        }
                }
                printf("\n");
        }

}

#define MX_ALLOC_LEN (1024 * 4)
#define RDAC_CONTROLLER_PAGE 0x2c
#define RDAC_CONTROLLER_PAGE_LEN 0x68
#define LEGACY_PAGE 0x00
#define EXPANDED_LUN_SPACE_PAGE 0x01
#define EXPANDED_LUN_SPACE_PAGE_LEN 0x128
#define RDAC_FAIL_ALL_PATHS 0x1
#define RDAC_FAIL_SELECTED_PATHS 0x2
#define RDAC_FORCE_QUIESCENCE 0x2
#define RDAC_QUIESCENCE_TIME 10

static int fail_all_paths(int fd, bool use_6_byte)
{
        struct rdac_legacy_page *rdac_page;
        struct rdac_expanded_page *rdac_page_exp;
        struct rdac_page_common *rdac_common = NULL;
        uint8_t fail_paths_pg[308];

        int res;
        char b[80];

        memset(fail_paths_pg, 0, 308);
        if (use_6_byte) {
                memcpy(fail_paths_pg, mode6_hdr, 4);
                memcpy(fail_paths_pg + 4, block_descriptor, 8);
                rdac_page = (struct rdac_legacy_page *)(fail_paths_pg + 4 + 8);
                rdac_page->page_code = RDAC_CONTROLLER_PAGE;
                rdac_page->page_length = RDAC_CONTROLLER_PAGE_LEN;
                rdac_common = &rdac_page->attr;
        } else {
                memcpy(fail_paths_pg, mode10_hdr, 8);
                rdac_page_exp = (struct rdac_expanded_page *)
                                (fail_paths_pg + 8);
                rdac_page_exp->page_code = RDAC_CONTROLLER_PAGE | 0x40;
                rdac_page_exp->subpage_code = 0x1;
                sg_put_unaligned_be16(EXPANDED_LUN_SPACE_PAGE_LEN,
                                      rdac_page_exp->page_length + 0);
                rdac_common = &rdac_page_exp->attr;
        }

        rdac_common->current_mode_lsb =  RDAC_FAIL_ALL_PATHS;
        rdac_common->quiescence = RDAC_QUIESCENCE_TIME;
        rdac_common->options = RDAC_FORCE_QUIESCENCE;

        if (use_6_byte) {
                res = sg_ll_mode_select6(fd, 1 /* pf */, 0 /* sp */,
                                        fail_paths_pg, 118,
                                        true, (do_verbose ? 2 : 0));
        } else {
                res = sg_ll_mode_select10(fd, 1 /* pf */, 0 /* sp */,
                                        fail_paths_pg, 308,
                                        true, (do_verbose ? 2: 0));
        }

        switch (res) {
        case 0:
                if (do_verbose)
                        pr2serr("fail paths successful\n");
                break;
        default:
                sg_get_category_sense_str(res, sizeof(b), b, do_verbose);
                pr2serr("fail paths failed: %s\n", b);
                break;
        }

        return res;
}

static int fail_this_path(int fd, int lun, bool use_6_byte)
{
        int res;
        struct rdac_legacy_page *rdac_page;
        struct rdac_expanded_page *rdac_page_exp;
        struct rdac_page_common *rdac_common = NULL;
        uint8_t fail_paths_pg[308];
        char b[80];

        if (use_6_byte) {
                if (lun > 31) {
                        pr2serr("must use 10 byte cdb to fail luns over 31\n");
                        return -1;
                }
        } else {        /* 10 byte cdb case */
                if (lun > 255) {
                        pr2serr("lun cannot exceed 255\n");
                        return -1;
                }
        }

        memset(fail_paths_pg, 0, 308);
        if (use_6_byte) {
                memcpy(fail_paths_pg, mode6_hdr, 4);
                memcpy(fail_paths_pg + 4, block_descriptor, 8);
                rdac_page = (struct rdac_legacy_page *)(fail_paths_pg + 4 + 8);
                rdac_page->page_code = RDAC_CONTROLLER_PAGE;
                rdac_page->page_length = RDAC_CONTROLLER_PAGE_LEN;
                rdac_common = &rdac_page->attr;
                memset(rdac_page->lun_table, 0x0, 32);
                rdac_page->lun_table[lun] = 0x81;
        } else {
                memcpy(fail_paths_pg, mode10_hdr, 8);
                rdac_page_exp = (struct rdac_expanded_page *)
                                (fail_paths_pg + 8);
                rdac_page_exp->page_code = RDAC_CONTROLLER_PAGE | 0x40;
                rdac_page_exp->subpage_code = 0x1;
                sg_put_unaligned_be16(EXPANDED_LUN_SPACE_PAGE_LEN,
                                      rdac_page_exp->page_length + 0);
                rdac_common = &rdac_page_exp->attr;
                memset(rdac_page_exp->lun_table, 0x0, 256);
                rdac_page_exp->lun_table[lun] = 0x81;
        }

        rdac_common->current_mode_lsb =  RDAC_FAIL_SELECTED_PATHS;
        rdac_common->quiescence = RDAC_QUIESCENCE_TIME;
        rdac_common->options = RDAC_FORCE_QUIESCENCE;

        if (use_6_byte) {
                res = sg_ll_mode_select6(fd, 1 /* pf */, 0 /* sp */,
                                        fail_paths_pg, 118,
                                        true, (do_verbose ? 2 : 0));
        } else {
                res = sg_ll_mode_select10(fd, 1 /* pf */, 0 /* sp */,
                                        fail_paths_pg, 308,
                                        true, (do_verbose ? 2: 0));
        }

        switch (res) {
        case 0:
                if (do_verbose)
                        pr2serr("fail paths successful\n");
                break;
        default:
                sg_get_category_sense_str(res, sizeof(b), b, do_verbose);
                pr2serr("fail paths page (lun=%d) failed: %s\n", lun, b);
                break;
        }

        return res;
}

static void print_rdac_mode(uint8_t *ptr, bool exp_subpg)
{
        int i, k, bd_len, lun_table_len;
        uint8_t * lun_table = NULL;
        struct rdac_legacy_page *legacy;
        struct rdac_expanded_page *expanded;
        struct rdac_page_common *rdac_ptr = NULL;

        if (exp_subpg) {
                bd_len = ptr[7];
                expanded = (struct rdac_expanded_page *)(ptr + 8 + bd_len);
                rdac_ptr = &expanded->attr;
                lun_table = expanded->lun_table;
                lun_table_len = 256;
        } else {
                bd_len = ptr[3];
                legacy = (struct rdac_legacy_page *)(ptr + 4 + bd_len);
                rdac_ptr = &legacy->attr;
                lun_table = legacy->lun_table;
                lun_table_len = 32;
        }

        printf("RDAC %s page\n", exp_subpg ? "Expanded" : "Legacy");
        printf("  Controller serial: %s\n",
               rdac_ptr->current_serial);
        printf("  Alternate controller serial: %s\n",
               rdac_ptr->alternate_serial);
        printf("  RDAC mode (redundant processor): ");
        switch (rdac_ptr->current_mode_msb) {
        case 0x00:
                printf("alternate controller not present; ");
                break;
        case 0x01:
                printf("alternate controller present; ");
                break;
        default:
                printf("(Unknown controller status 0x%x); ",
                       rdac_ptr->current_mode_msb);
                break;
        }
        switch (rdac_ptr->current_mode_lsb) {
        case 0x0:
                printf("inactive\n");
                break;
        case 0x1:
                printf("active\n");
                break;
        case 0x2:
                printf("Dual active mode\n");
                break;
        default:
                printf("(Unknown mode 0x%x)\n",
                       rdac_ptr->current_mode_lsb);
        }

        printf("  RDAC mode (alternate processor): ");
        switch (rdac_ptr->alternate_mode_msb) {
        case 0x00:
                printf("alternate controller not present; ");
                break;
        case 0x01:
                printf("alternate controller present; ");
                break;
        default:
                printf("(Unknown status 0x%x); ",
                       rdac_ptr->alternate_mode_msb);
                break;
        }
        switch (rdac_ptr->alternate_mode_lsb) {
        case 0x0:
                printf("inactive\n");
                break;
        case 0x1:
                printf("active\n");
                break;
        case 0x2:
                printf("Dual active mode\n");
                break;
        case 0x3:
                printf("Not present\n");
                break;
        case 0x4:
                printf("held in reset\n");
                break;
        default:
                printf("(Unknown mode 0x%x)\n",
                       rdac_ptr->alternate_mode_lsb);
        }
        printf("  Quiescence timeout: %d\n", rdac_ptr->quiescence);
        printf("  RDAC option 0x%x\n", rdac_ptr->options);
        printf("    ALUA: %s\n", (rdac_ptr->options & 0x4 ? "Enabled" :
                                                            "Disabled" ));
        printf("    Force Quiescence: %s\n", (rdac_ptr->options & 0x2 ?
                                              "Enabled" : "Disabled" ));
        printf ("  LUN Table: (p = preferred, a = alternate, u = utm lun)\n");
        printf("         0 1 2 3 4 5 6 7  8 9 a b c d e f\n");
        for (k = 0; k < lun_table_len; k += 16) {
                printf("    0x%x:",k / 16);
                for (i = 0; i < 16; i++) {
                        switch (lun_table[k + i]) {
                        case 0x0:
                                printf(" x");
                                break;
                        case 0x1:
                                printf(" p");
                                break;
                        case 0x2:
                                printf(" a");
                                break;
                        case 0x3:
                                printf(" u");
                                break;
                        default:
                                printf(" ?");
                                break;
                        }
                        if (i == 7) {
                                printf(" ");
                        }
                }
                printf("\n");
        }
}

static void usage()
{
    printf("Usage:  sg_rdac [-6] [-a] [-f=LUN] [-v] [-V] DEVICE\n"
           "  where:\n"
           "    -6        use 6 byte cdbs for mode sense/select\n"
           "    -a        transfer all devices to the controller\n"
           "              serving DEVICE.\n"
           "    -f=LUN    transfer the device at LUN to the\n"
           "              controller serving DEVICE\n"
           "    -v        verbose\n"
           "    -V        print version then exit\n\n"
           " Display/Modify RDAC Redundant Controller Page 0x2c.\n"
           " If [-a] or [-f] is not specified the current settings"
           " are displayed.\n");
}

int main(int argc, char * argv[])
{
        bool fail_all = false;
        bool fail_path = false;
        bool use_6_byte = false;
        int res, fd, k, resid, len, lun = -1;
        int ret = 0;
        char **argptr;
        char * file_name = 0;
        uint8_t rsp_buff[MX_ALLOC_LEN];

        if (argc < 2) {
                usage ();
                return SG_LIB_SYNTAX_ERROR;
        }

        for (k = 1; k < argc; ++k) {
                argptr = argv + k;
                if (!strcmp (*argptr, "-v"))
                        ++do_verbose;
                else if (!strncmp(*argptr, "-f=",3)) {
                        fail_path = true;
                        lun = strtoul(*argptr + 3, NULL, 0);
                }
                else if (!strcmp(*argptr, "-a")) {
                        fail_all = true;
                }
                else if (!strcmp(*argptr, "-6")) {
                        use_6_byte = true;
                }
                else if (!strcmp(*argptr, "-V")) {
                        pr2serr("sg_rdac version: %s\n", version_str);
                        return 0;
                }
                else if (*argv[k] == '-') {
                        pr2serr("Unrecognized switch: %s\n", argv[k]);
                        file_name = 0;
                        break;
                }
                else if (0 == file_name)
                        file_name = argv[k];
                else {
                        pr2serr("too many arguments\n");
                        file_name = 0;
                        break;
                }
        }
        if (0 == file_name) {
                usage();
                return SG_LIB_SYNTAX_ERROR;
        }

        fd = sg_cmds_open_device(file_name, false /* rw */, do_verbose);
        if (fd < 0) {
                pr2serr("open error: %s: %s\n", file_name, safe_strerror(-fd));
                usage();
                ret = sg_convert_errno(-fd);
                goto fini;
        }

        if (fail_all) {
                res = fail_all_paths(fd, use_6_byte);
        } else if (fail_path) {
                res = fail_this_path(fd, lun, use_6_byte);
        } else {
                resid = 0;
                if (use_6_byte)
                        res = sg_ll_mode_sense6(fd, /* DBD */ false,
                                                /* PC */ 0,
                                                0x2c /* page */,
                                                0 /*subpage */,
                                                rsp_buff, 252,
                                                true, do_verbose);
                else
                        res = sg_ll_mode_sense10_v2(fd, /* llbaa */ false,
                                                    /* DBD */ false,
                                                    /* page control */0,
                                                    0x2c, 0x1 /* subpage */,
                                                    rsp_buff, 308, 0, &resid,
                                                    true, do_verbose);

                if (! res) {
                        len = sg_msense_calc_length(rsp_buff, 308, use_6_byte,
                                                    NULL);
                        if (resid > 0) {
                                len = ((308 - resid) < len) ? (308 - resid) :
                                                              len;
                                if (len < 2)
                                        pr2serr("MS(10) residual value (%d) "
                                                "a worry\n", resid);
                        }
                        if (do_verbose && (len > 1))
                                dump_mode_page(rsp_buff, len);
                        print_rdac_mode(rsp_buff, ! use_6_byte);
                } else {
                        if (SG_LIB_CAT_INVALID_OP == res)
                                pr2serr(">>>>>> try again without the '-6' "
                                        "switch for a 10 byte MODE SENSE "
                                        "command\n");
                        else if (SG_LIB_CAT_ILLEGAL_REQ == res)
                                pr2serr("mode sense: invalid field in cdb "
                                        "(perhaps subpages or page control "
                                        "(PC) not supported)\n");
                        else {
                                char b[80];

                                sg_get_category_sense_str(res, sizeof(b), b,
                                                          do_verbose);
                                pr2serr("mode sense failed: %s\n", b);
                        }
                }
        }
        ret = res;

        res = sg_cmds_close_device(fd);
        if (res < 0) {
                pr2serr("close error: %s\n", safe_strerror(-res));
                if (0 == ret)
                        ret = sg_convert_errno(res);
        }
fini:
        if (0 == do_verbose) {
                if (! sg_if_can2stderr("sg_rdac failed: ", ret))
                        pr2serr("Some error occurred, try again with '-v' "
                                "or '-vv' for more information\n");
        }
        return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
}
