1 /*
2  * Copyright (c) 2014-2016 Douglas Gilbert.
3  * All rights reserved.
4  * Use of this source code is governed by a BSD-style
5  * license that can be found in the BSD_LICENSE file.
6  */
7 
8 #include <unistd.h>
9 #include <fcntl.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <ctype.h>
14 #include <getopt.h>
15 #define __STDC_FORMAT_MACROS 1
16 #include <inttypes.h>
17 
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 #include "sg_lib.h"
22 #include "sg_lib_data.h"
23 #include "sg_pt.h"
24 #include "sg_cmds_basic.h"
25 #include "sg_unaligned.h"
26 #include "sg_pr2serr.h"
27 
28 /* A utility program originally written for the Linux OS SCSI subsystem.
29  *
30  *
31  * This program issues the SCSI REPORT ZONES command to the given SCSI device
32  * and decodes the response. Based on zbc-r02.pdf
33  */
34 
35 static const char * version_str = "1.08 20160203";
36 
37 #define MAX_RZONES_BUFF_LEN (1024 * 1024)
38 #define DEF_RZONES_BUFF_LEN (1024 * 8)
39 
40 #define SG_ZONING_IN_CMDLEN 16
41 
42 #define REPORT_ZONES_SA 0x0
43 
44 #define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
45 #define DEF_PT_TIMEOUT  60      /* 60 seconds */
46 
47 
48 static struct option long_options[] = {
49         {"help", no_argument, 0, 'h'},
50         {"hex", no_argument, 0, 'H'},
51         {"maxlen", required_argument, 0, 'm'},
52         {"partial", no_argument, 0, 'p'},
53         {"raw", no_argument, 0, 'r'},
54         {"readonly", no_argument, 0, 'R'},
55         {"report", required_argument, 0, 'o'},
56         {"start", required_argument, 0, 's'},
57         {"verbose", no_argument, 0, 'v'},
58         {"version", no_argument, 0, 'V'},
59         {0, 0, 0, 0},
60 };
61 
62 
63 static void
usage()64 usage()
65 {
66     pr2serr("Usage: "
67             "sg_rep_zones  [--help] [--hex] [--maxlen=LEN] [--partial]\n"
68             "                     [--raw] [--readonly] [--report=OPT] "
69             "[--start=LBA]\n"
70             "                     [--verbose] [--version] DEVICE\n");
71     pr2serr("  where:\n"
72             "    --help|-h          print out usage message\n"
73             "    --hex|-H           output response in hexadecimal; used "
74             "twice\n"
75             "                       shows decoded values in hex\n"
76             "    --maxlen=LEN|-m LEN    max response length (allocation "
77             "length in cdb)\n"
78             "                           (def: 0 -> 8192 bytes)\n"
79             "    --partial|-p       sets PARTIAL bit in cdb\n"
80             "    --raw|-r           output response in binary\n"
81             "    --readonly|-R      open DEVICE read-only (def: read-write)\n"
82             "    --report=OPT|-o OP    reporting options (def: 0: all "
83             "zones)\n"
84             "    --start=LBA|-s LBA    report zones from the LBA (def: 0)\n"
85             "                          need not be a zone starting LBA\n"
86             "    --verbose|-v       increase verbosity\n"
87             "    --version|-V       print version string and exit\n\n"
88             "Performs a SCSI REPORT ZONES command.\n");
89 }
90 
91 /* Invokes a SCSI REPORT ZONES command (ZBC).  Return of 0 -> success,
92  * various SG_LIB_CAT_* positive values or -1 -> other errors */
93 static int
sg_ll_report_zones(int sg_fd,uint64_t zs_lba,int partial,int report_opts,void * resp,int mx_resp_len,int * residp,int noisy,int verbose)94 sg_ll_report_zones(int sg_fd, uint64_t zs_lba, int partial, int report_opts,
95                    void * resp, int mx_resp_len, int * residp, int noisy,
96                    int verbose)
97 {
98     int k, ret, res, sense_cat;
99     unsigned char rzCmdBlk[SG_ZONING_IN_CMDLEN] =
100           {SG_ZONING_IN, REPORT_ZONES_SA, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0,
101            0, 0, 0, 0};
102     unsigned char sense_b[SENSE_BUFF_LEN];
103     struct sg_pt_base * ptvp;
104 
105     sg_put_unaligned_be64(zs_lba, rzCmdBlk + 2);
106     sg_put_unaligned_be32((uint32_t)mx_resp_len, rzCmdBlk + 10);
107     rzCmdBlk[14] = report_opts & 0x3f;
108     if (partial)
109         rzCmdBlk[14] |= 0x80;
110     if (verbose) {
111         pr2serr("    Report zones cdb: ");
112         for (k = 0; k < SG_ZONING_IN_CMDLEN; ++k)
113             pr2serr("%02x ", rzCmdBlk[k]);
114         pr2serr("\n");
115     }
116 
117     ptvp = construct_scsi_pt_obj();
118     if (NULL == ptvp) {
119         pr2serr("%s: out of memory\n", __func__);
120         return -1;
121     }
122     set_scsi_pt_cdb(ptvp, rzCmdBlk, sizeof(rzCmdBlk));
123     set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
124     set_scsi_pt_data_in(ptvp, (unsigned char *)resp, mx_resp_len);
125     res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
126     ret = sg_cmds_process_resp(ptvp, "report zones", res, mx_resp_len,
127                                sense_b, noisy, verbose, &sense_cat);
128     if (-1 == ret)
129         ;
130     else if (-2 == ret) {
131         switch (sense_cat) {
132         case SG_LIB_CAT_RECOVERED:
133         case SG_LIB_CAT_NO_SENSE:
134             ret = 0;
135             break;
136         default:
137             ret = sense_cat;
138             break;
139         }
140     } else
141         ret = 0;
142     if (residp)
143         *residp = get_scsi_pt_resid(ptvp);
144     destruct_scsi_pt_obj(ptvp);
145     return ret;
146 }
147 
148 static void
dStrRaw(const char * str,int len)149 dStrRaw(const char* str, int len)
150 {
151     int k;
152 
153     for (k = 0 ; k < len; ++k)
154         printf("%c", str[k]);
155 }
156 
157 static const char *
zone_type_str(int zt,char * b,int blen,int vb)158 zone_type_str(int zt, char * b, int blen, int vb)
159 {
160     const char * cp;
161 
162     if (NULL == b)
163         return "zone_type_str: NULL ptr)";
164     switch (zt) {
165     case 1:
166         cp = "Conventional";
167         break;
168     case 2:
169         cp = "Sequential write required";
170         break;
171     case 3:
172         cp = "Sequential write preferred";
173         break;
174     default:
175         cp = NULL;
176         break;
177     }
178     if (cp) {
179         if (vb)
180             snprintf(b, blen, "%s [0x%x]", cp, zt);
181         else
182             snprintf(b, blen, "%s", cp);
183     } else
184         snprintf(b, blen, "Reserved [0x%x]", zt);
185     return b;
186 }
187 
188 static const char *
zone_condition_str(int zc,char * b,int blen,int vb)189 zone_condition_str(int zc, char * b, int blen, int vb)
190 {
191     const char * cp;
192 
193     if (NULL == b)
194         return "zone_condition_str: NULL ptr)";
195     switch (zc) {
196     case 0:
197         cp = "Not write pointer";
198         break;
199     case 1:
200         cp = "Empty";
201         break;
202     case 2:
203         cp = "Implicitly opened";
204         break;
205     case 3:
206         cp = "Explicitly opened";
207         break;
208     case 4:
209         cp = "Closed";
210         break;
211     case 0xd:
212         cp = "Read only";
213         break;
214     case 0xe:
215         cp = "Full";
216         break;
217     case 0xf:
218         cp = "Offline";
219         break;
220     default:
221         cp = NULL;
222         break;
223     }
224     if (cp) {
225         if (vb)
226             snprintf(b, blen, "%s [0x%x]", cp, zc);
227         else
228             snprintf(b, blen, "%s", cp);
229     } else
230         snprintf(b, blen, "Reserved [0x%x]", zc);
231     return b;
232 }
233 
234 static const char * same_desc_arr[16] = {
235     "zone type and length may differ in each descriptor",
236     "zone type and length same in each descriptor",
237     "zone type and length same apart from length in last descriptor",
238     "zone type for each descriptor may be different",
239     "Reserved [0x4]", "Reserved [0x5]", "Reserved [0x6]", "Reserved [0x7]",
240     "Reserved [0x8]", "Reserved [0x9]", "Reserved [0xa]", "Reserved [0xb]",
241     "Reserved [0xc]", "Reserved [0xd]", "Reserved [0xe]", "Reserved [0xf]",
242 };
243 
244 
245 int
main(int argc,char * argv[])246 main(int argc, char * argv[])
247 {
248     int sg_fd, k, res, c, zl_len, len, zones, resid, rlen, zt, zc, same;
249     int do_hex = 0;
250     int maxlen = 0;
251     int do_partial = 0;
252     int do_raw = 0;
253     int o_readonly = 0;
254     int reporting_opt = 0;
255     int verbose = 0;
256     uint64_t st_lba = 0;
257     int64_t ll;
258     const char * device_name = NULL;
259     unsigned char * reportZonesBuff = NULL;
260     unsigned char * ucp;
261     int ret = 0;
262     char b[80];
263 
264     while (1) {
265         int option_index = 0;
266 
267         c = getopt_long(argc, argv, "hHm:o:prRs:vV", long_options,
268                         &option_index);
269         if (c == -1)
270             break;
271 
272         switch (c) {
273         case 'h':
274         case '?':
275             usage();
276             return 0;
277         case 'H':
278             ++do_hex;
279             break;
280         case 'm':
281             maxlen = sg_get_num(optarg);
282             if ((maxlen < 0) || (maxlen > MAX_RZONES_BUFF_LEN)) {
283                 pr2serr("argument to '--maxlen' should be %d or "
284                         "less\n", MAX_RZONES_BUFF_LEN);
285                 return SG_LIB_SYNTAX_ERROR;
286             }
287             break;
288         case 'o':
289            reporting_opt = sg_get_num(optarg);
290            if ((reporting_opt < 0) || (reporting_opt > 63)) {
291                 pr2serr("bad argument to '--report=OPT', expect 0 to "
292                         "63\n");
293                 return SG_LIB_SYNTAX_ERROR;
294             }
295             break;
296         case 'p':
297             ++do_partial;
298             break;
299         case 'r':
300             ++do_raw;
301             break;
302         case 'R':
303             ++o_readonly;
304             break;
305         case 's':
306             ll = sg_get_llnum(optarg);
307             if (-1 == ll) {
308                 pr2serr("bad argument to '--start=LBA'\n");
309                 return SG_LIB_SYNTAX_ERROR;
310             }
311             st_lba = (uint64_t)ll;
312             break;
313         case 'v':
314             ++verbose;
315             break;
316         case 'V':
317             pr2serr("version: %s\n", version_str);
318             return 0;
319         default:
320             pr2serr("unrecognised option code 0x%x ??\n", c);
321             usage();
322             return SG_LIB_SYNTAX_ERROR;
323         }
324     }
325     if (optind < argc) {
326         if (NULL == device_name) {
327             device_name = argv[optind];
328             ++optind;
329         }
330         if (optind < argc) {
331             for (; optind < argc; ++optind)
332                 pr2serr("Unexpected extra argument: %s\n", argv[optind]);
333             usage();
334             return SG_LIB_SYNTAX_ERROR;
335         }
336     }
337 
338     if (NULL == device_name) {
339         pr2serr("missing device name!\n");
340         usage();
341         return SG_LIB_SYNTAX_ERROR;
342     }
343 
344     if (do_raw) {
345         if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
346             perror("sg_set_binary_mode");
347             return SG_LIB_FILE_ERROR;
348         }
349     }
350 
351     sg_fd = sg_cmds_open_device(device_name, o_readonly, verbose);
352     if (sg_fd < 0) {
353         pr2serr("open error: %s: %s\n", device_name,
354                 safe_strerror(-sg_fd));
355         return SG_LIB_FILE_ERROR;
356     }
357 
358     if (0 == maxlen)
359         maxlen = DEF_RZONES_BUFF_LEN;
360     reportZonesBuff = (unsigned char *)calloc(1, maxlen);
361     if (NULL == reportZonesBuff) {
362         pr2serr("unable to malloc %d bytes\n", maxlen);
363         return SG_LIB_CAT_OTHER;
364     }
365 
366     res = sg_ll_report_zones(sg_fd, st_lba, do_partial, reporting_opt,
367                              reportZonesBuff, maxlen, &resid, 1, verbose);
368     ret = res;
369     if (0 == res) {
370         rlen = maxlen - resid;
371         if (rlen < 4) {
372             pr2serr("Response length (%d) too short\n", rlen);
373             ret = SG_LIB_CAT_MALFORMED;
374             goto the_end;
375         }
376         zl_len = sg_get_unaligned_be32(reportZonesBuff + 0) + 64;
377         if (zl_len > rlen) {
378             if (verbose)
379                 pr2serr("zl_len available is %d, response length is %d\n",
380                         zl_len, rlen);
381             len = rlen;
382         } else
383             len = zl_len;
384         if (do_raw) {
385             dStrRaw((const char *)reportZonesBuff, len);
386             goto the_end;
387         }
388         if (do_hex && (2 != do_hex)) {
389             dStrHex((const char *)reportZonesBuff, len,
390                     ((1 == do_hex) ? 1 : -1));
391             goto the_end;
392         }
393         printf("Report zones response:\n");
394         if (len < 64) {
395             pr2serr("Zone length [%d] too short (perhaps after truncation\n)",
396                     len);
397             ret = SG_LIB_CAT_MALFORMED;
398             goto the_end;
399         }
400         same = reportZonesBuff[4] & 0xf;
401         printf("  Same=%d: %s\n\n", same, same_desc_arr[same]);
402         printf("  Maximum LBA: 0x%" PRIx64 "\n",
403                sg_get_unaligned_be64(reportZonesBuff + 8));
404         zones = (len - 64) / 64;
405         for (k = 0, ucp = reportZonesBuff + 64; k < zones; ++k, ucp += 64) {
406             printf(" Zone descriptor: %d\n", k);
407             if (do_hex) {
408                 dStrHex((const char *)ucp, len, -1);
409                 continue;
410             }
411             zt = ucp[0] & 0xf;
412             zc = (ucp[1] >> 4) & 0xf;
413             printf("   Zone type: %s\n", zone_type_str(zt, b, sizeof(b),
414                    verbose));
415             printf("   Zone condition: %s\n", zone_condition_str(zc, b,
416                    sizeof(b), verbose));
417             printf("   Non_seq: %d\n", !!(ucp[1] & 0x2));
418             printf("   Reset: %d\n", ucp[1] & 0x1);
419             printf("   Zone Length: 0x%" PRIx64 "\n",
420                    sg_get_unaligned_be64(ucp + 8));
421             printf("   Zone start LBA: 0x%" PRIx64 "\n",
422                    sg_get_unaligned_be64(ucp + 16));
423             printf("   Write pointer LBA: 0x%" PRIx64 "\n",
424                    sg_get_unaligned_be64(ucp + 24));
425         }
426         if ((64 + (64 * zones)) < zl_len)
427             printf("\n>>> Beware: Zone list truncated, may need another "
428                    "call\n");
429     } else if (SG_LIB_CAT_INVALID_OP == res)
430         pr2serr("Report zones command not supported\n");
431     else {
432         sg_get_category_sense_str(res, sizeof(b), b, verbose);
433         pr2serr("Report zones command: %s\n", b);
434     }
435 
436 the_end:
437     if (reportZonesBuff)
438         free(reportZonesBuff);
439     res = sg_cmds_close_device(sg_fd);
440     if (res < 0) {
441         pr2serr("close error: %s\n", safe_strerror(-res));
442         if (0 == ret)
443             return SG_LIB_FILE_ERROR;
444     }
445     return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
446 }
447