1 /*
2 * Copyright (c) 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 <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 * This program issues one of the following SCSI commands:
36 * - REMOVE ELEMENT AND TRUNCATE
37 * - RESTORE ELEMENTS AND REBUILD
38 */
39
40 static const char * version_str = "1.01 20221027";
41
42 #define REMOVE_ELEM_SA 0x18
43 #define RESTORE_ELEMS_SA 0x19
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 {"capacity", required_argument, 0, 'c'},
51 {"element", required_argument, 0, 'e'},
52 {"help", no_argument, 0, 'h'},
53 {"quick", no_argument, 0, 'q'},
54 {"remove", no_argument, 0, 'r'},
55 {"restore", no_argument, 0, 'R'},
56 {"verbose", no_argument, 0, 'v'},
57 {"version", no_argument, 0, 'V'},
58 {0, 0, 0, 0},
59 };
60
61 static const char * remove_cmd_s = "Remove element and truncate";
62 static const char * restore_cmd_s = "Restore elements and rebuild";
63
64
65 static void
usage()66 usage()
67 {
68 pr2serr("Usage: "
69 "sg_rem_rest_elem [--capacity=RC] [--element=EID] [--help] "
70 "[--quick]\n"
71 " [--remove] [--restore] [--verbose] "
72 "[--version]\n"
73 " DEVICE\n");
74 pr2serr(" where:\n"
75 " --capacity=RC|-c RC RC is requested capacity (unit: "
76 "block; def: 0)\n"
77 " --element=EID|-e EID EID is the element identifier to "
78 "remove;\n"
79 " default is 0 which is an invalid "
80 "EID\n"
81 " --help|-h print out usage message\n"
82 " --quick|-q bypass 15 second warn and wait\n"
83 " --remove|-r issue REMOVE ELEMENT AND TRUNCATE "
84 "command\n"
85 " --restore|-R issue RESTORE ELEMENTS AND REBUILD "
86 "command\n"
87 " --verbose|-v increase verbosity\n"
88 " --version|-V print version string and exit\n\n"
89 "Performs a SCSI REMOVE ELEMENT AND TRUNCATE or RESTORE "
90 "ELEMENTS AND\nREBUILD command. Either the --remove or "
91 "--restore option needs to be given.\n");
92 }
93
94 /* Return of 0 -> success, various SG_LIB_CAT_* positive values or -1 ->
95 * other errors */
96 static int
sg_ll_rem_rest_elem(int sg_fd,int sa,uint64_t req_cap,uint32_t e_id,bool noisy,int verbose)97 sg_ll_rem_rest_elem(int sg_fd, int sa, uint64_t req_cap, uint32_t e_id,
98 bool noisy, int verbose)
99 {
100 int ret, res, sense_cat;
101 struct sg_pt_base * ptvp;
102 uint8_t sai16_cdb[16] =
103 {SG_SERVICE_ACTION_IN_16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
104 0, 0, 0, 0};
105 uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
106 const char * cmd_name;
107
108 sai16_cdb[1] = 0x1f & sa;
109 if (REMOVE_ELEM_SA == sa) {
110 sg_put_unaligned_be64(req_cap, sai16_cdb + 2);
111 sg_put_unaligned_be32(e_id, sai16_cdb + 10);
112 cmd_name = remove_cmd_s;
113 } else
114 cmd_name = restore_cmd_s;
115 if (verbose) {
116 char d[128];
117
118 pr2serr(" %s cdb: %s\n", cmd_name,
119 sg_get_command_str(sai16_cdb, 16, false, sizeof(d), d));
120 }
121
122 ptvp = construct_scsi_pt_obj();
123 if (NULL == ptvp) {
124 pr2serr("%s: out of memory\n", cmd_name);
125 return -1;
126 }
127 set_scsi_pt_cdb(ptvp, sai16_cdb, sizeof(sai16_cdb));
128 set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
129 res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
130 ret = sg_cmds_process_resp(ptvp, cmd_name, res, noisy,
131 verbose, &sense_cat);
132 if (-1 == ret) {
133 if (get_scsi_pt_transport_err(ptvp))
134 ret = SG_LIB_TRANSPORT_ERROR;
135 else
136 ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
137 } else if (-2 == ret) {
138 switch (sense_cat) {
139 case SG_LIB_CAT_RECOVERED:
140 case SG_LIB_CAT_NO_SENSE:
141 ret = 0;
142 break;
143 default:
144 ret = sense_cat;
145 break;
146 }
147 } else
148 ret = 0;
149 destruct_scsi_pt_obj(ptvp);
150 return ret;
151 }
152
153
154 int
main(int argc,char * argv[])155 main(int argc, char * argv[])
156 {
157 bool quick = false;
158 bool reat = false;
159 bool resar = false;
160 bool verbose_given = false;
161 bool version_given = false;
162 int res, c;
163 int sg_fd = -1;
164 int verbose = 0;
165 int ret = 0;
166 int sa = 0;
167 uint32_t e_id = 0;
168 uint64_t req_cap = 0;
169 int64_t ll;
170 const char * device_name = NULL;
171 const char * cmd_name;
172
173 while (1) {
174 int option_index = 0;
175
176 c = getopt_long(argc, argv, "c:e:hqrRvV", long_options,
177 &option_index);
178 if (c == -1)
179 break;
180
181 switch (c) {
182 case 'c':
183 ll = sg_get_llnum(optarg);
184 if (-1 == ll) {
185 pr2serr("--capacity= expects a numeric argument\n");
186 return SG_LIB_SYNTAX_ERROR;
187 }
188 req_cap = (uint64_t)ll;
189 break;
190 case 'e':
191 ll = sg_get_llnum(optarg);
192 if ((ll < 0) || (ll > UINT32_MAX)) {
193 pr2serr("bad argument to '--element=EID'\n");
194 return SG_LIB_SYNTAX_ERROR;
195 }
196 if (0 == ll)
197 pr2serr("Warning: 0 is an invalid element identifier\n");
198 e_id = (uint64_t)ll;
199 break;
200 case 'h':
201 case '?':
202 usage();
203 return 0;
204 case 'q':
205 quick = true;
206 break;
207 case 'r':
208 reat = true;
209 sa = REMOVE_ELEM_SA;
210 break;
211 case 'R':
212 resar = true;
213 sa = RESTORE_ELEMS_SA;
214 break;
215 case 'v':
216 verbose_given = true;
217 ++verbose;
218 break;
219 case 'V':
220 version_given = true;
221 break;
222 default:
223 pr2serr("unrecognised option code 0x%x ??\n", c);
224 usage();
225 return SG_LIB_SYNTAX_ERROR;
226 }
227 }
228 if (optind < argc) {
229 if (NULL == device_name) {
230 device_name = argv[optind];
231 ++optind;
232 }
233 if (optind < argc) {
234 for (; optind < argc; ++optind)
235 pr2serr("Unexpected extra argument: %s\n",
236 argv[optind]);
237 usage();
238 return SG_LIB_SYNTAX_ERROR;
239 }
240 }
241
242 #ifdef DEBUG
243 pr2serr("In DEBUG mode, ");
244 if (verbose_given && version_given) {
245 pr2serr("but override: '-vV' given, zero verbose and continue\n");
246 verbose_given = false;
247 version_given = false;
248 verbose = 0;
249 } else if (! verbose_given) {
250 pr2serr("set '-vv'\n");
251 verbose = 2;
252 } else
253 pr2serr("keep verbose=%d\n", verbose);
254 #else
255 if (verbose_given && version_given)
256 pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
257 #endif
258 if (version_given) {
259 pr2serr("version: %s\n", version_str);
260 return 0;
261 }
262
263 if (1 != ((int)reat + (int)resar)) {
264 pr2serr("One, and only one, of these options needs to be given:\n"
265 " --remove or --restore\n\n");
266 usage();
267 return SG_LIB_CONTRADICT;
268 }
269 cmd_name = reat ? remove_cmd_s : restore_cmd_s;
270
271 if (NULL == device_name) {
272 pr2serr("missing device name!\n");
273 usage();
274 return SG_LIB_SYNTAX_ERROR;
275 }
276
277 sg_fd = sg_cmds_open_device(device_name, false /* rw */, verbose);
278 if (sg_fd < 0) {
279 int err = -sg_fd;
280 if (verbose)
281 pr2serr("open error: %s: %s\n", device_name,
282 safe_strerror(err));
283 ret = sg_convert_errno(err);
284 goto fini;
285 }
286 if (! quick) {
287 int k;
288 char b[80] SG_C_CPP_ZERO_INIT;
289 char ch;
290
291 for (k = 0; k < (int)sizeof(b) - 1; ++k) {
292 ch = cmd_name[k];
293 if ('\0' == ch)
294 break;
295 else if (islower(ch))
296 b[k] = toupper(ch);
297 else
298 b[k] = ch;
299 }
300 sg_warn_and_wait(b, device_name, false);
301 }
302
303 res = sg_ll_rem_rest_elem(sg_fd, sa, req_cap, e_id, true, verbose);
304 ret = res;
305 if (res) {
306 if (SG_LIB_CAT_INVALID_OP == res)
307 pr2serr("%s command not supported\n", cmd_name);
308 else {
309 char b[80];
310
311 sg_get_category_sense_str(res, sizeof(b), b, verbose);
312 pr2serr("%s command: %s\n", cmd_name, b);
313 }
314 }
315
316 fini:
317 if (sg_fd >= 0) {
318 res = sg_cmds_close_device(sg_fd);
319 if (res < 0) {
320 pr2serr("close error: %s\n", safe_strerror(-res));
321 if (0 == ret)
322 ret = sg_convert_errno(-res);
323 }
324 }
325 if (0 == verbose) {
326 if (! sg_if_can2stderr("sg_rem_rest_elem failed: ", ret))
327 pr2serr("Some error occurred, try again with '-v' or '-vv' for "
328 "more information\n");
329 }
330 return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
331 }
332