xref: /aosp_15_r20/external/sg3_utils/src/sg_scan_linux.c (revision 44704f698541f6367e81f991ef8bb54ccbf3fc18)
1 /* A utility program originally written for the Linux OS SCSI subsystem.
2  *  Copyright (C) 1999 - 2022 D. Gilbert
3  *  This program is free software; you can redistribute it and/or modify
4  *  it under the terms of the GNU General Public License as published by
5  *  the Free Software Foundation; either version 2, or (at your option)
6  *  any later version.
7  *
8  * SPDX-License-Identifier: GPL-2.0-or-later
9  *
10  * This program scans the "sg" device space (ie actual + simulated SCSI
11  * generic devices). Optionally sg_scan can be given other device names
12  * to scan (in place of the sg devices).
13  * Options: -a   alpha scan: scan /dev/sga,b,c, ....
14  *          -i   do SCSI inquiry on device (implies -w)
15  *          -n   numeric scan: scan /dev/sg0,1,2, ....
16  *          -V   output version string and exit
17  *          -w   open writable (new driver opens readable unless -i)
18  *          -x   extra information output
19  *
20  * By default this program will look for /dev/sg0 first (i.e. numeric scan)
21  *
22  * Note: This program is written to work under both the original and
23  * the new sg driver.
24  *
25  * F. Jansen - modification to extend beyond 26 sg devices.
26  */
27 
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31 
32 
33 #ifndef _GNU_SOURCE
34 #define _GNU_SOURCE 1
35 #endif
36 
37 #include <unistd.h>
38 #include <fcntl.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <ctype.h>
43 #include <errno.h>
44 #include <dirent.h>
45 #include <libgen.h>
46 #include <sys/ioctl.h>
47 #include <sys/types.h>
48 #include <sys/stat.h>
49 #include <scsi/scsi_ioctl.h>
50 
51 #include "sg_lib.h"
52 #include "sg_io_linux.h"
53 #include "sg_pr2serr.h"
54 
55 
56 static const char * version_str = "4.18 20220118";
57 
58 #define ME "sg_scan: "
59 
60 #define NUMERIC_SCAN_DEF true /* change to false to make alpha scan default */
61 
62 #define INQ_REPLY_LEN 36
63 #define INQ_CMD_LEN 6
64 #define MAX_ERRORS 4
65 
66 #define EBUFF_SZ 256
67 #define FNAME_SZ 64
68 #define PRESENT_ARRAY_SIZE 8192
69 
70 static const char * sysfs_sg_dir = "/sys/class/scsi_generic";
71 static int * gen_index_arr;
72 
73 typedef struct my_scsi_idlun {
74 /* why can't userland see this structure ??? */
75     int dev_id;
76     int host_unique_id;
77 } My_scsi_idlun;
78 
79 typedef struct my_sg_scsi_id {
80     int host_no;        /* as in "scsi<n>" where 'n' is one of 0, 1, 2 etc */
81     int channel;
82     int scsi_id;        /* scsi id of target device */
83     int lun;
84     int scsi_type;      /* TYPE_... defined in scsi/scsi.h */
85     short h_cmd_per_lun;/* host (adapter) maximum commands per lun */
86     short d_queue_depth;/* device (or adapter) maximum queue length */
87     int unused1;        /* probably find a good use, set 0 for now */
88     int unused2;        /* ditto */
89 } My_sg_scsi_id;
90 
91 int sg3_inq(int sg_fd, uint8_t * inqBuff, bool do_extra);
92 int scsi_inq(int sg_fd, uint8_t * inqBuff);
93 int try_ata_identity(const char * file_namep, int ata_fd, bool do_inq);
94 
95 static uint8_t inq_cdb[INQ_CMD_LEN] =
96                                 {0x12, 0, 0, 0, INQ_REPLY_LEN, 0};
97 
98 
usage()99 void usage()
100 {
101     printf("Usage: sg_scan [-a] [-i] [-n] [-v] [-V] [-w] [-x] "
102            "[DEVICE]*\n");
103     printf("  where:\n");
104     printf("    -a    do alpha scan (ie sga, sgb, sgc)\n");
105     printf("    -i    do SCSI INQUIRY, output results\n");
106     printf("    -n    do numeric scan (ie sg0, sg1...) [default]\n");
107     printf("    -v    increase verbosity\n");
108     printf("    -V    output version string then exit\n");
109     printf("    -w    force open with read/write flag\n");
110     printf("    -x    extra information output about queuing\n");
111     printf("   DEVICE    name of device\n");
112 }
113 
scandir_select(const struct dirent * s)114 static int scandir_select(const struct dirent * s)
115 {
116     int k;
117 
118     if (1 == sscanf(s->d_name, "sg%d", &k)) {
119         if ((k >= 0) && (k < PRESENT_ARRAY_SIZE)) {
120             gen_index_arr[k] = 1;
121             return 1;
122         }
123     }
124     return 0;
125 }
126 
sysfs_sg_scan(const char * dir_name)127 static int sysfs_sg_scan(const char * dir_name)
128 {
129     struct dirent ** namelist;
130     int num, k;
131 
132     num = scandir(dir_name, &namelist, scandir_select, NULL);
133     if (num < 0)
134         return -errno;
135     for (k = 0; k < num; ++k)
136         free(namelist[k]);
137     free(namelist);
138     return num;
139 }
140 
make_dev_name(char * fname,int k,bool do_numeric)141 void make_dev_name(char * fname, int k, bool do_numeric)
142 {
143     char buff[FNAME_SZ];
144     int  big,little;
145 
146     strcpy(fname, "/dev/sg");
147     if (do_numeric) {
148         snprintf(buff, sizeof(buff), "%d", k);
149         strcat(fname, buff);
150     }
151     else {
152         if (k < 26) {
153             buff[0] = 'a' + (char)k;
154             buff[1] = '\0';
155             strcat(fname, buff);
156         }
157         else if (k <= 255) { /* assumes sequence goes x,y,z,aa,ab,ac etc */
158             big    = k/26;
159             little = k - (26 * big);
160             big    = big - 1;
161 
162             buff[0] = 'a' + (char)big;
163             buff[1] = 'a' + (char)little;
164             buff[2] = '\0';
165             strcat(fname, buff);
166         }
167         else
168             strcat(fname, "xxxx");
169     }
170 }
171 
172 
main(int argc,char * argv[])173 int main(int argc, char * argv[])
174 {
175     bool do_extra = false;
176     bool do_inquiry = false;
177     bool do_numeric = NUMERIC_SCAN_DEF;
178     bool eacces_err = false;
179     bool has_file_args = false;
180     bool has_sysfs_sg = false;
181     bool jmp_out;
182     bool sg_ver3 = false;
183     bool sg_ver3_set = false;
184     bool writeable = false;
185     int sg_fd, res, k, j, f, plen;
186     int emul = -1;
187     int flags;
188     int host_no;
189     const int max_file_args = PRESENT_ARRAY_SIZE;
190     int num_errors = 0;
191     int num_silent = 0;
192     int verbose = 0;
193     char * file_namep;
194     const char * cp;
195     char fname[FNAME_SZ];
196     char ebuff[EBUFF_SZ];
197     uint8_t inqBuff[INQ_REPLY_LEN];
198     My_scsi_idlun my_idlun;
199     struct stat a_stat;
200 
201     if (NULL == (gen_index_arr =
202                  (int *)calloc(max_file_args + 1, sizeof(int)))) {
203         printf(ME "Out of memory\n");
204         return SG_LIB_CAT_OTHER;
205     }
206     strcpy(fname, "<null>");
207 
208     for (k = 1, j = 0; k < argc; ++k) {
209         cp = argv[k];
210         plen = strlen(cp);
211         if (plen <= 0)
212             continue;
213         if ('-' == *cp) {
214             for (--plen, ++cp, jmp_out = false; plen > 0; --plen, ++cp) {
215                 switch (*cp) {
216                 case 'a':
217                     do_numeric = false;
218                     break;
219                 case 'h':
220                 case '?':
221                     printf("Scan sg device names and optionally do an "
222                            "INQUIRY\n\n");
223                     usage();
224                     return 0;
225                 case 'i':
226                     do_inquiry = true;
227                     break;
228                 case 'n':
229                     do_numeric = true;
230                     break;
231                 case 'v':
232                     ++verbose;
233                     break;
234                 case 'V':
235                     pr2serr("Version string: %s\n", version_str);
236                     exit(0);
237                 case 'w':
238                     writeable = true;
239                     break;
240                 case 'x':
241                     do_extra = true;
242                     break;
243                 default:
244                     jmp_out = true;
245                     break;
246                 }
247                 if (jmp_out)
248                     break;
249             }
250             if (plen <= 0)
251                 continue;
252             if (jmp_out) {
253                 pr2serr("Unrecognized option: %s\n", cp);
254                 usage();
255                 return SG_LIB_SYNTAX_ERROR;
256             }
257         } else {
258             if (j < max_file_args) {
259                 has_file_args = true;
260                 gen_index_arr[j++] = k;
261             } else {
262                 printf("Too many command line arguments\n");
263                 return SG_LIB_SYNTAX_ERROR;
264             }
265         }
266     }
267 
268     if ((! has_file_args) && (stat(sysfs_sg_dir, &a_stat) >= 0) &&
269         (S_ISDIR(a_stat.st_mode)))
270         has_sysfs_sg = !! sysfs_sg_scan(sysfs_sg_dir);
271 
272     flags = O_NONBLOCK | (writeable ? O_RDWR : O_RDONLY);
273 
274     for (k = 0, res = 0, j = 0, sg_fd = -1;
275          (k < max_file_args)  && (has_file_args || (num_errors < MAX_ERRORS));
276          ++k, res = ((sg_fd >= 0) ? close(sg_fd) : 0)) {
277         if (res < 0) {
278             snprintf(ebuff, EBUFF_SZ, ME "Error closing %s ", fname);
279             perror(ebuff);
280             return SG_LIB_FILE_ERROR;
281         }
282         if (has_file_args) {
283             if (gen_index_arr[j])
284                 file_namep = argv[gen_index_arr[j++]];
285             else
286                 break;
287         } else if (has_sysfs_sg) {
288             if (0 == gen_index_arr[k]) {
289                 sg_fd = -1;
290                 continue;
291             }
292             make_dev_name(fname, k, 1);
293             file_namep = fname;
294         } else {
295             make_dev_name(fname, k, do_numeric);
296             file_namep = fname;
297         }
298 
299         sg_fd = open(file_namep, flags);
300         if (sg_fd < 0) {
301             if (EBUSY == errno) {
302                 printf("%s: device busy (O_EXCL lock), skipping\n",
303                        file_namep);
304                 continue;
305             }
306             else if ((ENODEV == errno) || (ENOENT == errno) ||
307                      (ENXIO == errno)) {
308                 if (verbose)
309                     pr2serr("Unable to open: %s, errno=%d\n", file_namep,
310                             errno);
311                 ++num_errors;
312                 ++num_silent;
313                 continue;
314             }
315             else {
316                 if (EACCES == errno)
317                     eacces_err = true;
318                 snprintf(ebuff, EBUFF_SZ, ME "Error opening %s ", file_namep);
319                 perror(ebuff);
320                 ++num_errors;
321                 continue;
322             }
323         }
324         res = ioctl(sg_fd, SCSI_IOCTL_GET_IDLUN, &my_idlun);
325         if (res < 0) {
326             res = try_ata_identity(file_namep, sg_fd, do_inquiry);
327             if (res == 0)
328                 continue;
329             snprintf(ebuff, EBUFF_SZ, ME "device %s failed on scsi+ata "
330                      "ioctl, skip", file_namep);
331             perror(ebuff);
332             ++num_errors;
333             continue;
334         }
335         res = ioctl(sg_fd, SCSI_IOCTL_GET_BUS_NUMBER, &host_no);
336         if (res < 0) {
337             snprintf(ebuff, EBUFF_SZ, ME "device %s failed on scsi "
338                      "ioctl(2), skip", file_namep);
339             perror(ebuff);
340             ++num_errors;
341             continue;
342         }
343         res = ioctl(sg_fd, SG_EMULATED_HOST, &emul);
344         if (res < 0)
345             emul = -1;
346         printf("%s: scsi%d channel=%d id=%d lun=%d", file_namep, host_no,
347                (my_idlun.dev_id >> 16) & 0xff, my_idlun.dev_id & 0xff,
348                (my_idlun.dev_id >> 8) & 0xff);
349         if (1 == emul)
350             printf(" [em]");
351 #if 0
352         printf(", huid=%d", my_idlun.host_unique_id);
353 #endif
354         if (! has_file_args) {
355             My_sg_scsi_id m_id; /* compatible with sg_scsi_id_t in sg.h */
356 
357             res = ioctl(sg_fd, SG_GET_SCSI_ID, &m_id);
358             if (res < 0) {
359                 snprintf(ebuff, EBUFF_SZ, ME "device %s failed "
360                          "SG_GET_SCSI_ID ioctl(4), skip", file_namep);
361                 perror(ebuff);
362                 ++num_errors;
363                 continue;
364             }
365             /* printf("  type=%d", m_id.scsi_type); */
366             if (do_extra)
367                 printf("  cmd_per_lun=%hd queue_depth=%hd\n",
368                        m_id.h_cmd_per_lun, m_id.d_queue_depth);
369             else
370                 printf("\n");
371         }
372         else
373             printf("\n");
374         if (do_inquiry) {
375             if (! sg_ver3_set) {
376                 sg_ver3 = false;
377                 sg_ver3_set = true;
378                 if ((ioctl(sg_fd, SG_GET_VERSION_NUM, &f) >= 0) &&
379                     (f >= 30000))
380                     sg_ver3 = true;
381             }
382             if (sg_ver3) {
383                 res = sg3_inq(sg_fd, inqBuff, do_extra);
384                 if (res)
385                     ++num_errors;
386             }
387         }
388     }
389     if ((num_errors >= MAX_ERRORS) && (num_silent < num_errors) &&
390         (! has_file_args)) {
391         printf("Stopping because there are too many error\n");
392         if (eacces_err)
393             printf("    root access may be required\n");
394     }
395     return 0;
396 }
397 
sg3_inq(int sg_fd,uint8_t * inqBuff,bool do_extra)398 int sg3_inq(int sg_fd, uint8_t * inqBuff, bool do_extra)
399 {
400     bool ok;
401     int err, sg_io;
402     uint8_t sense_buffer[32] SG_C_CPP_ZERO_INIT;
403     struct sg_io_hdr io_hdr SG_C_CPP_ZERO_INIT;
404 
405     memset(inqBuff, 0, INQ_REPLY_LEN);
406     inqBuff[0] = 0x7f;
407     io_hdr.interface_id = 'S';
408     io_hdr.cmd_len = sizeof(inq_cdb);
409     io_hdr.mx_sb_len = sizeof(sense_buffer);
410     io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
411     io_hdr.dxfer_len = INQ_REPLY_LEN;
412     io_hdr.dxferp = inqBuff;
413     io_hdr.cmdp = inq_cdb;
414     io_hdr.sbp = sense_buffer;
415     io_hdr.timeout = 20000;     /* 20000 millisecs == 20 seconds */
416 
417     ok = true;
418     sg_io = 0;
419     if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
420         if ((err = scsi_inq(sg_fd, inqBuff)) < 0) {
421             perror(ME "Inquiry SG_IO + SCSI_IOCTL_SEND_COMMAND ioctl error");
422             return 1;
423         } else if (err) {
424             printf(ME "SCSI_IOCTL_SEND_COMMAND ioctl error=0x%x\n", err);
425             return 1;
426         }
427     } else {
428         sg_io = 1;
429         /* now for the error processing */
430         switch (sg_err_category3(&io_hdr)) {
431         case SG_LIB_CAT_RECOVERED:
432             sg_chk_n_print3("Inquiry, continuing", &io_hdr, true);
433 #if defined(__GNUC__)
434 #if (__GNUC__ >= 7)
435             __attribute__((fallthrough));
436             /* FALL THROUGH */
437 #endif
438 #endif
439         case SG_LIB_CAT_CLEAN:
440             break;
441         default: /* won't bother decoding other categories */
442             ok = false;
443             sg_chk_n_print3("INQUIRY command error", &io_hdr, true);
444             break;
445         }
446     }
447 
448     if (ok) { /* output result if it is available */
449         char * p = (char *)inqBuff;
450 
451         printf("    %.8s  %.16s  %.4s ", p + 8, p + 16, p + 32);
452         printf("[rmb=%d cmdq=%d pqual=%d pdev=0x%x] ",
453                !!(p[1] & 0x80), !!(p[7] & 2), (p[0] & 0xe0) >> 5,
454                (p[0] & PDT_MASK));
455         if (do_extra && sg_io)
456             printf("dur=%ums\n", io_hdr.duration);
457         else
458             printf("\n");
459     }
460     return 0;
461 }
462 
463 struct lscsi_ioctl_command {
464         unsigned int inlen;  /* _excluding_ scsi command length */
465         unsigned int outlen;
466         uint8_t data[1];  /* was 0 but that's not ISO C!! */
467                 /* on input, scsi command starts here then opt. data */
468 };
469 
470 /* fallback INQUIRY using scsi mid-level's SCSI_IOCTL_SEND_COMMAND ioctl */
scsi_inq(int sg_fd,uint8_t * inqBuff)471 int scsi_inq(int sg_fd, uint8_t * inqBuff)
472 {
473     int res;
474     uint8_t buff[1024];
475     struct lscsi_ioctl_command * sicp = (struct lscsi_ioctl_command *)buff;
476 
477     memset(buff, 0, sizeof(buff));
478     sicp->inlen = 0;
479     sicp->outlen = INQ_REPLY_LEN;
480     memcpy(sicp->data, inq_cdb, INQ_CMD_LEN);
481     res = ioctl(sg_fd, SCSI_IOCTL_SEND_COMMAND, sicp);
482     if (0 == res)
483         memcpy(inqBuff, sicp->data, INQ_REPLY_LEN);
484     return res;
485 }
486 
487 /* Following code permits ATA IDENTIFY commands to be performed on
488    ATA non "Packet Interface" devices (e.g. ATA disks).
489    GPL-ed code borrowed from smartmontools (smartmontools.sf.net).
490    Copyright (C) 2002-4 Bruce Allen
491                 <[email protected]>
492  */
493 #ifndef ATA_IDENTIFY_DEVICE
494 #define ATA_IDENTIFY_DEVICE 0xec
495 #endif
496 #ifndef HDIO_DRIVE_CMD
497 #define HDIO_DRIVE_CMD    0x031f
498 #endif
499 
500 /* Needed parts of the ATA DRIVE IDENTIFY Structure. Those labeled
501  * word* are NOT used.
502  */
503 struct ata_identify_device {
504   unsigned short words000_009[10];
505   uint8_t  serial_no[20];
506   unsigned short words020_022[3];
507   uint8_t  fw_rev[8];
508   uint8_t  model[40];
509   unsigned short words047_079[33];
510   unsigned short major_rev_num;
511   unsigned short minor_rev_num;
512   unsigned short command_set_1;
513   unsigned short command_set_2;
514   unsigned short command_set_extension;
515   unsigned short cfs_enable_1;
516   unsigned short word086;
517   unsigned short csf_default;
518   unsigned short words088_255[168];
519 };
520 
521 /* Copies n bytes (or n-1 if n is odd) from in to out, but swaps adjacents
522  * bytes.
523  */
swapbytes(char * out,const char * in,size_t n)524 void swapbytes(char *out, const char *in, size_t n)
525 {
526     size_t k;
527 
528     if (n > 1) {
529         for (k = 0; k < (n - 1); k += 2) {
530             out[k] = in[k + 1];
531             out[k + 1] = in[k];
532         }
533     }
534 }
535 
536 /* Copies in to out, but removes leading and trailing whitespace. */
trim(char * out,const char * in)537 void trim(char *out, const char *in)
538 {
539     int k, first, last, num;
540 
541     /* Find the first non-space character (maybe none). */
542     first = -1;
543     for (k = 0; in[k]; k++) {
544         if (! isspace((int)in[k])) {
545             first = k;
546             break;
547         }
548     }
549 
550     if (first == -1) {
551         /* There are no non-space characters. */
552         out[0] = '\0';
553         return;
554     }
555 
556     /* Find the last non-space character. */
557     for (k = strlen(in) - 1; k >= first && isspace((int)in[k]); k--)
558         ;
559     last = k;
560     num = last - first + 1;
561     for (k = 0; k < num; ++k)
562         out[k] = in[first + k];
563     out[num] = '\0';
564 }
565 
566 /* Convenience function for formatting strings from ata_identify_device */
formatdriveidstring(char * out,const char * in,int n)567 void formatdriveidstring(char *out, const char *in, int n)
568 {
569     char tmp[65];
570 
571     n = n > 64 ? 64 : n;
572     swapbytes(tmp, in, n);
573     tmp[n] = '\0';
574     trim(out, tmp);
575 }
576 
577 /* Function for printing ASCII byte-swapped strings, skipping white
578  * space. Please note that this is needed on both big- and
579  * little-endian hardware.
580  */
printswap(char * output,char * in,unsigned int n)581 void printswap(char *output, char *in, unsigned int n)
582 {
583     formatdriveidstring(output, in, n);
584     if (*output)
585         printf("%.*s   ", (int)n, output);
586     else
587         printf("%.*s   ", (int)n, "[No Information Found]\n");
588 }
589 
590 #define ATA_IDENTIFY_BUFF_SZ  sizeof(struct ata_identify_device)
591 #define HDIO_DRIVE_CMD_OFFSET 4
592 
ata_command_interface(int device,char * data)593 int ata_command_interface(int device, char *data)
594 {
595     uint8_t buff[ATA_IDENTIFY_BUFF_SZ + HDIO_DRIVE_CMD_OFFSET];
596     int retval;
597 
598     buff[0] = ATA_IDENTIFY_DEVICE;
599     buff[3] = 1;
600     /* We are now doing the HDIO_DRIVE_CMD type ioctl. */
601     if ((retval = ioctl(device, HDIO_DRIVE_CMD, buff)))
602         return retval;
603 
604     /* if the command returns data, copy it back */
605     memcpy(data, buff + HDIO_DRIVE_CMD_OFFSET, ATA_IDENTIFY_BUFF_SZ);
606     return 0;
607 }
608 
try_ata_identity(const char * file_namep,int ata_fd,bool do_inq)609 int try_ata_identity(const char * file_namep, int ata_fd, bool do_inq)
610 {
611     struct ata_identify_device ata_ident;
612     char model[64];
613     char serial[64];
614     char firm[64];
615     int res;
616 
617     res = ata_command_interface(ata_fd, (char *)&ata_ident);
618     if (res)
619         return res;
620     printf("%s: ATA device\n", file_namep);
621     if (do_inq) {
622         printf("    ");
623         printswap(model, (char *)ata_ident.model, 40);
624         printswap(serial, (char *)ata_ident.serial_no, 20);
625         printswap(firm, (char *)ata_ident.fw_rev, 8);
626         printf("\n");
627     }
628     return res;
629 }
630