1 /*
2 * Copyright (c) 2014-2021 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 <ctype.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
26 #include "sg_lib.h"
27 #include "sg_lib_data.h"
28 #include "sg_pt.h"
29 #include "sg_cmds_basic.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 RESET WRITE POINTER command to the given SCSI
37 * device. Based on zbc-r04c.pdf .
38 */
39
40 static const char * version_str = "1.16 20211114";
41
42 #define SG_ZONING_OUT_CMDLEN 16
43 #define RESET_WRITE_POINTER_SA 0x4
44
45 #define SENSE_BUFF_LEN 64 /* Arbitrary, could be larger */
46 #define DEF_PT_TIMEOUT 60 /* 60 seconds */
47
48
49 static struct option long_options[] = {
50 {"all", no_argument, 0, 'a'},
51 {"count", required_argument, 0, 'C'},
52 {"help", no_argument, 0, 'h'},
53 {"reset-all", no_argument, 0, 'R'},
54 {"reset_all", no_argument, 0, 'R'},
55 {"verbose", no_argument, 0, 'v'},
56 {"version", no_argument, 0, 'V'},
57 {"zone", required_argument, 0, 'z'},
58 {0, 0, 0, 0},
59 };
60
61
62 static void
usage()63 usage()
64 {
65 pr2serr("Usage: "
66 "sg_reset_wp [--all] [--count=ZC] [--help] [--verbose]\n"
67 " [--version] [--zone=ID] DEVICE\n");
68 pr2serr(" where:\n"
69 " --all|-a sets the ALL flag in the cdb\n"
70 " --count=ZC|-C ZC set zone count field (def: 0)\n"
71 " --help|-h print out usage message\n"
72 " --verbose|-v increase verbosity\n"
73 " --version|-V print version string and exit\n"
74 " --zone=ID|-z ID ID is the starting LBA of the zone "
75 "whose\n"
76 " write pointer is to be reset\n\n"
77 "Performs a SCSI RESET WRITE POINTER command. ID is decimal by "
78 "default,\nfor hex use a leading '0x' or a trailing 'h'. "
79 "Either the --zone=ID\nor --all option needs to be given.\n");
80 }
81
82 /* Invokes a SCSI RESET WRITE POINTER command (ZBC). Return of 0 -> success,
83 * various SG_LIB_CAT_* positive values or -1 -> other errors */
84 static int
sg_ll_reset_write_pointer(int sg_fd,uint64_t zid,uint16_t zc,bool all,bool noisy,int verbose)85 sg_ll_reset_write_pointer(int sg_fd, uint64_t zid, uint16_t zc, bool all,
86 bool noisy, int verbose)
87 {
88 int ret, res, sense_cat;
89 uint8_t rwp_cdb[SG_ZONING_OUT_CMDLEN] = {SG_ZONING_OUT,
90 RESET_WRITE_POINTER_SA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
91 uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
92 struct sg_pt_base * ptvp;
93
94 sg_put_unaligned_be64(zid, rwp_cdb + 2);
95 sg_put_unaligned_be16(zc, rwp_cdb + 12);
96 if (all)
97 rwp_cdb[14] = 0x1;
98 if (verbose) {
99 char b[128];
100
101 pr2serr(" Reset write pointer cdb: %s\n",
102 sg_get_command_str(rwp_cdb, SG_ZONING_OUT_CMDLEN, false,
103 sizeof(b), b));
104 }
105
106 ptvp = construct_scsi_pt_obj();
107 if (NULL == ptvp) {
108 pr2serr("Reset write pointer: out of memory\n");
109 return -1;
110 }
111 set_scsi_pt_cdb(ptvp, rwp_cdb, sizeof(rwp_cdb));
112 set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
113 res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
114 ret = sg_cmds_process_resp(ptvp, "reset write pointer", res, noisy,
115 verbose, &sense_cat);
116 if (-1 == ret) {
117 if (get_scsi_pt_transport_err(ptvp))
118 ret = SG_LIB_TRANSPORT_ERROR;
119 else
120 ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
121 } else if (-2 == ret) {
122 switch (sense_cat) {
123 case SG_LIB_CAT_RECOVERED:
124 case SG_LIB_CAT_NO_SENSE:
125 ret = 0;
126 break;
127 default:
128 ret = sense_cat;
129 break;
130 }
131 } else
132 ret = 0;
133 destruct_scsi_pt_obj(ptvp);
134 return ret;
135 }
136
137
138 int
main(int argc,char * argv[])139 main(int argc, char * argv[])
140 {
141 bool all = false;
142 bool verbose_given = false;
143 bool version_given = false;
144 bool zid_given = false;
145 int res, c, n;
146 int sg_fd = -1;
147 int ret = 0;
148 int verbose = 0;
149 uint16_t zc = 0;
150 uint64_t zid = 0;
151 int64_t ll;
152 const char * device_name = NULL;
153
154 while (1) {
155 int option_index = 0;
156
157 c = getopt_long(argc, argv, "aC:hRvVz:", long_options,
158 &option_index);
159 if (c == -1)
160 break;
161
162 switch (c) {
163 case 'a':
164 case 'R':
165 all = true;
166 break;
167 case 'C':
168 n = sg_get_num(optarg);
169 if ((n < 0) || (n > 0xffff)) {
170 pr2serr("--count= expects an argument between 0 and 0xffff "
171 "inclusive\n");
172 return SG_LIB_SYNTAX_ERROR;
173 }
174 zc = (uint16_t)n;
175 break;
176 case 'h':
177 case '?':
178 usage();
179 return 0;
180 case 'v':
181 verbose_given = true;
182 ++verbose;
183 break;
184 case 'V':
185 version_given = true;
186 break;
187 case 'z':
188 ll = sg_get_llnum(optarg);
189 if (-1 == ll) {
190 pr2serr("bad argument to '--zone=ID'\n");
191 return SG_LIB_SYNTAX_ERROR;
192 }
193 zid = (uint64_t)ll;
194 zid_given = true;
195 break;
196 default:
197 pr2serr("unrecognised option code 0x%x ??\n", c);
198 usage();
199 return SG_LIB_SYNTAX_ERROR;
200 }
201 }
202 if (optind < argc) {
203 if (NULL == device_name) {
204 device_name = argv[optind];
205 ++optind;
206 }
207 if (optind < argc) {
208 for (; optind < argc; ++optind)
209 pr2serr("Unexpected extra argument: %s\n",
210 argv[optind]);
211 usage();
212 return SG_LIB_SYNTAX_ERROR;
213 }
214 }
215
216 #ifdef DEBUG
217 pr2serr("In DEBUG mode, ");
218 if (verbose_given && version_given) {
219 pr2serr("but override: '-vV' given, zero verbose and continue\n");
220 verbose_given = false;
221 version_given = false;
222 verbose = 0;
223 } else if (! verbose_given) {
224 pr2serr("set '-vv'\n");
225 verbose = 2;
226 } else
227 pr2serr("keep verbose=%d\n", verbose);
228 #else
229 if (verbose_given && version_given)
230 pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
231 #endif
232 if (version_given) {
233 pr2serr("version: %s\n", version_str);
234 return 0;
235 }
236
237 if ((! zid_given) && (! all)) {
238 pr2serr("either the --zone=ID or --all option is required\n\n");
239 usage();
240 return SG_LIB_CONTRADICT;
241 }
242 if (NULL == device_name) {
243 pr2serr("Missing device name!\n\n");
244 usage();
245 return SG_LIB_SYNTAX_ERROR;
246 }
247
248 sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
249 if (sg_fd < 0) {
250 int err = -sg_fd;
251
252 if (verbose)
253 pr2serr("open error: %s: %s\n", device_name,
254 safe_strerror(err));
255 ret = sg_convert_errno(err);
256 goto fini;
257 }
258
259 res = sg_ll_reset_write_pointer(sg_fd, zid, zc, all, true, verbose);
260 ret = res;
261 if (res) {
262 if (SG_LIB_CAT_INVALID_OP == res)
263 pr2serr("Reset write pointer command not supported\n");
264 else {
265 char b[80];
266
267 sg_get_category_sense_str(res, sizeof(b), b, verbose);
268 pr2serr("Reset write pointer command: %s\n", b);
269 }
270 }
271
272 fini:
273 if (sg_fd >= 0) {
274 res = sg_cmds_close_device(sg_fd);
275 if (res < 0) {
276 pr2serr("close error: %s\n", safe_strerror(-res));
277 if (0 == ret)
278 ret = sg_convert_errno(-res);
279 }
280 }
281 if (0 == verbose) {
282 if (! sg_if_can2stderr("sg_reset_wp failed: ", ret))
283 pr2serr("Some error occurred, try again with '-v' or '-vv' for "
284 "more information\n");
285 }
286 return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
287 }
288