xref: /aosp_15_r20/external/sg3_utils/src/sg_get_elem_status.c (revision 44704f698541f6367e81f991ef8bb54ccbf3fc18)
1 /*
2  * Copyright (c) 2019-2022 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  * SPDX-License-Identifier: BSD-2-Clause
8  */
9 
10 #include <unistd.h>
11 #include <fcntl.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <stdarg.h>
15 #include <stdbool.h>
16 #include <string.h>
17 #include <errno.h>
18 #include <getopt.h>
19 #define __STDC_FORMAT_MACROS 1
20 #include <inttypes.h>
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include "sg_lib.h"
26 #include "sg_lib_data.h"
27 #include "sg_pt.h"
28 #include "sg_cmds_basic.h"
29 #include "sg_cmds_extra.h"
30 #include "sg_unaligned.h"
31 #include "sg_pr2serr.h"
32 
33 /* A utility program originally written for the Linux OS SCSI subsystem.
34  *
35  *
36  * This program issues the SCSI GET PHYSICAL ELEMENT STATUS command to the
37  * given SCSI device.
38  */
39 
40 static const char * version_str = "1.15 20220807";      /* sbc5r03 */
41 
42 #define MY_NAME "sg_get_elem_status"
43 
44 #ifndef UINT32_MAX
45 #define UINT32_MAX ((uint32_t)-1)
46 #endif
47 
48 #define GET_PHY_ELEM_STATUS_SA 0x17
49 #define DEF_GPES_BUFF_LEN (1024 + 32)
50 #define MAX_GPES_BUFF_LEN ((1024 * 1024) + DEF_GPES_BUFF_LEN)
51 #define GPES_DESC_OFFSET 32     /* descriptors starts at this byte offset */
52 #define GPES_DESC_LEN 32
53 #define MIN_MAXLEN 16
54 
55 #define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
56 #define DEF_PT_TIMEOUT  60      /* 60 seconds */
57 
58 struct gpes_desc_t {    /* info in returned physical status descriptor */
59     bool restoration_allowed;   /* RALWD bit in sbc4r20a */
60     uint32_t elem_id;
61     uint8_t phys_elem_type;
62     uint8_t phys_elem_health;
63     uint64_t assoc_cap;   /* number of LBs removed if depopulated */
64 };
65 
66 static uint8_t gpesBuff[DEF_GPES_BUFF_LEN];
67 
68 
69 static struct option long_options[] = {
70     {"brief", no_argument, 0, 'b'},
71     {"filter", required_argument, 0, 'f'},
72     {"help", no_argument, 0, 'h'},
73     {"hex", no_argument, 0, 'H'},
74     {"in", required_argument, 0, 'i'},      /* silent, same as --inhex= */
75     {"inhex", required_argument, 0, 'i'},
76     {"json", optional_argument, 0, 'j'},
77     {"maxlen", required_argument, 0, 'm'},
78     {"raw", no_argument, 0, 'r'},
79     {"readonly", no_argument, 0, 'R'},
80     {"report-type", required_argument, 0, 't'},
81     {"report_type", required_argument, 0, 't'},
82     {"starting", required_argument, 0, 's'},
83     {"verbose", no_argument, 0, 'v'},
84     {"version", no_argument, 0, 'V'},
85     {0, 0, 0, 0},
86 };
87 
88 static void
usage()89 usage()
90 {
91     pr2serr("Usage: sg_get_elem_status  [--brief] [--filter=FLT] [--help] "
92             "[--hex]\n"
93             "                           [--inhex=FN] [--json[=JO]] "
94             "[--maxlen=LEN]\n"
95             "                           [--raw] [--readonly] "
96             "[--report-type=RT]\n"
97             "                           [--starting=ELEM] [--verbose] "
98             "[--version]\n"
99             "                           DEVICE\n"
100             "  where:\n"
101             "    --brief|-b        one descriptor per line\n"
102             "    --filter=FLT|-f FLT    FLT is 0 (def) for all physical "
103             "elements;\n"
104             "                           1 for out of spec and depopulated "
105             "elements\n"
106             "    --help|-h         print out usage message\n"
107             "    --hex|-H          output in hexadecimal\n"
108             "    --inhex=FN|-i FN    input taken from file FN rather than "
109             "DEVICE,\n"
110             "                        assumed to be ASCII hex or, if --raw, "
111             "in binary\n"
112             "    --json[=JO]|-j[JO]     output in JSON instead of human "
113             "readable text\n"
114             "                           use --json=? for JSON help\n"
115             "    --maxlen=LEN|-m LEN    max response length (allocation "
116             "length in cdb)\n"
117             "                           (def: 0 -> %d bytes)\n",
118             DEF_GPES_BUFF_LEN );
119     pr2serr("    --raw|-r          output in binary, unless --inhex=FN is "
120             "given in\n"
121             "                      in which case the input is assumed to be "
122             "binary\n"
123             "    --readonly|-R     open DEVICE read-only (def: read-write)\n"
124             "    --report-type=RT|-t RT    report type: 0-> physical "
125             "elements (def);\n"
126             "                                           1-> storage "
127             "elements\n"
128             "    --starting=ELEM|-s ELEM    ELEM is the lowest identifier "
129             "returned\n"
130             "                               (def: 1 which is lowest "
131             "identifier)\n"
132             "    --verbose|-v      increase verbosity\n"
133             "    --version|-V      print version string and exit\n\n"
134             "Performs a SCSI GET PHYSICAL ELEMENT STATUS command (see SBC-3 "
135             "or SBC-4).\nStorage elements are a sub-set of physical "
136             "elements. Currently the only\ntype of physical element is a "
137             "storage element. If --inhex=FN is given then\ncontents of FN "
138             "is assumed to be a response to this command in ASCII hex.\n"
139             "Returned element descriptors should be in ascending "
140             "identifier order.\n"
141             );
142 }
143 
144 /* Invokes a SCSI GET PHYSICAL ELEMENT STATUS command (SBC-4).  Return of
145  * 0 -> success, various SG_LIB_CAT_* positive values or -1 -> other errors */
146 static int
sg_ll_get_phy_elem_status(int sg_fd,uint32_t starting_elem,uint8_t filter,uint8_t report_type,uint8_t * resp,uint32_t alloc_len,int * residp,bool noisy,int verbose)147 sg_ll_get_phy_elem_status(int sg_fd, uint32_t starting_elem, uint8_t filter,
148                           uint8_t report_type, uint8_t * resp,
149                           uint32_t alloc_len, int * residp, bool noisy,
150                           int verbose)
151 {
152     int k, ret, res, sense_cat;
153     uint8_t gpesCmd[16] = {SG_SERVICE_ACTION_IN_16,
154                            GET_PHY_ELEM_STATUS_SA, 0, 0, 0, 0,
155                            0, 0, 0, 0,  0, 0, 0, 0,  0, 0};
156     uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
157     struct sg_pt_base * ptvp;
158     static const char * const cmd_name = "Get physical element status";
159 
160     if (starting_elem)
161         sg_put_unaligned_be32(starting_elem, gpesCmd + 6);
162     sg_put_unaligned_be32(alloc_len, gpesCmd + 10);
163     if (filter)
164         gpesCmd[14] |= filter << 6;
165     if (report_type)
166         gpesCmd[14] |= (0xf & report_type);
167     if (verbose) {
168         char b[128];
169 
170         pr2serr("    %s cdb: %s\n", cmd_name,
171                 sg_get_command_str(gpesCmd, (int)sizeof(gpesCmd), false,
172                                    sizeof(b), b));
173     }
174 
175     ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
176     if (NULL == ptvp) {
177         pr2serr("%s: out of memory\n", cmd_name);
178         return -1;
179     }
180     set_scsi_pt_cdb(ptvp, gpesCmd, sizeof(gpesCmd));
181     set_scsi_pt_data_in(ptvp, resp, alloc_len);
182     set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
183     res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose);
184     ret = sg_cmds_process_resp(ptvp, cmd_name, res, noisy, verbose,
185                                &sense_cat);
186     if (-1 == ret) {
187         if (get_scsi_pt_transport_err(ptvp))
188             ret = SG_LIB_TRANSPORT_ERROR;
189         else
190             ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
191     } else if (-2 == ret) {
192         switch (sense_cat) {
193         case SG_LIB_CAT_RECOVERED:
194         case SG_LIB_CAT_NO_SENSE:
195             ret = 0;
196             break;
197         default:
198             ret = sense_cat;
199             break;
200         }
201     } else
202         ret = 0;
203     k = ret ? (int)alloc_len : get_scsi_pt_resid(ptvp);
204     if (residp)
205         *residp = k;
206     if ((verbose > 2) && ((alloc_len - k) > 0)) {
207         pr2serr("%s: parameter data returned:\n", cmd_name);
208         hex2stderr((const uint8_t *)resp, alloc_len - k,
209                    ((verbose > 3) ? -1 : 1));
210     }
211     destruct_scsi_pt_obj(ptvp);
212     return ret;
213 }
214 
215 static void
dStrRaw(const char * str,int len)216 dStrRaw(const char * str, int len)
217 {
218     int k;
219 
220     for (k = 0; k < len; ++k)
221         printf("%c", str[k]);
222 }
223 
224 /* Decodes given physical element status descriptor.  */
225 static void
decode_elem_status_desc(const uint8_t * bp,struct gpes_desc_t * pedp)226 decode_elem_status_desc(const uint8_t * bp, struct gpes_desc_t * pedp)
227 {
228     if ((NULL == bp) || (NULL == pedp))
229         return;
230     pedp->elem_id = sg_get_unaligned_be32(bp + 4);
231     pedp->restoration_allowed = (bool)(bp[13] & 1);
232     pedp->phys_elem_type = bp[14];
233     pedp->phys_elem_health = bp[15];
234     pedp->assoc_cap = sg_get_unaligned_be64(bp + 16);
235 }
236 
237 static bool
fetch_health_str(uint8_t health,char * bp,int max_blen)238 fetch_health_str(uint8_t health, char * bp, int max_blen)
239 {
240     bool add_val = false;
241     const char * cp = NULL;
242 
243     if  (0 == health)
244         cp = "not reported";
245     else if (health < 0x64) {
246         cp = "within manufacturer's specification limits";
247         add_val = true;
248     } else if (0x64 == health) {
249         cp = "at manufacturer's specification limits";
250         add_val = true;
251     } else if (health < 0xd0) {
252         cp = "outside manufacturer's specification limits";
253         add_val = true;
254     } else if (health < 0xfb) {
255         cp = "reserved";
256         add_val = true;
257     } else if (0xfb == health)
258         cp = "depopulation revocation completed, errors detected";
259     else if (0xfc == health)
260         cp = "depopulation revocation in progress";
261     else if (0xfd == health)
262         cp = "depopulation completed, errors detected";
263     else if (0xfe == health)
264         cp = "depopulation operations in progress";
265     else if (0xff == health)
266         cp = "depopulation completed, no errors";
267     snprintf(bp, max_blen, "%s", cp);
268     return add_val;
269 }
270 
271 
272 int
main(int argc,char * argv[])273 main(int argc, char * argv[])
274 {
275     bool do_raw = false;
276     bool no_final_msg = false;
277     bool o_readonly = false;
278     bool verbose_given = false;
279     bool version_given = false;
280     int k, j, m, n, res, c, rlen, in_len;
281     int sg_fd = -1;
282     int do_brief = 0;
283     int do_hex = 0;
284     int resid = 0;
285     int ret = 0;
286     int maxlen = DEF_GPES_BUFF_LEN;
287     int verbose = 0;
288     uint8_t filter = 0;
289     uint8_t rt = 0;
290     uint32_t num_desc, num_desc_ret, id_elem_depop;
291     uint32_t starting_elem = 0;
292     int64_t ll;
293     const char * device_name = NULL;
294     const char * in_fn = NULL;
295     const char * cp;
296     const uint8_t * bp;
297     uint8_t * gpesBuffp = gpesBuff;
298     uint8_t * free_gpesBuffp = NULL;
299     sgj_opaque_p jop = NULL;
300     sgj_opaque_p jo2p;
301     sgj_opaque_p jap = NULL;
302     struct gpes_desc_t a_ped;
303     sgj_state json_st SG_C_CPP_ZERO_INIT;
304     sgj_state * jsp = &json_st;
305     char b[80];
306     static const int blen = sizeof(b);
307 
308     while (1) {
309         int option_index = 0;
310 
311         c = getopt_long(argc, argv, "bf:hHi:j::m:rRs:St:TvV", long_options,
312                         &option_index);
313         if (c == -1)
314             break;
315 
316         switch (c) {
317         case 'b':
318             ++do_brief;
319             break;
320         case 'f':
321             n = sg_get_num_nomult(optarg);
322             if ((n < 0) || (n > 15)) {
323                 pr2serr("'--filter=RT' should be between 0 and 15 "
324                         "(inclusive)\n");
325                 return SG_LIB_SYNTAX_ERROR;
326             }
327             filter = n;
328             break;
329         case 'h':
330         case '?':
331             usage();
332             return 0;
333         case 'H':
334             ++do_hex;
335             break;
336         case 'i':
337             in_fn = optarg;
338             break;
339         case 'j':
340             if (! sgj_init_state(&json_st, optarg)) {
341                 int bad_char = json_st.first_bad_char;
342                 char e[1500];
343 
344                 if (bad_char) {
345                     pr2serr("bad argument to --json= option, unrecognized "
346                             "character '%c'\n\n", bad_char);
347                 }
348                 sg_json_usage(0, e, sizeof(e));
349                 pr2serr("%s", e);
350                 return SG_LIB_SYNTAX_ERROR;
351             }
352             break;
353         case 'm':
354             maxlen = sg_get_num(optarg);
355             if ((maxlen < 0) || (maxlen > MAX_GPES_BUFF_LEN)) {
356                 pr2serr("argument to '--maxlen' should be %d or less\n",
357                         MAX_GPES_BUFF_LEN);
358                 return SG_LIB_SYNTAX_ERROR;
359             }
360             if (0 == maxlen)
361                 maxlen = DEF_GPES_BUFF_LEN;
362             else if (maxlen < MIN_MAXLEN) {
363                 pr2serr("Warning: --maxlen=LEN less than %d ignored\n",
364                         MIN_MAXLEN);
365                 maxlen = DEF_GPES_BUFF_LEN;
366             }
367             break;
368         case 'r':
369             do_raw = true;
370             break;
371         case 'R':
372             o_readonly = true;
373             break;
374         case 's':
375             ll = sg_get_llnum(optarg);
376             if ((ll < 0) || (ll > UINT32_MAX)) {
377                 pr2serr("bad argument to '--starting='\n");
378                 return SG_LIB_SYNTAX_ERROR;
379             }
380             starting_elem = (uint32_t)ll;
381             break;
382         case 't':       /* --report-type=RT */
383             n = sg_get_num_nomult(optarg);
384             if ((n < 0) || (n > 15)) {
385                 pr2serr("'--report-type=RT' should be between 0 and 15 "
386                         "(inclusive)\n");
387                 return SG_LIB_SYNTAX_ERROR;
388             }
389             rt = n;
390             break;
391         case 'v':
392             verbose_given = true;
393             ++verbose;
394             break;
395         case 'V':
396             version_given = true;
397             break;
398         default:
399             pr2serr("unrecognised option code 0x%x ??\n", c);
400             usage();
401             return SG_LIB_SYNTAX_ERROR;
402         }
403     }
404     if (optind < argc) {
405         if (NULL == device_name) {
406             device_name = argv[optind];
407             ++optind;
408         }
409         if (optind < argc) {
410             for (; optind < argc; ++optind)
411                 pr2serr("Unexpected extra argument: %s\n", argv[optind]);
412             usage();
413             return SG_LIB_SYNTAX_ERROR;
414         }
415     }
416 
417 #ifdef DEBUG
418     pr2serr("In DEBUG mode, ");
419     if (verbose_given && version_given) {
420         pr2serr("but override: '-vV' given, zero verbose and continue\n");
421         verbose_given = false;
422         version_given = false;
423         verbose = 0;
424     } else if (! verbose_given) {
425         pr2serr("set '-vv'\n");
426         verbose = 2;
427     } else
428         pr2serr("keep verbose=%d\n", verbose);
429 #else
430     if (verbose_given && version_given)
431         pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
432 #endif
433     if (version_given) {
434         pr2serr("version: %s\n", version_str);
435         return 0;
436     }
437     if (jsp->pr_as_json)
438         jop = sgj_start_r(MY_NAME, version_str, argc, argv, jsp);
439 
440     if (maxlen > DEF_GPES_BUFF_LEN) {
441         gpesBuffp = (uint8_t *)sg_memalign(maxlen, 0, &free_gpesBuffp,
442                                            verbose > 3);
443         if (NULL == gpesBuffp) {
444             pr2serr("unable to allocate %d bytes on heap\n", maxlen);
445             return sg_convert_errno(ENOMEM);
446         }
447     }
448     if (device_name && in_fn) {
449         pr2serr("ignoring DEVICE, best to give DEVICE or --inhex=FN, but "
450                 "not both\n");
451         device_name = NULL;
452     }
453     if (NULL == device_name) {
454         if (in_fn) {
455             if ((ret = sg_f2hex_arr(in_fn, do_raw, false, gpesBuffp,
456                                     &in_len, maxlen))) {
457                 if (SG_LIB_LBA_OUT_OF_RANGE == ret) {
458                     pr2serr("--maxlen=%d needs to be increased", maxlen);
459                     if (in_len > 7) {
460                         n = (sg_get_unaligned_be32(gpesBuffp + 4) *
461                              GPES_DESC_LEN) + GPES_DESC_OFFSET;
462                         pr2serr(" to at least %d\n", n);
463                     } else
464                         pr2serr("\n");
465                     pr2serr("... decode what we have\n");
466                     no_final_msg = true;
467                 } else
468                     goto fini;
469             }
470             if (verbose > 2)
471                 pr2serr("Read %d [0x%x] bytes of user supplied data\n",
472                         in_len, in_len);
473             if (do_raw)
474                 do_raw = false;    /* can interfere on decode */
475             if (in_len < 4) {
476                 pr2serr("--in=%s only decoded %d bytes (needs 4 at least)\n",
477                         in_fn, in_len);
478                 ret = SG_LIB_SYNTAX_ERROR;
479                 goto fini;
480             }
481             res = 0;
482             goto start_response;
483         } else {
484             pr2serr("missing device name!\n\n");
485             usage();
486             ret = SG_LIB_FILE_ERROR;
487             no_final_msg = true;
488             goto fini;
489         }
490     }
491     if (do_raw) {
492         if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
493             perror("sg_set_binary_mode");
494             ret = SG_LIB_FILE_ERROR;
495             goto fini;
496         }
497     }
498     sg_fd = sg_cmds_open_device(device_name, o_readonly, verbose);
499     if (sg_fd < 0) {
500         pr2serr("open error: %s: %s\n", device_name, safe_strerror(-sg_fd));
501         ret = sg_convert_errno(-sg_fd);
502         goto fini;
503     }
504 
505     res = sg_ll_get_phy_elem_status(sg_fd, starting_elem, filter, rt,
506                                     gpesBuffp, maxlen, &resid, true, verbose);
507     ret = res;
508     if (res)
509         goto error;
510 
511 start_response:
512     k = maxlen - resid;
513     if (k < 4) {
514         pr2serr("Response too short (%d bytes) due to resid (%d)\n", k,
515                 resid);
516         if ((k > 0) && (do_raw || do_hex)) {
517             if (do_hex) {
518                 if (do_hex > 2)
519                     hex2stdout(gpesBuffp, k, -1);
520                 else
521                     hex2stdout(gpesBuffp, k, (2 == do_hex) ? 0 : 1);
522             } else
523                 dStrRaw((const char *)gpesBuffp, k);
524         }
525         ret = SG_LIB_CAT_MALFORMED;
526         goto fini;
527     } else
528         maxlen -= resid;
529     num_desc = sg_get_unaligned_be32(gpesBuffp + 0);
530     if (maxlen > 7) {
531         num_desc_ret = sg_get_unaligned_be32(gpesBuffp + 4);
532         id_elem_depop = (maxlen > 11) ? sg_get_unaligned_be32(gpesBuffp + 8) :
533                                         0;
534     } else {
535         num_desc_ret = 0;
536         id_elem_depop = 0;
537     }
538     rlen = (num_desc_ret * GPES_DESC_LEN) + GPES_DESC_OFFSET;
539     if ((verbose > 1) || (verbose && (rlen > maxlen))) {
540         pr2serr("response length %d bytes\n", rlen);
541         if (rlen > maxlen)
542             pr2serr("  ... which is greater than maxlen (allocation "
543                     "length %d), truncation\n", maxlen);
544     }
545     if (rlen > maxlen)
546         rlen = maxlen;
547     if (do_raw) {
548         dStrRaw((const char *)gpesBuffp, rlen);
549         goto fini;
550     }
551     if (do_hex) {
552         if (do_hex > 2)
553             hex2stdout(gpesBuffp, rlen, -1);
554         else
555             hex2stdout(gpesBuffp, rlen,  (2 == do_hex) ? 0 : 1);
556         goto fini;
557     }
558 
559     sgj_haj_vi(jsp, jop, 0, "Number of descriptors",
560                SGJ_SEP_COLON_1_SPACE, num_desc, true);
561     sgj_haj_vi(jsp, jop, 0, "Number of descriptors returned",
562                SGJ_SEP_COLON_1_SPACE, num_desc_ret, true);
563     sgj_haj_vi(jsp, jop, 0, "Identifier of element being depopulated",
564                SGJ_SEP_COLON_1_SPACE, id_elem_depop, true);
565     if (rlen < 64) {
566         sgj_pr_hr(jsp, "No complete physical element status descriptors "
567                   "available\n");
568         goto fini;
569     } else {
570         if (do_brief > 2)
571             goto fini;
572         sgj_pr_hr(jsp, "\n");
573     }
574 
575     if (jsp->pr_as_json)
576         jap = sgj_named_subarray_r(jsp, jop,
577                                    "physical_element_status_descriptor");
578     for (bp = gpesBuffp + GPES_DESC_OFFSET, k = 0; k < (int)num_desc_ret;
579          bp += GPES_DESC_LEN, ++k) {
580         if ((0 == k) && (do_brief < 2))
581             sgj_pr_hr(jsp, "Element descriptors:\n");
582         decode_elem_status_desc(bp, &a_ped);
583         if (jsp->pr_as_json) {
584             jo2p = sgj_new_unattached_object_r(jsp);
585             sgj_js_nv_ihex(jsp, jo2p, "element_identifier",
586                            (int64_t)a_ped.elem_id);
587             cp = (1 == a_ped.phys_elem_type) ? "storage" : "reserved";
588             sgj_js_nv_istr(jsp, jo2p, "physical_element_type",
589                            a_ped.phys_elem_type, "meaning", cp);
590             j = a_ped.phys_elem_health;
591             fetch_health_str(j, b, blen);
592             sgj_js_nv_istr(jsp, jo2p, "physical_element_health", j, NULL, b);
593             sgj_js_nv_ihex(jsp, jo2p, "associated_capacity",
594                            (int64_t)a_ped.assoc_cap);
595             sgj_js_nv_o(jsp, jap, NULL /* name */, jo2p);
596         } else if (do_brief) {
597             sgj_pr_hr(jsp, "%u: %u,%u\n", a_ped.elem_id, a_ped.phys_elem_type,
598                       a_ped.phys_elem_health);
599         } else {
600             char b2[144];
601             static const int b2len = sizeof(b2);
602 
603             m = 0;
604             m += sg_scnpr(b2 + m, b2len - m, "[%d] identifier: 0x%06x",
605                           k + 1, a_ped.elem_id);
606             if (sg_all_ffs((const uint8_t *)&a_ped.assoc_cap, 8))
607                 m += sg_scnpr(b2 + m, b2len - m,
608                               "  associated LBs: not specified;  ");
609             else
610                 m += sg_scnpr(b2 + m, b2len - m, "  associated LBs: 0x%"
611                               PRIx64 ";  ", a_ped.assoc_cap);
612             m += sg_scnpr(b2 + m, b2len - m, "health: ");
613             j = a_ped.phys_elem_health;
614             if (fetch_health_str(j, b, blen))
615                 m += sg_scnpr(b2 + m, b2len - m, "%s <%d>", b, j);
616             else
617                 m += sg_scnpr(b2 + m, b2len - m, "%s", b);
618             if (a_ped.restoration_allowed)
619                 m += sg_scnpr(b2 + m, b2len - m,
620                               " [restoration allowed [RALWD]]");
621             sgj_pr_hr(jsp, "%s\n", b2);
622         }
623     }
624     goto fini;
625 
626 error:
627     if (SG_LIB_CAT_INVALID_OP == res)
628         pr2serr("Get LBA Status command not supported\n");
629     else if (SG_LIB_CAT_ILLEGAL_REQ == res)
630         pr2serr("Get LBA Status command: bad field in cdb\n");
631     else {
632         sg_get_category_sense_str(res, sizeof(b), b, verbose);
633         pr2serr("Get LBA Status command: %s\n", b);
634     }
635 
636 fini:
637     if (sg_fd >= 0) {
638         res = sg_cmds_close_device(sg_fd);
639         if (res < 0) {
640             pr2serr("close error: %s\n", safe_strerror(-res));
641             if (0 == ret)
642                 ret = sg_convert_errno(-res);
643         }
644     }
645     if (free_gpesBuffp)
646         free(free_gpesBuffp);
647     if ((0 == verbose) && (! no_final_msg)) {
648         if (! sg_if_can2stderr("sg_get_elem_status failed: ", ret))
649             pr2serr("Some error occurred, try again with '-v' or '-vv' for "
650                     "more information\n");
651     }
652     ret = (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
653     if (jsp->pr_as_json) {
654         if (0 == do_hex)
655             sgj_js2file(jsp, NULL, ret, stdout);
656         sgj_finish(jsp);
657     }
658     return ret;
659 }
660