/*
 * Copyright (c) 2011-2023 Douglas Gilbert.
 * All rights reserved.
 * Use of this source code is governed by a BSD-style
 * license that can be found in the BSD_LICENSE file.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <getopt.h>
#define __STDC_FORMAT_MACROS 1
#include <inttypes.h>

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

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

static const char * version_str = "1.22 20230622";

#define ME "sg_sanitize: "

#define SANITIZE_OP 0x48
#define SANITIZE_OP_LEN 10
#define SANITIZE_SA_OVERWRITE 0x1
#define SANITIZE_SA_BLOCK_ERASE 0x2
#define SANITIZE_SA_CRYPTO_ERASE 0x3
#define SANITIZE_SA_EXIT_FAIL_MODE 0x1f
#define DEF_REQS_RESP_LEN 252
#define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
#define MAX_XFER_LEN 65535
#define EBUFF_SZ 256

#define SHORT_TIMEOUT 20   /* 20 seconds unless immed=0 ... */
#define LONG_TIMEOUT (15 * 3600)       /* 15 hours ! */
                /* Seagate ST32000444SS 2TB disk takes 9.5 hours to format */
#define POLL_DURATION_SECS 60


static const struct option long_options[] = {
    {"ause", no_argument, 0, 'A'},
    {"block", no_argument, 0, 'B'},
    {"count", required_argument, 0, 'c'},
    {"crypto", no_argument, 0, 'C'},
    {"desc", no_argument, 0, 'd'},
    {"dry-run", no_argument, 0, 'D'},
    {"dry_run", no_argument, 0, 'D'},
    {"early", no_argument, 0, 'e'},
    {"fail", no_argument, 0, 'F'},
    {"help", no_argument, 0, 'h'},
    {"invert", no_argument, 0, 'I'},
    {"ipl", required_argument, 0, 'i'},
    {"overwrite", no_argument, 0, 'O'},
    {"pattern", required_argument, 0, 'p'},
    {"quick", no_argument, 0, 'Q'},
    {"test", required_argument, 0, 'T'},
    {"timeout", required_argument, 0, 't'},
    {"tmo", required_argument, 0, 't'},
    {"verbose", no_argument, 0, 'v'},
    {"version", no_argument, 0, 'V'},
    {"wait", no_argument, 0, 'w'},
    {"zero", no_argument, 0, 'z'},
    {0, 0, 0, 0},
};

struct opts_t {
    bool ause;
    bool block;
    bool crypto;
    bool desc;
    bool dry_run;
    bool early;
    bool fail;
    bool invert;
    bool overwrite;
    bool quick;
    bool verbose_given;
    bool version_given;
    bool wait;
    bool znr;
    int count;
    int ipl;    /* initialization pattern length */
    int test;
    int timeout;        /* in seconds */
    int verbose;
    int zero;
    const char * pattern_fn;
};


static void
usage()
{
  pr2serr("Usage: sg_sanitize [--ause] [--block] [--count=OC] [--crypto] "
          "[--dry-run]\n"
          "                   [--early] [--fail] [--help] [--invert] "
          "[--ipl=LEN]\n"
          "                   [--overwrite] [--pattern=PF] [--quick] "
          "[--test=TE]\n"
          "                   [--timeout=SECS] [--verbose] [--version] "
          "[--wait]\n"
          "                   [--zero] [--znr] DEVICE\n"
          "  where:\n"
          "    --ause|-A            set AUSE bit in cdb\n"
          "    --block|-B           do BLOCK ERASE sanitize\n"
          "    --count=OC|-c OC     OC is overwrite count field (from 1 "
          "(def) to 31)\n"
          "    --crypto|-C          do CRYPTOGRAPHIC ERASE sanitize\n"
          "    --desc|-d            polling request sense sets 'desc' "
          "field\n"
          "                         (def: clear 'desc' field)\n"
          "    --dry-run|-D         to preparation but bypass SANITIZE "
          "command\n"
          "    --early|-e           exit once sanitize started (IMMED set "
          "in cdb)\n"
          "                         user can monitor progress with REQUEST "
          "SENSE\n"
          "    --fail|-F            do EXIT FAILURE MODE sanitize\n"
          "    --help|-h            print out usage message\n"
          "    --invert|-I          set INVERT bit in OVERWRITE parameter "
          "list\n"
          "    --ipl=LEN|-i LEN     initialization pattern length (in "
          "bytes)\n"
          "    --overwrite|-O       do OVERWRITE sanitize\n"
          "    --pattern=PF|-p PF    PF is file containing initialization "
          "pattern\n"
          "                          for OVERWRITE\n"
          "    --quick|-Q           start sanitize without pause for user\n"
          "                         intervention (i.e. no time to "
          "reconsider)\n"
          "    --test=TE|-T TE      TE is placed in TEST field of "
          "OVERWRITE\n"
          "                         parameter list (def: 0)\n"
          "    --timeout=SECS|-t SECS    SANITIZE command timeout in "
          "seconds\n"
          "    --verbose|-v         increase verbosity\n"
          "    --version|-V         print version string then exit\n"
          "    --wait|-w            wait for command to finish (could "
          "take hours)\n"
          "    --zero|-z            use pattern of zeros for "
          "OVERWRITE\n"
          "    --znr|-Z             set ZNR (zone no reset) bit in cdb\n\n"
          "Performs a SCSI SANITIZE command.\n    <<<WARNING>>>: all data "
          "on DEVICE will be lost.\nDefault action is to give user time to "
          "reconsider; then execute SANITIZE\ncommand with IMMED bit set; "
          "then use REQUEST SENSE command every 60\nseconds to poll for a "
          "progress indication; then exit when there is no\nmore progress "
          "indication.\n"
          );
}

/* Invoke SCSI SANITIZE command. Returns 0 if successful, otherwise error */
static int
do_sanitize(int sg_fd, const struct opts_t * op, const void * param_lstp,
            int param_lst_len)
{
    bool immed;
    int ret, res, sense_cat, timeout;
    uint8_t san_cdb[SANITIZE_OP_LEN];
    uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
    struct sg_pt_base * ptvp;

    if (op->early || op->wait)
        immed = op->early;
    else
        immed = true;
    timeout = (immed ? SHORT_TIMEOUT : LONG_TIMEOUT);
    /* only use command line timeout if it exceeds previous defaults */
    if (op->timeout > timeout)
        timeout = op->timeout;
    memset(san_cdb, 0, sizeof(san_cdb));
    san_cdb[0] = SANITIZE_OP;
    if (op->overwrite)
        san_cdb[1] = SANITIZE_SA_OVERWRITE;
    else if (op->block)
        san_cdb[1] = SANITIZE_SA_BLOCK_ERASE;
    else if (op->crypto)
        san_cdb[1] = SANITIZE_SA_CRYPTO_ERASE;
    else if (op->fail)
        san_cdb[1] = SANITIZE_SA_EXIT_FAIL_MODE;
    else
        return SG_LIB_SYNTAX_ERROR;
    if (immed)
        san_cdb[1] |= 0x80;
    if (op->znr)        /* added sbc4r07 */
        san_cdb[1] |= 0x40;
    if (op->ause)
        san_cdb[1] |= 0x20;
    sg_put_unaligned_be16((uint16_t)param_lst_len, san_cdb + 7);

    if (op->verbose > 1) {
        char b[128];

        pr2serr("    Sanitize cdb: %s\n",
                sg_get_command_str(san_cdb, SANITIZE_OP_LEN, false,
                                   sizeof(b), b));
        if (op->verbose > 2) {
            if (param_lst_len > 0) {
                pr2serr("    Parameter list contents:\n");
                hex2stderr((const uint8_t *)param_lstp, param_lst_len, -1);
            }
            pr2serr("    Sanitize command timeout: %d seconds\n", timeout);
        }
    }
    if (op->dry_run) {
        pr2serr("Due to --dry-run option, bypassing SANITIZE command\n");
        return 0;
    }
    ptvp = construct_scsi_pt_obj();
    if (NULL == ptvp) {
        pr2serr("Sanitize: out of memory\n");
        return -1;
    }
    set_scsi_pt_cdb(ptvp, san_cdb, sizeof(san_cdb));
    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
    set_scsi_pt_data_out(ptvp, (uint8_t *)param_lstp, param_lst_len);
    res = do_scsi_pt(ptvp, sg_fd, timeout, op->verbose);
    ret = sg_cmds_process_resp(ptvp, "Sanitize", res, true /*noisy */,
                               op->verbose, &sense_cat);
    if (-1 == ret) {
        if (get_scsi_pt_transport_err(ptvp))
            ret = SG_LIB_TRANSPORT_ERROR;
        else
            ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
    } else if (-2 == ret) {
        switch (sense_cat) {
        case SG_LIB_CAT_RECOVERED:
        case SG_LIB_CAT_NO_SENSE:
            ret = 0;
            break;
        case SG_LIB_CAT_MEDIUM_HARD:
            {
                bool valid;
                int slen;
                uint64_t ull = 0;

                slen = get_scsi_pt_sense_len(ptvp);
                valid = sg_get_sense_info_fld(sense_b, slen, &ull);
                if (valid)
                    pr2serr("Medium or hardware error starting at "
                            "lba=%" PRIu64 " [0x%" PRIx64 "]\n", ull, ull);
            }
            ret = sense_cat;
            break;
        case SG_LIB_CAT_INVALID_PARAM:
            pr2serr("Invalid field in parameter list, add '-vv' to find "
                    "invalid field location\n");
            ret = sense_cat;
            break;
        default:
            ret = sense_cat;
            break;
        }
    } else {
        ret = 0;
        if (op->verbose)
            pr2serr("Sanitize command %s without error\n",
                    (immed ? "launched" : "completed"));
    }

    destruct_scsi_pt_obj(ptvp);
    return ret;
}

#define VPD_DEVICE_ID 0x83
#define VPD_ASSOC_LU 0
#define VPD_ASSOC_TPORT 1
#define TPROTO_ISCSI 5

static char *
get_lu_name(const uint8_t * bp, int u_len, char * b, int b_len)
{
    int len, off, sns_dlen, dlen, k;
    uint8_t u_sns[512];
    char * cp;

    len = u_len - 4;
    bp += 4;
    off = -1;
    if (0 == sg_vpd_dev_id_iter(bp, len, &off, VPD_ASSOC_LU,
                                8 /* SCSI name string (sns) */,
                                3 /* UTF-8 */)) {
        sns_dlen = bp[off + 3];
        memcpy(u_sns, bp + off + 4, sns_dlen);
        /* now want to check if this is iSCSI */
        off = -1;
        if (0 == sg_vpd_dev_id_iter(bp, len, &off, VPD_ASSOC_TPORT,
                                    8 /* SCSI name string (sns) */,
                                    3 /* UTF-8 */)) {
            if ((0x80 & bp[1]) && (TPROTO_ISCSI == (bp[0] >> 4))) {
                snprintf(b, b_len, "%.*s", sns_dlen, u_sns);
                return b;
            }
        }
    } else
        sns_dlen = 0;
    if (0 == sg_vpd_dev_id_iter(bp, len, &off, VPD_ASSOC_LU,
                                3 /* NAA */, 1 /* binary */)) {
        dlen = bp[off + 3];
        if (! ((8 == dlen) || (16 ==dlen)))
            return b;
        cp = b;
        for (k = 0; ((k < dlen) && (b_len > 1)); ++k) {
            snprintf(cp, b_len, "%02x", bp[off + 4 + k]);
            cp += 2;
            b_len -= 2;
        }
    } else if (0 == sg_vpd_dev_id_iter(bp, len, &off, VPD_ASSOC_LU,
                                       2 /* EUI */, 1 /* binary */)) {
        dlen = bp[off + 3];
        if (! ((8 == dlen) || (12 == dlen) || (16 ==dlen)))
            return b;
        cp = b;
        for (k = 0; ((k < dlen) && (b_len > 1)); ++k) {
            snprintf(cp, b_len, "%02x", bp[off + 4 + k]);
            cp += 2;
            b_len -= 2;
        }
    } else if (sns_dlen > 0)
        snprintf(b, b_len, "%.*s", sns_dlen, u_sns);
    return b;
}

#define SAFE_STD_INQ_RESP_LEN 36
#define VPD_SUPPORTED_VPDS 0x0
#define VPD_UNIT_SERIAL_NUM 0x80
#define VPD_DEVICE_ID 0x83

static int
print_dev_id(int fd, uint8_t * sinq_resp, int max_rlen, int verbose)
{
    int res, k, n, verb, pdt, has_sn, has_di;
    uint8_t b[256];
    char a[256];
    char pdt_name[64];

    verb = (verbose > 1) ? verbose - 1 : 0;
    memset(sinq_resp, 0, max_rlen);
    res = sg_ll_inquiry(fd, false, false /* evpd */, 0 /* pg_op */, b,
                        SAFE_STD_INQ_RESP_LEN, 1, verb);
    if (res)
        return res;
    n = b[4] + 5;
    if (n > SAFE_STD_INQ_RESP_LEN)
        n = SAFE_STD_INQ_RESP_LEN;
    memcpy(sinq_resp, b, (n < max_rlen) ? n : max_rlen);
    if (n == SAFE_STD_INQ_RESP_LEN) {
        pdt = b[0] & PDT_MASK;
        printf("    %.8s  %.16s  %.4s   peripheral_type: %s [0x%x]\n",
               (const char *)(b + 8), (const char *)(b + 16),
               (const char *)(b + 32),
               sg_get_pdt_str(pdt, sizeof(pdt_name), pdt_name), pdt);
        if (verbose)
            printf("      PROTECT=%d\n", !!(b[5] & 1));
        if (b[5] & 1)
            printf("      << supports protection information>>\n");
    } else {
        pr2serr("Short INQUIRY response: %d bytes, expect at least 36\n", n);
        return SG_LIB_CAT_OTHER;
    }
    res = sg_ll_inquiry(fd, false, true /* evpd */, VPD_SUPPORTED_VPDS, b,
                        SAFE_STD_INQ_RESP_LEN, 1, verb);
    if (res) {
        if (verbose)
            pr2serr("VPD_SUPPORTED_VPDS gave res=%d\n", res);
        return 0;
    }
    if (VPD_SUPPORTED_VPDS != b[1]) {
        if (verbose)
            pr2serr("VPD_SUPPORTED_VPDS corrupted\n");
        return 0;
    }
    n = sg_get_unaligned_be16(b + 2);
    if (n > (SAFE_STD_INQ_RESP_LEN - 4))
        n = (SAFE_STD_INQ_RESP_LEN - 4);
    for (k = 0, has_sn = 0, has_di = 0; k < n; ++k) {
        if (VPD_UNIT_SERIAL_NUM == b[4 + k])
            ++has_sn;
        else if (VPD_DEVICE_ID == b[4 + k]) {
            ++has_di;
            break;
        }
    }
    if (has_sn) {
        res = sg_ll_inquiry(fd, false, true /* evpd */, VPD_UNIT_SERIAL_NUM,
                            b, sizeof(b), 1, verb);
        if (res) {
            if (verbose)
                pr2serr("VPD_UNIT_SERIAL_NUM gave res=%d\n", res);
            return 0;
        }
        if (VPD_UNIT_SERIAL_NUM != b[1]) {
            if (verbose)
                pr2serr("VPD_UNIT_SERIAL_NUM corrupted\n");
            return 0;
        }
        n = sg_get_unaligned_be16(b + 2);
        if (n > (int)(sizeof(b) - 4))
            n = (sizeof(b) - 4);
        printf("      Unit serial number: %.*s\n", n, (const char *)(b + 4));
    }
    if (has_di) {
        res = sg_ll_inquiry(fd, false, true /* evpd */, VPD_DEVICE_ID, b,
                            sizeof(b), 1, verb);
        if (res) {
            if (verbose)
                pr2serr("VPD_DEVICE_ID gave res=%d\n", res);
            return 0;
        }
        if (VPD_DEVICE_ID != b[1]) {
            if (verbose)
                pr2serr("VPD_DEVICE_ID corrupted\n");
            return 0;
        }
        n = sg_get_unaligned_be16(b + 2);
        if (n > (int)(sizeof(b) - 4))
            n = (sizeof(b) - 4);
        n = strlen(get_lu_name(b, n + 4, a, sizeof(a)));
        if (n > 0)
            printf("      LU name: %.*s\n", n, a);
    }
    return 0;
}


int
main(int argc, char * argv[])
{
    bool got_stdin = false;
    int k, res, c, infd, progress, vb, n, resp_len, err;
    int sg_fd = -1;
    int param_lst_len = 0;
    int ret = -1;
    const char * device_name = NULL;
    char ebuff[EBUFF_SZ];
    char b[80];
    uint8_t rsBuff[DEF_REQS_RESP_LEN];
    uint8_t * wBuff = NULL;
    uint8_t * free_wBuff = NULL;
    struct opts_t opts;
    struct opts_t * op;
    struct stat a_stat;
    uint8_t inq_resp[SAFE_STD_INQ_RESP_LEN];

    op = &opts;
    memset(op, 0, sizeof(opts));
    op->count = 1;
    while (1) {
        int option_index = 0;

        c = getopt_long(argc, argv, "ABc:CdDeFhi:IOp:Qt:T:vVwzZ",
                        long_options, &option_index);
        if (c == -1)
            break;

        switch (c) {
        case 'A':
            op->ause = true;
            break;
        case 'B':
            op->block = true;
            break;
        case 'c':
            op->count = sg_get_num(optarg);
            if ((op->count < 1) || (op->count > 31))  {
                pr2serr("bad argument to '--count', expect 1 to 31\n");
                return SG_LIB_SYNTAX_ERROR;
            }
            break;
        case 'C':
            op->crypto = true;
            break;
        case 'd':
            op->desc = true;
            break;
        case 'D':
            op->dry_run = true;
            break;
        case 'e':
            op->early = true;
            break;
        case 'F':
            op->fail = true;
            break;
        case 'h':
        case '?':
            usage();
            return 0;
        case 'i':
            op->ipl = sg_get_num(optarg);
            if ((op->ipl < 1) || (op->ipl > 65535))  {
                pr2serr("bad argument to '--ipl', expect 1 to 65535\n");
                return SG_LIB_SYNTAX_ERROR;
            }
            break;
        case 'I':
            op->invert = true;
            break;
        case 'O':
            op->overwrite = true;
            break;
        case 'p':
            op->pattern_fn = optarg;
            break;
        case 'Q':
            op->quick = true;
            break;
        case 't':
            op->timeout = sg_get_num(optarg);
            if (op->timeout < 0) {
                pr2serr("bad argument to '--timeout=SECS', want 0 or more\n");
                return SG_LIB_SYNTAX_ERROR;
            }
            break;
        case 'T':
            op->test = sg_get_num(optarg);
            if ((op->test < 0) || (op->test > 3))  {
                pr2serr("bad argument to '--test', expect 0 to 3\n");
                return SG_LIB_SYNTAX_ERROR;
            }
            break;
        case 'v':
            op->verbose_given = true;
            ++op->verbose;
            break;
        case 'V':
            op->version_given = true;
            break;
        case 'w':
            op->wait = true;
            break;
        case 'z':
            ++op->zero;
            break;
        case 'Z':
            op->znr = true;
            break;
        default:
            pr2serr("unrecognised option code 0x%x ??\n", c);
            usage();
            return SG_LIB_SYNTAX_ERROR;
        }
    }
    if (optind < argc) {
        if (NULL == device_name) {
            device_name = argv[optind];
            ++optind;
        }
        if (optind < argc) {
            for (; optind < argc; ++optind)
                pr2serr("Unexpected extra argument: %s\n", argv[optind]);
            usage();
            return SG_LIB_SYNTAX_ERROR;
        }
    }
#ifdef DEBUG
    pr2serr("In DEBUG mode, ");
    if (op->verbose_given && op->version_given) {
        pr2serr("but override: '-vV' given, zero verbose and continue\n");
        op->verbose_given = false;
        op->version_given = false;
        op->verbose = 0;
    } else if (! op->verbose_given) {
        pr2serr("set '-vv'\n");
        op->verbose = 2;
    } else
        pr2serr("keep verbose=%d\n", op->verbose);
#else
    if (op->verbose_given && op->version_given)
        pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
#endif
    if (op->version_given) {
        pr2serr(ME "version: %s\n", version_str);
        return 0;
    }

    if (NULL == device_name) {
        pr2serr("Missing device name!\n\n");
        usage();
        return SG_LIB_SYNTAX_ERROR;
    }
    vb = op->verbose;
    n = (int)op->block + (int)op->crypto + (int)op->fail + (int)op->overwrite;
    if (1 != n) {
        pr2serr("one and only one of '--block', '--crypto', '--fail' or "
                "'--overwrite' please\n");
        return SG_LIB_CONTRADICT;
    }
    if (op->overwrite) {
        if (op->zero) {
            if (op->pattern_fn) {
                pr2serr("confused: both '--pattern=PF' and '--zero' "
                        "options\n");
                return SG_LIB_CONTRADICT;
            }
            op->ipl = 4;
        } else {
            if (NULL == op->pattern_fn) {
                pr2serr("'--overwrite' requires '--pattern=PF' or '--zero' "
                        "option\n");
                return SG_LIB_CONTRADICT;
            }
            got_stdin = (0 == strcmp(op->pattern_fn, "-"));
            if (! got_stdin) {
                memset(&a_stat, 0, sizeof(a_stat));
                if (stat(op->pattern_fn, &a_stat) < 0) {
                    err = errno;
                    pr2serr("pattern file: unable to stat(%s): %s\n",
                            op->pattern_fn, safe_strerror(err));
                    ret = sg_convert_errno(err);
                    goto err_out;
                }
                if (op->ipl <= 0) {
                    op->ipl = (int)a_stat.st_size;
                    if (op->ipl > MAX_XFER_LEN) {
                        pr2serr("pattern file length exceeds 65535 bytes, "
                                "need '--ipl=LEN' option\n");
                         return SG_LIB_FILE_ERROR;
                    }
                }
            }
            if (op->ipl < 1) {
                pr2serr("'--overwrite' requires '--ipl=LEN' option if can't "
                        "get PF length\n");
                return SG_LIB_CONTRADICT;
            }
        }
    }

    sg_fd = sg_cmds_open_device(device_name, false /* rw */, vb);
    if (sg_fd < 0) {
        if (op->verbose)
            pr2serr(ME "open error: %s: %s\n", device_name,
                    safe_strerror(-sg_fd));
        ret = sg_convert_errno(-sg_fd);
        goto err_out;
    }

    ret = print_dev_id(sg_fd, inq_resp, sizeof(inq_resp), op->verbose);
    if (ret)
        goto err_out;

    if (op->overwrite) {
        param_lst_len = op->ipl + 4;
        wBuff = (uint8_t*)sg_memalign(op->ipl + 4, 0, &free_wBuff, false);
        if (NULL == wBuff) {
            pr2serr("unable to allocate %d bytes of memory with calloc()\n",
                    op->ipl + 4);
            ret = sg_convert_errno(ENOMEM);
            goto err_out;
        }
        if (op->zero) {
            if (2 == op->zero)  /* treat -zz as fill with 0xff bytes */
                memset(wBuff + 4, 0xff, op->ipl);
            else
                memset(wBuff + 4, 0, op->ipl);
        } else {
            if (got_stdin) {
                infd = STDIN_FILENO;
                if (sg_set_binary_mode(STDIN_FILENO) < 0)
                    perror("sg_set_binary_mode");
            } else {
                if ((infd = open(op->pattern_fn, O_RDONLY)) < 0) {
                    err = errno;
                    snprintf(ebuff, EBUFF_SZ, ME "could not open %s for "
                             "reading", op->pattern_fn);
                    perror(ebuff);
                    ret = sg_convert_errno(err);
                    goto err_out;
                } else if (sg_set_binary_mode(infd) < 0)
                    perror("sg_set_binary_mode");
            }
            res = read(infd, wBuff + 4, op->ipl);
            if (res < 0) {
                err = errno;
                snprintf(ebuff, EBUFF_SZ, ME "couldn't read from %s",
                         op->pattern_fn);
                perror(ebuff);
                if (! got_stdin)
                    close(infd);
                ret = sg_convert_errno(err);
                goto err_out;
            }
            if (res < op->ipl) {
                pr2serr("tried to read %d bytes from %s, got %d bytes\n",
                         op->ipl, op->pattern_fn, res);
                pr2serr("  so pad with 0x0 bytes and continue\n");
            }
            if (! got_stdin)
                close(infd);
        }
        wBuff[0] = op->count & 0x1f;
        if (op->test)
            wBuff[0] |= ((op->test & 0x3) << 5);
        if (op->invert)
            wBuff[0] |= 0x80;
        sg_put_unaligned_be16((uint16_t)op->ipl, wBuff + 2);
    }

    if ((! op->quick) && (! op->fail))
        sg_warn_and_wait("SANITIZE", device_name, true);

    ret = do_sanitize(sg_fd, op, wBuff, param_lst_len);
    if (ret) {
        sg_get_category_sense_str(ret, sizeof(b), b, vb);
        pr2serr("Sanitize failed: %s\n", b);
    }

    if ((0 == ret) && (! op->early) && (! op->wait)) {
        for (k = 0; ;++k) {     /* unbounded, exits via break */
            if (op->dry_run && (k > 0)) {
                pr2serr("Due to --dry-run option, leave poll loop\n");
                break;
            }
            sg_sleep_secs(POLL_DURATION_SECS);
            memset(rsBuff, 0x0, sizeof(rsBuff));
            res = sg_ll_request_sense(sg_fd, op->desc, rsBuff, sizeof(rsBuff),
                                      1, vb);
            if (res) {
                ret = res;
                if (SG_LIB_CAT_INVALID_OP == res)
                    pr2serr("Request Sense command not supported\n");
                else if (SG_LIB_CAT_ILLEGAL_REQ == res) {
                    pr2serr("bad field in Request Sense cdb\n");
                    if (op->desc) {
                        pr2serr("Descriptor type sense may not be supported, "
                                "try again with fixed type\n");
                        op->desc = false;
                        continue;
                    }
                } else {
                    sg_get_category_sense_str(res, sizeof(b), b, vb);
                    pr2serr("Request Sense: %s\n", b);
                    if (0 == vb)
                        pr2serr("    try the '-v' option for more "
                                "information\n");
                }
                break;
            }
            /* "Additional sense length" same in descriptor and fixed */
            resp_len = rsBuff[7] + 8;
            if (vb > 2) {
                pr2serr("Parameter data in hex\n");
                hex2stderr(rsBuff, resp_len, -1);
            }
            progress = -1;
            sg_get_sense_progress_fld(rsBuff, resp_len, &progress);
            if (progress < 0) {
                ret = res;
                if (vb > 1)
                     pr2serr("No progress indication found, iteration %d\n",
                             k + 1);
                if ((0 == k) && vb)
                     pr2serr("Sanitize seems to be successful and finished "
                             "quickly\n");
                /* N.B. exits first time there isn't a progress indication */
                break;
            } else
                printf("Progress indication: %d%% done\n",
                       (progress * 100) / 65536);
        }
    }

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