blob: 51951937aa555df9a297890d06f2abf3bd32f240 [file] [log] [blame]
/* N.B. There are two programs in this file, the first is for linux
* and the second is for Windows.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef SG3_UTILS_LINUX
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <dirent.h>
#include <libgen.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <scsi/scsi_ioctl.h>
#include "sg_lib.h"
#include "sg_io_linux.h"
/* Test code for D. Gilbert's extensions to the Linux OS SCSI generic ("sg")
device driver.
* Copyright (C) 1999 - 2007 D. Gilbert
* 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 scans the "sg" device space (ie actual + simulated SCSI
generic devices). Optionally sg_scan can be given other device names
to scan (in place of the sg devices).
Options: -a alpha scan: scan /dev/sga,b,c, ....
-i do SCSI inquiry on device (implies -w)
-n numeric scan: scan /dev/sg0,1,2, ....
-V output version string and exit
-w open writable (new driver opens readable unless -i)
-x extra information output
By default this program will look for /dev/sg0 first (i.e. numeric scan)
Note: This program is written to work under both the original and
the new sg driver.
F. Jansen - modification to extend beyond 26 sg devices.
*/
static char * version_str = "4.09 20070714";
#define ME "sg_scan: "
#define NUMERIC_SCAN_DEF 1 /* change to 0 to make alpha scan default */
#define INQ_REPLY_LEN 36
#define INQ_CMD_LEN 6
#define MAX_ERRORS 4
#define EBUFF_SZ 256
#define FNAME_SZ 64
#define PRESENT_ARRAY_SIZE 4096
static const char * sysfs_sg_dir = "/sys/class/scsi_generic";
static int * gen_index_arr;
typedef struct my_scsi_idlun {
/* why can't userland see this structure ??? */
int dev_id;
int host_unique_id;
} My_scsi_idlun;
typedef struct my_sg_scsi_id {
int host_no; /* as in "scsi<n>" where 'n' is one of 0, 1, 2 etc */
int channel;
int scsi_id; /* scsi id of target device */
int lun;
int scsi_type; /* TYPE_... defined in scsi/scsi.h */
short h_cmd_per_lun;/* host (adapter) maximum commands per lun */
short d_queue_depth;/* device (or adapter) maximum queue length */
int unused1; /* probably find a good use, set 0 for now */
int unused2; /* ditto */
} My_sg_scsi_id;
int sg3_inq(int sg_fd, unsigned char * inqBuff, int do_extra);
int scsi_inq(int sg_fd, unsigned char * inqBuff);
int try_ata_identity(const char * file_namep, int ata_fd, int do_inq);
static unsigned char inqCmdBlk[INQ_CMD_LEN] =
{0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
void usage()
{
printf("Usage: sg_scan [-a] [-i] [-n] [-v] [-V] [-w] [-x] "
"[DEVICE]*\n");
printf(" where:\n");
printf(" -a do alpha scan (ie sga, sgb, sgc)\n");
printf(" -i do SCSI INQUIRY, output results\n");
printf(" -n do numeric scan (ie sg0, sg1...) [default]\n");
printf(" -v increase verbosity\n");
printf(" -V output version string then exit\n");
printf(" -w force open with read/write flag\n");
printf(" -x extra information output about queuing\n");
printf(" DEVICE name of device\n");
}
static int scandir_select(const struct dirent * s)
{
int k;
if (1 == sscanf(s->d_name, "sg%d", &k)) {
if ((k >= 0) && (k < PRESENT_ARRAY_SIZE)) {
gen_index_arr[k] = 1;
return 1;
}
}
return 0;
}
static int sysfs_sg_scan(const char * dir_name)
{
struct dirent ** namelist;
int num, k;
num = scandir(dir_name, &namelist, scandir_select, NULL);
if (num < 0)
return -errno;
for (k = 0; k < num; ++k)
free(namelist[k]);
free(namelist);
return num;
}
void make_dev_name(char * fname, int k, int do_numeric)
{
char buff[FNAME_SZ];
int big,little;
strcpy(fname, "/dev/sg");
if (do_numeric) {
snprintf(buff, sizeof(buff), "%d", k);
strcat(fname, buff);
}
else {
if (k < 26) {
buff[0] = 'a' + (char)k;
buff[1] = '\0';
strcat(fname, buff);
}
else if (k <= 255) { /* assumes sequence goes x,y,z,aa,ab,ac etc */
big = k/26;
little = k - (26 * big);
big = big - 1;
buff[0] = 'a' + (char)big;
buff[1] = 'a' + (char)little;
buff[2] = '\0';
strcat(fname, buff);
}
else
strcat(fname, "xxxx");
}
}
int main(int argc, char * argv[])
{
int sg_fd, res, k, j, f, plen, jmp_out;
unsigned char inqBuff[INQ_REPLY_LEN];
int do_numeric = NUMERIC_SCAN_DEF;
int do_inquiry = 0;
int do_extra = 0;
int verbose = 0;
int writeable = 0;
int num_errors = 0;
int num_silent = 0;
int sg_ver3 = -1;
int eacces_err = 0;
char fname[FNAME_SZ];
char * file_namep;
char ebuff[EBUFF_SZ];
My_scsi_idlun my_idlun;
int host_no;
int flags;
int emul = -1;
int has_file_args = 0;
int has_sysfs_sg = 0;
const int max_file_args = PRESENT_ARRAY_SIZE;
const char * cp;
struct stat a_stat;
if ((gen_index_arr = (int *)malloc(max_file_args * sizeof(int))))
memset(gen_index_arr, 0, max_file_args * sizeof(int));
else {
printf(ME "Out of memory\n");
return SG_LIB_CAT_OTHER;
}
for (k = 1, j = 0; k < argc; ++k) {
cp = argv[k];
plen = strlen(cp);
if (plen <= 0)
continue;
if ('-' == *cp) {
for (--plen, ++cp, jmp_out = 0; plen > 0; --plen, ++cp) {
switch (*cp) {
case 'a':
do_numeric = 0;
break;
case 'h':
case '?':
printf("Scan sg device names and optionally do an "
"INQUIRY\n\n");
usage();
return 0;
case 'i':
do_inquiry = 1;
break;
case 'n':
do_numeric = 1;
break;
case 'v':
++verbose;
break;
case 'V':
fprintf(stderr, "Version string: %s\n", version_str);
exit(0);
case 'w':
writeable = 1;
break;
case 'x':
do_extra = 1;
break;
default:
jmp_out = 1;
break;
}
if (jmp_out)
break;
}
if (plen <= 0)
continue;
if (jmp_out) {
fprintf(stderr, "Unrecognized option: %s\n", cp);
usage();
return SG_LIB_SYNTAX_ERROR;
}
} else {
if (j < max_file_args) {
has_file_args = 1;
gen_index_arr[j++] = k;
} else {
printf("Too many command line arguments\n");
return SG_LIB_SYNTAX_ERROR;
}
}
}
if ((! has_file_args) && (stat(sysfs_sg_dir, &a_stat) >= 0) &&
(S_ISDIR(a_stat.st_mode)))
has_sysfs_sg = sysfs_sg_scan(sysfs_sg_dir);
flags = O_NONBLOCK | (writeable ? O_RDWR : O_RDONLY);
for (k = 0, res = 0, j = 0, sg_fd = -1;
(k < max_file_args) && (has_file_args || (num_errors < MAX_ERRORS));
++k, res = ((sg_fd >= 0) ? close(sg_fd) : 0)) {
if (res < 0) {
snprintf(ebuff, EBUFF_SZ, ME "Error closing %s ", fname);
perror(ebuff);
return SG_LIB_FILE_ERROR;
}
if (has_file_args) {
if (gen_index_arr[j])
file_namep = argv[gen_index_arr[j++]];
else
break;
} else if (has_sysfs_sg) {
if (0 == gen_index_arr[k]) {
sg_fd = -1;
continue;
}
make_dev_name(fname, k, 1);
file_namep = fname;
} else {
make_dev_name(fname, k, do_numeric);
file_namep = fname;
}
sg_fd = open(file_namep, flags);
if (sg_fd < 0) {
if (EBUSY == errno) {
printf("%s: device busy (O_EXCL lock), skipping\n", file_namep);
continue;
}
else if ((ENODEV == errno) || (ENOENT == errno) ||
(ENXIO == errno)) {
if (verbose)
fprintf(stderr, "Unable to open: %s, errno=%d\n",
file_namep, errno);
++num_errors;
++num_silent;
continue;
}
else {
if (EACCES == errno)
eacces_err = 1;
snprintf(ebuff, EBUFF_SZ, ME "Error opening %s ", file_namep);
perror(ebuff);
++num_errors;
continue;
}
}
res = ioctl(sg_fd, SCSI_IOCTL_GET_IDLUN, &my_idlun);
if (res < 0) {
res = try_ata_identity(file_namep, sg_fd, do_inquiry);
if (res == 0)
continue;
snprintf(ebuff, EBUFF_SZ, ME "device %s failed on scsi+ata "
"ioctl, skip", file_namep);
perror(ebuff);
++num_errors;
continue;
}
res = ioctl(sg_fd, SCSI_IOCTL_GET_BUS_NUMBER, &host_no);
if (res < 0) {
snprintf(ebuff, EBUFF_SZ, ME "device %s failed on scsi "
"ioctl(2), skip", file_namep);
perror(ebuff);
++num_errors;
continue;
}
res = ioctl(sg_fd, SG_EMULATED_HOST, &emul);
if (res < 0)
emul = -1;
printf("%s: scsi%d channel=%d id=%d lun=%d", file_namep, host_no,
(my_idlun.dev_id >> 16) & 0xff, my_idlun.dev_id & 0xff,
(my_idlun.dev_id >> 8) & 0xff);
if (1 == emul)
printf(" [em]");
#if 0
printf(", huid=%d", my_idlun.host_unique_id);
#endif
if (! has_file_args) {
My_sg_scsi_id m_id; /* compatible with sg_scsi_id_t in sg.h */
res = ioctl(sg_fd, SG_GET_SCSI_ID, &m_id);
if (res < 0) {
snprintf(ebuff, EBUFF_SZ, ME "device %s failed "
"SG_GET_SCSI_ID ioctl(4), skip", file_namep);
perror(ebuff);
++num_errors;
continue;
}
/* printf(" type=%d", m_id.scsi_type); */
if (do_extra)
printf(" cmd_per_lun=%hd queue_depth=%hd\n",
m_id.h_cmd_per_lun, m_id.d_queue_depth);
else
printf("\n");
}
else
printf("\n");
if (do_inquiry) {
if (-1 == sg_ver3) {
sg_ver3 = 0;
if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &f) >= 0) &&
(f >= 30000))
sg_ver3 = 1;
}
if (1 == sg_ver3)
res = sg3_inq(sg_fd, inqBuff, do_extra);
}
}
if ((num_errors >= MAX_ERRORS) && (num_silent < num_errors) &&
(! has_file_args)) {
printf("Stopping because there are too many error\n");
if (eacces_err)
printf(" root access may be required\n");
}
return 0;
}
int sg3_inq(int sg_fd, unsigned char * inqBuff, int do_extra)
{
struct sg_io_hdr io_hdr;
unsigned char sense_buffer[32];
int ok, err, sg_io;
memset(&io_hdr, 0, sizeof(struct sg_io_hdr));
memset(inqBuff, 0, INQ_REPLY_LEN);
inqBuff[0] = 0x7f;
io_hdr.interface_id = 'S';
io_hdr.cmd_len = sizeof(inqCmdBlk);
io_hdr.mx_sb_len = sizeof(sense_buffer);
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
io_hdr.dxfer_len = INQ_REPLY_LEN;
io_hdr.dxferp = inqBuff;
io_hdr.cmdp = inqCmdBlk;
io_hdr.sbp = sense_buffer;
io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
ok = 1;
sg_io = 0;
if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
if ((err = scsi_inq(sg_fd, inqBuff)) < 0) {
perror(ME "Inquiry SG_IO + SCSI_IOCTL_SEND_COMMAND ioctl error");
return 1;
} else if (err) {
printf(ME "SCSI_IOCTL_SEND_COMMAND ioctl error=0x%x\n", err);
return 1;
}
} else {
sg_io = 1;
/* now for the error processing */
switch (sg_err_category3(&io_hdr)) {
case SG_LIB_CAT_RECOVERED:
sg_chk_n_print3("Inquiry, continuing", &io_hdr, 1);
/* fall through */
case SG_LIB_CAT_CLEAN:
break;
default: /* won't bother decoding other categories */
ok = 0;
sg_chk_n_print3("INQUIRY command error", &io_hdr, 1);
break;
}
}
if (ok) { /* output result if it is available */
char * p = (char *)inqBuff;
printf(" %.8s %.16s %.4s ", p + 8, p + 16, p + 32);
printf("[rmb=%d cmdq=%d pqual=%d pdev=0x%x] ",
!!(p[1] & 0x80), !!(p[7] & 2), (p[0] & 0xe0) >> 5,
(p[0] & 0x1f));
if (do_extra && sg_io)
printf("dur=%ums\n", io_hdr.duration);
else
printf("\n");
}
return 0;
}
struct lscsi_ioctl_command {
unsigned int inlen; /* _excluding_ scsi command length */
unsigned int outlen;
unsigned char data[1]; /* was 0 but that's not ISO C!! */
/* on input, scsi command starts here then opt. data */
};
/* fallback INQUIRY using scsi mid-level's SCSI_IOCTL_SEND_COMMAND ioctl */
int scsi_inq(int sg_fd, unsigned char * inqBuff)
{
int res;
unsigned char buff[512];
struct lscsi_ioctl_command * sicp = (struct lscsi_ioctl_command *)buff;
memset(buff, 0, sizeof(buff));
sicp->inlen = 0;
sicp->outlen = INQ_REPLY_LEN;
memcpy(sicp->data, inqCmdBlk, INQ_CMD_LEN);
res = ioctl(sg_fd, SCSI_IOCTL_SEND_COMMAND, sicp);
if (0 == res)
memcpy(inqBuff, sicp->data, INQ_REPLY_LEN);
return res;
}
/* Following code permits ATA IDENTIFY commands to be performed on
ATA non "Packet Interface" devices (e.g. ATA disks).
GPL-ed code borrowed from smartmontools (smartmontools.sf.net).
Copyright (C) 2002-4 Bruce Allen
<smartmontools-support@lists.sourceforge.net>
*/
#ifndef ATA_IDENTIFY_DEVICE
#define ATA_IDENTIFY_DEVICE 0xec
#endif
#ifndef HDIO_DRIVE_CMD
#define HDIO_DRIVE_CMD 0x031f
#endif
/* Needed parts of the ATA DRIVE IDENTIFY Structure. Those labeled
* word* are NOT used.
*/
struct ata_identify_device {
unsigned short words000_009[10];
unsigned char serial_no[20];
unsigned short words020_022[3];
unsigned char fw_rev[8];
unsigned char model[40];
unsigned short words047_079[33];
unsigned short major_rev_num;
unsigned short minor_rev_num;
unsigned short command_set_1;
unsigned short command_set_2;
unsigned short command_set_extension;
unsigned short cfs_enable_1;
unsigned short word086;
unsigned short csf_default;
unsigned short words088_255[168];
};
/* Copies n bytes (or n-1 if n is odd) from in to out, but swaps adjacents
* bytes.
*/
void swapbytes(char *out, const char *in, size_t n)
{
size_t k;
if (n > 1) {
for (k = 0; k < (n - 1); k += 2) {
out[k] = in[k + 1];
out[k + 1] = in[k];
}
}
}
/* Copies in to out, but removes leading and trailing whitespace. */
void trim(char *out, const char *in)
{
int k, first, last;
/* Find the first non-space character (maybe none). */
first = -1;
for (k = 0; in[k]; k++) {
if (! isspace((int)in[k])) {
first = k;
break;
}
}
if (first == -1) {
/* There are no non-space characters. */
out[0] = '\0';
return;
}
/* Find the last non-space character. */
for (k = strlen(in) - 1; k >= first && isspace((int)in[k]); k--)
;
last = k;
strncpy(out, in + first, last - first + 1);
out[last - first + 1] = '\0';
}
/* Convenience function for formatting strings from ata_identify_device */
void formatdriveidstring(char *out, const char *in, int n)
{
char tmp[65];
n = n > 64 ? 64 : n;
swapbytes(tmp, in, n);
tmp[n] = '\0';
trim(out, tmp);
}
/* Function for printing ASCII byte-swapped strings, skipping white
* space. Please note that this is needed on both big- and
* little-endian hardware.
*/
void printswap(char *output, char *in, unsigned int n)
{
formatdriveidstring(output, in, n);
if (*output)
printf("%.*s ", (int)n, output);
else
printf("%.*s ", (int)n, "[No Information Found]\n");
}
#define ATA_IDENTIFY_BUFF_SZ sizeof(struct ata_identify_device)
#define HDIO_DRIVE_CMD_OFFSET 4
int ata_command_interface(int device, char *data)
{
unsigned char buff[ATA_IDENTIFY_BUFF_SZ + HDIO_DRIVE_CMD_OFFSET];
int retval;
buff[0] = ATA_IDENTIFY_DEVICE;
buff[3] = 1;
/* We are now doing the HDIO_DRIVE_CMD type ioctl. */
if ((retval = ioctl(device, HDIO_DRIVE_CMD, buff)))
return retval;
/* if the command returns data, copy it back */
memcpy(data, buff + HDIO_DRIVE_CMD_OFFSET, ATA_IDENTIFY_BUFF_SZ);
return 0;
}
int try_ata_identity(const char * file_namep, int ata_fd, int do_inq)
{
struct ata_identify_device ata_ident;
char model[64];
char serial[64];
char firm[64];
int res;
res = ata_command_interface(ata_fd, (char *)&ata_ident);
if (res)
return res;
printf("%s: ATA device\n", file_namep);
if (do_inq) {
printf(" ");
printswap(model, (char *)ata_ident.model, 40);
printswap(serial, (char *)ata_ident.serial_no, 20);
printswap(firm, (char *)ata_ident.fw_rev, 8);
printf("\n");
}
return res;
}
#endif
#ifdef SG3_UTILS_WIN32
/*
* Copyright (c) 2006 Douglas Gilbert.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*/
/*
* This utility shows the relationship between various SCSI device naming
* schemes in Windows OSes (Windows 200, 2003 and XP) as seen by
* The SCSI Pass Through (SPT) interface. N.B. ASPI32 is not used.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include "sg_lib.h"
#include "sg_pt_win32.h"
#define MAX_SCSI_ELEMS 1024
#define MAX_ADAPTER_NUM 64
#define MAX_PHYSICALDRIVE_NUM 512
#define MAX_CDROM_NUM 512
#define MAX_TAPE_NUM 512
#define MAX_HOLE_COUNT 8
#define SCSI2_INQ_RESP_LEN 36
#define DEF_TIMEOUT 20
#define INQUIRY_CMD 0x12
#define INQUIRY_CMDLEN 6
static char * version_str = "1.04 (win32) 20070101";
struct w_scsi_elem {
char in_use;
char scsi_adapter_valid;
UCHAR port_num; /* <n> in '\\.\SCSI<n>:' adapter name */
UCHAR bus; /* also known as pathId */
UCHAR target;
UCHAR lun;
UCHAR device_claimed; /* class driver claimed this lu */
UCHAR dubious_scsi; /* set if inq_resp[4] is zero */
char pdt; /* peripheral device type (see SPC-4) */
char volume_valid;
char volume_multiple; /* multiple partitions mapping to volumes */
UCHAR volume_letter; /* lowest 'C:' through to 'Z:' */
char physicaldrive_valid;
char cdrom_valid;
char tape_valid;
int physicaldrive_num;
int cdrom_num;
int tape_num;
unsigned char inq_resp[SCSI2_INQ_RESP_LEN];
};
static struct w_scsi_elem * w_scsi_arr;
static int next_unused_scsi_elem = 0;
static int next_elem_after_scsi_adapter_valid = 0;
static struct option long_options[] = {
{"help", 0, 0, 'h'},
{"letter", 1, 0, '1'},
{"verbose", 0, 0, 'v'},
{"version", 0, 0, 'V'},
{0, 0, 0, 0},
};
static void usage()
{
fprintf(stderr,
"Usage: sg_scan [--help] [--letter=VL] [--verbose] "
"[--version]\n");
fprintf(stderr,
" --help|-h output this usage message then exit\n"
" --letter=VL|-l VL volume letter (e.g. 'F' for F:) "
"to find\n"
" --verbose|-v increase verbosity\n"
" --version|-V print version string and exit\n\n"
"Scan for SCSI and related device names\n");
}
static char * get_err_str(DWORD err, int max_b_len, char * b)
{
LPVOID lpMsgBuf;
int k, num, ch;
if (max_b_len < 2) {
if (1 == max_b_len)
b[0] = '\0';
return b;
}
memset(b, 0, max_b_len);
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, err,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf, 0, NULL );
num = lstrlen((LPCTSTR)lpMsgBuf);
if (num < 1)
return b;
num = (num < max_b_len) ? num : (max_b_len - 1);
for (k = 0; k < num; ++k) {
ch = *((LPCTSTR)lpMsgBuf + k);
if ((ch >= 0x0) && (ch < 0x7f))
b[k] = ch & 0x7f;
else
b[k] = '?';
}
return b;
}
static int findElemIndex(UCHAR port_num, UCHAR bus, UCHAR target, UCHAR lun)
{
int k;
struct w_scsi_elem * sep;
for (k = 0; k < next_unused_scsi_elem; ++k) {
sep = w_scsi_arr + k;
if ((port_num == sep->port_num) && (bus == sep->bus) &&
(target == sep->target) && (lun == sep->lun))
return k;
#if 0
if (port_num < sep->port_num)
break; /* assume port_num sorted ascending */
#endif
}
return -1;
}
static BOOL fetchInquiry(HANDLE fh, unsigned char * resp, int max_resp_len,
SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER * afterCall,
int verbose)
{
BOOL success;
int len;
SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sptdw;
ULONG dummy; /* also acts to align next array */
BYTE inqResp[SCSI2_INQ_RESP_LEN];
unsigned char inqCdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0,
SCSI2_INQ_RESP_LEN, 0};
DWORD err;
char b[256];
memset(&sptdw, 0, sizeof(sptdw));
memset(inqResp, 0, sizeof(inqResp));
sptdw.spt.Length = sizeof (SCSI_PASS_THROUGH_DIRECT);
sptdw.spt.CdbLength = sizeof(inqCdb);
sptdw.spt.SenseInfoLength = SCSI_MAX_SENSE_LEN;
sptdw.spt.DataIn = SCSI_IOCTL_DATA_IN;
sptdw.spt.DataTransferLength = SCSI2_INQ_RESP_LEN;
sptdw.spt.TimeOutValue = DEF_TIMEOUT;
sptdw.spt.DataBuffer = inqResp;
sptdw.spt.SenseInfoOffset =
offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER, ucSenseBuf);
memcpy(sptdw.spt.Cdb, inqCdb, sizeof(inqCdb));
success = DeviceIoControl(fh, IOCTL_SCSI_PASS_THROUGH_DIRECT,
&sptdw, sizeof(sptdw),
&sptdw, sizeof(sptdw),
&dummy, NULL);
if (! success) {
if (verbose) {
err = GetLastError();
fprintf(stderr, "fetchInquiry: DeviceIoControl for INQUIRY, "
"err=%lu\n\t%s", err, get_err_str(err, sizeof(b), b));
}
return success;
}
if (afterCall)
memcpy(afterCall, &sptdw, sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER));
if (resp) {
len = (SCSI2_INQ_RESP_LEN > max_resp_len) ?
max_resp_len : SCSI2_INQ_RESP_LEN;
memcpy(resp, inqResp, len);
}
return success;
}
static int sg_do_wscan(char letter, int verbose)
{
int k, j, m, hole_count, index, matched;
DWORD err;
HANDLE fh;
ULONG dummy;
BOOL success;
BYTE bus;
PSCSI_ADAPTER_BUS_INFO ai;
SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sptdw;
unsigned char inqResp[SCSI2_INQ_RESP_LEN];
char adapter_name[64];
char inqDataBuff[2048];
char b[256];
struct w_scsi_elem * sep;
memset(w_scsi_arr, 0, sizeof(w_scsi_arr));
hole_count = 0;
for (k = 0; k < MAX_ADAPTER_NUM; ++k) {
snprintf(adapter_name, sizeof (adapter_name), "\\\\.\\SCSI%d:", k);
fh = CreateFile(adapter_name, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, 0, NULL);
if (fh != INVALID_HANDLE_VALUE) {
hole_count = 0;
success = DeviceIoControl(fh, IOCTL_SCSI_GET_INQUIRY_DATA,
NULL, 0, inqDataBuff, sizeof(inqDataBuff),
&dummy, FALSE);
if (success) {
PSCSI_BUS_DATA pbd;
PSCSI_INQUIRY_DATA pid;
int num_lus, off, len;
ai = (PSCSI_ADAPTER_BUS_INFO)inqDataBuff;
for (bus = 0; bus < ai->NumberOfBusses; bus++) {
pbd = ai->BusData + bus;
num_lus = pbd->NumberOfLogicalUnits;
off = pbd->InquiryDataOffset;
for (j = 0; j < num_lus; ++j) {
if ((off < (int)sizeof(SCSI_ADAPTER_BUS_INFO)) ||
(off > ((int)sizeof(inqDataBuff) -
(int)sizeof(SCSI_INQUIRY_DATA))))
break;
pid = (PSCSI_INQUIRY_DATA)(inqDataBuff + off);
m = next_unused_scsi_elem++;
if (next_unused_scsi_elem > MAX_SCSI_ELEMS) {
fprintf(stderr, "Too many scsi devices (more "
"than %d)\n", MAX_SCSI_ELEMS);
return SG_LIB_CAT_OTHER;
}
next_elem_after_scsi_adapter_valid =
next_unused_scsi_elem;
sep = w_scsi_arr + m;
sep->in_use = 1;
sep->scsi_adapter_valid = 1;
sep->port_num = k;
sep->bus = pid->PathId;
sep->target = pid->TargetId;
sep->lun = pid->Lun;
sep->device_claimed = pid->DeviceClaimed;
len = pid->InquiryDataLength;
len = (len > SCSI2_INQ_RESP_LEN) ?
SCSI2_INQ_RESP_LEN : len;
memcpy(sep->inq_resp, pid->InquiryData, len);
sep->pdt = sep->inq_resp[0] & 0x3f;
if (0 == sep->inq_resp[4])
sep->dubious_scsi = 1;
if (verbose > 1) {
fprintf(stderr, "%s: PathId=%d TargetId=%d "
"Lun=%d ", adapter_name, pid->PathId,
pid->TargetId, pid->Lun);
fprintf(stderr, " DeviceClaimed=%d\n",
pid->DeviceClaimed);
dStrHex((const char *)(pid->InquiryData),
pid->InquiryDataLength, 0);
}
off = pid->NextInquiryDataOffset;
}
}
} else {
err = GetLastError();
fprintf(stderr, "%s: IOCTL_SCSI_GET_INQUIRY_DATA failed "
"err=%lu\n\t%s",
adapter_name, err, get_err_str(err, sizeof(b), b));
}
CloseHandle(fh);
} else {
if (verbose > 2) {
err = GetLastError();
fprintf(stderr, "%s: CreateFile failed err=%lu\n\t%s",
adapter_name, err, get_err_str(err, sizeof(b), b));
}
if (++hole_count >= MAX_HOLE_COUNT)
break;
}
}
for (k = 0; k < 24; ++k) {
matched = 0;
sep = NULL;
snprintf(adapter_name, sizeof (adapter_name), "\\\\.\\%c:", 'C' + k);
fh = CreateFile(adapter_name, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, 0, NULL);
if (fh != INVALID_HANDLE_VALUE) {
success = DeviceIoControl(fh, IOCTL_SCSI_GET_ADDRESS,
NULL, 0, inqDataBuff,
sizeof(inqDataBuff), &dummy, FALSE);
if (success) {
PSCSI_ADDRESS pa;
pa = (PSCSI_ADDRESS)inqDataBuff;
index = findElemIndex(pa->PortNumber, pa->PathId,
pa->TargetId, pa->Lun);
if (index >= 0) {
sep = w_scsi_arr + index;
matched = 1;
} else {
m = next_unused_scsi_elem++;
if (next_unused_scsi_elem > MAX_SCSI_ELEMS) {
fprintf(stderr, "Too many scsi devices (more "
"than %d)\n", MAX_SCSI_ELEMS);
return SG_LIB_CAT_OTHER;
}
sep = w_scsi_arr + m;
sep->in_use = 1;
sep->port_num = pa->PortNumber;
sep->bus = pa->PathId;
sep->target = pa->TargetId;
sep->lun = pa->Lun;
sep->device_claimed = 1;
}
if (sep->volume_valid) {
sep->volume_multiple = 1;
if (('C' + k) == letter)
sep->volume_letter = letter;
} else {
sep->volume_valid = 1;
sep->volume_letter = 'C' + k;
}
if (verbose > 1)
fprintf(stderr, "%c: PortNum=%d PathId=%d TargetId=%d "
"Lun=%d index=%d\n", 'C' + k, pa->PortNumber,
pa->PathId, pa->TargetId, pa->Lun, index);
if (matched) {
CloseHandle(fh);
continue;
}
} else {
if (verbose > 1) {
err = GetLastError();
fprintf(stderr, "%c: IOCTL_SCSI_GET_ADDRESS err=%lu\n\t"
"%s", 'C' + k, err,
get_err_str(err, sizeof(b), b));
}
}
if (fetchInquiry(fh, inqResp, sizeof(inqResp), &sptdw,
verbose)) {
if (sptdw.spt.ScsiStatus) {
if (verbose) {
fprintf(stderr, "%c: INQUIRY failed: ", 'C' + k);
sg_print_scsi_status(sptdw.spt.ScsiStatus);
sg_print_sense(" ", sptdw.ucSenseBuf,
sizeof(sptdw.ucSenseBuf), 0);
}
CloseHandle(fh);
continue;
}
if (NULL == sep) {
m = next_unused_scsi_elem++;
if (next_unused_scsi_elem > MAX_SCSI_ELEMS) {
fprintf(stderr, "Too many scsi devices (more "
"than %d)\n", MAX_SCSI_ELEMS);
return SG_LIB_CAT_OTHER;
}
sep = w_scsi_arr + m;
sep->in_use = 1;
sep->device_claimed = 1;
sep->volume_valid = 1;
sep->volume_letter = 'C' + k;
}
memcpy(sep->inq_resp, inqResp, sizeof(sep->inq_resp));
sep->pdt = sep->inq_resp[0] & 0x3f;
if (0 == sep->inq_resp[4])
sep->dubious_scsi = 1;
}
CloseHandle(fh);
}
}
hole_count = 0;
for (k = 0; k < MAX_PHYSICALDRIVE_NUM; ++k) {
matched = 0;
sep = NULL;
snprintf(adapter_name, sizeof (adapter_name),
"\\\\.\\PhysicalDrive%d", k);
fh = CreateFile(adapter_name, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, 0, NULL);
if (fh != INVALID_HANDLE_VALUE) {
hole_count = 0;
success = DeviceIoControl(fh, IOCTL_SCSI_GET_ADDRESS,
NULL, 0, inqDataBuff,
sizeof(inqDataBuff), &dummy, FALSE);
if (success) {
PSCSI_ADDRESS pa;
pa = (PSCSI_ADDRESS)inqDataBuff;
index = findElemIndex(pa->PortNumber, pa->PathId,
pa->TargetId, pa->Lun);
if (index >= 0) {
sep = w_scsi_arr + index;
matched = 1;
} else {
m = next_unused_scsi_elem++;
if (next_unused_scsi_elem > MAX_SCSI_ELEMS) {
fprintf(stderr, "Too many scsi devices (more "
"than %d)\n", MAX_SCSI_ELEMS);
return SG_LIB_CAT_OTHER;
}
sep = w_scsi_arr + m;
sep->in_use = 1;
sep->port_num = pa->PortNumber;
sep->bus = pa->PathId;
sep->target = pa->TargetId;
sep->lun = pa->Lun;
sep->device_claimed = 1;
}
sep->physicaldrive_valid = 1;
sep->physicaldrive_num = k;
if (verbose > 1)
fprintf(stderr, "PD%d: PortNum=%d PathId=%d TargetId=%d "
"Lun=%d index=%d\n", k, pa->PortNumber,
pa->PathId, pa->TargetId, pa->Lun, index);
if (matched) {
CloseHandle(fh);
continue;
}
} else {
if (verbose > 1) {
err = GetLastError();
fprintf(stderr, "PD%d: IOCTL_SCSI_GET_ADDRESS err=%lu\n\t"
"%s", k, err, get_err_str(err, sizeof(b), b));
}
}
if (fetchInquiry(fh, inqResp, sizeof(inqResp), &sptdw,
verbose)) {
if (sptdw.spt.ScsiStatus) {
if (verbose) {
fprintf(stderr, "PD%d: INQUIRY failed: ", k);
sg_print_scsi_status(sptdw.spt.ScsiStatus);
sg_print_sense(" ", sptdw.ucSenseBuf,
sizeof(sptdw.ucSenseBuf), 0);
}
CloseHandle(fh);
continue;
}
if (NULL == sep) {
m = next_unused_scsi_elem++;
if (next_unused_scsi_elem > MAX_SCSI_ELEMS) {
fprintf(stderr, "Too many scsi devices (more "
"than %d)\n", MAX_SCSI_ELEMS);
return SG_LIB_CAT_OTHER;
}
sep = w_scsi_arr + m;
sep->in_use = 1;
sep->device_claimed = 1;
sep->physicaldrive_valid = 1;
sep->physicaldrive_num = k;
}
memcpy(sep->inq_resp, inqResp, sizeof(sep->inq_resp));
sep->pdt = sep->inq_resp[0] & 0x3f;
if (0 == sep->inq_resp[4])
sep->dubious_scsi = 1;
}
CloseHandle(fh);
} else {
if (verbose > 2) {
err = GetLastError();
fprintf(stderr, "%s: CreateFile failed err=%lu\n\t%s",
adapter_name, err, get_err_str(err, sizeof(b), b));
}
if (++hole_count >= MAX_HOLE_COUNT)
break;
}
}
hole_count = 0;
for (k = 0; k < MAX_CDROM_NUM; ++k) {
matched = 0;
sep = NULL;
snprintf(adapter_name, sizeof (adapter_name), "\\\\.\\CDROM%d", k);
fh = CreateFile(adapter_name, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, 0, NULL);
if (fh != INVALID_HANDLE_VALUE) {
hole_count = 0;
success = DeviceIoControl(fh, IOCTL_SCSI_GET_ADDRESS,
NULL, 0, inqDataBuff,
sizeof(inqDataBuff), &dummy, FALSE);
if (success) {
PSCSI_ADDRESS pa;
pa = (PSCSI_ADDRESS)inqDataBuff;
index = findElemIndex(pa->PortNumber, pa->PathId,
pa->TargetId, pa->Lun);
if (index >= 0) {
sep = w_scsi_arr + index;
matched = 1;
} else {
m = next_unused_scsi_elem++;
if (next_unused_scsi_elem > MAX_SCSI_ELEMS) {
fprintf(stderr, "Too many scsi devices (more "
"than %d)\n", MAX_SCSI_ELEMS);
return SG_LIB_CAT_OTHER;
}
sep = w_scsi_arr + m;
sep->in_use = 1;
sep->port_num = pa->PortNumber;
sep->bus = pa->PathId;
sep->target = pa->TargetId;
sep->lun = pa->Lun;
sep->device_claimed = 1;
}
sep->cdrom_valid = 1;
sep->cdrom_num = k;
if (verbose > 1)
fprintf(stderr, "CDROM%d: PortNum=%d PathId=%d TargetId=%d "
"Lun=%d index=%d\n", k, pa->PortNumber,
pa->PathId, pa->TargetId, pa->Lun, index);
if (matched) {
CloseHandle(fh);
continue;
}
} else {
if (verbose > 1) {
err = GetLastError();
fprintf(stderr, "CDROM%d: IOCTL_SCSI_GET_ADDRESS "
"err=%lu\n\t%s", k, err,
get_err_str(err, sizeof(b), b));
}
}
if (fetchInquiry(fh, inqResp, sizeof(inqResp), &sptdw,
verbose)) {
if (sptdw.spt.ScsiStatus) {
if (verbose) {
fprintf(stderr, "CDROM%d: INQUIRY failed: ", k);
sg_print_scsi_status(sptdw.spt.ScsiStatus);
sg_print_sense(" ", sptdw.ucSenseBuf,
sizeof(sptdw.ucSenseBuf), 0);
}
CloseHandle(fh);
continue;
}
if (NULL == sep) {
m = next_unused_scsi_elem++;
if (next_unused_scsi_elem > MAX_SCSI_ELEMS) {
fprintf(stderr, "Too many scsi devices (more "
"than %d)\n", MAX_SCSI_ELEMS);
return SG_LIB_CAT_OTHER;
}
sep = w_scsi_arr + m;
sep->in_use = 1;
sep->device_claimed = 1;
sep->cdrom_valid = 1;
sep->cdrom_num = k;
}
memcpy(sep->inq_resp, inqResp, sizeof(sep->inq_resp));
sep->pdt = sep->inq_resp[0] & 0x3f;
if (0 == sep->inq_resp[4])
sep->dubious_scsi = 1;
}
CloseHandle(fh);
} else {
if (verbose > 3) {
err = GetLastError();
fprintf(stderr, "%s: CreateFile failed err=%lu\n\t%s",
adapter_name, err, get_err_str(err, sizeof(b), b));
}
if (++hole_count >= MAX_HOLE_COUNT)
break;
}
}
hole_count = 0;
for (k = 0; k < MAX_TAPE_NUM; ++k) {
matched = 0;
sep = NULL;
snprintf(adapter_name, sizeof (adapter_name), "\\\\.\\TAPE%d", k);
fh = CreateFile(adapter_name, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, 0, NULL);
if (fh != INVALID_HANDLE_VALUE) {
hole_count = 0;
success = DeviceIoControl(fh, IOCTL_SCSI_GET_ADDRESS,
NULL, 0, inqDataBuff,
sizeof(inqDataBuff), &dummy, FALSE);
if (success) {
PSCSI_ADDRESS pa;
pa = (PSCSI_ADDRESS)inqDataBuff;
index = findElemIndex(pa->PortNumber, pa->PathId,
pa->TargetId, pa->Lun);
if (index >= 0) {
sep = w_scsi_arr + index;
matched = 1;
} else {
m = next_unused_scsi_elem++;
if (next_unused_scsi_elem > MAX_SCSI_ELEMS) {
fprintf(stderr, "Too many scsi devices (more "
"than %d)\n", MAX_SCSI_ELEMS);
return SG_LIB_CAT_OTHER;
}
sep = w_scsi_arr + m;
sep->in_use = 1;
sep->port_num = pa->PortNumber;
sep->bus = pa->PathId;
sep->target = pa->TargetId;
sep->lun = pa->Lun;
sep->device_claimed = 1;
}
sep->tape_valid = 1;
sep->tape_num = k;
if (verbose > 1)
fprintf(stderr, "TAPE%d: PortNum=%d PathId=%d TargetId=%d "
"Lun=%d index=%d\n", k, pa->PortNumber,
pa->PathId, pa->TargetId, pa->Lun, index);
if (matched) {
CloseHandle(fh);
continue;
}
} else {
if (verbose > 1) {
err = GetLastError();
fprintf(stderr, "TAPE%d: IOCTL_SCSI_GET_ADDRESS "
"err=%lu\n\t%s", k, err,
get_err_str(err, sizeof(b), b));
}
}
if (fetchInquiry(fh, inqResp, sizeof(inqResp), &sptdw,
verbose)) {
if (sptdw.spt.ScsiStatus) {
if (verbose) {
fprintf(stderr, "TAPE%d: INQUIRY failed: ", k);
sg_print_scsi_status(sptdw.spt.ScsiStatus);
sg_print_sense(" ", sptdw.ucSenseBuf,
sizeof(sptdw.ucSenseBuf), 0);
}
CloseHandle(fh);
continue;
}
if (NULL == sep) {
m = next_unused_scsi_elem++;
if (next_unused_scsi_elem > MAX_SCSI_ELEMS) {
fprintf(stderr, "Too many scsi devices (more "
"than %d)\n", MAX_SCSI_ELEMS);
return SG_LIB_CAT_OTHER;
}
sep = w_scsi_arr + m;
sep->in_use = 1;
sep->device_claimed = 1;
sep->tape_valid = 1;
sep->tape_num = k;
}
memcpy(sep->inq_resp, inqResp, sizeof(sep->inq_resp));
sep->pdt = sep->inq_resp[0] & 0x3f;
if (0 == sep->inq_resp[4])
sep->dubious_scsi = 1;
}
CloseHandle(fh);
} else {
if (verbose > 4) {
err = GetLastError();
fprintf(stderr, "%s: CreateFile failed err=%lu\n\t%s",
adapter_name, err, get_err_str(err, sizeof(b), b));
}
if (++hole_count >= MAX_HOLE_COUNT)
break;
}
}
for (k = 0; k < MAX_SCSI_ELEMS; ++k) {
sep = w_scsi_arr + k;
if (0 == sep->in_use)
break;
if (sep->scsi_adapter_valid) {
snprintf(b, sizeof(b), "SCSI%d:%d,%d,%d ", sep->port_num,
sep->bus, sep->target, sep->lun);
printf("%-18s", b);
} else
printf(" ");
if (sep->volume_valid)
printf("%c: %c ", sep->volume_letter,
(sep->volume_multiple ? '+' : ' '));
else
printf(" ");
if (sep->physicaldrive_valid) {
snprintf(b, sizeof(b), "PD%d ", sep->physicaldrive_num);
printf("%-9s", b);
} else if (sep->cdrom_valid) {
snprintf(b, sizeof(b), "CDROM%d ", sep->cdrom_num);
printf("%-9s", b);
} else if (sep->tape_valid) {
snprintf(b, sizeof(b), "TAPE%d ", sep->tape_num);
printf("%-9s", b);
} else
printf(" ");
memcpy(b, sep->inq_resp + 8, SCSI2_INQ_RESP_LEN);
for (j = 0; j < 28; ++j) {
if ((b[j] < 0x20) || (b[j] > 0x7e))
b[j] = ' ';
}
b[28] = '\0';
printf("%-30s", b);
if (sep->dubious_scsi)
printf("* ");
else if ((! sep->physicaldrive_valid) && (! sep->cdrom_valid) &&
(! sep->tape_valid))
printf("pdt=%-2d", sep->pdt);
else
printf(" ");
printf("\n");
}
return 0;
}
int main(int argc, char * argv[])
{
int c, ret;
int verbose = 0;
int vol_letter = 0;
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "hHl:vV", long_options,
&option_index);
if (c == -1)
break;
switch (c) {
case 'h':
case '?':
usage();
return 0;
case 'l':
vol_letter = toupper(optarg[0]);
if ((vol_letter < 'C') || (vol_letter > 'Z')) {
fprintf(stderr, "'--letter=' expects a letter in the "
"'C' to 'Z' range\n");
usage();
return SG_LIB_SYNTAX_ERROR;
}
break;
case 'v':
++verbose;
break;
case 'V':
fprintf(stderr, "version: %s\n", version_str);
return 0;
default:
fprintf(stderr, "unrecognised option code 0x%x ??\n", c);
usage();
return SG_LIB_SYNTAX_ERROR;
}
}
if (optind < argc) {
if (optind < argc) {
for (; optind < argc; ++optind)
fprintf(stderr, "Unexpected extra argument: %s\n",
argv[optind]);
usage();
return SG_LIB_SYNTAX_ERROR;
}
}
w_scsi_arr = malloc(sizeof(struct w_scsi_elem) * MAX_SCSI_ELEMS);
ret = sg_do_wscan(vol_letter, verbose);
free(w_scsi_arr);
return ret;
}
#endif