734 lines
23 KiB
C
734 lines
23 KiB
C
|
|
/*
|
||
|
|
* Copyright (c) 2006-2022 Douglas Gilbert.
|
||
|
|
* All rights reserved.
|
||
|
|
* Use of this source code is governed by a BSD-style
|
||
|
|
* license that can be found in the BSD_LICENSE file.
|
||
|
|
*
|
||
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
||
|
|
*/
|
||
|
|
|
||
|
|
/*
|
||
|
|
* This utility shows the relationship between various device names and
|
||
|
|
* volumes in Windows OSes (Windows 2000, 2003, XP and Vista). There is
|
||
|
|
* an optional scsi adapter scan.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#ifdef HAVE_CONFIG_H
|
||
|
|
#include "config.h"
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#include <stdio.h>
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <stddef.h>
|
||
|
|
#include <stdarg.h>
|
||
|
|
#include <stdbool.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <ctype.h>
|
||
|
|
#include <getopt.h>
|
||
|
|
#include <errno.h>
|
||
|
|
|
||
|
|
#include "sg_lib.h"
|
||
|
|
#include "sg_pt.h"
|
||
|
|
#include "sg_pr2serr.h"
|
||
|
|
|
||
|
|
#ifdef _WIN32_WINNT
|
||
|
|
#if _WIN32_WINNT < 0x0602
|
||
|
|
#undef _WIN32_WINNT
|
||
|
|
#define _WIN32_WINNT 0x0602
|
||
|
|
#endif
|
||
|
|
#else
|
||
|
|
#define _WIN32_WINNT 0x0602
|
||
|
|
/* claim its W8 */
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#include "sg_pt_win32.h"
|
||
|
|
|
||
|
|
static const char * version_str = "1.23 (win32) 20220127";
|
||
|
|
|
||
|
|
#define MAX_SCSI_ELEMS 4096
|
||
|
|
#define MAX_ADAPTER_NUM 256
|
||
|
|
#define MAX_PHYSICALDRIVE_NUM 2048
|
||
|
|
#define MAX_CDROM_NUM 512
|
||
|
|
#define MAX_TAPE_NUM 512
|
||
|
|
#define MAX_HOLE_COUNT 16
|
||
|
|
#define MAX_GET_INQUIRY_DATA_SZ (32 * 1024)
|
||
|
|
|
||
|
|
|
||
|
|
union STORAGE_DEVICE_DESCRIPTOR_DATA {
|
||
|
|
STORAGE_DEVICE_DESCRIPTOR desc;
|
||
|
|
char raw[256];
|
||
|
|
};
|
||
|
|
|
||
|
|
union STORAGE_DEVICE_UID_DATA {
|
||
|
|
STORAGE_DEVICE_UNIQUE_IDENTIFIER desc;
|
||
|
|
char raw[1060];
|
||
|
|
};
|
||
|
|
|
||
|
|
struct storage_elem {
|
||
|
|
char name[32];
|
||
|
|
char volume_letters[32];
|
||
|
|
bool qp_descriptor_valid;
|
||
|
|
bool qp_uid_valid;
|
||
|
|
union STORAGE_DEVICE_DESCRIPTOR_DATA qp_descriptor;
|
||
|
|
union STORAGE_DEVICE_UID_DATA qp_uid;
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
static struct storage_elem * storage_arr;
|
||
|
|
static uint8_t * free_storage_arr;
|
||
|
|
static int next_unused_elem = 0;
|
||
|
|
static int verbose = 0;
|
||
|
|
|
||
|
|
static struct option long_options[] = {
|
||
|
|
{"bus", no_argument, 0, 'b'},
|
||
|
|
{"help", no_argument, 0, 'h'},
|
||
|
|
{"letter", required_argument, 0, 'l'},
|
||
|
|
{"verbose", no_argument, 0, 'v'},
|
||
|
|
{"scsi", no_argument, 0, 's'},
|
||
|
|
{"version", no_argument, 0, 'V'},
|
||
|
|
{0, 0, 0, 0},
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
static void
|
||
|
|
usage()
|
||
|
|
{
|
||
|
|
pr2serr("Usage: sg_scan [--bus] [--help] [--letter=VL] [--scsi] "
|
||
|
|
"[--verbose] [--version]\n");
|
||
|
|
pr2serr(" --bus|-b output bus type\n"
|
||
|
|
" --help|-h output this usage message then exit\n"
|
||
|
|
" --letter=VL|-l VL volume letter (e.g. 'F' for F:) "
|
||
|
|
"to match\n"
|
||
|
|
" --scsi|-s used once: show SCSI adapters (tuple) "
|
||
|
|
"scan after\n"
|
||
|
|
" device scan; default: show no "
|
||
|
|
"adapters;\n"
|
||
|
|
" used twice: show only adapters\n"
|
||
|
|
" --verbose|-v increase verbosity\n"
|
||
|
|
" --version|-V print version string and exit\n\n"
|
||
|
|
"Scan for storage and related device names\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
static char *
|
||
|
|
get_err_str(DWORD err, int max_b_len, char * b)
|
||
|
|
{
|
||
|
|
char * cp;
|
||
|
|
struct sg_pt_base * tmp_p = construct_scsi_pt_obj();
|
||
|
|
|
||
|
|
if ((NULL == b) || (max_b_len < 2)) {
|
||
|
|
if (b && (max_b_len > 0))
|
||
|
|
b[0] = '\0';
|
||
|
|
return b;
|
||
|
|
}
|
||
|
|
if (NULL == tmp_p) {
|
||
|
|
snprintf(b, max_b_len, "%s: construct_scsi_pt_obj() failed\n",
|
||
|
|
__func__);
|
||
|
|
|
||
|
|
return b;
|
||
|
|
}
|
||
|
|
set_scsi_pt_transport_err(tmp_p, (int)err);
|
||
|
|
cp = get_scsi_pt_transport_err_str(tmp_p, max_b_len, b);
|
||
|
|
destruct_scsi_pt_obj(tmp_p);
|
||
|
|
return cp;
|
||
|
|
}
|
||
|
|
|
||
|
|
static const char *
|
||
|
|
get_bus_type(int bt)
|
||
|
|
{
|
||
|
|
switch (bt)
|
||
|
|
{
|
||
|
|
case BusTypeUnknown:
|
||
|
|
return "Unkno";
|
||
|
|
case BusTypeScsi:
|
||
|
|
return "Scsi ";
|
||
|
|
case BusTypeAtapi:
|
||
|
|
return "Atapi";
|
||
|
|
case BusTypeAta:
|
||
|
|
return "Ata ";
|
||
|
|
case BusType1394:
|
||
|
|
return "1394 ";
|
||
|
|
case BusTypeSsa:
|
||
|
|
return "Ssa ";
|
||
|
|
case BusTypeFibre:
|
||
|
|
return "Fibre";
|
||
|
|
case BusTypeUsb:
|
||
|
|
return "Usb ";
|
||
|
|
case BusTypeRAID:
|
||
|
|
return "RAID ";
|
||
|
|
case BusTypeiScsi:
|
||
|
|
return "iScsi";
|
||
|
|
case BusTypeSas:
|
||
|
|
return "Sas ";
|
||
|
|
case BusTypeSata:
|
||
|
|
return "Sata ";
|
||
|
|
case BusTypeSd:
|
||
|
|
return "Sd ";
|
||
|
|
case BusTypeMmc:
|
||
|
|
return "Mmc ";
|
||
|
|
case BusTypeVirtual:
|
||
|
|
return "Virt ";
|
||
|
|
case BusTypeFileBackedVirtual:
|
||
|
|
return "FBVir";
|
||
|
|
#ifdef BusTypeSpaces
|
||
|
|
case BusTypeSpaces:
|
||
|
|
#else
|
||
|
|
case 0x10:
|
||
|
|
#endif
|
||
|
|
return "Spaces";
|
||
|
|
#ifdef BusTypeNvme
|
||
|
|
case BusTypeNvme:
|
||
|
|
#else
|
||
|
|
case 0x11:
|
||
|
|
#endif
|
||
|
|
return "NVMe ";
|
||
|
|
#ifdef BusTypeSCM
|
||
|
|
case BusTypeSCM:
|
||
|
|
#else
|
||
|
|
case 0x12:
|
||
|
|
#endif
|
||
|
|
return "SCM ";
|
||
|
|
#ifdef BusTypeUfs
|
||
|
|
case BusTypeUfs:
|
||
|
|
#else
|
||
|
|
case 0x13:
|
||
|
|
#endif
|
||
|
|
return "Ufs ";
|
||
|
|
case 0x14:
|
||
|
|
return "Max ";
|
||
|
|
case 0x7f:
|
||
|
|
return "Max Reserved";
|
||
|
|
default:
|
||
|
|
return "_unkn";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static int
|
||
|
|
query_dev_property(HANDLE hdevice,
|
||
|
|
union STORAGE_DEVICE_DESCRIPTOR_DATA * data)
|
||
|
|
{
|
||
|
|
DWORD num_out, err;
|
||
|
|
char b[256];
|
||
|
|
STORAGE_PROPERTY_QUERY query = {StorageDeviceProperty,
|
||
|
|
PropertyStandardQuery, {0} };
|
||
|
|
|
||
|
|
memset(data, 0, sizeof(*data));
|
||
|
|
if (! DeviceIoControl(hdevice, IOCTL_STORAGE_QUERY_PROPERTY,
|
||
|
|
&query, sizeof(query), data, sizeof(*data),
|
||
|
|
&num_out, NULL)) {
|
||
|
|
if (verbose > 2) {
|
||
|
|
err = GetLastError();
|
||
|
|
pr2serr(" IOCTL_STORAGE_QUERY_PROPERTY(Devprop) failed, "
|
||
|
|
"Error=%u %s\n", (unsigned int)err,
|
||
|
|
get_err_str(err, sizeof(b), b));
|
||
|
|
}
|
||
|
|
return -ENOSYS;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (verbose > 3)
|
||
|
|
pr2serr(" IOCTL_STORAGE_QUERY_PROPERTY(DevProp) num_out=%u\n",
|
||
|
|
(unsigned int)num_out);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int
|
||
|
|
query_dev_uid(HANDLE hdevice, union STORAGE_DEVICE_UID_DATA * data)
|
||
|
|
{
|
||
|
|
DWORD num_out, err;
|
||
|
|
char b[256];
|
||
|
|
STORAGE_PROPERTY_QUERY query = {StorageDeviceUniqueIdProperty,
|
||
|
|
PropertyStandardQuery, {0} };
|
||
|
|
|
||
|
|
memset(data, 0, sizeof(*data));
|
||
|
|
num_out = 0;
|
||
|
|
query.QueryType = PropertyExistsQuery;
|
||
|
|
if (! DeviceIoControl(hdevice, IOCTL_STORAGE_QUERY_PROPERTY,
|
||
|
|
&query, sizeof(query), NULL, 0, &num_out, NULL)) {
|
||
|
|
if (verbose > 2) {
|
||
|
|
err = GetLastError();
|
||
|
|
pr2serr(" IOCTL_STORAGE_QUERY_PROPERTY(DevUid(exists)) failed, "
|
||
|
|
"Error=%u %s\n", (unsigned int)err,
|
||
|
|
get_err_str(err, sizeof(b), b));
|
||
|
|
}
|
||
|
|
if (verbose > 3)
|
||
|
|
pr2serr(" num_out=%u\n", (unsigned int)num_out);
|
||
|
|
/* interpret any error to mean this property doesn't exist */
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
query.QueryType = PropertyStandardQuery;
|
||
|
|
if (! DeviceIoControl(hdevice, IOCTL_STORAGE_QUERY_PROPERTY,
|
||
|
|
&query, sizeof(query), data, sizeof(*data),
|
||
|
|
&num_out, NULL)) {
|
||
|
|
if (verbose > 2) {
|
||
|
|
err = GetLastError();
|
||
|
|
pr2serr(" IOCTL_STORAGE_QUERY_PROPERTY(DevUid) failed, Error=%u "
|
||
|
|
"%s\n", (unsigned int)err,
|
||
|
|
get_err_str(err, sizeof(b), b));
|
||
|
|
}
|
||
|
|
return -ENOSYS;
|
||
|
|
}
|
||
|
|
if (verbose > 3)
|
||
|
|
pr2serr(" IOCTL_STORAGE_QUERY_PROPERTY(DevUid) num_out=%u\n",
|
||
|
|
(unsigned int)num_out);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Updates storage_arr based on sep. Returns 1 if update occurred, 0 if
|
||
|
|
* no update occurred. */
|
||
|
|
static int
|
||
|
|
check_devices(const struct storage_elem * sep)
|
||
|
|
{
|
||
|
|
int k, j;
|
||
|
|
struct storage_elem * sarr = storage_arr;
|
||
|
|
|
||
|
|
for (k = 0; k < next_unused_elem; ++k, ++sarr) {
|
||
|
|
if ('\0' == sarr->name[0])
|
||
|
|
continue;
|
||
|
|
if (sep->qp_uid_valid && sarr->qp_uid_valid) {
|
||
|
|
if (0 == memcmp(&sep->qp_uid, &sarr->qp_uid,
|
||
|
|
sizeof(sep->qp_uid))) {
|
||
|
|
for (j = 0; j < (int)sizeof(sep->volume_letters); ++j) {
|
||
|
|
if ('\0' == sarr->volume_letters[j]) {
|
||
|
|
sarr->volume_letters[j] = sep->name[0];
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
} else if (sep->qp_descriptor_valid && sarr->qp_descriptor_valid) {
|
||
|
|
if (0 == memcmp(&sep->qp_descriptor, &sarr->qp_descriptor,
|
||
|
|
sizeof(sep->qp_descriptor))) {
|
||
|
|
for (j = 0; j < (int)sizeof(sep->volume_letters); ++j) {
|
||
|
|
if ('\0' == sarr->volume_letters[j]) {
|
||
|
|
sarr->volume_letters[j] = sep->name[0];
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int
|
||
|
|
enum_scsi_adapters(void)
|
||
|
|
{
|
||
|
|
int k, j;
|
||
|
|
int hole_count = 0;
|
||
|
|
HANDLE fh;
|
||
|
|
ULONG dummy;
|
||
|
|
DWORD err = 0;
|
||
|
|
BYTE bus;
|
||
|
|
BOOL success;
|
||
|
|
char adapter_name[64];
|
||
|
|
char * inq_dbp;
|
||
|
|
uint8_t * free_inq_dbp = NULL;
|
||
|
|
PSCSI_ADAPTER_BUS_INFO ai;
|
||
|
|
char b[256];
|
||
|
|
|
||
|
|
inq_dbp = (char *)sg_memalign(MAX_GET_INQUIRY_DATA_SZ, 0, &free_inq_dbp,
|
||
|
|
false);
|
||
|
|
if (NULL == inq_dbp) {
|
||
|
|
pr2serr("%s: unable to allocate %d bytes on heap\n", __func__,
|
||
|
|
MAX_GET_INQUIRY_DATA_SZ);
|
||
|
|
return sg_convert_errno(ENOMEM);
|
||
|
|
}
|
||
|
|
|
||
|
|
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, inq_dbp, MAX_GET_INQUIRY_DATA_SZ,
|
||
|
|
&dummy, NULL);
|
||
|
|
if (success) {
|
||
|
|
PSCSI_BUS_DATA pbd;
|
||
|
|
PSCSI_INQUIRY_DATA pid;
|
||
|
|
int num_lus, off;
|
||
|
|
|
||
|
|
ai = (PSCSI_ADAPTER_BUS_INFO)inq_dbp;
|
||
|
|
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 > (MAX_GET_INQUIRY_DATA_SZ -
|
||
|
|
(int)sizeof(SCSI_INQUIRY_DATA))))
|
||
|
|
break;
|
||
|
|
pid = (PSCSI_INQUIRY_DATA)(inq_dbp + off);
|
||
|
|
snprintf(b, sizeof(b) - 1, "SCSI%d:%d,%d,%d ", k,
|
||
|
|
pid->PathId, pid->TargetId, pid->Lun);
|
||
|
|
printf("%-15s", b);
|
||
|
|
snprintf(b, sizeof(b) - 1, "claimed=%d pdt=%xh %s ",
|
||
|
|
pid->DeviceClaimed,
|
||
|
|
pid->InquiryData[0] % PDT_MASK,
|
||
|
|
((0 == pid->InquiryData[4]) ? "dubious" :
|
||
|
|
""));
|
||
|
|
printf("%-26s", b);
|
||
|
|
printf("%.8s %.16s %.4s\n", pid->InquiryData + 8,
|
||
|
|
pid->InquiryData + 16, pid->InquiryData + 32);
|
||
|
|
off = pid->NextInquiryDataOffset;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
err = GetLastError();
|
||
|
|
pr2serr("%s: IOCTL_SCSI_GET_INQUIRY_DATA failed err=%u\n\t%s",
|
||
|
|
adapter_name, (unsigned int)err,
|
||
|
|
get_err_str(err, sizeof(b), b));
|
||
|
|
err = SG_LIB_WINDOWS_ERR;
|
||
|
|
}
|
||
|
|
CloseHandle(fh);
|
||
|
|
} else {
|
||
|
|
err = GetLastError();
|
||
|
|
if (ERROR_SHARING_VIOLATION == err)
|
||
|
|
pr2serr("%s: in use by other process (sharing violation "
|
||
|
|
"[34])\n", adapter_name);
|
||
|
|
else if (verbose > 3)
|
||
|
|
pr2serr("%s: CreateFile failed err=%u\n\t%s", adapter_name,
|
||
|
|
(unsigned int)err, get_err_str(err, sizeof(b), b));
|
||
|
|
if (++hole_count >= MAX_HOLE_COUNT)
|
||
|
|
break;
|
||
|
|
/* hope problem is local to this adapter so continue to next */
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (free_inq_dbp)
|
||
|
|
free(free_inq_dbp);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int
|
||
|
|
enum_volumes(char letter)
|
||
|
|
{
|
||
|
|
int k;
|
||
|
|
HANDLE fh;
|
||
|
|
char adapter_name[64];
|
||
|
|
struct storage_elem tmp_se;
|
||
|
|
|
||
|
|
if (verbose > 2)
|
||
|
|
pr2serr("%s: enter\n", __FUNCTION__ );
|
||
|
|
for (k = 0; k < 24; ++k) {
|
||
|
|
memset(&tmp_se, 0, sizeof(tmp_se));
|
||
|
|
snprintf(adapter_name, sizeof (adapter_name), "\\\\.\\%c:", 'C' + k);
|
||
|
|
tmp_se.name[0] = '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) {
|
||
|
|
if (query_dev_property(fh, &tmp_se.qp_descriptor) < 0)
|
||
|
|
pr2serr("%s: query_dev_property failed\n", __FUNCTION__ );
|
||
|
|
else
|
||
|
|
tmp_se.qp_descriptor_valid = true;
|
||
|
|
if (query_dev_uid(fh, &tmp_se.qp_uid) < 0) {
|
||
|
|
if (verbose > 2)
|
||
|
|
pr2serr("%s: query_dev_uid failed\n", __FUNCTION__ );
|
||
|
|
} else
|
||
|
|
tmp_se.qp_uid_valid = true;
|
||
|
|
if (('\0' == letter) || (letter == tmp_se.name[0]))
|
||
|
|
check_devices(&tmp_se);
|
||
|
|
CloseHandle(fh);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int
|
||
|
|
enum_pds(void)
|
||
|
|
{
|
||
|
|
int k;
|
||
|
|
int hole_count = 0;
|
||
|
|
HANDLE fh;
|
||
|
|
DWORD err;
|
||
|
|
char adapter_name[64];
|
||
|
|
char b[256];
|
||
|
|
struct storage_elem tmp_se;
|
||
|
|
|
||
|
|
if (verbose > 2)
|
||
|
|
pr2serr("%s: enter\n", __FUNCTION__ );
|
||
|
|
for (k = 0; k < MAX_PHYSICALDRIVE_NUM; ++k) {
|
||
|
|
memset(&tmp_se, 0, sizeof(tmp_se));
|
||
|
|
snprintf(adapter_name, sizeof (adapter_name),
|
||
|
|
"\\\\.\\PhysicalDrive%d", k);
|
||
|
|
snprintf(tmp_se.name, sizeof(tmp_se.name), "PD%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) {
|
||
|
|
if (query_dev_property(fh, &tmp_se.qp_descriptor) < 0)
|
||
|
|
pr2serr("%s: query_dev_property failed\n", __FUNCTION__ );
|
||
|
|
else
|
||
|
|
tmp_se.qp_descriptor_valid = true;
|
||
|
|
if (query_dev_uid(fh, &tmp_se.qp_uid) < 0) {
|
||
|
|
if (verbose > 2)
|
||
|
|
pr2serr("%s: query_dev_uid failed\n", __FUNCTION__ );
|
||
|
|
} else
|
||
|
|
tmp_se.qp_uid_valid = true;
|
||
|
|
hole_count = 0;
|
||
|
|
memcpy(&storage_arr[next_unused_elem++], &tmp_se, sizeof(tmp_se));
|
||
|
|
CloseHandle(fh);
|
||
|
|
} else {
|
||
|
|
err = GetLastError();
|
||
|
|
if ((0 == k) && (ERROR_ACCESS_DENIED == err))
|
||
|
|
pr2serr("Access denied on %s, may need Administrator\n",
|
||
|
|
adapter_name);
|
||
|
|
if (ERROR_SHARING_VIOLATION == err)
|
||
|
|
pr2serr("%s: in use by other process (sharing violation "
|
||
|
|
"[34])\n", adapter_name);
|
||
|
|
else if (verbose > 3)
|
||
|
|
pr2serr("%s: CreateFile failed err=%u\n\t%s", adapter_name,
|
||
|
|
(unsigned int)err, get_err_str(err, sizeof(b), b));
|
||
|
|
if (++hole_count >= MAX_HOLE_COUNT)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int
|
||
|
|
enum_cdroms(void)
|
||
|
|
{
|
||
|
|
int k;
|
||
|
|
int hole_count = 0;
|
||
|
|
HANDLE fh;
|
||
|
|
DWORD err;
|
||
|
|
char adapter_name[64];
|
||
|
|
char b[256];
|
||
|
|
struct storage_elem tmp_se;
|
||
|
|
|
||
|
|
if (verbose > 2)
|
||
|
|
pr2serr("%s: enter\n", __FUNCTION__ );
|
||
|
|
for (k = 0; k < MAX_CDROM_NUM; ++k) {
|
||
|
|
memset(&tmp_se, 0, sizeof(tmp_se));
|
||
|
|
snprintf(adapter_name, sizeof (adapter_name), "\\\\.\\CDROM%d", k);
|
||
|
|
snprintf(tmp_se.name, sizeof(tmp_se.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) {
|
||
|
|
if (query_dev_property(fh, &tmp_se.qp_descriptor) < 0)
|
||
|
|
pr2serr("%s: query_dev_property failed\n", __FUNCTION__ );
|
||
|
|
else
|
||
|
|
tmp_se.qp_descriptor_valid = true;
|
||
|
|
if (query_dev_uid(fh, &tmp_se.qp_uid) < 0) {
|
||
|
|
if (verbose > 2)
|
||
|
|
pr2serr("%s: query_dev_uid failed\n", __FUNCTION__ );
|
||
|
|
} else
|
||
|
|
tmp_se.qp_uid_valid = true;
|
||
|
|
hole_count = 0;
|
||
|
|
memcpy(&storage_arr[next_unused_elem++], &tmp_se, sizeof(tmp_se));
|
||
|
|
CloseHandle(fh);
|
||
|
|
} else {
|
||
|
|
err = GetLastError();
|
||
|
|
if (ERROR_SHARING_VIOLATION == err)
|
||
|
|
pr2serr("%s: in use by other process (sharing violation "
|
||
|
|
"[34])\n", adapter_name);
|
||
|
|
else if (verbose > 3)
|
||
|
|
pr2serr("%s: CreateFile failed err=%u\n\t%s", adapter_name,
|
||
|
|
(unsigned int)err, get_err_str(err, sizeof(b), b));
|
||
|
|
if (++hole_count >= MAX_HOLE_COUNT)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int
|
||
|
|
enum_tapes(void)
|
||
|
|
{
|
||
|
|
int k;
|
||
|
|
int hole_count = 0;
|
||
|
|
HANDLE fh;
|
||
|
|
DWORD err;
|
||
|
|
char adapter_name[64];
|
||
|
|
char b[256];
|
||
|
|
struct storage_elem tmp_se;
|
||
|
|
|
||
|
|
if (verbose > 2)
|
||
|
|
pr2serr("%s: enter\n", __FUNCTION__ );
|
||
|
|
for (k = 0; k < MAX_TAPE_NUM; ++k) {
|
||
|
|
memset(&tmp_se, 0, sizeof(tmp_se));
|
||
|
|
snprintf(adapter_name, sizeof (adapter_name), "\\\\.\\TAPE%d", k);
|
||
|
|
snprintf(tmp_se.name, sizeof(tmp_se.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) {
|
||
|
|
if (query_dev_property(fh, &tmp_se.qp_descriptor) < 0)
|
||
|
|
pr2serr("%s: query_dev_property failed\n", __FUNCTION__ );
|
||
|
|
else
|
||
|
|
tmp_se.qp_descriptor_valid = true;
|
||
|
|
if (query_dev_uid(fh, &tmp_se.qp_uid) < 0) {
|
||
|
|
if (verbose > 2)
|
||
|
|
pr2serr("%s: query_dev_uid failed\n", __FUNCTION__ );
|
||
|
|
} else
|
||
|
|
tmp_se.qp_uid_valid = true;
|
||
|
|
hole_count = 0;
|
||
|
|
memcpy(&storage_arr[next_unused_elem++], &tmp_se, sizeof(tmp_se));
|
||
|
|
CloseHandle(fh);
|
||
|
|
} else {
|
||
|
|
err = GetLastError();
|
||
|
|
if (ERROR_SHARING_VIOLATION == err)
|
||
|
|
pr2serr("%s: in use by other process (sharing violation "
|
||
|
|
"[34])\n", adapter_name);
|
||
|
|
else if (verbose > 3)
|
||
|
|
pr2serr("%s: CreateFile failed err=%u\n\t%s", adapter_name,
|
||
|
|
(unsigned int)err, get_err_str(err, sizeof(b), b));
|
||
|
|
if (++hole_count >= MAX_HOLE_COUNT)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int
|
||
|
|
sg_do_wscan(char letter, bool show_bt, int scsi_scan)
|
||
|
|
{
|
||
|
|
int k, j, n;
|
||
|
|
struct storage_elem * sp;
|
||
|
|
|
||
|
|
if (scsi_scan < 2) {
|
||
|
|
k = enum_pds();
|
||
|
|
if (k)
|
||
|
|
return k;
|
||
|
|
k = enum_cdroms();
|
||
|
|
if (k)
|
||
|
|
return k;
|
||
|
|
k = enum_tapes();
|
||
|
|
if (k)
|
||
|
|
return k;
|
||
|
|
k = enum_volumes(letter);
|
||
|
|
if (k)
|
||
|
|
return k;
|
||
|
|
|
||
|
|
for (k = 0; k < next_unused_elem; ++k) {
|
||
|
|
sp = storage_arr + k;
|
||
|
|
if ('\0' == sp->name[0])
|
||
|
|
continue;
|
||
|
|
printf("%-7s ", sp->name);
|
||
|
|
n = strlen(sp->volume_letters);
|
||
|
|
if (0 == n)
|
||
|
|
printf(" ");
|
||
|
|
else if (1 == n)
|
||
|
|
printf("[%s] ", sp->volume_letters);
|
||
|
|
else if (2 == n)
|
||
|
|
printf("[%s] ", sp->volume_letters);
|
||
|
|
else if (3 == n)
|
||
|
|
printf("[%s] ", sp->volume_letters);
|
||
|
|
else if (4 == n)
|
||
|
|
printf("[%s] ", sp->volume_letters);
|
||
|
|
else
|
||
|
|
printf("[%4s+] ", sp->volume_letters);
|
||
|
|
if (sp->qp_descriptor_valid) {
|
||
|
|
if (show_bt)
|
||
|
|
printf("<%s> ",
|
||
|
|
get_bus_type(sp->qp_descriptor.desc.BusType));
|
||
|
|
j = sp->qp_descriptor.desc.VendorIdOffset;
|
||
|
|
if (j > 0)
|
||
|
|
printf("%s ", sp->qp_descriptor.raw + j);
|
||
|
|
j = sp->qp_descriptor.desc.ProductIdOffset;
|
||
|
|
if (j > 0)
|
||
|
|
printf("%s ", sp->qp_descriptor.raw + j);
|
||
|
|
j = sp->qp_descriptor.desc.ProductRevisionOffset;
|
||
|
|
if (j > 0)
|
||
|
|
printf("%s ", sp->qp_descriptor.raw + j);
|
||
|
|
j = sp->qp_descriptor.desc.SerialNumberOffset;
|
||
|
|
if (j > 0)
|
||
|
|
printf("%s", sp->qp_descriptor.raw + j);
|
||
|
|
printf("\n");
|
||
|
|
if (verbose > 2)
|
||
|
|
hex2stderr((const uint8_t *)sp->qp_descriptor.raw, 144, 0);
|
||
|
|
} else
|
||
|
|
printf("\n");
|
||
|
|
if ((verbose > 3) && sp->qp_uid_valid) {
|
||
|
|
printf(" UID valid, in hex:\n");
|
||
|
|
hex2stderr((const uint8_t *)sp->qp_uid.raw,
|
||
|
|
sizeof(sp->qp_uid.raw), 0);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (scsi_scan) {
|
||
|
|
if (scsi_scan < 2)
|
||
|
|
printf("\n");
|
||
|
|
enum_scsi_adapters();
|
||
|
|
}
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
int
|
||
|
|
main(int argc, char * argv[])
|
||
|
|
{
|
||
|
|
bool show_bt = false;
|
||
|
|
int c, ret;
|
||
|
|
int vol_letter = 0;
|
||
|
|
int scsi_scan = 0;
|
||
|
|
|
||
|
|
while (1) {
|
||
|
|
int option_index = 0;
|
||
|
|
|
||
|
|
c = getopt_long(argc, argv, "bhHl:svV", long_options,
|
||
|
|
&option_index);
|
||
|
|
if (c == -1)
|
||
|
|
break;
|
||
|
|
|
||
|
|
switch (c) {
|
||
|
|
case 'b':
|
||
|
|
show_bt = true;
|
||
|
|
break;
|
||
|
|
case 'h':
|
||
|
|
case '?':
|
||
|
|
usage();
|
||
|
|
return 0;
|
||
|
|
case 'l':
|
||
|
|
vol_letter = toupper(optarg[0]);
|
||
|
|
if ((vol_letter < 'C') || (vol_letter > 'Z')) {
|
||
|
|
pr2serr("'--letter=' expects a letter in the 'C' to 'Z' "
|
||
|
|
"range\n");
|
||
|
|
usage();
|
||
|
|
return SG_LIB_SYNTAX_ERROR;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 's':
|
||
|
|
++scsi_scan;
|
||
|
|
break;
|
||
|
|
case 'v':
|
||
|
|
++verbose;
|
||
|
|
break;
|
||
|
|
case 'V':
|
||
|
|
pr2serr("version: %s\n", version_str);
|
||
|
|
return 0;
|
||
|
|
default:
|
||
|
|
pr2serr("unrecognised option code 0x%x ??\n", c);
|
||
|
|
usage();
|
||
|
|
return SG_LIB_SYNTAX_ERROR;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (optind < argc) {
|
||
|
|
if (optind < argc) {
|
||
|
|
for (; optind < argc; ++optind)
|
||
|
|
pr2serr("Unexpected extra argument: %s\n", argv[optind]);
|
||
|
|
usage();
|
||
|
|
return SG_LIB_SYNTAX_ERROR;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
storage_arr = (struct storage_elem *)
|
||
|
|
sg_memalign(sizeof(struct storage_elem) * MAX_SCSI_ELEMS, 0,
|
||
|
|
&free_storage_arr, false);
|
||
|
|
if (storage_arr) {
|
||
|
|
ret = sg_do_wscan(vol_letter, show_bt, scsi_scan);
|
||
|
|
if (free_storage_arr)
|
||
|
|
free(free_storage_arr);
|
||
|
|
} else {
|
||
|
|
pr2serr("Failed to allocate storage_arr (%d bytes) on heap\n",
|
||
|
|
(int)(sizeof(struct storage_elem) * MAX_SCSI_ELEMS));
|
||
|
|
ret = sg_convert_errno(ENOMEM);
|
||
|
|
}
|
||
|
|
return ret;
|
||
|
|
}
|