1 /*
2  * Copyright (c) 2014-2015 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 RESET WRITE POINTER command to the given SCSI
32  * device. Based on zbc-r04c.pdf .
33  */
34 
35 static const char * version_str = "1.04 20151219";
36 
37 #define SG_ZONING_OUT_CMDLEN 16
38 #define RESET_WRITE_POINTER_SA 0x4
39 
40 #define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
41 #define DEF_PT_TIMEOUT  60      /* 60 seconds */
42 
43 
44 static struct option long_options[] = {
45         {"all", no_argument, 0, 'a'},
46         {"help", no_argument, 0, 'h'},
47         {"reset-all", no_argument, 0, 'R'},
48         {"reset_all", no_argument, 0, 'R'},
49         {"verbose", no_argument, 0, 'v'},
50         {"version", no_argument, 0, 'V'},
51         {"zone", required_argument, 0, 'z'},
52         {0, 0, 0, 0},
53 };
54 
55 
56 static void
usage()57 usage()
58 {
59     pr2serr("Usage: "
60             "sg_reset_wp  [--all] [--help] [--verbose] [--version]\n"
61             "                    [--zone=ID] DEVICE\n");
62     pr2serr("  where:\n"
63             "    --all|-a           sets the ALL flag in the cdb\n"
64             "    --help|-h          print out usage message\n"
65             "    --verbose|-v       increase verbosity\n"
66             "    --version|-V       print version string and exit\n\n"
67             "    --zone=ID|-z ID    ID is the starting LBA of the zone "
68             "whose\n"
69             "                       write pointer is to be reset\n"
70             "Performs a SCSI RESET WRITE POINTER command. ID is decimal by "
71             "default,\nfor hex use a leading '0x' or a trailing 'h'. "
72             "Either the --zone=ID\nor --all option needs to be given.\n");
73 }
74 
75 /* Invokes a SCSI RESET WRITE POINTER command (ZBC).  Return of 0 -> success,
76  * various SG_LIB_CAT_* positive values or -1 -> other errors */
77 static int
sg_ll_reset_write_pointer(int sg_fd,uint64_t zid,int all,int noisy,int verbose)78 sg_ll_reset_write_pointer(int sg_fd, uint64_t zid, int all, int noisy,
79                           int verbose)
80 {
81     int k, ret, res, sense_cat;
82     unsigned char rwpCmdBlk[SG_ZONING_OUT_CMDLEN] =
83           {SG_ZONING_OUT, RESET_WRITE_POINTER_SA, 0, 0, 0, 0, 0, 0,
84            0, 0, 0, 0,  0, 0, 0, 0};
85     unsigned char sense_b[SENSE_BUFF_LEN];
86     struct sg_pt_base * ptvp;
87 
88     sg_put_unaligned_be64(zid, rwpCmdBlk + 2);
89     if (all)
90         rwpCmdBlk[14] = 0x1;
91     if (verbose) {
92         pr2serr("    Reset write pointer cdb: ");
93         for (k = 0; k < SG_ZONING_OUT_CMDLEN; ++k)
94             pr2serr("%02x ", rwpCmdBlk[k]);
95         pr2serr("\n");
96     }
97 
98     ptvp = construct_scsi_pt_obj();
99     if (NULL == ptvp) {
100         pr2serr("Reset write pointer: out of memory\n");
101         return -1;
102     }
103     set_scsi_pt_cdb(ptvp, rwpCmdBlk, sizeof(rwpCmdBlk));
104     set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
105     res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
106     ret = sg_cmds_process_resp(ptvp, "reset write pointer", res, 0, sense_b,
107                                noisy, verbose, &sense_cat);
108     if (-1 == ret)
109         ;
110     else if (-2 == ret) {
111         switch (sense_cat) {
112         case SG_LIB_CAT_RECOVERED:
113         case SG_LIB_CAT_NO_SENSE:
114             ret = 0;
115             break;
116         default:
117             ret = sense_cat;
118             break;
119         }
120     } else
121         ret = 0;
122     destruct_scsi_pt_obj(ptvp);
123     return ret;
124 }
125 
126 
127 int
main(int argc,char * argv[])128 main(int argc, char * argv[])
129 {
130     int sg_fd, res, c;
131     int all = 0;
132     int verbose = 0;
133     int zid_given = 0;
134     uint64_t zid = 0;
135     int64_t ll;
136     const char * device_name = NULL;
137     int ret = 0;
138 
139     while (1) {
140         int option_index = 0;
141 
142         c = getopt_long(argc, argv, "ahRvVz:", long_options,
143                         &option_index);
144         if (c == -1)
145             break;
146 
147         switch (c) {
148         case 'a':
149         case 'R':
150             ++all;
151             break;
152         case 'h':
153         case '?':
154             usage();
155             return 0;
156         case 'v':
157             ++verbose;
158             break;
159         case 'V':
160             pr2serr("version: %s\n", version_str);
161             return 0;
162         case 'z':
163             ll = sg_get_llnum(optarg);
164             if (-1 == ll) {
165                 pr2serr("bad argument to '--zone=ID'\n");
166                 return SG_LIB_SYNTAX_ERROR;
167             }
168             zid = (uint64_t)ll;
169             ++zid_given;
170             break;
171         default:
172             pr2serr("unrecognised option code 0x%x ??\n", c);
173             usage();
174             return SG_LIB_SYNTAX_ERROR;
175         }
176     }
177     if (optind < argc) {
178         if (NULL == device_name) {
179             device_name = argv[optind];
180             ++optind;
181         }
182         if (optind < argc) {
183             for (; optind < argc; ++optind)
184                 pr2serr("Unexpected extra argument: %s\n",
185                         argv[optind]);
186             usage();
187             return SG_LIB_SYNTAX_ERROR;
188         }
189     }
190 
191     if ((! zid_given) && (0 == all)) {
192         pr2serr("either the --zone=ID or --all option is required\n");
193         usage();
194         return SG_LIB_SYNTAX_ERROR;
195     }
196     if (NULL == device_name) {
197         pr2serr("missing device name!\n");
198         usage();
199         return SG_LIB_SYNTAX_ERROR;
200     }
201 
202     sg_fd = sg_cmds_open_device(device_name, 0, verbose);
203     if (sg_fd < 0) {
204         pr2serr("open error: %s: %s\n", device_name,
205                 safe_strerror(-sg_fd));
206         return SG_LIB_FILE_ERROR;
207     }
208 
209     res = sg_ll_reset_write_pointer(sg_fd, zid, all, 1, verbose);
210     ret = res;
211     if (res) {
212         if (SG_LIB_CAT_INVALID_OP == res)
213             pr2serr("Reset write pointer command not supported\n");
214         else {
215             char b[80];
216 
217             sg_get_category_sense_str(res, sizeof(b), b, verbose);
218             pr2serr("Reset write pointer command: %s\n", b);
219         }
220     }
221 
222     res = sg_cmds_close_device(sg_fd);
223     if (res < 0) {
224         pr2serr("close error: %s\n", safe_strerror(-res));
225         if (0 == ret)
226             return SG_LIB_FILE_ERROR;
227     }
228     return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
229 }
230