/* N.B. There are two programs in this file, the first is for linux * and the second is for Windows. */ #ifdef SG3_UTILS_LINUX #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #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 20070125"; #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" 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 */ #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 #include #include #include #include #include #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; /* in '\\.\SCSI:' 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 switch 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