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