text: /* * This program reads various mode pages and bits of other
   * information from a scsi device and interprets the raw data for you
   * with a report written to stdout.  Usage:
   *
   * ./sginfo [options] /dev/sg2 [replace parameters]
   *
   * Options are:
   * -6    do 6 byte mode sense + select (default: 10 byte)
   * -a    display all mode pages reported by the device: equivalent to '-t 63'.
   * -A    display all mode pages and subpages reported by the device: equivalent
   *       to '-t 63,255'.
   * -c    access Cache control page.
   * -C    access Control Page.
   * -d    display defect lists (default format: index).
   * -D    access disconnect-reconnect page.
   * -e    access Read-Write error recovery page.
   * -E    access Control Extension page.
   * -f    access Format Device Page.
   * -Farg defect list format (-Flogical, -flba64, -Fphysical, -Findex, -Fhead)
   * -g    access rigid disk geometry page.
   * -G    display only "grown" defect list (default format: index)
   * -i    display information from Inquiry command.
   * -I    access Informational Exceptions page.
   * -l    list known scsi devices on the system [deprecated]
   * -n    access notch parameters page.
   * -N    Negate (stop) storing to saved page (active with -R)
   * -P    access Power Condition Page.
   * -r    list known raw scsi devices on the system
   * -s    display serial number (from INQUIRY VPD page)
   * -t <n[,spn]> access page number <n> [and subpage <spn>], try to decode
   * -u <n[,spn]> access page number <n> [and subpage <spn>], output in hex
   * -v    show this program's version number
   * -V    access Verify Error Recovery Page.
   * -T    trace commands (for debugging, double for more debug)
   * -z    do a single fetch for mode pages (rather than double fetch)
   *
   * Only one of the following three options can be specified.
   * None of these three implies the current values are returned.
   * -m    Display modifiable fields instead of current values
   * -M    Display manufacturer defaults instead of current values
   * -S    Display saved defaults instead of current values
   *
   * -X    Display output values in a list.
   * -R    Replace parameters - best used with -X
   *
   * Eric Youngdale - 11/1/93.  Version 1.0.
   *
   * Version 1.1: Ability to change parameters on cache page, support for
   *  X front end.
   *
   *   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.
   *
   *   This program is distributed in the hope that it will be useful,
   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *   GNU General Public License for more details.
   *
   *   You should have received a copy of the GNU General Public License
   *   along with this program; if not, write to the Free Software
   *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   *
   * SPDX-License-Identifier: GPL-2.0-or-later
   *
   * Michael Weller (eowmob at exp-math dot uni-essen dot de)
   *      11/23/94 massive extensions from 1.4a
   *      08/23/97 fix problems with defect lists
   *
   * Douglas Gilbert (dgilbert at interlog dot com)
   *      990628   port to sg .... (version 1.81)
   *               up 4KB limit on defect list to 32KB
   *               'sginfo -l' also shows sg devices and mapping to other
   *                    scsi devices
   *               'sginfo' commands can take either an sd, sr (scd), st
   *                    or an sg device (all non-sg devices converted to a
   *                    sg device)
   *
   *      001208   Add Kurt Garloff's "-uno" flag for displaying info
   *               from a page number. <garloff at suse dot de> [version 1.90]
   *
   * Kurt Garloff <garloff at suse dot de>
   *    20000715  allow displaying and modification of vendor specific pages
   *                      (unformatted - @ hexdatafield)
   *              accept vendor lengths for those pages
   *              enabled page saving
   *              cleaned parameter parsing a bit (it's still a terrible mess!)
   *              Use sr (instead of scd) and sg%d (instead of sga,b,...) in -l
   *                      and support much more devs in -l (incl. nosst)
   *              Fix segfault in defect list (len=0xffff) and adapt formatting
   *                      to large disks. Support up to 256kB defect lists with
   *                      0xB7 (12byte) command if necessary and fallback to 0x37
   *                      (10byte) in case of failure. Report truncation.
   *              sizeof(buffer) (which is sizeof(char*) == 4 or 32 bit archs)
   *                      was used incorrectly all over the place. Fixed.
   *                                      [version 1.95]
   * Douglas Gilbert (dgilbert at interlog dot com)
   *    20020113  snprintf() type cleanup [version 1.96]
   *    20021211  correct sginfo MODE_SELECT, protect against block devices
   *              that answer sg's ioctls. [version 1.97]
   *    20021228  scan for some "scd<n>" as well as "sr<n>" device names [1.98]
   *    20021020  Update control page [1.99]
   *
   * Thomas Steudten (thomas at steudten dot com)
   *    20040521  add -Fhead feature [version 2.04]
   *
   * Tim Hunt (tim at timhunt dot net)
   *    20050427  increase number of mapped SCSI disks devices
   *
   * Dave Johnson (djj at ccv dot brown dot edu)
   *    20051218  improve disk defect list handling
   */
  
  
  /*
   * N.B. This utility is in maintenance mode only. This means that serious
   * bugs will be fixed but no new features or mode page changes will be
   * added. Please use the sdparm utility.     D. Gilbert 20090316
   */
  
  #define _XOPEN_SOURCE 500
  #ifndef _GNU_SOURCE
  #define _GNU_SOURCE 1
  #endif
  
  static const char * version_str = "2.43 [20190913]";
  
  #include <stdio.h>
  #include <string.h>
  #include <getopt.h>
  #include <unistd.h>
  #include <stdlib.h>
  #include <fcntl.h>
  #include <errno.h>
  #include <stdlib.h>
  #include <ctype.h>
  #define __STDC_FORMAT_MACROS 1
  #include <inttypes.h>
  #include <sys/ioctl.h>
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <dirent.h>
  
  #ifdef HAVE_CONFIG_H
  #include "config.h"
  #endif
  #include "sg_io_linux.h"
  
  
  static int glob_fd;
  static char *device_name;
  
  #define MAX_SG_DEVS 8192
  #define MAX_RESP6_SIZE 252
  #define MAX_RESP10_SIZE (4*1024)
  #define MAX_BUFFER_SIZE MAX_RESP10_SIZE
  
  #define INQUIRY_RESP_INITIAL_LEN 36
  #define MAX_INQFIELD_LEN 17
  
  #define MAX_HEADS 127
  #define HEAD_SORT_TOKEN 0x55
  
  #define SIZEOF_BUFFER (16*1024)
  #define SIZEOF_BUFFER1 (16*1024)
  static uint8_t cbuffer[SIZEOF_BUFFER];
  static uint8_t cbuffer1[SIZEOF_BUFFER1];
  static uint8_t cbuffer2[SIZEOF_BUFFER1];
  
  static char defect = 0;
  static char defectformat = 0x4;
  static char grown_defect = 0;
  static char negate_sp_bit = 0;
  static char replace = 0;
  static char serial_number = 0;
  static char x_interface = 0;
  static char single_fetch = 0;
  
  static char mode6byte = 0;      /* defaults to 10 byte mode sense + select */
  static char trace_cmd = 0;
  
  struct mpage_info {
      int page;
      int subpage;
      int page_control;
      int peri_type;
      int inq_byte6;      /* EncServ and MChngr bits of interest */
      int resp_len;
  };
  
  /* declarations of functions decoding known mode pages */
  static int common_disconnect_reconnect(struct mpage_info * mpi,
                                         const char * prefix);
  static int common_control(struct mpage_info * mpi, const char * prefix);
  static int common_control_extension(struct mpage_info * mpi,
                                      const char * prefix);
  static int common_proto_spec_lu(struct mpage_info * mpi, const char * prefix);
  static int common_proto_spec_port(struct mpage_info * mpi,
                                    const char * prefix);
  static int common_proto_spec_port_sp1(struct mpage_info * mpi,
                                        const char * prefix);
  static int common_proto_spec_port_sp2(struct mpage_info * mpi,
                                        const char * prefix);
  static int common_power_condition(struct mpage_info * mpi,
                                    const char * prefix);
  static int common_informational(struct mpage_info * mpi, const char * prefix);
  static int disk_error_recovery(struct mpage_info * mpi, const char * prefix);
  static int disk_format(struct mpage_info * mpi, const char * prefix);
  static int disk_verify_error_recovery(struct mpage_info * mpi,
                                        const char * prefix);
  static int disk_geometry(struct mpage_info * mpi, const char * prefix);
  static int disk_notch_parameters(struct mpage_info * mpi, const char * prefix);
  static int disk_cache(struct mpage_info * mpi, const char * prefix);
  static int disk_xor_control(struct mpage_info * mpi, const char * prefix);
  static int disk_background(struct mpage_info * mpi, const char * prefix);
  static int optical_memory(struct mpage_info * mpi, const char * prefix);
  static int cdvd_error_recovery(struct mpage_info * mpi, const char * prefix);
  static int cdvd_mrw(struct mpage_info * mpi, const char * prefix);
  static int cdvd_write_param(struct mpage_info * mpi, const char * prefix);
  static int cdvd_audio_control(struct mpage_info * mpi, const char * prefix);
  static int cdvd_timeout(struct mpage_info * mpi, const char * prefix);
  static int cdvd_device_param(struct mpage_info * mpi, const char * prefix);
  static int cdvd_cache(struct mpage_info * mpi, const char * prefix);
  static int cdvd_mm_capab(struct mpage_info * mpi, const char * prefix);
  static int cdvd_feature(struct mpage_info * mpi, const char * prefix);
  static int tape_data_compression(struct mpage_info * mpi, const char * prefix);
  static int tape_dev_config(struct mpage_info * mpi, const char * prefix);
  static int tape_medium_part1(struct mpage_info * mpi, const char * prefix);
  static int tape_medium_part2_4(struct mpage_info * mpi, const char * prefix);
  static int ses_services_manag(struct mpage_info * mpi, const char * prefix);
  static int spi4_training_config(struct mpage_info * mpi, const char * prefix);
  static int spi4_negotiated(struct mpage_info * mpi, const char * prefix);
  static int spi4_report_xfer(struct mpage_info * mpi, const char * prefix);
  
  enum page_class {PC_COMMON, PC_DISK, PC_TAPE, PC_CDVD, PC_SES, PC_SMC};
  
  struct mpage_name_func {
      int page;
      int subpage;
      enum page_class pg_class;
      const char * name;
      int (*func)(struct mpage_info *, const char *);
  };
  
  #define MP_LIST_PAGES 0x3f
  #define MP_LIST_SUBPAGES 0xff
  
  static struct mpage_name_func mpage_common[] =
  {
      { 0, 0, PC_COMMON, "Vendor (non-page format)", NULL},
      { 2, 0, PC_COMMON, "Disconnect-Reconnect", common_disconnect_reconnect},
      { 9, 0, PC_COMMON, "Peripheral device (obsolete)", NULL},
      { 0xa, 0, PC_COMMON, "Control", common_control},
      { 0xa, 1, PC_COMMON, "Control Extension", common_control_extension},
      { 0x15, 0, PC_COMMON, "Extended", NULL},
      { 0x16, 0, PC_COMMON, "Extended, device-type specific", NULL},
      { 0x18, 0, PC_COMMON, "Protocol specific lu", common_proto_spec_lu},
      { 0x19, 0, PC_COMMON, "Protocol specific port", common_proto_spec_port},
      { 0x19, 1, PC_COMMON, "Protocol specific port, subpage 1 overload",
        common_proto_spec_port_sp1},
      { 0x19, 2, PC_COMMON, "Protocol specific port, subpage 2 overload",
        common_proto_spec_port_sp2},
  /*    { 0x19, 2, PC_COMMON, "SPI-4 Saved Training configuration",
          spi4_training_config}, */
      { 0x19, 3, PC_COMMON, "SPI-4 Negotiated Settings", spi4_negotiated},
      { 0x19, 4, PC_COMMON, "SPI-4 Report transfer capabilities",
        spi4_report_xfer},
      { 0x1a, 0, PC_COMMON, "Power Condition", common_power_condition},
      { 0x1c, 0, PC_COMMON, "Informational Exceptions", common_informational},
      { MP_LIST_PAGES, 0, PC_COMMON, "Return all pages", NULL},
  };
  static const int mpage_common_len = sizeof(mpage_common) /
                                      sizeof(mpage_common[0]);
  
  static struct mpage_name_func mpage_disk[] =
  {
      { 1, 0, PC_DISK, "Read-Write Error Recovery", disk_error_recovery},
      { 3, 0, PC_DISK, "Format Device", disk_format},
      { 4, 0, PC_DISK, "Rigid Disk Geometry", disk_geometry},
      { 5, 0, PC_DISK, "Flexible Disk", NULL},
      { 6, 0, PC_DISK, "Optical memory", optical_memory},
      { 7, 0, PC_DISK, "Verify Error Recovery", disk_verify_error_recovery},
      { 8, 0, PC_DISK, "Caching", disk_cache},
      { 0xa, 0xf1, PC_DISK, "Parallel ATA control (SAT)", NULL},
      { 0xb, 0, PC_DISK, "Medium Types Supported", NULL},
      { 0xc, 0, PC_DISK, "Notch and Partition", disk_notch_parameters},
      { 0x10, 0, PC_DISK, "XOR control", disk_xor_control},
      { 0x1c, 1, PC_DISK, "Background control", disk_background},
  };
  static const int mpage_disk_len = sizeof(mpage_disk) / sizeof(mpage_disk[0]);
  
  static struct mpage_name_func mpage_cdvd[] =
  {
      { 1, 0, PC_CDVD, "Read-Write Error Recovery (cdvd)",
        cdvd_error_recovery},
      { 3, 0, PC_CDVD, "MRW", cdvd_mrw},
      { 5, 0, PC_CDVD, "Write parameters", cdvd_write_param},
      { 8, 0, PC_CDVD, "Caching", cdvd_cache},
      { 0xd, 0, PC_CDVD, "CD device parameters", cdvd_device_param},
      { 0xe, 0, PC_CDVD, "CD audio control", cdvd_audio_control},
      { 0x18, 0, PC_CDVD, "Feature set support & version", cdvd_feature},
      { 0x1a, 0, PC_CDVD, "Power Condition", common_power_condition},
      { 0x1c, 0, PC_CDVD, "Fault/failure reporting control",
        common_informational},
      { 0x1d, 0, PC_CDVD, "Time-out & protect", cdvd_timeout},
      { 0x2a, 0, PC_CDVD, "MM capabilities & mechanical status", cdvd_mm_capab},
  };
  static const int mpage_cdvd_len = sizeof(mpage_cdvd) / sizeof(mpage_cdvd[0]);
  
  static struct mpage_name_func mpage_tape[] =
  {
      { 1, 0, PC_TAPE, "Read-Write Error Recovery", disk_error_recovery},
      { 0xf, 0, PC_TAPE, "Data compression", tape_data_compression},
      { 0x10, 0, PC_TAPE, "Device configuration", tape_dev_config},
      { 0x10, 1, PC_TAPE, "Device configuration extension", NULL},
      { 0x11, 0, PC_TAPE, "Medium partition(1)", tape_medium_part1},
      { 0x12, 0, PC_TAPE, "Medium partition(2)", tape_medium_part2_4},
      { 0x13, 0, PC_TAPE, "Medium partition(3)", tape_medium_part2_4},
      { 0x14, 0, PC_TAPE, "Medium partition(4)", tape_medium_part2_4},
      { 0x1c, 0, PC_TAPE, "Informational Exceptions", common_informational},
      { 0x1d, 0, PC_TAPE, "Medium configuration", NULL},
  };
  static const int mpage_tape_len = sizeof(mpage_tape) / sizeof(mpage_tape[0]);
  
  static struct mpage_name_func mpage_ses[] =
  {
      { 0x14, 0, PC_SES, "Enclosure services management", ses_services_manag},
  };
  static const int mpage_ses_len = sizeof(mpage_ses) / sizeof(mpage_ses[0]);
  
  static struct mpage_name_func mpage_smc[] =
  {
      { 0x1d, 0, PC_SMC, "Element address assignment", NULL},
      { 0x1e, 0, PC_SMC, "Transport geometry parameters", NULL},
      { 0x1f, 0, PC_SMC, "Device capabilities", NULL},
      { 0x1f, 1, PC_SMC, "Extended device capabilities", NULL},
  };
  static const int mpage_smc_len = sizeof(mpage_smc) / sizeof(mpage_smc[0]);
  
  
  #define MAXPARM 64
  
  static int next_parameter;
  static int n_replacement_values;
  static uint64_t replacement_values[MAXPARM];
  static char is_hex[MAXPARM];
  
  #define SMODE_SENSE 0x1a
  #define SMODE_SENSE_10 0x5a
  #define SMODE_SELECT 0x15
  #define SMODE_SELECT_10 0x55
  
  #define MPHEADER6_LEN 4
  #define MPHEADER10_LEN 8
  
  
  /* forward declarations */
  static void usage(const char *);
  static void dump(void *buffer, unsigned int length);
  
  #define DXFER_NONE        0
  #define DXFER_FROM_DEVICE 1
  #define DXFER_TO_DEVICE   2
  
  
  struct scsi_cmnd_io
  {
      uint8_t * cmnd;       /* ptr to SCSI command block (cdb) */
      size_t  cmnd_len;           /* number of bytes in SCSI command */
      int dxfer_dir;              /* DXFER_NONE, DXFER_FROM_DEVICE, or
                                     DXFER_TO_DEVICE */
      uint8_t * dxferp;     /* ptr to outgoing/incoming data */
      size_t dxfer_len;           /* bytes to be transferred to/from dxferp */
  };
  
  #define SENSE_BUFF_LEN   64
  #define CMD_TIMEOUT   60000 /* 60,000 milliseconds (60 seconds) */
  #define EBUFF_SZ   512
  
  
  #define GENERAL_ERROR           1
  #define UNKNOWN_OPCODE          2
  #define BAD_CDB_FIELD           3
  #define UNSUPPORTED_PARAM       4
  #define DEVICE_ATTENTION        5
  #define DEVICE_NOT_READY        6
  
  #define DECODE_FAILED_TRY_HEX   9999
  
  /* Returns 0 -> ok, 1 -> general error, 2 -> unknown opcode,
     3 -> unsupported field in cdb, 4 -> unsupported param in data-in */
  static int
  do_scsi_io(struct scsi_cmnd_io * sio)
  {
      uint8_t sense_b[SENSE_BUFF_LEN];
      struct sg_io_hdr io_hdr;
      struct sg_scsi_sense_hdr ssh;
      int res;
  
      memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
      io_hdr.interface_id = 'S';
      io_hdr.cmd_len = sio->cmnd_len;
      io_hdr.mx_sb_len = sizeof(sense_b);
      if (DXFER_NONE == sio->dxfer_dir)
          io_hdr.dxfer_direction = SG_DXFER_NONE;
      else
          io_hdr.dxfer_direction = (DXFER_TO_DEVICE == sio->dxfer_dir) ?
                                  SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV;
      io_hdr.dxfer_len = sio->dxfer_len;
      io_hdr.dxferp = sio->dxferp;
      io_hdr.cmdp = sio->cmnd;
      io_hdr.sbp = sense_b;
      io_hdr.timeout = CMD_TIMEOUT;
  
      if (trace_cmd) {
          printf("  cdb:");
          dump(sio->cmnd, sio->cmnd_len);
      }
      if ((trace_cmd > 1) && (DXFER_TO_DEVICE == sio->dxfer_dir)) {
          printf("  additional data:\n");
          dump(sio->dxferp, sio->dxfer_len);
      }
  
      if (ioctl(glob_fd, SG_IO, &io_hdr) < 0) {
          perror("do_scsi_cmd: SG_IO error");
          return GENERAL_ERROR;
      }
      res = sg_err_category3(&io_hdr);
      switch (res) {
      case SG_LIB_CAT_RECOVERED:
          sg_chk_n_print3("do_scsi_cmd, continuing", &io_hdr, true);
  #if defined(__GNUC__)
  #if (__GNUC__ >= 7)
          __attribute__((fallthrough));
          /* FALL THROUGH */
  #endif
  #endif
      case SG_LIB_CAT_CLEAN:
          return 0;
      default:
          if (trace_cmd) {
              char ebuff[EBUFF_SZ];
  
              snprintf(ebuff, EBUFF_SZ, "do_scsi_io: opcode=0x%x", sio->cmnd[0]);
              sg_chk_n_print3(ebuff, &io_hdr, true);
          }
          if (sg_normalize_sense(&io_hdr, &ssh)) {
              if (ILLEGAL_REQUEST == ssh.sense_key) {
                  if (0x20 == ssh.asc)
                      return UNKNOWN_OPCODE;
                  else if (0x24 == ssh.asc)
                      return BAD_CDB_FIELD;
                  else if (0x26 == ssh.asc)
                      return UNSUPPORTED_PARAM;
              } else if (UNIT_ATTENTION == ssh.sense_key)
                  return DEVICE_ATTENTION;
              else if (NOT_READY == ssh.sense_key)
                  return DEVICE_NOT_READY;
          }
          return GENERAL_ERROR;
      }
  }
  
  struct mpage_name_func * get_mpage_info(int page_no, int subpage_no,
                                      struct mpage_name_func * mpp, int elems)
  {
      int k;
  
      for (k = 0; k < elems; ++k, ++mpp) {
          if ((mpp->page == page_no) && (mpp->subpage == subpage_no))
              return mpp;
          if (mpp->page > page_no)
              break;
      }
      return NULL;
  }
  
  enum page_class get_page_class(struct mpage_info * mpi)
  {
      switch (mpi->peri_type)
      {
      case 0:
      case 4:
      case 7:
      case 0xe:   /* should be RBC */
          return PC_DISK;
      case 1:
      case 2:
          return PC_TAPE;
      case 8:
          return PC_SMC;
      case 5:
          return PC_CDVD;
      case 0xd:
          return PC_SES;
      default:
          return PC_COMMON;
      }
  }
  
  struct mpage_name_func * get_mpage_name_func(struct mpage_info * mpi)
  {
      struct mpage_name_func * mpf = NULL;
  
      switch (get_page_class(mpi))
      {
      case PC_DISK:
          mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_disk,
                               mpage_disk_len);
          break;
      case PC_CDVD:
          mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_cdvd,
                               mpage_cdvd_len);
          break;
      case PC_TAPE:
          mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_tape,
                               mpage_tape_len);
          break;
      case PC_SES:
          mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_ses,
                               mpage_ses_len);
          break;
      case PC_SMC:
          mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_smc,
                               mpage_smc_len);
          break;
      case PC_COMMON:
          /* picked up it catch all next */
          break;
      }
      if (NULL == mpf) {
          if ((PC_SES != get_page_class(mpi)) && (mpi->inq_byte6 & 0x40)) {
              /* check for attached enclosure services processor */
              mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_ses,
                                   mpage_ses_len);
          }
          if ((PC_SMC != get_page_class(mpi)) && (mpi->inq_byte6 & 0x8)) {
              /* check for attached medium changer device */
              mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_smc,
                                   mpage_smc_len);
          }
      }
      if (NULL == mpf)
          mpf = get_mpage_info(mpi->page, mpi->subpage, mpage_common,
                               mpage_common_len);
      return mpf;
  }
  
  
  static char unkn_page_str[64];
  
  static const char *
  get_page_name(struct mpage_info * mpi)
  {
      struct mpage_name_func * mpf;
  
      if (MP_LIST_PAGES == mpi->page) {
          if (MP_LIST_SUBPAGES == mpi->subpage)
              return "List supported pages and subpages";
          else
              return "List supported pages";
      }
      mpf = get_mpage_name_func(mpi);
      if ((NULL == mpf) || (NULL == mpf->name)) {
          if (mpi->subpage)
              snprintf(unkn_page_str, sizeof(unkn_page_str),
                       "page number=0x%x, subpage number=0x%x",
                       mpi->page, mpi->subpage);
          else
              snprintf(unkn_page_str, sizeof(unkn_page_str),
                       "page number=0x%x", mpi->page);
          return unkn_page_str;
      }
      return mpf->name;
  }
  
  static void
  dump(void *buffer, unsigned int length)
  {
      unsigned int i;
  
      printf("    ");
      for (i = 0; i < length; i++) {
  #if 0
          if (((uint8_t *) buffer)[i] > 0x20)
              printf(" %c ", (unsigned int) ((uint8_t *) buffer)[i]);
          else
  #endif
              printf("%02x ", (unsigned int) ((uint8_t *) buffer)[i]);
          if ((i % 16 == 15) && (i < (length - 1))) {
              printf("\n    ");
          }
      }
      printf("\n");
  
  }
  
  static int
  getnbyte(const uint8_t *pnt, int nbyte)
  {
      unsigned int result;
      int i;
  
      if (nbyte > 4)
          fprintf(stderr, "getnbyte() limited to 32 bits, nbyte=%d\n", nbyte);
      result = 0;
      for (i = 0; i < nbyte; i++)
          result = (result << 8) | (pnt[i] & 0xff);
      return result;
  }
  
  static int64_t
  getnbyte_ll(const uint8_t *pnt, int nbyte)
  {
      int64_t result;
      int i;
  
      if (nbyte > 8)
          fprintf(stderr, "getnbyte_ll() limited to 64 bits, nbyte=%d\n",
                  nbyte);
      result = 0;
      for (i = 0; i < nbyte; i++)
          result = (result << 8) + (pnt[i] & 0xff);
      return result;
  }
  
  static int
  putnbyte(uint8_t *pnt, unsigned int value,
                      unsigned int nbyte)
  {
      int i;
  
      for (i = nbyte - 1; i >= 0; i--) {
          pnt[i] = value & 0xff;
          value = value >> 8;
      }
      return 0;
  }
  
  #define REASON_SZ 128
  
  static void
  check_parm_type(int i)
  {
      char reason[REASON_SZ];
  
      if (i == 1 && is_hex[next_parameter] != 1) {
          snprintf(reason, REASON_SZ,
                   "simple number (pos %i) instead of @ hexdatafield: %"
                   PRIu64 , next_parameter, replacement_values[next_parameter]);
          usage(reason);
      }
      if (i != 1 && is_hex[next_parameter]) {
          snprintf(reason, REASON_SZ,
                   "@ hexdatafield (pos %i) instead of a simple number: %"
                   PRIu64 , next_parameter, replacement_values[next_parameter]);
          usage(reason);
      }
  }
  
  static void
  bitfield(uint8_t *pageaddr, const char * text, int mask, int shift)
  {
      if (x_interface && replace) {
          check_parm_type(0);
          *pageaddr = (*pageaddr & ~(mask << shift)) |
              ((replacement_values[next_parameter++] & mask) << shift);
      } else if (x_interface)
          printf("%d ", (*pageaddr >> shift) & mask);
      else
          printf("%-35s%d\n", text, (*pageaddr >> shift) & mask);
  }
  
  #if 0
  static void
  notbitfield(uint8_t *pageaddr, char * text, int mask,
                          int shift)
  {
      if (modifiable) {
          bitfield(pageaddr, text, mask, shift);
          return;
      }
      if (x_interface && replace) {
          check_parm_type(0);
          *pageaddr = (*pageaddr & ~(mask << shift)) |
              (((!replacement_values[next_parameter++]) & mask) << shift);
      } else if (x_interface)
          printf("%d ", !((*pageaddr >> shift) & mask));
      else
          printf("%-35s%d\n", text, !((*pageaddr >> shift) & mask));
  }
  #endif
  
  static void
  intfield(uint8_t * pageaddr, int nbytes, const char * text)
  {
      if (x_interface && replace) {
          check_parm_type(0);
          putnbyte(pageaddr, replacement_values[next_parameter++], nbytes);
      } else if (x_interface)
          printf("%d ", getnbyte(pageaddr, nbytes));
      else
          printf("%-35s%d\n", text, getnbyte(pageaddr, nbytes));
  }
  
  static void
  hexfield(uint8_t * pageaddr, int nbytes, const char * text)
  {
      if (x_interface && replace) {
          check_parm_type(0);
          putnbyte(pageaddr, replacement_values[next_parameter++], nbytes);
      } else if (x_interface)
          printf("%d ", getnbyte(pageaddr, nbytes));
      else
          printf("%-35s0x%x\n", text, getnbyte(pageaddr, nbytes));
  }
  
  static void
  hexdatafield(uint8_t * pageaddr, int nbytes, const char * text)
  {
      if (x_interface && replace) {
          uint8_t *ptr;
          unsigned tmp;
  
          /* Though in main we ensured that a @string has the right format,
             we have to check that we are working on a @ hexdata field */
  
          check_parm_type(1);
  
          ptr = (uint8_t *) (unsigned long)
                (replacement_values[next_parameter++]);
          ptr++;                  /* Skip @ */
  
          while (*ptr) {
              if (!nbytes)
                  goto illegal;
              tmp = (*ptr >= 'a') ? (*ptr - 'a' + 'A') : *ptr;
              tmp -= (tmp >= 'A') ? 'A' - 10 : '0';
  
              *pageaddr = tmp << 4;
              ptr++;
  
              tmp = (*ptr >= 'a') ? (*ptr - 'a' + 'A') : *ptr;
              tmp -= (tmp >= 'A') ? 'A' - 10 : '0';
  
              *pageaddr++ += tmp;
              ptr++;
              nbytes--;
          }
  
          if (nbytes) {
            illegal:
              fputs("sginfo: incorrect number of bytes in @hexdatafield.\n",
                    stdout);
              exit(2);
          }
      } else if (x_interface) {
          putchar('@');
          while (nbytes-- > 0)
              printf("%02x", *pageaddr++);
          putchar(' ');
      } else {
          printf("%-35s0x", text);
          while (nbytes-- > 0)
              printf("%02x", *pageaddr++);
          putchar('\n');
      }
  }
  
  
  /* Offset into mode sense (6 or 10 byte) response that actual mode page
   * starts at (relative to resp[0]). Returns -1 if problem */
  static int
  modePageOffset(const uint8_t * resp, int len, int modese_6)
  {
      int bd_len;
      int resp_len = 0;
      int offset = -1;
  
      if (resp) {
          if (modese_6) {
              resp_len = resp[0] + 1;
              bd_len = resp[3];
              offset = bd_len + MPHEADER6_LEN;
          } else {
              resp_len = (resp[0] << 8) + resp[1] + 2;
              bd_len = (resp[6] << 8) + resp[7];
              /* LongLBA doesn't change this calculation */
              offset = bd_len + MPHEADER10_LEN;
          }
          if ((offset + 2) > len) {
              printf("modePageOffset: raw_curr too small, offset=%d "
                     "resp_len=%d bd_len=%d\n", offset, resp_len, bd_len);
              offset = -1;
          } else if ((offset + 2) > resp_len) {
              printf("modePageOffset: response length too short, resp_len=%d"
                     " offset=%d bd_len=%d\n", resp_len, offset, bd_len);
              offset = -1;
          }
      }
      return offset;
  }
  
  /* Reads mode (sub-)page via 6 byte MODE SENSE, returns 0 if ok */
  static int
  get_mode_page6(struct mpage_info * mpi, int dbd, uint8_t * resp,
                 int sngl_fetch)
  {
      int status, off;
      uint8_t cmd[6];
      struct scsi_cmnd_io sci;
      int initial_len = (sngl_fetch ? MAX_RESP6_SIZE : 4);
  
      memset(resp, 0, 4);
      cmd[0] = SMODE_SENSE;       /* MODE SENSE (6) */
      cmd[1] = 0x00 | (dbd ? 0x8 : 0); /* disable block descriptors bit */
      cmd[2] = (mpi->page_control << 6) | mpi->page;
      cmd[3] = mpi->subpage;      /* subpage code */
      cmd[4] = initial_len;
      cmd[5] = 0x00;              /* control */
  
      sci.cmnd = cmd;
      sci.cmnd_len = sizeof(cmd);
      sci.dxfer_dir = DXFER_FROM_DEVICE;
      sci.dxfer_len = initial_len;
      sci.dxferp = resp;
      status = do_scsi_io(&sci);
      if (status) {
          if (mpi->subpage)
              fprintf(stdout, ">>> Unable to read %s mode page 0x%x, subpage "
                      "0x%x [mode_sense_6]\n", get_page_name(mpi), mpi->page,
                      mpi->subpage);
          else
              fprintf(stdout, ">>> Unable to read %s mode page (0x%x) "
                      "[mode_sense_6]\n", get_page_name(mpi), mpi->page);
          return status;
      }
      mpi->resp_len = resp[0] + 1;
      if (sngl_fetch) {
          if (trace_cmd > 1) {
              off = modePageOffset(resp, mpi->resp_len, 1);
              if (off >= 0) {
                  printf("  cdb response:\n");
                  dump(resp, mpi->resp_len);
              }
          }
          return status;
      }
  
      cmd[4] = mpi->resp_len;
      sci.cmnd = cmd;
      sci.cmnd_len = sizeof(cmd);
      sci.dxfer_dir = DXFER_FROM_DEVICE;
      sci.dxfer_len = mpi->resp_len;
      sci.dxferp = resp;
      status = do_scsi_io(&sci);
      if (status) {
          if (mpi->subpage)
              fprintf(stdout, ">>> Unable to read %s mode page 0x%x, subpage "
                      "0x%x [mode_sense_6]\n", get_page_name(mpi), mpi->page,
                      mpi->subpage);
          else
              fprintf(stdout, ">>> Unable to read %s mode page (0x%x) "
                      "[mode_sense_6]\n", get_page_name(mpi), mpi->page);
      } else if (trace_cmd > 1) {
          off = modePageOffset(resp, mpi->resp_len, 1);
          if (off >= 0) {
              printf("  cdb response:\n");
              dump(resp, mpi->resp_len);
          }
      }
      return status;
  }
  
  /* Reads mode (sub-)page via 10 byte MODE SENSE, returns 0 if ok */
  static int
  get_mode_page10(struct mpage_info * mpi, int llbaa, int dbd,
                  uint8_t * resp, int sngl_fetch)
  {
      int status, off;
      uint8_t cmd[10];
      struct scsi_cmnd_io sci;
      int initial_len = (sngl_fetch ? MAX_RESP10_SIZE : 4);
  
      memset(resp, 0, 4);
      cmd[0] = SMODE_SENSE_10;     /* MODE SENSE (10) */
      cmd[1] = 0x00 | (llbaa ? 0x10 : 0) | (dbd ? 0x8 : 0);
      cmd[2] = (mpi->page_control << 6) | mpi->page;
      cmd[3] = mpi->subpage;
      cmd[4] = 0x00;              /* (reserved) */
      cmd[5] = 0x00;              /* (reserved) */
      cmd[6] = 0x00;              /* (reserved) */
      cmd[7] = (initial_len >> 8) & 0xff;
      cmd[8] = initial_len & 0xff;
      cmd[9] = 0x00;              /* control */
  
      sci.cmnd = cmd;
      sci.cmnd_len = sizeof(cmd);
      sci.dxfer_dir = DXFER_FROM_DEVICE;
      sci.dxfer_len = initial_len;
      sci.dxferp = resp;
      status = do_scsi_io(&sci);
      if (status) {
          if (mpi->subpage)
              fprintf(stdout, ">>> Unable to read %s mode page 0x%x, subpage "
                      "0x%x [mode_sense_10]\n", get_page_name(mpi), mpi->page,
                      mpi->subpage);
          else {
              fprintf(stdout, ">>> Unable to read %s mode page (0x%x) "
                      "[mode_sense_10]\n", get_page_name(mpi), mpi->page);
              return status;
          }
      }
      mpi->resp_len = (resp[0] << 8) + resp[1] + 2;
      if (sngl_fetch) {
          if (trace_cmd > 1) {
              off = modePageOffset(resp, mpi->resp_len, 0);
              if (off >= 0) {
                  printf("  cdb response:\n");
                  dump(resp, mpi->resp_len);
              }
          }
          return status;
      }
  
      cmd[7] = (mpi->resp_len >> 8) & 0xff;
      cmd[8] = (mpi->resp_len & 0xff);
      sci.cmnd = cmd;
      sci.cmnd_len = sizeof(cmd);
      sci.dxfer_dir = DXFER_FROM_DEVICE;
      sci.dxfer_len = mpi->resp_len;
      sci.dxferp = resp;
      status = do_scsi_io(&sci);
      if (status) {
          if (mpi->subpage)
              fprintf(stdout, ">>> Unable to read %s mode page 0x%x, subpage "
                      "0x%x [mode_sense_10]\n", get_page_name(mpi), mpi->page,
                      mpi->subpage);
          else
              fprintf(stdout, ">>> Unable to read %s mode page (0x%x) "
                      "[mode_sense_10]\n", get_page_name(mpi), mpi->page);
      } else if (trace_cmd > 1) {
          off = modePageOffset(resp, mpi->resp_len, 0);
          if (off >= 0) {
              printf("  cdb response:\n");
              dump(resp, mpi->resp_len);
          }
      }
      return status;
  }
  
  static int
  get_mode_page(struct mpage_info * mpi, int dbd, uint8_t * resp)
  {
      int res;
  
      if (mode6byte)
          res = get_mode_page6(mpi, dbd, resp, single_fetch);
      else
          res = get_mode_page10(mpi, 0, dbd, resp, single_fetch);
      if (UNKNOWN_OPCODE == res)
          fprintf(stdout, ">>>>> Try command again with%s '-6' "
                  "argument\n", (mode6byte ? "out the" : " a"));
      else if (mpi->subpage && (BAD_CDB_FIELD == res))
          fprintf(stdout, ">>>>> device doesn't seem to support "
                  "subpages\n");
      else if (DEVICE_ATTENTION == res)
          fprintf(stdout, ">>>>> device reports UNIT ATTENTION, check it or"
                  " just try again\n");
      else if (DEVICE_NOT_READY == res)
          fprintf(stdout, ">>>>> device NOT READY, does it need media?\n");
      return res;
  }
  
  /* Contents should point to the mode parameter header that we obtained
     in a prior read operation.  This way we do not have to work out the
     format of the beast. Assume 0 or 1 block descriptors. */
  static int
  put_mode_page6(struct mpage_info * mpi, const uint8_t * msense6_resp,
                 int sp_bit)
  {
      int status;
      int bdlen, resplen;
      uint8_t cmd[6];
      struct scsi_cmnd_io sci;
  
      bdlen = msense6_resp[3];
      resplen = msense6_resp[0] + 1;
  
      cmd[0] = SMODE_SELECT;
      cmd[1] = 0x10 | (sp_bit ? 1 : 0); /* always set PF bit */
      cmd[2] = 0x00;
      cmd[3] = 0x00;              /* (reserved) */
      cmd[4] = resplen;           /* parameter list length */
      cmd[5] = 0x00;              /* (reserved) */
  
      memcpy(cbuffer1, msense6_resp, resplen);
      cbuffer1[0] = 0;            /* Mask off the mode data length
                                     - reserved field */
      cbuffer1[2] = 0;            /* device-specific parameter is not defined
                                     and/or reserved for mode select */
  
  #if 0   /* leave block descriptor alone */
      if (bdlen > 0) {
          memset(cbuffer1 + MPHEADER6_LEN, 0, 4);  /* clear 'number of blocks'
                                                     for DAD device */
          cbuffer1[MPHEADER6_LEN + 4] = 0; /* clear DAD density code. Why? */
          /* leave DAD block length */
      }
  #endif
      cbuffer1[MPHEADER6_LEN + bdlen] &= 0x7f;   /* Mask PS bit */
  
      sci.cmnd = cmd;
      sci.cmnd_len = sizeof(cmd);
      sci.dxfer_dir = DXFER_TO_DEVICE;
      sci.dxfer_len = resplen;
      sci.dxferp = cbuffer1;
      status = do_scsi_io(&sci);
      if (status) {
          if (mpi->subpage)
              fprintf(stdout, ">>> Unable to store %s mode page 0x%x,"
                      " subpage 0x%x [msel_6]\n", get_page_name(mpi),
                      mpi->page, mpi->subpage);
          else
              fprintf(stdout, ">>> Unable to store %s mode page 0x%x [msel_6]\n",
                      get_page_name(mpi), mpi->page);
      }
      return status;
  }
  
  /* Contents should point to the mode parameter header that we obtained
     in a prior read operation.  This way we do not have to work out the
     format of the beast. Assume 0 or 1 block descriptors. */
  static int
  put_mode_page10(struct mpage_info * mpi, const uint8_t * msense10_resp,
                  int sp_bit)
  {
      int status;
      int bdlen, resplen;
      uint8_t cmd[10];
      struct scsi_cmnd_io sci;
  
      bdlen = (msense10_resp[6] << 8) + msense10_resp[7];
      resplen = (msense10_resp[0] << 8) + msense10_resp[1] + 2;
  
      cmd[0] = SMODE_SELECT_10;
      cmd[1] = 0x10 | (sp_bit ? 1 : 0); /* always set PF bit */
      cmd[2] = 0x00;              /* (reserved) */
      cmd[3] = 0x00;              /* (reserved) */
      cmd[4] = 0x00;              /* (reserved) */
      cmd[5] = 0x00;              /* (reserved) */
      cmd[6] = 0x00;              /* (reserved) */
      cmd[7] = (resplen >> 8) & 0xff;
      cmd[8] = resplen & 0xff;
      cmd[9] = 0x00;              /* (reserved) */
  
      memcpy(cbuffer1, msense10_resp, resplen);
      cbuffer1[0] = 0;            /* Mask off the mode data length */
      cbuffer1[1] = 0;            /* Mask off the mode data length */
      cbuffer1[3] = 0;            /* device-specific parameter is not defined
                                     and/or reserved for mode select */
  #if 0   /* leave block descriptor alone */
      if (bdlen > 0) {
          memset(cbuffer1 + MPHEADER10_LEN, 0, 4);  /* clear 'number of blocks'
                                                      for DAD device */
          cbuffer1[MPHEADER10_LEN + 4] = 0; /* clear DAD density code. Why? */
          /* leave DAD block length */
      }
  #endif
      cbuffer1[MPHEADER10_LEN + bdlen] &= 0x7f;   /* Mask PS bit */
  
      sci.cmnd = cmd;
      sci.cmnd_len = sizeof(cmd);
      sci.dxfer_dir = DXFER_TO_DEVICE;
      sci.dxfer_len = resplen;
      sci.dxferp = cbuffer1;
      status = do_scsi_io(&sci);
      if (status) {
          if (mpi->subpage)
              fprintf(stdout, ">>> Unable to store %s mode page 0x%x,"
                      " subpage 0x%x [msel_10]\n", get_page_name(mpi),
                      mpi->page, mpi->subpage);
          else
              fprintf(stdout, ">>> Unable to store %s mode page 0x%x "
                      "[msel_10]\n", get_page_name(mpi), mpi->page);
      }
      return status;
  }
  
  static int
  put_mode_page(struct mpage_info * mpi, const uint8_t * msense_resp)
  {
      if (mode6byte)
          return put_mode_page6(mpi, msense_resp, ! negate_sp_bit);
      else
          return put_mode_page10(mpi, msense_resp, ! negate_sp_bit);
  }
  
  static int
  setup_mode_page(struct mpage_info * mpi, int nparam, uint8_t * buff,
                  uint8_t ** o_pagestart)
  {
      int status, offset, rem_pglen;
      uint8_t * pgp;
  
      status = get_mode_page(mpi, 0, buff);
      if (status) {
          printf("\n");
          return status;
      }
      offset = modePageOffset(buff, mpi->resp_len, mode6byte);
      if (offset < 0) {
          fprintf(stdout, "mode page=0x%x has bad page format\n", mpi->page);
          fprintf(stdout, "   perhaps '-z' switch may help\n");
          return -1;
      }
      pgp = buff + offset;
      *o_pagestart = pgp;
      rem_pglen = (0x40 & pgp[0]) ? ((pgp[2] << 8) + pgp[3]) : pgp[1];
  
      if (x_interface && replace) {
          if ((nparam && (n_replacement_values != nparam)) ||
              ((! nparam) && (n_replacement_values != rem_pglen))) {
              fprintf(stdout, "Wrong number of replacement values (%i instead "
                      "of %i)\n", n_replacement_values,
                      nparam ? nparam : rem_pglen);
              return 1;
          }
          next_parameter = 1;
      }
      return 0;
  }
  
  static int
  get_protocol_id(int port_not_lu, uint8_t * buff, int * proto_idp,
                  int * offp)
  {
      int status, off, proto_id, spf;
      struct mpage_info mp_i;
      char b[64];
  
      memset(&mp_i, 0, sizeof(mp_i));
      mp_i.page = (port_not_lu ? 0x19 : 0x18);
      /* N.B. getting port or lu specific mode page (not subpage) */
      status = get_mode_page(&mp_i, 0, buff);
      if (status)
          return status;
      off = modePageOffset(buff, mp_i.resp_len, mode6byte);
      if (off < 0)
          return off;
      spf = (buff[off] & 0x40) ? 1 : 0;  /* subpages won't happen here */
      proto_id = buff[off + (spf ? 5 : 2)] & 0xf;
      if (trace_cmd > 0)
          printf("Protocol specific %s, protocol_id=%s\n",
                 (port_not_lu ? "port" : "lu"),
                 sg_get_trans_proto_str(proto_id, sizeof(b), b));
      if (proto_idp)
          *proto_idp = proto_id;
      if (offp)
          *offp = off;
      return 0;
  }
  
  static int
  disk_geometry(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 9, cbuffer, &pagestart);
      if (status)
          return status;
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("-----------------------------------\n");
      };
      intfield(pagestart + 2, 3, "Number of cylinders");
      intfield(pagestart + 5, 1, "Number of heads");
      intfield(pagestart + 6, 3, "Starting cyl. write precomp");
      intfield(pagestart + 9, 3, "Starting cyl. reduced current");
      intfield(pagestart + 12, 2, "Device step rate");
      intfield(pagestart + 14, 3, "Landing Zone Cylinder");
      bitfield(pagestart + 17, "RPL", 3, 0);
      intfield(pagestart + 18, 1, "Rotational Offset");
      intfield(pagestart + 20, 2, "Rotational Rate");
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  common_disconnect_reconnect(struct mpage_info * mpi,
                                         const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 11, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("------------------------------------\n");
      };
      intfield(pagestart + 2, 1, "Buffer full ratio");
      intfield(pagestart + 3, 1, "Buffer empty ratio");
      intfield(pagestart + 4, 2, "Bus Inactivity Limit (SAS: 100us)");
      intfield(pagestart + 6, 2, "Disconnect Time Limit");
      intfield(pagestart + 8, 2, "Connect Time Limit (SAS: 100us)");
      intfield(pagestart + 10, 2, "Maximum Burst Size");
      bitfield(pagestart + 12, "EMDP", 1, 7);
      bitfield(pagestart + 12, "Fair Arbitration (fcp:faa,fab,fac)", 0x7, 4);
      bitfield(pagestart + 12, "DIMM", 1, 3);
      bitfield(pagestart + 12, "DTDC", 0x7, 0);
      intfield(pagestart + 14, 2, "First Burst Size");
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  
  }
  
  static int
  common_control(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 21, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("-----------------------\n");
      }
      bitfield(pagestart + 2, "TST", 0x7, 5);
      bitfield(pagestart + 2, "TMF_ONLY", 1, 4);
      bitfield(pagestart + 2, "D_SENSE", 1, 2);
      bitfield(pagestart + 2, "GLTSD", 1, 1);
      bitfield(pagestart + 2, "RLEC", 1, 0);
      bitfield(pagestart + 3, "Queue Algorithm Modifier", 0xf, 4);
      bitfield(pagestart + 3, "QErr", 0x3, 1);
      bitfield(pagestart + 3, "DQue [obsolete]", 1, 0);
      bitfield(pagestart + 4, "TAS", 1, 7);
      bitfield(pagestart + 4, "RAC", 1, 6);
      bitfield(pagestart + 4, "UA_INTLCK_CTRL", 0x3, 4);
      bitfield(pagestart + 4, "SWP", 1, 3);
      bitfield(pagestart + 4, "RAERP [obs.]", 1, 2);
      bitfield(pagestart + 4, "UAAERP [obs.]", 1, 1);
      bitfield(pagestart + 4, "EAERP [obs.]", 1, 0);
      bitfield(pagestart + 5, "ATO", 1, 7);
      bitfield(pagestart + 5, "TAS", 1, 6);
      bitfield(pagestart + 5, "AUTOLOAD MODE", 0x7, 0);
      intfield(pagestart + 6, 2, "Ready AER Holdoff Period [obs.]");
      intfield(pagestart + 8, 2, "Busy Timeout Period");
      intfield(pagestart + 10, 2, "Extended self-test completion time");
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  common_control_extension(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 4, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode subpage (0x%x,0x%x)\n", get_page_name(mpi), mpi->page,
                 mpi->subpage);
          printf("--------------------------------------------\n");
      }
      bitfield(pagestart + 4, "TCMOS", 1, 2);
      bitfield(pagestart + 4, "SCSIP", 1, 1);
      bitfield(pagestart + 4, "IALUAE", 1, 0);
      bitfield(pagestart + 5, "Initial Priority", 0xf, 0);
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  common_informational(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 10, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("-----------------------------------------\n");
      }
      bitfield(pagestart + 2, "PERF", 1, 7);
      bitfield(pagestart + 2, "EBF", 1, 5);
      bitfield(pagestart + 2, "EWASC", 1, 4);
      bitfield(pagestart + 2, "DEXCPT", 1, 3);
      bitfield(pagestart + 2, "TEST", 1, 2);
      bitfield(pagestart + 2, "EBACKERR", 1, 1);
      bitfield(pagestart + 2, "LOGERR", 1, 0);
      bitfield(pagestart + 3, "MRIE", 0xf, 0);
      intfield(pagestart + 4, 4, "Interval Timer");
      intfield(pagestart + 8, 4, "Report Count");
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  disk_error_recovery(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 14, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("-----------------------------------------\n");
      }
      bitfield(pagestart + 2, "AWRE", 1, 7);
      bitfield(pagestart + 2, "ARRE", 1, 6);
      bitfield(pagestart + 2, "TB", 1, 5);
      bitfield(pagestart + 2, "RC", 1, 4);
      bitfield(pagestart + 2, "EER", 1, 3);
      bitfield(pagestart + 2, "PER", 1, 2);
      bitfield(pagestart + 2, "DTE", 1, 1);
      bitfield(pagestart + 2, "DCR", 1, 0);
      intfield(pagestart + 3, 1, "Read Retry Count");
      intfield(pagestart + 4, 1, "Correction Span");
      intfield(pagestart + 5, 1, "Head Offset Count");
      intfield(pagestart + 6, 1, "Data Strobe Offset Count");
      intfield(pagestart + 8, 1, "Write Retry Count");
      intfield(pagestart + 10, 2, "Recovery Time Limit (ms)");
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  cdvd_error_recovery(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 10, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("------------------------------------------------\n");
      }
      bitfield(pagestart + 2, "AWRE", 1, 7);
      bitfield(pagestart + 2, "ARRE", 1, 6);
      bitfield(pagestart + 2, "TB", 1, 5);
      bitfield(pagestart + 2, "RC", 1, 4);
      bitfield(pagestart + 2, "PER", 1, 2);
      bitfield(pagestart + 2, "DTE", 1, 1);
      bitfield(pagestart + 2, "DCR", 1, 0);
      intfield(pagestart + 3, 1, "Read Retry Count");
      bitfield(pagestart + 7, "EMCDR", 3, 0);
      intfield(pagestart + 8, 1, "Write Retry Count");
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  cdvd_mrw(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 1, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("------------------------------------------------\n");
      }
      bitfield(pagestart + 3, "LBA space", 1, 0);
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  disk_notch_parameters(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 6, cbuffer, &pagestart);
      if (status) {
          fprintf(stdout, "Special case: only give 6 fields to '-XR' since"
                  " 'Pages Notched' is unchangeable\n");
          return status;
      }
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("-----------------------------------\n");
      };
      bitfield(pagestart + 2, "Notched Drive", 1, 7);
      bitfield(pagestart + 2, "Logical or Physical Notch", 1, 6);
      intfield(pagestart + 4, 2, "Max # of notches");
      intfield(pagestart + 6, 2, "Active Notch");
      if (pagestart[2] & 0x40) {
          intfield(pagestart + 8, 4, "Starting Boundary");
          intfield(pagestart + 12, 4, "Ending Boundary");
      } else {           /* Hex is more meaningful for physical notches */
          hexfield(pagestart + 8, 4, "Starting Boundary");
          hexfield(pagestart + 12, 4, "Ending Boundary");
      }
  
      if (x_interface && !replace) {
  #if 1
          ;       /* do nothing, skip this field */
  #else
          if (1 == mpi->page_control)     /* modifiable */
              printf("0");
          else
              printf("0x%8.8x%8.8x", getnbyte(pagestart + 16, 4),
                     getnbyte(pagestart + 20, 4));
  #endif
      };
      if (!x_interface)
          printf("Pages Notched                      %8.8x %8.8x\n",
                 getnbyte(pagestart + 16, 4), getnbyte(pagestart + 20, 4));
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static const char *
  formatname(int format)
  {
      switch(format) {
          case 0x0: return "logical block addresses (32 bit)";
          case 0x3: return "logical block addresses (64 bit)";
          case 0x4: return "bytes from index [Cyl:Head:Off]\n"
                  "Offset -1 marks whole track as bad.\n";
          case 0x5: return "physical blocks [Cyl:Head:Sect]\n"
                  "Sector -1 marks whole track as bad.\n";
      }
      return "Weird, unknown format";
  }
  
  static int
  read_defect_list(int grown_only)
  {
      int i, len, reallen, table, k, defect_format;
      int status = 0;
      int header = 1;
      int sorthead = 0;
      uint8_t cmd[10];
      uint8_t cmd12[12];
      uint8_t *df = NULL;
      uint8_t *bp = NULL;
      uint8_t *heapp = NULL;
      unsigned int  *headsp = NULL;
      int trunc;
      struct scsi_cmnd_io sci;
  
      if (defectformat == HEAD_SORT_TOKEN) {
          defectformat = 0x04;
          sorthead = 1;
          headsp = (unsigned int *)calloc(MAX_HEADS, sizeof(unsigned int));
          if (headsp == NULL) {
             perror("malloc failed");
             return status;
          }
      }
      for (table = grown_only; table < 2; table++) {
          if (heapp) {
              free(heapp);
              heapp = NULL;
          }
          bp = cbuffer;
          memset(bp, 0, 4);
          trunc = 0;
          reallen = -1;
  
          cmd[0] = 0x37;          /* READ DEFECT DATA (10) */
          cmd[1] = 0x00;
          cmd[2] = (table ? 0x08 : 0x10) | defectformat;  /*  List, Format */
          cmd[3] = 0x00;          /* (reserved) */
          cmd[4] = 0x00;          /* (reserved) */
          cmd[5] = 0x00;          /* (reserved) */
          cmd[6] = 0x00;          /* (reserved) */
          cmd[7] = 0x00;          /* Alloc len */
          cmd[8] = 0x04;          /* Alloc len (size finder) */
          cmd[9] = 0x00;          /* control */
  
          sci.cmnd = cmd;
          sci.cmnd_len = sizeof(cmd);
          sci.dxfer_dir = DXFER_FROM_DEVICE;
          sci.dxfer_len = 4;
          sci.dxferp = bp;
          i = do_scsi_io(&sci);
          if (i) {
              fprintf(stdout, ">>> Unable to read %s defect data.\n",
                      (table ? "grown (GLIST)" : "primary (PLIST)"));
              status |= i;
              continue;
          }
          if (trace_cmd > 1) {
              printf("  cdb response:\n");
              dump(bp, 4);
          }
          /*
           * Check validity of response:
           * bp[0] reserved, must be zero
           * bp[1] bits 7-5 reserved, must be zero
           * bp[1] bits 4-3 should match table requested
           */
          if (0 != bp[0] || (table ? 0x08 : 0x10) != (bp[1] & 0xf8)) {
              fprintf(stdout, ">>> Invalid header for %s defect list.\n",
                      (table ? "grown (GLIST)" : "primary (PLIST)"));
              status |= 1;
              continue;
          }
          if (header) {
              printf("Defect Lists\n"
                     "------------\n");
              header = 0;
          }
          len = (bp[2] << 8) + bp[3];
          if (len < 0xfff8)
              reallen = len;
          else {
              /*
               * List length is at or over capacity of READ DEFECT DATA (10)
               * Try to get actual length with READ DEFECT DATA (12)
               */
              bp = cbuffer;
              memset(bp, 0, 8);
              cmd12[0] = 0xB7;          /* READ DEFECT DATA (12) */
              cmd12[1] = (table ? 0x08 : 0x10) | defectformat;/*  List, Format */
              cmd12[2] = 0x00;          /* (reserved) */
              cmd12[3] = 0x00;          /* (reserved) */
              cmd12[4] = 0x00;          /* (reserved) */
              cmd12[5] = 0x00;          /* (reserved) */
              cmd12[6] = 0x00;          /* Alloc len */
              cmd12[7] = 0x00;          /* Alloc len */
              cmd12[8] = 0x00;          /* Alloc len */
              cmd12[9] = 0x08;          /* Alloc len (size finder) */
              cmd12[10] = 0x00;         /* reserved */
              cmd12[11] = 0x00;         /* control */
  
              sci.cmnd = cmd12;
              sci.cmnd_len = sizeof(cmd12);
              sci.dxfer_dir = DXFER_FROM_DEVICE;
              sci.dxfer_len = 8;
              sci.dxferp = bp;
              i = do_scsi_io(&sci);
              if (i) {
                  if (trace_cmd) {
                      fprintf(stdout, ">>> No 12 byte command support, "
                              "but list is too long for 10 byte version.\n"
                              "List will be truncated at 8191 elements\n");
                  }
                  goto trytenbyte;
              }
              if (trace_cmd > 1) {
                  printf("  cdb response:\n");
                  dump(bp, 8);
              }
              /*
               * Check validity of response:
               *    bp[0], bp[2] and bp[3] reserved, must be zero
               *    bp[1] bits 7-5 reserved, must be zero
               *    bp[1] bits 4-3 should match table we requested
               */
              if (0 != bp[0] || 0 != bp[2] || 0 != bp[3] ||
                      ((table ? 0x08 : 0x10) != (bp[1] & 0xf8))) {
                  if (trace_cmd)
                      fprintf(stdout,
                              ">>> Invalid header for %s defect list.\n",
                              (table ? "grown (GLIST)" : "primary (PLIST)"));
                  goto trytenbyte;
              }
              len = (bp[4] << 24) + (bp[5] << 16) + (bp[6] << 8) + bp[7];
              reallen = len;
          }
  
          if (len > 0) {
              k = len + 8;              /* length of defect list + header */
              if (k > (int)sizeof(cbuffer)) {
                  heapp = (uint8_t *)malloc(k);
  
                  if (len > 0x80000 && NULL == heapp) {
                      len = 0x80000;      /* go large: 512 KB */
                      k = len + 8;
                      heapp = (uint8_t *)malloc(k);
                  }
                  if (heapp != NULL)
                      bp = heapp;
              }
              if (len > 0xfff0 && heapp != NULL) {
                  cmd12[0] = 0xB7;          /* READ DEFECT DATA (12) */
                  cmd12[1] = (table ? 0x08 : 0x10) | defectformat;
                                                  /*  List, Format */
                  cmd12[2] = 0x00;          /* (reserved) */
                  cmd12[3] = 0x00;          /* (reserved) */
                  cmd12[4] = 0x00;          /* (reserved) */
                  cmd12[5] = 0x00;          /* (reserved) */
                  cmd12[6] = 0x00;          /* Alloc len */
                  cmd12[7] = (k >> 16) & 0xff;     /* Alloc len */
                  cmd12[8] = (k >> 8) & 0xff;      /* Alloc len */
                  cmd12[9] = (k & 0xff);    /* Alloc len */
                  cmd12[10] = 0x00;         /* reserved */
                  cmd12[11] = 0x00;         /* control */
  
                  sci.cmnd = cmd12;
                  sci.cmnd_len = sizeof(cmd12);
                  sci.dxfer_dir = DXFER_FROM_DEVICE;
                  sci.dxfer_len = k;
                  sci.dxferp = bp;
                  i = do_scsi_io(&sci);
                  if (i)
                      goto trytenbyte;
                  if (trace_cmd > 1) {
                      printf("  cdb response:\n");
                      dump(bp, 8);
                  }
                  reallen = (bp[4] << 24) + (bp[5] << 16) + (bp[6] << 8) +
                            bp[7];
                  if (reallen > len) {
                      trunc = 1;
                  }
                  df = (uint8_t *) (bp + 8);
              }
              else {
  trytenbyte:
                  if (len > 0xfff8) {
                      len = 0xfff8;
                      trunc = 1;
                  }
                  k = len + 4;            /* length of defect list + header */
                  if (k > (int)sizeof(cbuffer) && NULL == heapp) {
                      heapp = (uint8_t *)malloc(k);
                      if (heapp != NULL)
                          bp = heapp;
                  }
                  if (k > (int)sizeof(cbuffer) && NULL == heapp) {
                      bp = cbuffer;
                      k = sizeof(cbuffer);
                      len = k - 4;
                      trunc = 1;
                  }
                  cmd[0] = 0x37;          /* READ DEFECT DATA (10) */
                  cmd[1] = 0x00;
                  cmd[2] = (table ? 0x08 : 0x10) | defectformat;
                                          /*  List, Format */
                  cmd[3] = 0x00;          /* (reserved) */
                  cmd[4] = 0x00;          /* (reserved) */
                  cmd[5] = 0x00;          /* (reserved) */
                  cmd[6] = 0x00;          /* (reserved) */
                  cmd[7] = (k >> 8);      /* Alloc len */
                  cmd[8] = (k & 0xff);    /* Alloc len */
                  cmd[9] = 0x00;          /* control */
  
                  sci.cmnd = cmd;
                  sci.cmnd_len = sizeof(cmd);
                  sci.dxfer_dir = DXFER_FROM_DEVICE;
                  sci.dxfer_len = k;
                  sci.dxferp = bp;
                  i = do_scsi_io(&sci);
                  df = (uint8_t *) (bp + 4);
              }
          }
          if (i) {
              fprintf(stdout, ">>> Unable to read %s defect data.\n",
                      (table ? "grown (GLIST)" : "primary (PLIST)"));
              status |= i;
              continue;
          }
          else {
              if (table && !status && !sorthead)
                  printf("\n");
              defect_format = (bp[1] & 0x7);
              if (-1 == reallen) {
                  printf("at least ");
                  reallen = len;
              }
              printf("%d entries (%d bytes) in %s table.\n",
                     reallen / ((0 == defect_format) ? 4 : 8), reallen,
                     table ? "grown (GLIST)" : "primary (PLIST)");
              if (!sorthead)
                  printf("Format (%x) is: %s\n", defect_format,
                     formatname(defect_format));
              i = 0;
              switch (defect_format) {
              case 4:     /* bytes from index */
                  while (len > 0) {
                      snprintf((char *)cbuffer1, 40, "%6d:%3u:%8d",
                               getnbyte(df, 3), df[3], getnbyte(df + 4, 4));
                      if (sorthead == 0)
                          printf("%19s", (char *)cbuffer1);
                      else
                          if (df[3] < MAX_HEADS) headsp[df[3]]++;
                      len -= 8;
                      df += 8;
                      i++;
                      if (i >= 4 && !sorthead) {
                          printf("\n");
                          i = 0;
                      }
                      else if (!sorthead) printf("|");
                  }
  		break;
              case 5:     /* physical sector */
                  while (len > 0) {
                      snprintf((char *)cbuffer1, 40, "%6d:%2u:%5d",
                               getnbyte(df, 3),
                               df[3], getnbyte(df + 4, 4));
                      if (sorthead == 0)
                          printf("%15s", (char *)cbuffer1);
                      else
                          if (df[3] < MAX_HEADS) headsp[df[3]]++;
                      len -= 8;
                      df += 8;
                      i++;
                      if (i >= 5 && !sorthead) {
                          printf("\n");
                          i = 0;
                      }
                      else if (!sorthead) printf("|");
                  }
  		break;
              case 0:     /* lba (32 bit) */
                  while (len > 0) {
                      printf("%10d", getnbyte(df, 4));
                      len -= 4;
                      df += 4;
                      i++;
                      if (i >= 7) {
                          printf("\n");
                          i = 0;
                      }
                      else
                          printf("|");
                  }
  		break;
              case 3:     /* lba (64 bit) */
                  while (len > 0) {
                      printf("%15" PRId64 , getnbyte_ll(df, 8));
                      len -= 8;
                      df += 8;
                      i++;
                      if (i >= 5) {
                          printf("\n");
                          i = 0;
                      }
                      else
                          printf("|");
                  }
                  break;
              default:
                  printf("unknown defect list format: %d\n", defect_format);
                  break;
              }
              if (i && !sorthead)
                  printf("\n");
          }
          if (trunc)
                  printf("[truncated]\n");
      }
      if (heapp) {
          free(heapp);
          heapp = NULL;
      }
      if (sorthead) {
          printf("Format is: [head:# entries for this head in list]\n\n");
          for (i=0; i<MAX_HEADS; i++) {
              if (headsp[i] > 0) {
                 printf("%3d: %u\n", i, headsp[i]);
              }
          }
          free(headsp);
      }
      printf("\n");
      return status;
  }
  
  static int
  disk_cache(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 21, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("-----------------------\n");
      };
      bitfield(pagestart + 2, "Initiator Control", 1, 7);
      bitfield(pagestart + 2, "ABPF", 1, 6);
      bitfield(pagestart + 2, "CAP", 1, 5);
      bitfield(pagestart + 2, "DISC", 1, 4);
      bitfield(pagestart + 2, "SIZE", 1, 3);
      bitfield(pagestart + 2, "Write Cache Enabled", 1, 2);
      bitfield(pagestart + 2, "MF", 1, 1);
      bitfield(pagestart + 2, "Read Cache Disabled", 1, 0);
      bitfield(pagestart + 3, "Demand Read Retention Priority", 0xf, 4);
      bitfield(pagestart + 3, "Demand Write Retention Priority", 0xf, 0);
      intfield(pagestart + 4, 2, "Disable Pre-fetch Transfer Length");
      intfield(pagestart + 6, 2, "Minimum Pre-fetch");
      intfield(pagestart + 8, 2, "Maximum Pre-fetch");
      intfield(pagestart + 10, 2, "Maximum Pre-fetch Ceiling");
      bitfield(pagestart + 12, "FSW", 1, 7);
      bitfield(pagestart + 12, "LBCSS", 1, 6);
      bitfield(pagestart + 12, "DRA", 1, 5);
      bitfield(pagestart + 12, "NV_DIS", 1, 0);
      intfield(pagestart + 13, 1, "Number of Cache Segments");
      intfield(pagestart + 14, 2, "Cache Segment size");
      intfield(pagestart + 17, 3, "Non-Cache Segment size");
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  disk_format(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 13, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("-----------------------------\n");
      };
      intfield(pagestart + 2, 2, "Tracks per Zone");
      intfield(pagestart + 4, 2, "Alternate sectors per zone");
      intfield(pagestart + 6, 2, "Alternate tracks per zone");
      intfield(pagestart + 8, 2, "Alternate tracks per lu");
      intfield(pagestart + 10, 2, "Sectors per track");
      intfield(pagestart + 12, 2, "Data bytes per physical sector");
      intfield(pagestart + 14, 2, "Interleave");
      intfield(pagestart + 16, 2, "Track skew factor");
      intfield(pagestart + 18, 2, "Cylinder skew factor");
      bitfield(pagestart + 20, "Supports Soft Sectoring", 1, 7);
      bitfield(pagestart + 20, "Supports Hard Sectoring", 1, 6);
      bitfield(pagestart + 20, "Removable Medium", 1, 5);
      bitfield(pagestart + 20, "Surface", 1, 4);
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  
  }
  
  static int
  disk_verify_error_recovery(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 7, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("-------------------------------------\n");
      }
      bitfield(pagestart + 2, "EER", 1, 3);
      bitfield(pagestart + 2, "PER", 1, 2);
      bitfield(pagestart + 2, "DTE", 1, 1);
      bitfield(pagestart + 2, "DCR", 1, 0);
      intfield(pagestart + 3, 1, "Verify Retry Count");
      intfield(pagestart + 4, 1, "Verify Correction Span (bits)");
      intfield(pagestart + 10, 2, "Verify Recovery Time Limit (ms)");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  #if 0
  static int
  peripheral_device_page(struct mpage_info * mpi, const char * prefix)
  {
      static char *idents[] =
      {
          "X3.131: Small Computer System Interface",
          "X3.91M-1987: Storage Module Interface",
          "X3.170: Enhanced Small Device Interface",
          "X3.130-1986; X3T9.3/87-002: IPI-2",
          "X3.132-1987; X3.147-1988: IPI-3"
      };
      int status;
      unsigned ident;
      uint8_t *pagestart;
      char *name;
  
      status = setup_mode_page(mpi, 2, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("---------------------------------\n");
      };
  
  #if 0
      dump(pagestart, 20);
      pagestart[1] += 2;          /*TEST */
      cbuffer[8] += 2;             /*TEST */
  #endif
  
      ident = getnbyte(pagestart + 2, 2);
      if (ident < (sizeof(idents) / sizeof(char *)))
           name = idents[ident];
      else if (ident < 0x8000)
          name = "Reserved";
      else
          name = "Vendor Specific";
  
  #ifdef DPG_CHECK_THIS_OUT
      bdlen = pagestart[1] - 6;
      if (bdlen < 0)
          bdlen = 0;
      else {
          status = setup_mode_page(mpi, 2, cbuffer, &bdlen,
                                   &pagestart);
          if (status)
              return status;
      }
  
      hexfield(pagestart + 2, 2, "Interface Identifier");
      if (!x_interface) {
          for (ident = 0; ident < 35; ident++)
              putchar(' ');
          puts(name);
      }
      hexdatafield(pagestart + 8, bdlen, "Vendor Specific Data");
  #endif
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      if (x_interface)
          puts(name);
      return 0;
  }
  #endif
  
  static int
  common_power_condition(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 4, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("--------------------------------\n");
      }
      bitfield(pagestart + 3, "Idle", 1, 1);
      bitfield(pagestart + 3, "Standby", 1, 0);
      intfield(pagestart + 4, 4, "Idle Condition counter (100ms)");
      intfield(pagestart + 8, 4, "Standby Condition counter (100ms)");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  disk_xor_control(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 5, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("--------------------------------\n");
      }
      bitfield(pagestart + 2, "XORDS", 1, 1);
      intfield(pagestart + 4, 4, "Maximum XOR write size");
      intfield(pagestart + 12, 4, "Maximum regenerate size");
      intfield(pagestart + 16, 4, "Maximum rebuild transfer size");
      intfield(pagestart + 22, 2, "Rebuild delay");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  disk_background(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 4, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode subpage (0x%x,0x%x)\n", get_page_name(mpi), mpi->page,
                 mpi->subpage);
          printf("--------------------------------------------\n");
      }
      bitfield(pagestart + 4, "Enable background medium scan", 1, 0);
      bitfield(pagestart + 5, "Enable pre-scan", 1, 0);
      intfield(pagestart + 6, 2, "BMS interval time (hour)");
      intfield(pagestart + 8, 2, "Pre-scan timeout value (hour)");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  optical_memory(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 1, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("--------------------------------\n");
      }
      bitfield(pagestart + 2, "RUBR", 1, 0);
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  cdvd_write_param(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 20, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("--------------------------------\n");
      }
      bitfield(pagestart + 2, "BUFE", 1, 6);
      bitfield(pagestart + 2, "LS_V", 1, 5);
      bitfield(pagestart + 2, "Test Write", 1, 4);
      bitfield(pagestart + 2, "Write Type", 0xf, 0);
      bitfield(pagestart + 3, "MultiSession", 3, 6);
      bitfield(pagestart + 3, "FP", 1, 5);
      bitfield(pagestart + 3, "Copy", 1, 4);
      bitfield(pagestart + 3, "Track Mode", 0xf, 0);
      bitfield(pagestart + 4, "Data Block type", 0xf, 0);
      intfield(pagestart + 5, 1, "Link size");
      bitfield(pagestart + 7, "Initiator app. code", 0x3f, 0);
      intfield(pagestart + 8, 1, "Session Format");
      intfield(pagestart + 10, 4, "Packet size");
      intfield(pagestart + 14, 2, "Audio Pause Length");
      hexdatafield(pagestart + 16, 16, "Media Catalog number");
      hexdatafield(pagestart + 32, 16, "Int. standard recording code");
      hexdatafield(pagestart + 48, 1, "Subheader byte 1");
      hexdatafield(pagestart + 49, 1, "Subheader byte 2");
      hexdatafield(pagestart + 50, 1, "Subheader byte 3");
      hexdatafield(pagestart + 51, 1, "Subheader byte 4");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  cdvd_audio_control(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 10, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("--------------------------------\n");
      }
      bitfield(pagestart + 2, "IMMED", 1, 2);
      bitfield(pagestart + 2, "SOTC", 1, 1);
      bitfield(pagestart + 8, "CDDA out port 0, channel select", 0xf, 0);
      intfield(pagestart + 9, 1, "Channel port 0 volume");
      bitfield(pagestart + 10, "CDDA out port 1, channel select", 0xf, 0);
      intfield(pagestart + 11, 1, "Channel port 1 volume");
      bitfield(pagestart + 12, "CDDA out port 2, channel select", 0xf, 0);
      intfield(pagestart + 13, 1, "Channel port 2 volume");
      bitfield(pagestart + 14, "CDDA out port 3, channel select", 0xf, 0);
      intfield(pagestart + 15, 1, "Channel port 3 volume");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  cdvd_timeout(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 6, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("-----------------------------------\n");
      }
      bitfield(pagestart + 4, "G3Enable", 1, 3);
      bitfield(pagestart + 4, "TMOE", 1, 2);
      bitfield(pagestart + 4, "DISP", 1, 1);
      bitfield(pagestart + 4, "SWPP", 1, 0);
      intfield(pagestart + 6, 2, "Group 1 minimum time-out");
      intfield(pagestart + 8, 2, "Group 2 minimum time-out");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  cdvd_device_param(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 3, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("------------------------------------\n");
      }
      bitfield(pagestart + 3, "Inactivity timer multiplier", 0xf, 0);
      intfield(pagestart + 4, 2, "MSF-S units per MSF_M unit");
      intfield(pagestart + 6, 2, "MSF-F units per MSF_S unit");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  /* This is not a standard t10.org MMC mode page (it is now "protocol specific
     lu" mode page). This definition was found in Hitachi GF-2050/GF-2055
     DVD-RAM drive SCSI reference manual. */
  static int
  cdvd_feature(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 12, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("----------------------------------------------\n");
      }
      intfield(pagestart + 2, 2, "DVD feature set");
      intfield(pagestart + 4, 2, "CD audio");
      intfield(pagestart + 6, 2, "Embedded changer");
      intfield(pagestart + 8, 2, "Packet SMART");
      intfield(pagestart + 10, 2, "Persistent prevent(MESN)");
      intfield(pagestart + 12, 2, "Event status notification");
      intfield(pagestart + 14, 2, "Digital output");
      intfield(pagestart + 16, 2, "CD sequential recordable");
      intfield(pagestart + 18, 2, "DVD sequential recordable");
      intfield(pagestart + 20, 2, "Random recordable");
      intfield(pagestart + 22, 2, "Key management");
      intfield(pagestart + 24, 2, "Partial recorded CD media read");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  cdvd_mm_capab(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 49, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("----------------------------------------------------\n");
      }
      bitfield(pagestart + 2, "DVD-RAM read", 1, 5);
      bitfield(pagestart + 2, "DVD-R read", 1, 4);
      bitfield(pagestart + 2, "DVD-ROM read", 1, 3);
      bitfield(pagestart + 2, "Method 2", 1, 2);
      bitfield(pagestart + 2, "CD-RW read", 1, 1);
      bitfield(pagestart + 2, "CD-R read", 1, 0);
      bitfield(pagestart + 3, "DVD-RAM write", 1, 5);
      bitfield(pagestart + 3, "DVD-R write", 1, 4);
      bitfield(pagestart + 3, "DVD-ROM write", 1, 3);
      bitfield(pagestart + 3, "Test Write", 1, 2);
      bitfield(pagestart + 3, "CD-RW write", 1, 1);
      bitfield(pagestart + 3, "CD-R write", 1, 0);
      bitfield(pagestart + 4, "BUF", 1, 7);
      bitfield(pagestart + 4, "MultiSession", 1, 6);
      bitfield(pagestart + 4, "Mode 2 Form 2", 1, 5);
      bitfield(pagestart + 4, "Mode 2 Form 1", 1, 4);
      bitfield(pagestart + 4, "Digital port (2)", 1, 3);
      bitfield(pagestart + 4, "Digital port (1)", 1, 2);
      bitfield(pagestart + 4, "Composite", 1, 1);
      bitfield(pagestart + 4, "Audio play", 1, 0);
      bitfield(pagestart + 5, "Read bar code", 1, 7);
      bitfield(pagestart + 5, "UPC", 1, 6);
      bitfield(pagestart + 5, "ISRC", 1, 5);
      bitfield(pagestart + 5, "C2 pointers supported", 1, 4);
      bitfield(pagestart + 5, "R-W de-interleaved & corrected", 1, 3);
      bitfield(pagestart + 5, "R-W supported", 1, 2);
      bitfield(pagestart + 5, "CD-DA stream is accurate", 1, 1);
      bitfield(pagestart + 5, "CD-DA commands supported", 1, 0);
      bitfield(pagestart + 6, "Loading mechanism type", 7, 5);
      bitfield(pagestart + 6, "Eject (individual or magazine)", 1, 3);
      bitfield(pagestart + 6, "Prevent jumper", 1, 2);
      bitfield(pagestart + 6, "Lock state", 1, 1);
      bitfield(pagestart + 6, "Lock", 1, 0);
      bitfield(pagestart + 7, "R-W in lead-in", 1, 5);
      bitfield(pagestart + 7, "Side change capable", 1, 4);
      bitfield(pagestart + 7, "S/W slot selection", 1, 3);
      bitfield(pagestart + 7, "Changer supports disc present", 1, 2);
      bitfield(pagestart + 7, "Separate channel mute", 1, 1);
      bitfield(pagestart + 7, "Separate volume levels", 1, 0);
      intfield(pagestart + 10, 2, "number of volume level supported");
      intfield(pagestart + 12, 2, "Buffer size supported");
      bitfield(pagestart + 17, "Length", 3, 4);
      bitfield(pagestart + 17, "LSBF", 1, 3);
      bitfield(pagestart + 17, "RCK", 1, 2);
      bitfield(pagestart + 17, "BCKF", 1, 1);
      intfield(pagestart + 22, 2, "Copy management revision supported");
      bitfield(pagestart + 27, "Rotation control selected", 3, 0);
      intfield(pagestart + 28, 2, "Current write speed selected");
      intfield(pagestart + 30, 2, "# of lu speed performance tables");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  cdvd_cache(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 2, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("-----------------------\n");
      };
      bitfield(pagestart + 2, "Write Cache Enabled", 1, 2);
      bitfield(pagestart + 2, "Read Cache Disabled", 1, 0);
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  tape_data_compression(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 6, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("----------------------------------------------------\n");
      }
      bitfield(pagestart + 2, "DCE", 1, 7);
      bitfield(pagestart + 2, "DCC", 1, 6);
      bitfield(pagestart + 3, "DDE", 1, 7);
      bitfield(pagestart + 3, "RED", 3, 5);
      intfield(pagestart + 4, 4, "Compression algorithm");
      intfield(pagestart + 8, 4, "Decompression algorithm");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  tape_dev_config(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 25, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("----------------------------------------------------\n");
      }
      bitfield(pagestart + 2, "CAF", 1, 5);
      bitfield(pagestart + 2, "Active format", 0x1f, 0);
      intfield(pagestart + 3, 1, "Active partition");
      intfield(pagestart + 4, 1, "Write object cbuffer full ratio");
      intfield(pagestart + 5, 1, "Read object cbuffer full ratio");
      intfield(pagestart + 6, 2, "Wire delay time");
      bitfield(pagestart + 8, "OBR", 1, 7);
      bitfield(pagestart + 8, "LOIS", 1, 6);
      bitfield(pagestart + 8, "RSMK", 1, 5);
      bitfield(pagestart + 8, "AVC", 1, 4);
      bitfield(pagestart + 8, "SOCF", 3, 2);
      bitfield(pagestart + 8, "ROBO", 1, 1);
      bitfield(pagestart + 8, "REW", 1, 0);
      intfield(pagestart + 9, 1, "Gap size");
      bitfield(pagestart + 10, "EOD defined", 7, 5);
      bitfield(pagestart + 10, "EEG", 1, 4);
      bitfield(pagestart + 10, "SEW", 1, 3);
      bitfield(pagestart + 10, "SWP", 1, 2);
      bitfield(pagestart + 10, "BAML", 1, 1);
      bitfield(pagestart + 10, "BAM", 1, 0);
      intfield(pagestart + 11, 3, "Object cbuffer size at early warning");
      intfield(pagestart + 14, 1, "Select data compression algorithm");
      bitfield(pagestart + 15, "ASOCWP", 1, 2);
      bitfield(pagestart + 15, "PERSWO", 1, 1);
      bitfield(pagestart + 15, "PRMWP", 1, 0);
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  tape_medium_part1(struct mpage_info * mpi, const char * prefix)
  {
      int status, off, len;
      uint8_t *pagestart;
  
      /* variable length mode page, need to know its response length */
      status = get_mode_page(mpi, 0, cbuffer);
      if (status)
          return status;
      off = modePageOffset(cbuffer, mpi->resp_len, mode6byte);
      if (off < 0)
          return off;
      len = mpi->resp_len - off;
  
      status = setup_mode_page(mpi, 12 + ((len - 10) / 2), cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("----------------------------------------------------\n");
      }
      intfield(pagestart + 2, 1, "Maximum additional partitions");
      intfield(pagestart + 3, 1, "Additional partitions defined");
      bitfield(pagestart + 4, "FDP", 1, 7);
      bitfield(pagestart + 4, "SDP", 1, 6);
      bitfield(pagestart + 4, "IDP", 1, 5);
      bitfield(pagestart + 4, "PSUM", 3, 3);
      bitfield(pagestart + 4, "POFM", 1, 2);
      bitfield(pagestart + 4, "CLEAR", 1, 1);
      bitfield(pagestart + 4, "ADDP", 1, 0);
      intfield(pagestart + 5, 1, "Medium format recognition");
      bitfield(pagestart + 6, "Partition units", 0xf, 0);
      intfield(pagestart + 8, 2, "Partition size");
  
      for (off = 10; off < len; off += 2)
          intfield(pagestart + off, 2, "Partition size");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  tape_medium_part2_4(struct mpage_info * mpi, const char * prefix)
  {
      int status, off, len;
      uint8_t *pagestart;
  
      /* variable length mode page, need to know its response length */
      status = get_mode_page(mpi, 0, cbuffer);
      if (status)
          return status;
      off = modePageOffset(cbuffer, mpi->resp_len, mode6byte);
      if (off < 0)
          return off;
      len = mpi->resp_len - off;
  
      status = setup_mode_page(mpi, 1 + ((len - 4) / 2), cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("----------------------------------------------------\n");
      }
      intfield(pagestart + 2, 2, "Partition size");
  
      for (off = 4; off < len; off += 2)
          intfield(pagestart + off, 2, "Partition size");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  ses_services_manag(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 2, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", get_page_name(mpi), mpi->page);
          printf("----------------------------------------------------\n");
      }
      bitfield(pagestart + 5, "ENBLTC", 1, 0);
      intfield(pagestart + 6, 2, "Maximum time to completion (100 ms units)");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  fcp_proto_spec_lu(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 1, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", "Fibre Channel logical unit",
                 mpi->page);
          printf("----------------------------------------------------\n");
      }
      bitfield(pagestart + 3, "EPDC", 1, 0);
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  sas_proto_spec_lu(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 1, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", "SAS logical unit", mpi->page);
          printf("----------------------------------------------------\n");
      }
      bitfield(pagestart + 2, "Transport Layer Retries", 1, 4);
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  common_proto_spec_lu(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      int proto_id = 0;
  
      status = get_protocol_id(0, cbuffer, &proto_id, NULL);
      if (status)
          return status;
      if (0 == proto_id)
          return fcp_proto_spec_lu(mpi, prefix);
      else if (6 == proto_id)
          return sas_proto_spec_lu(mpi, prefix);
      else
          return DECODE_FAILED_TRY_HEX;
  }
  
  static int
  fcp_proto_spec_port(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 10, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", "Fibre Channel port control",
                 mpi->page);
          printf("----------------------------------------------------\n");
      }
      bitfield(pagestart + 3, "DTFD", 1, 7);
      bitfield(pagestart + 3, "PLPB", 1, 6);
      bitfield(pagestart + 3, "DDIS", 1, 5);
      bitfield(pagestart + 3, "DLM", 1, 4);
      bitfield(pagestart + 3, "RHA", 1, 3);
      bitfield(pagestart + 3, "ALWI", 1, 2);
      bitfield(pagestart + 3, "DTIPE", 1, 1);
      bitfield(pagestart + 3, "DTOLI", 1, 0);
      bitfield(pagestart + 6, "RR_TOV units", 7, 0);
      intfield(pagestart + 7, 1, "Resource recovery time-out");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  spi4_proto_spec_port(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 1, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", "SPI-4 port control", mpi->page);
          printf("-----------------------------------\n");
      }
      intfield(pagestart + 4, 2, "Synchronous transfer time-out");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  /* Protocol specific mode page for SAS, short format (subpage 0) */
  static int
  sas_proto_spec_port(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 3, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode page (0x%x)\n", "SAS SSP port control", mpi->page);
          printf("-------------------------------------\n");
      }
      bitfield(pagestart + 2, "Ready LED meaning", 0x1, 4);
      intfield(pagestart + 4, 2, "I_T Nexus Loss time");
      intfield(pagestart + 6, 2, "Initiator response time-out");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  common_proto_spec_port(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      int proto_id = 0;
  
      status = get_protocol_id(1, cbuffer, &proto_id, NULL);
      if (status)
          return status;
      if (0 == proto_id)
          return fcp_proto_spec_port(mpi, prefix);
      else if (1 == proto_id)
          return spi4_proto_spec_port(mpi, prefix);
      else if (6 == proto_id)
          return sas_proto_spec_port(mpi, prefix);
      else
          return DECODE_FAILED_TRY_HEX;
  }
  
  static int
  spi4_margin_control(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 5, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode subpage (0x%x,0x%x)\n", "SPI-4 Margin control",
                 mpi->page, mpi->subpage);
          printf("--------------------------------------------\n");
      }
      bitfield(pagestart + 5, "Protocol identifier", 0xf, 0);
      bitfield(pagestart + 7, "Driver Strength", 0xf, 4);
      bitfield(pagestart + 8, "Driver Asymmetry", 0xf, 4);
      bitfield(pagestart + 8, "Driver Precompensation", 0xf, 0);
      bitfield(pagestart + 9, "Driver Slew rate", 0xf, 4);
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  /* Protocol specific mode page for SAS, phy control + discover (subpage 1) */
  static int
  sas_phy_control_discover(struct mpage_info * mpi, const char * prefix)
  {
      int status, off, num_phys, k;
      uint8_t *pagestart;
      uint8_t *p;
  
     /* variable length mode page, need to know its response length */
      status = get_mode_page(mpi, 0, cbuffer);
      if (status)
          return status;
      off = modePageOffset(cbuffer, mpi->resp_len, mode6byte);
      if (off < 0)
          return off;
      num_phys = cbuffer[off + 7];
  
      status = setup_mode_page(mpi,  1 + (16 * num_phys), cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode subpage (0x%x,0x%x)\n", "SAS Phy Control and "
                 "Discover", mpi->page, mpi->subpage);
          printf("--------------------------------------------\n");
      }
      intfield(pagestart + 7, 1, "Number of phys");
      for (k = 0, p = pagestart + 8; k < num_phys; ++k, p += 48) {
          intfield(p + 1, 1, "Phy Identifier");
          bitfield(p + 4, "Attached Device type", 0x7, 4);
          bitfield(p + 5, "Negotiated Logical Link rate", 0xf, 0);
          bitfield(p + 6, "Attached SSP Initiator port", 0x1, 3);
          bitfield(p + 6, "Attached STP Initiator port", 0x1, 2);
          bitfield(p + 6, "Attached SMP Initiator port", 0x1, 1);
          bitfield(p + 7, "Attached SSP Target port", 0x1, 3);
          bitfield(p + 7, "Attached STP Target port", 0x1, 2);
          bitfield(p + 7, "Attached SMP Target port", 0x1, 1);
          hexdatafield(p + 8, 8, "SAS address");
          hexdatafield(p + 16, 8, "Attached SAS address");
          intfield(p + 24, 1, "Attached Phy identifier");
          bitfield(p + 32, "Programmed Min Physical Link rate", 0xf, 4);
          bitfield(p + 32, "Hardware Min Physical Link rate", 0xf, 0);
          bitfield(p + 33, "Programmed Max Physical Link rate", 0xf, 4);
          bitfield(p + 33, "Hardware Max Physical Link rate", 0xf, 0);
      }
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  
  static int
  common_proto_spec_port_sp1(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      int proto_id = 0;
  
      status = get_protocol_id(1, cbuffer, &proto_id, NULL);
      if (status)
          return status;
      if (1 == proto_id)
          return spi4_margin_control(mpi, prefix);
      else if (6 == proto_id)
          return sas_phy_control_discover(mpi, prefix);
      else
          return DECODE_FAILED_TRY_HEX;
  }
  
  static int
  spi4_training_config(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 27, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode subpage (0x%x,0x%x)\n", "training configuration",
                 mpi->page, mpi->subpage);
          printf("----------------------------------------------------------\n");
      }
      hexdatafield(pagestart + 10, 4, "DB(0) value");
      hexdatafield(pagestart + 14, 4, "DB(1) value");
      hexdatafield(pagestart + 18, 4, "DB(2) value");
      hexdatafield(pagestart + 22, 4, "DB(3) value");
      hexdatafield(pagestart + 26, 4, "DB(4) value");
      hexdatafield(pagestart + 30, 4, "DB(5) value");
      hexdatafield(pagestart + 34, 4, "DB(6) value");
      hexdatafield(pagestart + 38, 4, "DB(7) value");
      hexdatafield(pagestart + 42, 4, "DB(8) value");
      hexdatafield(pagestart + 46, 4, "DB(9) value");
      hexdatafield(pagestart + 50, 4, "DB(10) value");
      hexdatafield(pagestart + 54, 4, "DB(11) value");
      hexdatafield(pagestart + 58, 4, "DB(12) value");
      hexdatafield(pagestart + 62, 4, "DB(13) value");
      hexdatafield(pagestart + 66, 4, "DB(14) value");
      hexdatafield(pagestart + 70, 4, "DB(15) value");
      hexdatafield(pagestart + 74, 4, "P_CRCA value");
      hexdatafield(pagestart + 78, 4, "P1 value");
      hexdatafield(pagestart + 82, 4, "BSY value");
      hexdatafield(pagestart + 86, 4, "SEL value");
      hexdatafield(pagestart + 90, 4, "RST value");
      hexdatafield(pagestart + 94, 4, "REQ value");
      hexdatafield(pagestart + 98, 4, "ACK value");
      hexdatafield(pagestart + 102, 4, "ATN value");
      hexdatafield(pagestart + 106, 4, "C/D value");
      hexdatafield(pagestart + 110, 4, "I/O value");
      hexdatafield(pagestart + 114, 4, "MSG value");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  /* SAS(2) SSP, shared protocol specific port mode subpage (subpage 2) */
  static int
  sas_shared_spec_port(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 1, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode subpage (0x%x,0x%x)\n", "SAS SSP shared protocol "
                 "specific port", mpi->page, mpi->subpage);
          printf("-----------------------------------------------------\n");
      }
      intfield(pagestart + 6, 2, "Power loss timeout(ms)");
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  common_proto_spec_port_sp2(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      int proto_id = 0;
  
      status = get_protocol_id(1, cbuffer, &proto_id, NULL);
      if (status)
          return status;
      if (1 == proto_id)
          return spi4_training_config(mpi, prefix);
      else if (6 == proto_id)
          return sas_shared_spec_port(mpi, prefix);
      else
          return DECODE_FAILED_TRY_HEX;
  }
  
  static int
  spi4_negotiated(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 7, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode subpage (0x%x,0x%x)\n", get_page_name(mpi), mpi->page,
                 mpi->subpage);
          printf("--------------------------------------------\n");
      }
      intfield(pagestart + 6, 1, "Transfer period");
      intfield(pagestart + 8, 1, "REQ/ACK offset");
      intfield(pagestart + 9, 1, "Transfer width exponent");
      bitfield(pagestart + 10, "Protocol option bits", 0x7f, 0);
      bitfield(pagestart + 11, "Transceiver mode", 3, 2);
      bitfield(pagestart + 11, "Sent PCOMP_EN", 1, 1);
      bitfield(pagestart + 11, "Received PCOMP_EN", 1, 0);
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static int
  spi4_report_xfer(struct mpage_info * mpi, const char * prefix)
  {
      int status;
      uint8_t *pagestart;
  
      status = setup_mode_page(mpi, 4, cbuffer, &pagestart);
      if (status)
          return status;
  
      if (prefix[0])
          printf("%s", prefix);
      if (!x_interface && !replace) {
          printf("%s mode subpage (0x%x,0x%x)\n", get_page_name(mpi), mpi->page,
                 mpi->subpage);
          printf("--------------------------------------------\n");
      }
      intfield(pagestart + 6, 1, "Mimimum transfer period factor");
      intfield(pagestart + 8, 1, "Maximum REQ/ACK offset");
      intfield(pagestart + 9, 1, "Maximum transfer width exponent");
      bitfield(pagestart + 10, "Protocol option bits supported", 0xff, 0);
  
      if (x_interface && replace)
          return put_mode_page(mpi, cbuffer);
      else
          printf("\n");
      return 0;
  }
  
  static void
  print_hex_page(struct mpage_info * mpi, const char * prefix,
                 uint8_t *pagestart, int off, int len)
  {
      int k;
      const char * pg_name;
  
      if (prefix[0])
          printf("%s", prefix);
      if (! x_interface) {
          pg_name = get_page_name(mpi);
          if (mpi->subpage) {
              if (pg_name && (unkn_page_str != pg_name))
                  printf("mode page: 0x%02x  subpage: 0x%02x   [%s]\n",
                         mpi->page, mpi->subpage, pg_name);
              else
                  printf("mode page: 0x%02x  subpage: 0x%02x\n", mpi->page,
                     mpi->subpage);
              printf("------------------------------\n");
          } else {
              if (pg_name && (unkn_page_str != pg_name))
                  printf("mode page: 0x%02x   [%s]\n", mpi->page,
                         pg_name);
              else
                  printf("mode page: 0x%02x\n", mpi->page);
              printf("---------------\n");
          }
      }
      for (k = off; k < len; k++)
      {
          char nm[8];
  
          snprintf(nm, sizeof(nm), "0x%02x", k);
          hexdatafield(pagestart + k, 1, nm);
      }
      printf("\n");
  }
  
  static int
  do_user_page(struct mpage_info * mpi, int decode_in_hex)
  {
      int status = 0;
      int len, off, res, done;
      int offset = 0;
      uint8_t *pagestart;
      char prefix[96];
      struct mpage_info local_mp_i;
      struct mpage_name_func * mpf;
      int multiple = ((MP_LIST_PAGES == mpi->page) ||
                      (MP_LIST_SUBPAGES == mpi->subpage));
  
      if (replace && multiple) {
          printf("Can't list all (sub)pages and use replace (-R) together\n");
          return 1;
      }
      status = get_mode_page(mpi, 0, cbuffer2);
      if (status) {
          printf("\n");
          return status;
      } else {
          offset = modePageOffset(cbuffer2, mpi->resp_len, mode6byte);
          if (offset < 0) {
              fprintf(stdout, "mode page=0x%x has bad page format\n",
                      mpi->page);
              fprintf(stdout, "   perhaps '-z' switch may help\n");
              return -1;
          }
          pagestart = cbuffer2 + offset;
      }
  
      memset(&local_mp_i, 0, sizeof(local_mp_i));
      local_mp_i.page_control = mpi->page_control;
      local_mp_i.peri_type = mpi->peri_type;
      local_mp_i.inq_byte6 = mpi->inq_byte6;
      local_mp_i.resp_len = mpi->resp_len;
  
      do {
          local_mp_i.page = (pagestart[0] & 0x3f);
          local_mp_i.subpage = (pagestart[0] & 0x40) ? pagestart[1] : 0;
          if(0 == local_mp_i.page) { /* page==0 vendor (unknown) format */
              off = 0;
              len = mpi->resp_len - offset;  /* should be last listed page */
          } else if (local_mp_i.subpage) {
              off = 4;
              len = (pagestart[2] << 8) + pagestart[3] + 4;
          } else {
              off = 2;
              len = pagestart[1] + 2;
          }
  
          prefix[0] = '\0';
          done = 0;
          if ((! decode_in_hex) && ((mpf = get_mpage_name_func(&local_mp_i))) &&
              mpf->func) {
              if (multiple && x_interface && !replace) {
                  if (local_mp_i.subpage)
                      snprintf(prefix, sizeof(prefix), "sginfo -t 0x%x,0x%x"
                               " -XR %s ", local_mp_i.page, local_mp_i.subpage,
                               device_name);
                  else
                      snprintf(prefix, sizeof(prefix), "sginfo -t 0x%x -XR %s ",
                               local_mp_i.page, device_name);
              }
              res = mpf->func(&local_mp_i, prefix);
              if (DECODE_FAILED_TRY_HEX != res) {
                  done = 1;
                  status |= res;
              }
          }
          if (! done) {
              if (x_interface && replace)
                  return put_mode_page(&local_mp_i, cbuffer2);
              else {
                  if (multiple && x_interface && !replace) {
                      if (local_mp_i.subpage)
                          snprintf(prefix, sizeof(prefix), "sginfo -u 0x%x,0x%x"
                                   " -XR %s ", local_mp_i.page,
                                   local_mp_i.subpage, device_name);
                      else
                          snprintf(prefix, sizeof(prefix), "sginfo -u 0x%x -XR "
                                   "%s ", local_mp_i.page, device_name);
                  }
                  print_hex_page(&local_mp_i, prefix, pagestart, off, len);
              }
          }
          offset += len;
          pagestart = cbuffer2 + offset;
      } while (multiple && (offset < mpi->resp_len));
      return status;
  }
  
  static void
  inqfieldname(uint8_t *deststr, const uint8_t *srcbuf, int maxlen)
  {
          int i;
  
          memset(deststr, '\0', MAX_INQFIELD_LEN);
          for (i = maxlen - 1; i >= 0 && isspace(srcbuf[i]); --i)
                  ;
          memcpy(deststr, srcbuf, i + 1);
  }
  
  static int
  do_inquiry(int * peri_type, int * resp_byte6, int inquiry_verbosity)
  {
      int status;
      uint8_t cmd[6];
      uint8_t fieldname[MAX_INQFIELD_LEN];
      uint8_t *pagestart;
      struct scsi_cmnd_io sci;
  
      memset(cbuffer, 0, INQUIRY_RESP_INITIAL_LEN);
      cbuffer[0] = 0x7f;
  
      cmd[0] = 0x12;              /* INQUIRY */
      cmd[1] = 0x00;              /* evpd=0 */
      cmd[2] = 0x00;              /* page code = 0 */
      cmd[3] = 0x00;              /* (reserved) */
      cmd[4] = INQUIRY_RESP_INITIAL_LEN;      /* allocation length */
      cmd[5] = 0x00;              /* control */
  
      sci.cmnd = cmd;
      sci.cmnd_len = sizeof(cmd);
      sci.dxfer_dir = DXFER_FROM_DEVICE;
      sci.dxfer_len = INQUIRY_RESP_INITIAL_LEN;
      sci.dxferp = cbuffer;
      status = do_scsi_io(&sci);
      if (status) {
          printf("Error doing INQUIRY (1)\n");
          return status;
      }
      if (trace_cmd > 1) {
          printf("  inquiry response:\n");
          dump(cbuffer, INQUIRY_RESP_INITIAL_LEN);
      }
      pagestart = cbuffer;
      if (peri_type)
          *peri_type = pagestart[0] & 0x1f;
      if (resp_byte6)
          *resp_byte6 = pagestart[6];
      if (0 == inquiry_verbosity)
          return 0;
      if ((pagestart[4] + 5) < INQUIRY_RESP_INITIAL_LEN) {
          printf("INQUIRY response too short: expected 36 bytes, got %d\n",
                 pagestart[4] + 5);
          return -EINVAL;
      }
  
      if (!x_interface && !replace) {
          printf("INQUIRY response (cmd: 0x12)\n");
          printf("----------------------------\n");
      };
      bitfield(pagestart + 0, "Device Type", 0x1f, 0);
      if (2 == inquiry_verbosity) {
          bitfield(pagestart + 0, "Peripheral Qualifier", 0x7, 5);
          bitfield(pagestart + 1, "Removable", 1, 7);
          bitfield(pagestart + 2, "Version", 0xff, 0);
          bitfield(pagestart + 3, "NormACA", 1, 5);
          bitfield(pagestart + 3, "HiSup", 1, 4);
          bitfield(pagestart + 3, "Response Data Format", 0xf, 0);
          bitfield(pagestart + 5, "SCCS", 1, 7);
          bitfield(pagestart + 5, "ACC", 1, 6);
          bitfield(pagestart + 5, "ALUA", 3, 4);
          bitfield(pagestart + 5, "3PC", 1, 3);
          bitfield(pagestart + 5, "Protect", 1, 0);
          bitfield(pagestart + 6, "BQue", 1, 7);
          bitfield(pagestart + 6, "EncServ", 1, 6);
          bitfield(pagestart + 6, "MultiP", 1, 4);
          bitfield(pagestart + 6, "MChngr", 1, 3);
          bitfield(pagestart + 6, "Addr16", 1, 0);
          bitfield(pagestart + 7, "Relative Address", 1, 7);
          bitfield(pagestart + 7, "Wide bus 16", 1, 5);
          bitfield(pagestart + 7, "Synchronous neg.", 1, 4);
          bitfield(pagestart + 7, "Linked Commands", 1, 3);
          bitfield(pagestart + 7, "Command Queueing", 1, 1);
      }
      if (x_interface)
          printf("\n");
  
      inqfieldname(fieldname, pagestart + 8, 8);
      printf("%s%s\n", (!x_interface ? "Vendor:                    " : ""),
             fieldname);
  
      inqfieldname(fieldname, pagestart + 16, 16);
      printf("%s%s\n", (!x_interface ? "Product:                   " : ""),
             fieldname);
  
      inqfieldname(fieldname, pagestart + 32, 4);
      printf("%s%s\n", (!x_interface ? "Revision level:            " : ""),
             fieldname);
  
      printf("\n");
      return status;
  
  }
  
  static int
  do_serial_number(void)
  {
      int status, pagelen;
      uint8_t cmd[6];
      uint8_t *pagestart;
      struct scsi_cmnd_io sci;
      const uint8_t serial_vpd = 0x80;
      const uint8_t supported_vpd = 0x0;
  
      /* check supported VPD pages + unit serial number well formed */
      cmd[0] = 0x12;              /* INQUIRY */
      cmd[1] = 0x01;              /* evpd=1 */
      cmd[2] = supported_vpd;
      cmd[3] = 0x00;              /* (reserved) */
      cmd[4] = 0x04;              /* allocation length */
      cmd[5] = 0x00;              /* control */
  
      sci.cmnd = cmd;
      sci.cmnd_len = sizeof(cmd);
      sci.dxfer_dir = DXFER_FROM_DEVICE;
      sci.dxfer_len = 4;
      sci.dxferp = cbuffer;
      status = do_scsi_io(&sci);
      if (status) {
          printf("No serial number (error doing INQUIRY, supported VPDs)\n\n");
          return status;
      }
      if (! ((supported_vpd == cbuffer[1]) && (0 == cbuffer[2]))) {
          printf("No serial number (bad format for supported VPDs)\n\n");
          return -1;
      }
  
      cmd[0] = 0x12;              /* INQUIRY */
      cmd[1] = 0x01;              /* evpd=1 */
      cmd[2] = serial_vpd;
      cmd[3] = 0x00;              /* (reserved) */
      cmd[4] = 0x04;              /* allocation length */
      cmd[5] = 0x00;              /* control */
  
      sci.cmnd = cmd;
      sci.cmnd_len = sizeof(cmd);
      sci.dxfer_dir = DXFER_FROM_DEVICE;
      sci.dxfer_len = 4;
      sci.dxferp = cbuffer;
      status = do_scsi_io(&sci);
      if (status) {
          printf("No serial number (error doing INQUIRY, serial number)\n\n");
          return status;
      }
      if (! ((serial_vpd == cbuffer[1]) && (0 == cbuffer[2]))) {
          printf("No serial number (bad format for serial number)\n\n");
          return -1;
      }
  
      pagestart = cbuffer;
  
      pagelen = 4 + pagestart[3];
  
      cmd[0] = 0x12;              /* INQUIRY */
      cmd[1] = 0x01;              /* evpd=1 */
      cmd[2] = serial_vpd;
      cmd[3] = 0x00;              /* (reserved) */
      cmd[4] = (uint8_t)pagelen; /* allocation length */
      cmd[5] = 0x00;              /* control */
  
      sci.cmnd = cmd;
      sci.cmnd_len = sizeof(cmd);
      sci.dxfer_dir = DXFER_FROM_DEVICE;
      sci.dxfer_len = pagelen;
      sci.dxferp = cbuffer;
      status = do_scsi_io(&sci);
      if (status) {
          printf("No serial number (error doing INQUIRY, serial number)\n\n");
          return status;
      }
      if (trace_cmd > 1) {
          printf("  inquiry (vpd page 0x80) response:\n");
          dump(cbuffer, pagelen);
      }
  
      pagestart[pagestart[3] + 4] = '\0';
      printf("Serial Number '%s'\n\n", pagestart + 4);
      return status;
  }
  
  
  typedef struct sg_map {
      int bus;
      int channel;
      int target_id;
      int lun;
      char * dev_name;
  } Sg_map;
  
  typedef struct my_scsi_idlun
  {
      int mux4;
      int host_unique_id;
  
  } My_scsi_idlun;
  
  #define MDEV_NAME_SZ 256
  
  static void
  make_dev_name(char * fname, int k, int do_numeric)
  {
      char buff[MDEV_NAME_SZ];
      size_t len;
  
      strncpy(fname, "/dev/sg", MDEV_NAME_SZ);
      fname[MDEV_NAME_SZ - 1] = '\0';
      len = strlen(fname);
      if (do_numeric)
          snprintf(fname + len, MDEV_NAME_SZ - len, "%d", k);
      else {
          if (k <= 26) {
              buff[0] = 'a' + (char)k;
              buff[1] = '\0';
              strcat(fname, buff);
          }
          else
              strcat(fname, "xxxx");
      }
  }
  
  
  static Sg_map sg_map_arr[MAX_SG_DEVS + 1];
  
  #define MAX_HOLES 4
  
  /* Print out a list of the known devices on the system */
  static void
  show_devices(int raw)
  {
      int k, j, fd, err, bus, n;
      My_scsi_idlun m_idlun;
      char name[MDEV_NAME_SZ];
      char dev_name[MDEV_NAME_SZ + 6];
      char ebuff[EBUFF_SZ];
      int do_numeric = 1;
      int max_holes = MAX_HOLES;
      DIR *dir_ptr;
      struct dirent *entry;
      char *tmpptr;
  
      dir_ptr=opendir("/dev");
      if ( dir_ptr == NULL ) {
          perror("/dev");
          exit(1);
      }
  
      j=0;
      while ( (entry=readdir(dir_ptr)) != NULL ) {
          switch(entry->d_type) {
          case DT_LNK:
          case DT_CHR:
          case DT_BLK:
                  break;
          default:
                  continue;
          }
  
          switch(entry->d_name[0]) {
          case 's':
          case 'n':
                  break;
          default:
                  continue;
          }
  
          if ( strncmp("sg",entry->d_name,2) == 0 ) {
                  continue;
          }
          if ( strncmp("sd",entry->d_name,2) == 0 ) {
              continue;
          }
          if ( isdigit(entry->d_name[strlen(entry->d_name)-1]) ) {
              continue;
          }
          if ( strncmp("snapshot",entry->d_name,8) == 0 ) {
                  continue;
          }
  
          snprintf(dev_name, sizeof(dev_name),"/dev/%s",entry->d_name);
  
          fd = open(dev_name, O_RDONLY | O_NONBLOCK);
          if (fd < 0)
              continue;
          err = ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &(sg_map_arr[j].bus));
          if (err < 0) {
  #if 0
              snprintf(ebuff, EBUFF_SZ,
                       "SCSI(1) ioctl on %s failed", dev_name);
              perror(ebuff);
  #endif
              close(fd);
              continue;
          }
          err = ioctl(fd, SCSI_IOCTL_GET_IDLUN, &m_idlun);
          if (err < 0) {
              snprintf(ebuff, EBUFF_SZ,
                       "SCSI(2) ioctl on %s failed", dev_name);
              perror(ebuff);
              close(fd);
              continue;
          }
          sg_map_arr[j].channel = (m_idlun.mux4 >> 16) & 0xff;
          sg_map_arr[j].lun = (m_idlun.mux4 >> 8) & 0xff;
          sg_map_arr[j].target_id = m_idlun.mux4 & 0xff;
          n = strlen(dev_name);
          /* memory leak ... no free()s for this malloc() */
          tmpptr = (char *)malloc(n + 1);
          snprintf(tmpptr, n + 1, "%.*s", n, dev_name);
          /* strncpy(tmpptr,dev_name,strlen(dev_name)+1); */
  
          sg_map_arr[j].dev_name = tmpptr;
  #if 0
          printf("[scsi%d ch=%d id=%d lun=%d %s] ", sg_map_arr[j].bus,
          sg_map_arr[j].channel, sg_map_arr[j].target_id, sg_map_arr[j].lun,
          sg_map_arr[j].dev_name);
  #endif
          printf("%s ", dev_name);
          close(fd);
          if (++j >= MAX_SG_DEVS)
              break;
      }
      closedir(dir_ptr);
  
      printf("\n"); /* <<<<<<<<<<<<<<<<<<<<< */
      for (k = 0; k < MAX_SG_DEVS; k++) {
          if ( raw ) {
                  sprintf(name,"/dev/raw/raw%d",k);
                  fd = open(name, O_RDWR | O_NONBLOCK);
                  if (fd < 0) {
                          continue;
                  }
          }
          else {
                  make_dev_name(name, k, do_numeric);
                  fd = open(name, O_RDWR | O_NONBLOCK);
          if (fd < 0) {
              if ((ENOENT == errno) && (0 == k)) {
                  do_numeric = 0;
                  make_dev_name(name, k, do_numeric);
                  fd = open(name, O_RDWR | O_NONBLOCK);
              }
              if (fd < 0) {
                  if (EBUSY == errno)
                      continue;   /* step over if O_EXCL already on it */
                  else {
  #if 0
                      snprintf(ebuff, EBUFF_SZ,
                               "open on %s failed (%d)", name, errno);
                      perror(ebuff);
  #endif
                      if (max_holes-- > 0)
                          continue;
                      else
                          break;
                  }
              }
          }
          }
          max_holes = MAX_HOLES;
          err = ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &bus);
          if (err < 0) {
              if ( ! raw ) {
                  snprintf(ebuff, EBUFF_SZ, "SCSI(3) ioctl on %s failed", name);
                  perror(ebuff);
              }
              close(fd);
              continue;
          }
          err = ioctl(fd, SCSI_IOCTL_GET_IDLUN, &m_idlun);
          if (err < 0) {
              if ( ! raw ) {
                  snprintf(ebuff, EBUFF_SZ, "SCSI(3) ioctl on %s failed", name);
                  perror(ebuff);
              }
              close(fd);
              continue;
          }
  #if 0
          printf("[scsi%d ch=%d id=%d lun=%d %s]", bus,
                 (m_idlun.mux4 >> 16) & 0xff, m_idlun.mux4 & 0xff,
                 (m_idlun.mux4 >> 8) & 0xff, name);
  #endif
          for (j = 0; sg_map_arr[j].dev_name; ++j) {
              if ((bus == sg_map_arr[j].bus) &&
                  ((m_idlun.mux4 & 0xff) == sg_map_arr[j].target_id) &&
                  (((m_idlun.mux4 >> 16) & 0xff) == sg_map_arr[j].channel) &&
                  (((m_idlun.mux4 >> 8) & 0xff) == sg_map_arr[j].lun)) {
                  printf("%s [=%s  scsi%d ch=%d id=%d lun=%d]\n", name,
                         sg_map_arr[j].dev_name, bus,
                         ((m_idlun.mux4 >> 16) & 0xff), m_idlun.mux4 & 0xff,
                         ((m_idlun.mux4 >> 8) & 0xff));
                  break;
              }
          }
          if (NULL == sg_map_arr[j].dev_name)
              printf("%s [scsi%d ch=%d id=%d lun=%d]\n", name, bus,
                     ((m_idlun.mux4 >> 16) & 0xff), m_idlun.mux4 & 0xff,
                     ((m_idlun.mux4 >> 8) & 0xff));
          close(fd);
      }
      printf("\n");
  }
  
  #define DEVNAME_SZ 256
  
  static int
  open_sg_io_dev(char * devname)
  {
      int fd, fdrw, err, bus, bbus, k, v;
      My_scsi_idlun m_idlun, mm_idlun;
      int do_numeric = 1;
      char name[DEVNAME_SZ];
      struct stat a_st;
      int block_dev = 0;
  
      strncpy(name, devname, DEVNAME_SZ);
      name[DEVNAME_SZ - 1] = '\0';
      fd = open(name, O_RDONLY | O_NONBLOCK);
      if (fd < 0)
          return fd;
      if ((ioctl(fd, SG_GET_VERSION_NUM, &v) >= 0) && (v >= 30000)) {
          fdrw = open(name, O_RDWR | O_NONBLOCK);
          if (fdrw >= 0) {
              close(fd);
              return fdrw;
          }
          return fd;
      }
      if (fstat(fd, &a_st) < 0) {
          fprintf(stderr, "could do fstat() on fd ??\n");
          close(fd);
          return -9999;
      }
      if (S_ISBLK(a_st.st_mode))
          block_dev = 1;
  
      if (block_dev || (ioctl(fd, SG_GET_TIMEOUT, 0) < 0)) {
          err = ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &bus);
          if (err < 0) {
              fprintf(stderr, "A device name that understands SCSI commands "
                      "is required\n");
              close(fd);
              return -9999;
          }
          err = ioctl(fd, SCSI_IOCTL_GET_IDLUN, &m_idlun);
          if (err < 0) {
              fprintf(stderr, "A SCSI device name is required(2)\n");
              close(fd);
              return -9999;
          }
          close(fd);
  
          for (k = 0; k < MAX_SG_DEVS; k++) {
              make_dev_name(name, k, do_numeric);
              fd = open(name, O_RDWR | O_NONBLOCK);
              if (fd < 0) {
                  if ((ENOENT == errno) && (0 == k)) {
                      do_numeric = 0;
                      make_dev_name(name, k, do_numeric);
                      fd = open(name, O_RDWR | O_NONBLOCK);
                  }
                  if (fd < 0) {
                      if (EBUSY == errno)
                          continue;   /* step over if O_EXCL already on it */
                      else
                          break;
                  }
              }
              err = ioctl(fd, SCSI_IOCTL_GET_BUS_NUMBER, &bbus);
              if (err < 0) {
                  perror("sg ioctl failed");
                  close(fd);
                  fd = -9999;
              }
              err = ioctl(fd, SCSI_IOCTL_GET_IDLUN, &mm_idlun);
              if (err < 0) {
                  perror("sg ioctl failed");
                  close(fd);
                  fd = -9999;
              }
              if ((bus == bbus) &&
                  ((m_idlun.mux4 & 0xff) == (mm_idlun.mux4 & 0xff)) &&
                  (((m_idlun.mux4 >> 8) & 0xff) ==
                                          ((mm_idlun.mux4 >> 8) & 0xff)) &&
                  (((m_idlun.mux4 >> 16) & 0xff) ==
                                          ((mm_idlun.mux4 >> 16) & 0xff)))
                  break;
              else {
                  close(fd);
                  fd = -9999;
              }
          }
      }
      if (fd >= 0) {
          if ((ioctl(fd, SG_GET_VERSION_NUM, &v) < 0) || (v < 30000)) {
              fprintf(stderr, "requires lk 2.4 (sg driver), lk 2.6 or lk 3 "
                      "series\n");
              close(fd);
              return -9999;
          }
          close(fd);
          return open(name, O_RDWR | O_NONBLOCK);
      }
      else
          return fd;
  }
  
  static void
  usage(const char *errtext)
  {
      if (errtext)
          fprintf(stderr, "Error: sginfo: %s\n", errtext);
      fprintf(stderr, "Usage: sginfo [-options] [device] "
              "[replacement_values]\n");
      fputs("\tAllowed options are:\n"
            "\t-6    Do 6 byte mode sense and select commands (def: 10 "
            "bytes).\n"
            "\t-a    Display inquiry info, serial # and all mode pages.\n"
            "\t-A    Similar to '-a' but displays all subpages as well.\n"
            "\t-c    Access Caching Page.\n"
            "\t-C    Access Control Mode Page.\n"
            "\t-d    Display defect lists (default format: index).\n"
            "\t-D    Access Disconnect-Reconnect Page.\n"
            "\t-e    Access Read-Write Error Recovery page.\n"
            "\t-E    Access Control Extension page.\n"
            "\t-f    Access Format Device Page.\n"
            "\t-Farg Format of the defect list:\n"
            "\t\t-Flogical  - logical block addresses (32 bit)\n"
            "\t\t-Flba64    - logical block addresses (64 bit)\n"
            "\t\t-Fphysical - physical blocks\n"
            "\t\t-Findex    - defect bytes from index\n"
            "\t\t-Fhead     - sort by head\n", stdout);
      fputs("\t-g    Access Rigid Disk Drive Geometry Page.\n"
            "\t-G    Display 'grown' defect list (default format: index).\n"
            "\t-i    Display information from INQUIRY command.\n"
            "\t-I    Access Informational Exception page.\n"
            "\t-l    List known scsi devices on the system [DEPRECATED]\n"
            "\t-n    Access Notch and Partition Page.\n"
            "\t-N    Negate (stop) storing to saved page (active with -R).\n"
            "\t-P    Access Power Condition Page.\n"
            "\t-r    List known raw scsi devices on the system\n"
            "\t-s    Display serial number (from INQUIRY VPD page).\n"
            "\t-t<pn[,sp]> Access mode page <pn> [subpage <sp>] and decode.\n"
            "\t-T    Trace commands (for debugging, double for more)\n"
            "\t-u<pn[,sp]> Access mode page <pn> [subpage <sp>], output in hex\n"
            "\t-v    Show version number\n"
            "\t-V    Access Verify Error Recovery Page.\n"
            "\t-z    single fetch mode pages (rather than double fetch)\n"
            "\n", stdout);
      fputs("\tOnly one of the following three options can be specified.\n"
     "\tNone of these three implies the current values are returned.\n", stdout);
      fputs("\t-m    Access modifiable fields instead of current values\n"
            "\t-M    Access manufacturer defaults instead of current values\n"
            "\t-S    Access saved defaults instead of current values\n\n"
            "\t-X    Use list (space separated values) rather than table.\n"
      "\t-R    Replace parameters - best used with -X (expert use only)\n"
      "\t      [replacement parameters placed after device on command line]\n\n",
      stdout);
      printf("\t      sginfo version: %s; See man page for more details.\n",
             version_str);
      exit(2);
  }
  
  int main(int argc, char *argv[])
  {
      int k, j, n;
      unsigned int unum, unum2;
      int decode_in_hex = 0;
      char c;
      char * cp;
      int status = 0;
      long tmp;
      struct mpage_info mp_i;
      int inquiry_verbosity = 0;
      int show_devs = 0, show_raw = 0;
      int found = 0;
  
      if (argc < 2)
          usage(NULL);
      memset(&mp_i, 0, sizeof(mp_i));
      while ((k = getopt(argc, argv, "6aAcCdDeEfgGiIlmMnNPrRsSTvVXzF:t:u:")) !=
             EOF) {
          c = (char)k;
          switch (c) {
          case '6':
              mode6byte = 1;
              break;
          case 'a':
              inquiry_verbosity = 1;
              serial_number = 1;
              mp_i.page = MP_LIST_PAGES;
              break;
          case 'A':
              inquiry_verbosity = 1;
              serial_number = 1;
              mp_i.page = MP_LIST_PAGES;
              mp_i.subpage = MP_LIST_SUBPAGES;
              break;
          case 'c':
              mp_i.page = 0x8;
              break;
          case 'C':
              mp_i.page = 0xa;
              break;
          case 'd':
              defect = 1;
              break;
          case 'D':
              mp_i.page = 0x2;
              break;
          case 'e':
              mp_i.page = 0x1;
              break;
          case 'E':
              mp_i.page = 0xa;
              mp_i.subpage = 0x1;
              break;
          case 'f':
              mp_i.page = 0x3;
              break;
          case 'F':
              if (!strcasecmp(optarg, "logical"))
                  defectformat = 0x0;
              else if (!strcasecmp(optarg, "lba64"))
                  defectformat = 0x3;
              else if (!strcasecmp(optarg, "physical"))
                  defectformat = 0x5;
              else if (!strcasecmp(optarg, "index"))
                  defectformat = 0x4;
              else if (!strcasecmp(optarg, "head"))
                  defectformat = HEAD_SORT_TOKEN;
              else
                  usage("Illegal -F parameter, must be one of logical, "
                        "physical, index or head");
              break;
          case 'g':
              mp_i.page = 0x4;
              break;
          case 'G':
              grown_defect = 1;
              break;
          case 'i':       /* just vendor, product and revision for '-i -i' */
              inquiry_verbosity = (2 == inquiry_verbosity) ? 1 : 2;
              break;
          case 'I':
              mp_i.page = 0x1c;
              break;
          case 'l':
              show_devs = 1;
              break;
          case 'm': /* modifiable page control */
              if (0 == mp_i.page_control)
                  mp_i.page_control = 1;
              else
                  usage("can only have one of 'm', 'M' and 'S'");
              break;
          case 'M': /* manufacturer's==default page control */
              if (0 == mp_i.page_control)
                  mp_i.page_control = 2;
              else
                  usage("can only have one of 'M', 'm' and 'S'");
              break;
          case 'n':
              mp_i.page = 0xc;
              break;
          case 'N':
              negate_sp_bit = 1;
              break;
          case 'P':
              mp_i.page = 0x1a;
              break;
          case 'r':
              show_raw = 1;
              break;
          case 'R':
              replace = 1;
              break;
          case 's':
              serial_number = 1;
              break;
          case 'S': /* saved page control */
              if (0 == mp_i.page_control)
                  mp_i.page_control = 3;
              else
                  usage("can only have one of 'S', 'm' and 'M'");
              break;
          case 'T':
              trace_cmd++;
              break;
          case 't':
          case 'u':
              if ('u' == c)
                  decode_in_hex = 1;
              while (' ' == *optarg)
                  optarg++;
              if ('0' == *optarg) {
                  unum = 0;
                  unum2 = 0;
                  j = sscanf(optarg, "0x%x,0x%x", &unum, &unum2);
                  mp_i.page = unum;
                  if (1 == j) {
                      cp = strchr(optarg, ',');
                      if (cp && (1 == sscanf(cp, ",%d", &mp_i.subpage)))
                          j = 2;
                  } else
                      mp_i.subpage = unum2;
              } else
                  j = sscanf(optarg, "%d,%d", &mp_i.page, &mp_i.subpage);
              if (1 == j)
                  mp_i.subpage = 0;
              else if (j < 1)
                  usage("argument following '-u' should be of form "
                        "<pg>[,<subpg>]");
              if ((mp_i.page < 0) || (mp_i.page > MP_LIST_PAGES) ||
                  (mp_i.subpage < 0) || (mp_i.subpage > MP_LIST_SUBPAGES))
                  usage("mode pages range from 0 .. 63, subpages from "
                        "1 .. 255");
              found = 1;
              break;
          case 'v':
              fprintf(stdout, "sginfo version: %s\n", version_str);
              return 0;
          case 'V':
              mp_i.page = 0x7;
              break;
          case 'X':
              x_interface = 1;
              break;
          case 'z':
              single_fetch = 1;
              break;
          case '?':
              usage("Unknown option");
              break;
          default:
              fprintf(stdout, "Unknown option '-%c' (ascii 0x%02x)\n", c, c);
              usage("bad option");
          }
      }
  
      if (replace && !x_interface)
          usage("-R requires -X");
      if (replace && mp_i.page_control)
          usage("-R not allowed for -m, -M or -S");
      if (x_interface && replace && ((MP_LIST_PAGES == mp_i.page) ||
                          (MP_LIST_SUBPAGES == mp_i.subpage)))
          usage("-XR can be used only with exactly one page.");
  
      if (replace && (3 != mp_i.page_control)) {
          memset (is_hex, 0, 32);
          for (j = 1; j < argc - optind; j++) {
              if (strncmp(argv[optind + j], "0x", 2) == 0) {
                  char *pnt = argv[optind + j] + 2;
                  replacement_values[j] = 0;
          /* This is a kluge, but we can handle 64 bit quantities this way. */
                  while (*pnt) {
                      if (*pnt >= 'a' && *pnt <= 'f')
                          *pnt -= 32;
                      replacement_values[j] = (replacement_values[j] << 4) |
                          (*pnt > '9' ? (*pnt - 'A' + 10) : (*pnt - '0'));
                      pnt++;
                  }
                  continue;
              }
              if (argv[optind + j][0] == '@') {
          /*Ensure that this string contains an even number of hex-digits */
                  int len = strlen(argv[optind + j] + 1);
  
                  if ((len & 1) || (len != (int)strspn(argv[optind + j] + 1,
                                                  "0123456789ABCDEFabcdef")))
                              usage("Odd number of chars or non-hex digit in "
                                    "@hexdatafield");
  
                  replacement_values[j] = (unsigned long) argv[optind + j];
                  is_hex[j] = 1;
                  continue;
              }
              /* Using a tmp here is silly but the most clean approach */
              n = sscanf(argv[optind + j], "%ld", &tmp);
              replacement_values[j] = ((1 == n) ? tmp : 0);
          }
          n_replacement_values = argc - optind - 1;
      }
      if (show_devs) {
          show_devices(0);
          exit(0);
      }
      if (show_raw) {
          show_devices(1);
          exit(0);
      }
      if (optind >= argc)
          usage("no device name given");
      glob_fd = open_sg_io_dev(device_name = argv[optind]);
      if (glob_fd < 0) {
          if (-9999 == glob_fd)
              fprintf(stderr, "Couldn't find sg device corresponding to %s\n",
                      device_name);
          else {
              perror("sginfo(open)");
              fprintf(stderr, "file=%s, or no corresponding sg device found\n",
                      device_name);
              fprintf(stderr, "Is sg driver loaded?\n");
          }
          exit(1);
      }
  
  #if 0
      if (!x_interface)
          printf("\n");
  #endif
      if (! (found || mp_i.page || mp_i.subpage || inquiry_verbosity ||
             serial_number)) {
          if (trace_cmd > 0)
              fprintf(stdout, "nothing selected so do a short INQUIRY\n");
          inquiry_verbosity = 1;
      }
  
      status |= do_inquiry(&mp_i.peri_type, &mp_i.inq_byte6,
                           inquiry_verbosity);
      if (serial_number)
          do_serial_number();     /* ignore error */
      if (mp_i.page > 0)
          status |= do_user_page(&mp_i, decode_in_hex);
      if (defect)
          status |= read_defect_list(0);
      if (grown_defect)
          status |= read_defect_list(1);
  
      return status ? 1 : 0;
  }
  