aboutsummaryrefslogtreecommitdiff
path: root/src/sg_scan_linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sg_scan_linux.c')
-rw-r--r--src/sg_scan_linux.c629
1 files changed, 629 insertions, 0 deletions
diff --git a/src/sg_scan_linux.c b/src/sg_scan_linux.c
new file mode 100644
index 00000000..0dddaa83
--- /dev/null
+++ b/src/sg_scan_linux.c
@@ -0,0 +1,629 @@
+/* A utility program originally written for the Linux OS SCSI subsystem.
+ * Copyright (C) 1999 - 2022 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.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE 1
+#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"
+#include "sg_pr2serr.h"
+
+
+static const char * version_str = "4.18 20220118";
+
+#define ME "sg_scan: "
+
+#define NUMERIC_SCAN_DEF true /* change to false 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 8192
+
+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, uint8_t * inqBuff, bool do_extra);
+int scsi_inq(int sg_fd, uint8_t * inqBuff);
+int try_ata_identity(const char * file_namep, int ata_fd, bool do_inq);
+
+static uint8_t inq_cdb[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, bool 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[])
+{
+ bool do_extra = false;
+ bool do_inquiry = false;
+ bool do_numeric = NUMERIC_SCAN_DEF;
+ bool eacces_err = false;
+ bool has_file_args = false;
+ bool has_sysfs_sg = false;
+ bool jmp_out;
+ bool sg_ver3 = false;
+ bool sg_ver3_set = false;
+ bool writeable = false;
+ int sg_fd, res, k, j, f, plen;
+ int emul = -1;
+ int flags;
+ int host_no;
+ const int max_file_args = PRESENT_ARRAY_SIZE;
+ int num_errors = 0;
+ int num_silent = 0;
+ int verbose = 0;
+ char * file_namep;
+ const char * cp;
+ char fname[FNAME_SZ];
+ char ebuff[EBUFF_SZ];
+ uint8_t inqBuff[INQ_REPLY_LEN];
+ My_scsi_idlun my_idlun;
+ struct stat a_stat;
+
+ if (NULL == (gen_index_arr =
+ (int *)calloc(max_file_args + 1, sizeof(int)))) {
+ printf(ME "Out of memory\n");
+ return SG_LIB_CAT_OTHER;
+ }
+ strcpy(fname, "<null>");
+
+ 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 = false; plen > 0; --plen, ++cp) {
+ switch (*cp) {
+ case 'a':
+ do_numeric = false;
+ break;
+ case 'h':
+ case '?':
+ printf("Scan sg device names and optionally do an "
+ "INQUIRY\n\n");
+ usage();
+ return 0;
+ case 'i':
+ do_inquiry = true;
+ break;
+ case 'n':
+ do_numeric = true;
+ break;
+ case 'v':
+ ++verbose;
+ break;
+ case 'V':
+ pr2serr("Version string: %s\n", version_str);
+ exit(0);
+ case 'w':
+ writeable = true;
+ break;
+ case 'x':
+ do_extra = true;
+ break;
+ default:
+ jmp_out = true;
+ break;
+ }
+ if (jmp_out)
+ break;
+ }
+ if (plen <= 0)
+ continue;
+ if (jmp_out) {
+ pr2serr("Unrecognized option: %s\n", cp);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ } else {
+ if (j < max_file_args) {
+ has_file_args = true;
+ 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)
+ pr2serr("Unable to open: %s, errno=%d\n", file_namep,
+ errno);
+ ++num_errors;
+ ++num_silent;
+ continue;
+ }
+ else {
+ if (EACCES == errno)
+ eacces_err = true;
+ 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 (! sg_ver3_set) {
+ sg_ver3 = false;
+ sg_ver3_set = true;
+ if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &f) >= 0) &&
+ (f >= 30000))
+ sg_ver3 = true;
+ }
+ if (sg_ver3) {
+ res = sg3_inq(sg_fd, inqBuff, do_extra);
+ if (res)
+ ++num_errors;
+ }
+ }
+ }
+ 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, uint8_t * inqBuff, bool do_extra)
+{
+ bool ok;
+ int err, sg_io;
+ uint8_t sense_buffer[32] SG_C_CPP_ZERO_INIT;
+ struct sg_io_hdr io_hdr SG_C_CPP_ZERO_INIT;
+
+ memset(inqBuff, 0, INQ_REPLY_LEN);
+ inqBuff[0] = 0x7f;
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(inq_cdb);
+ 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 = inq_cdb;
+ io_hdr.sbp = sense_buffer;
+ io_hdr.timeout = 20000; /* 20000 millisecs == 20 seconds */
+
+ ok = true;
+ 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, true);
+#if defined(__GNUC__)
+#if (__GNUC__ >= 7)
+ __attribute__((fallthrough));
+ /* FALL THROUGH */
+#endif
+#endif
+ case SG_LIB_CAT_CLEAN:
+ break;
+ default: /* won't bother decoding other categories */
+ ok = false;
+ sg_chk_n_print3("INQUIRY command error", &io_hdr, true);
+ 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] & PDT_MASK));
+ 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;
+ uint8_t 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, uint8_t * inqBuff)
+{
+ int res;
+ uint8_t buff[1024];
+ 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, inq_cdb, 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];
+ uint8_t serial_no[20];
+ unsigned short words020_022[3];
+ uint8_t fw_rev[8];
+ uint8_t 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, num;
+
+ /* 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;
+ num = last - first + 1;
+ for (k = 0; k < num; ++k)
+ out[k] = in[first + k];
+ out[num] = '\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)
+{
+ uint8_t 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, bool 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;
+}