1 /*-
2 * Copyright (c) 2014 Spectra Logic Corporation
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions, and the following disclaimer,
10 * without modification.
11 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12 * substantially similar to the "NO WARRANTY" disclaimer below
13 * ("Disclaimer") and any redistribution must be conditioned upon
14 * including a substantially similar Disclaimer requirement for further
15 * binary redistribution.
16 *
17 * NO WARRANTY
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
27 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGES.
29 *
30 * Authors: Ken Merry (Spectra Logic Corporation)
31 */
32 /*
33 * SCSI Read and Write Attribute support for camcontrol(8).
34 */
35
36 #include <sys/cdefs.h>
37 #include <sys/ioctl.h>
38 #include <sys/stdint.h>
39 #include <sys/param.h>
40 #include <sys/endian.h>
41 #include <sys/sbuf.h>
42 #include <sys/queue.h>
43 #include <sys/chio.h>
44
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <inttypes.h>
48 #include <unistd.h>
49 #include <string.h>
50 #include <strings.h>
51 #include <fcntl.h>
52 #include <ctype.h>
53 #include <limits.h>
54 #include <err.h>
55 #include <locale.h>
56
57 #include <cam/cam.h>
58 #include <cam/cam_debug.h>
59 #include <cam/cam_ccb.h>
60 #include <cam/scsi/scsi_all.h>
61 #include <cam/scsi/scsi_pass.h>
62 #include <cam/scsi/scsi_ch.h>
63 #include <cam/scsi/scsi_message.h>
64 #include <camlib.h>
65 #include "camcontrol.h"
66
67 #if 0
68 struct scsi_attr_desc {
69 int attr_id;
70
71 STAILQ_ENTRY(scsi_attr_desc) links;
72 };
73 #endif
74
75 static struct scsi_nv elem_type_map[] = {
76 { "all", ELEMENT_TYPE_ALL },
77 { "picker", ELEMENT_TYPE_MT },
78 { "slot", ELEMENT_TYPE_ST },
79 { "portal", ELEMENT_TYPE_IE },
80 { "drive", ELEMENT_TYPE_DT },
81 };
82
83 static struct scsi_nv sa_map[] = {
84 { "attr_values", SRA_SA_ATTR_VALUES },
85 { "attr_list", SRA_SA_ATTR_LIST },
86 { "lv_list", SRA_SA_LOG_VOL_LIST },
87 { "part_list", SRA_SA_PART_LIST },
88 { "supp_attr", SRA_SA_SUPPORTED_ATTRS }
89 };
90
91 static struct scsi_nv output_format_map[] = {
92 { "text_esc", SCSI_ATTR_OUTPUT_TEXT_ESC },
93 { "text_raw", SCSI_ATTR_OUTPUT_TEXT_RAW },
94 { "nonascii_esc", SCSI_ATTR_OUTPUT_NONASCII_ESC },
95 { "nonascii_trim", SCSI_ATTR_OUTPUT_NONASCII_TRIM },
96 { "nonascii_raw", SCSI_ATTR_OUTPUT_NONASCII_RAW },
97 { "field_all", SCSI_ATTR_OUTPUT_FIELD_ALL },
98 { "field_none", SCSI_ATTR_OUTPUT_FIELD_NONE },
99 { "field_desc", SCSI_ATTR_OUTPUT_FIELD_DESC },
100 { "field_num", SCSI_ATTR_OUTPUT_FIELD_NUM },
101 { "field_size", SCSI_ATTR_OUTPUT_FIELD_SIZE },
102 { "field_rw", SCSI_ATTR_OUTPUT_FIELD_RW },
103 };
104
105 int
scsiattrib(struct cam_device * device,int argc,char ** argv,char * combinedopt,int task_attr,int retry_count,int timeout,int verbosemode,int err_recover)106 scsiattrib(struct cam_device *device, int argc, char **argv, char *combinedopt,
107 int task_attr, int retry_count, int timeout, int verbosemode,
108 int err_recover)
109 {
110 union ccb *ccb = NULL;
111 int attr_num = -1;
112 #if 0
113 int num_attrs = 0;
114 #endif
115 int start_attr = 0;
116 int cached_attr = 0;
117 int read_service_action = -1;
118 int read_attr = 0, write_attr = 0;
119 int element_address = 0;
120 int element_type = ELEMENT_TYPE_ALL;
121 int partition = 0;
122 int logical_volume = 0;
123 char *endptr;
124 uint8_t *data_buf = NULL;
125 uint32_t dxfer_len = UINT16_MAX - 1;
126 uint32_t valid_len;
127 uint32_t output_format;
128 STAILQ_HEAD(, scsi_attr_desc) write_attr_list;
129 int error = 0;
130 int c;
131
132 ccb = cam_getccb(device);
133 if (ccb == NULL) {
134 warnx("%s: error allocating CCB", __func__);
135 error = 1;
136 goto bailout;
137 }
138
139 STAILQ_INIT(&write_attr_list);
140
141 /*
142 * By default, when displaying attribute values, we trim out
143 * non-ASCII characters in ASCII fields. We display all fields
144 * (description, attribute number, attribute size, and readonly
145 * status). We default to displaying raw text.
146 *
147 * XXX KDM need to port this to stable/10 and newer FreeBSD
148 * versions that have iconv built in and can convert codesets.
149 */
150 output_format = SCSI_ATTR_OUTPUT_NONASCII_TRIM |
151 SCSI_ATTR_OUTPUT_FIELD_ALL |
152 SCSI_ATTR_OUTPUT_TEXT_RAW;
153
154 data_buf = malloc(dxfer_len);
155 if (data_buf == NULL) {
156 warn("%s: error allocating %u bytes", __func__, dxfer_len);
157 error = 1;
158 goto bailout;
159 }
160
161 while ((c = getopt(argc, argv, combinedopt)) != -1) {
162 switch (c) {
163 case 'a':
164 attr_num = strtol(optarg, &endptr, 0);
165 if (*endptr != '\0') {
166 warnx("%s: invalid attribute number %s",
167 __func__, optarg);
168 error = 1;
169 goto bailout;
170 }
171 start_attr = attr_num;
172 break;
173 case 'c':
174 cached_attr = 1;
175 break;
176 case 'e':
177 element_address = strtol(optarg, &endptr, 0);
178 if (*endptr != '\0') {
179 warnx("%s: invalid element address %s",
180 __func__, optarg);
181 error = 1;
182 goto bailout;
183 }
184 break;
185 case 'F': {
186 scsi_nv_status status;
187 scsi_attrib_output_flags new_outflags;
188 int entry_num = 0;
189 char *tmpstr;
190
191 if (isdigit(optarg[0])) {
192 output_format = strtoul(optarg, &endptr, 0);
193 if (*endptr != '\0') {
194 warnx("%s: invalid numeric output "
195 "format argument %s", __func__,
196 optarg);
197 error = 1;
198 goto bailout;
199 }
200 break;
201 }
202 new_outflags = SCSI_ATTR_OUTPUT_NONE;
203
204 while ((tmpstr = strsep(&optarg, ",")) != NULL) {
205 status = scsi_get_nv(output_format_map,
206 sizeof(output_format_map) /
207 sizeof(output_format_map[0]), tmpstr,
208 &entry_num, SCSI_NV_FLAG_IG_CASE);
209
210 if (status == SCSI_NV_FOUND)
211 new_outflags |=
212 output_format_map[entry_num].value;
213 else {
214 warnx("%s: %s format option %s",
215 __func__,
216 (status == SCSI_NV_AMBIGUOUS) ?
217 "ambiguous" : "invalid", tmpstr);
218 error = 1;
219 goto bailout;
220 }
221 }
222 output_format = new_outflags;
223 break;
224 }
225 case 'p':
226 partition = strtol(optarg, &endptr, 0);
227 if (*endptr != '\0') {
228 warnx("%s: invalid partition number %s",
229 __func__, optarg);
230 error = 1;
231 goto bailout;
232 }
233 break;
234 case 'r': {
235 scsi_nv_status status;
236 int entry_num = 0;
237
238 status = scsi_get_nv(sa_map, sizeof(sa_map) /
239 sizeof(sa_map[0]), optarg, &entry_num,
240 SCSI_NV_FLAG_IG_CASE);
241 if (status == SCSI_NV_FOUND)
242 read_service_action = sa_map[entry_num].value;
243 else {
244 warnx("%s: %s %s option %s", __func__,
245 (status == SCSI_NV_AMBIGUOUS) ?
246 "ambiguous" : "invalid", "service action",
247 optarg);
248 error = 1;
249 goto bailout;
250 }
251 read_attr = 1;
252 break;
253 }
254 case 's':
255 start_attr = strtol(optarg, &endptr, 0);
256 if (*endptr != '\0') {
257 warnx("%s: invalid starting attr argument %s",
258 __func__, optarg);
259 error = 1;
260 goto bailout;
261 }
262 break;
263 case 'T': {
264 scsi_nv_status status;
265 int entry_num = 0;
266
267 status = scsi_get_nv(elem_type_map,
268 nitems(elem_type_map),
269 optarg, &entry_num, SCSI_NV_FLAG_IG_CASE);
270 if (status == SCSI_NV_FOUND)
271 element_type = elem_type_map[entry_num].value;
272 else {
273 warnx("%s: %s %s option %s", __func__,
274 (status == SCSI_NV_AMBIGUOUS) ?
275 "ambiguous" : "invalid", "element type",
276 optarg);
277 error = 1;
278 goto bailout;
279 }
280 break;
281 }
282 case 'w':
283 warnx("%s: writing attributes is not implemented yet",
284 __func__);
285 error = 1;
286 goto bailout;
287 break;
288 case 'V':
289 logical_volume = strtol(optarg, &endptr, 0);
290
291 if (*endptr != '\0') {
292 warnx("%s: invalid logical volume argument %s",
293 __func__, optarg);
294 error = 1;
295 goto bailout;
296 }
297 break;
298 default:
299 break;
300 }
301 }
302
303 /*
304 * Default to reading attributes
305 */
306 if (((read_attr == 0) && (write_attr == 0))
307 || ((read_attr != 0) && (write_attr != 0))) {
308 warnx("%s: Must specify either -r or -w", __func__);
309 error = 1;
310 goto bailout;
311 }
312
313 if (read_attr != 0) {
314 scsi_read_attribute(&ccb->csio,
315 /*retries*/ retry_count,
316 /*cbfcnp*/ NULL,
317 /*tag_action*/ task_attr,
318 /*service_action*/ read_service_action,
319 /*element*/ element_address,
320 /*elem_type*/ element_type,
321 /*logical_volume*/ logical_volume,
322 /*partition*/ partition,
323 /*first_attribute*/ start_attr,
324 /*cache*/ cached_attr,
325 /*data_ptr*/ data_buf,
326 /*length*/ dxfer_len,
327 /*sense_len*/ SSD_FULL_SIZE,
328 /*timeout*/ timeout ? timeout : 60000);
329 #if 0
330 } else {
331 #endif
332
333 }
334
335 ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
336
337 if (err_recover != 0)
338 ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
339
340 if (cam_send_ccb(device, ccb) < 0) {
341 warn("error sending %s ATTRIBUTE", (read_attr != 0) ?
342 "READ" : "WRITE");
343 error = 1;
344 goto bailout;
345 }
346
347 if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
348 if (verbosemode != 0) {
349 cam_error_print(device, ccb, CAM_ESF_ALL,
350 CAM_EPF_ALL, stderr);
351 }
352 error = 1;
353 goto bailout;
354 }
355
356 if (read_attr == 0)
357 goto bailout;
358
359 valid_len = dxfer_len - ccb->csio.resid;
360
361 switch (read_service_action) {
362 case SRA_SA_ATTR_VALUES: {
363 uint32_t len_left, hdr_len, cur_len;
364 struct scsi_read_attribute_values *hdr;
365 struct scsi_mam_attribute_header *cur_id;
366 char error_str[512];
367 uint8_t *cur_pos;
368 struct sbuf *sb;
369
370 hdr = (struct scsi_read_attribute_values *)data_buf;
371
372 if (valid_len < sizeof(*hdr)) {
373 fprintf(stdout, "No attributes returned.\n");
374 error = 0;
375 goto bailout;
376 }
377
378 sb = sbuf_new_auto();
379 if (sb == NULL) {
380 warn("%s: Unable to allocate sbuf", __func__);
381 error = 1;
382 goto bailout;
383 }
384 /*
385 * XXX KDM grab more data if it is available.
386 */
387 hdr_len = scsi_4btoul(hdr->length);
388
389 for (len_left = MIN(valid_len, hdr_len),
390 cur_pos = &hdr->attribute_0[0]; len_left > sizeof(*cur_id);
391 len_left -= cur_len, cur_pos += cur_len) {
392 int cur_attr_num;
393 cur_id = (struct scsi_mam_attribute_header *)cur_pos;
394 cur_len = scsi_2btoul(cur_id->length) + sizeof(*cur_id);
395 cur_attr_num = scsi_2btoul(cur_id->id);
396
397 if ((attr_num != -1)
398 && (cur_attr_num != attr_num))
399 continue;
400
401 error = scsi_attrib_sbuf(sb, cur_id, len_left,
402 /*user_table*/ NULL, /*num_user_entries*/ 0,
403 /*prefer_user_table*/ 0, output_format, error_str,
404 sizeof(error_str));
405 if (error != 0) {
406 warnx("%s: %s", __func__, error_str);
407 sbuf_delete(sb);
408 error = 1;
409 goto bailout;
410 }
411 if (attr_num != -1)
412 break;
413 }
414
415 sbuf_finish(sb);
416 fprintf(stdout, "%s", sbuf_data(sb));
417 sbuf_delete(sb);
418 break;
419 }
420 case SRA_SA_SUPPORTED_ATTRS:
421 case SRA_SA_ATTR_LIST: {
422 uint32_t len_left, hdr_len;
423 struct scsi_attrib_list_header *hdr;
424 struct scsi_attrib_table_entry *entry = NULL;
425 const char *sa_name = "Supported Attributes";
426 const char *at_name = "Available Attributes";
427 int attr_id;
428 uint8_t *cur_id;
429
430 hdr = (struct scsi_attrib_list_header *)data_buf;
431 if (valid_len < sizeof(*hdr)) {
432 fprintf(stdout, "No %s\n",
433 (read_service_action == SRA_SA_SUPPORTED_ATTRS)?
434 sa_name : at_name);
435 error = 0;
436 goto bailout;
437 }
438 fprintf(stdout, "%s:\n",
439 (read_service_action == SRA_SA_SUPPORTED_ATTRS) ?
440 sa_name : at_name);
441 hdr_len = scsi_4btoul(hdr->length);
442 for (len_left = MIN(valid_len, hdr_len),
443 cur_id = &hdr->first_attr_0[0]; len_left > 1;
444 len_left -= sizeof(uint16_t), cur_id += sizeof(uint16_t)) {
445 attr_id = scsi_2btoul(cur_id);
446
447 if ((attr_num != -1)
448 && (attr_id != attr_num))
449 continue;
450
451 entry = scsi_get_attrib_entry(attr_id);
452 fprintf(stdout, "0x%.4x", attr_id);
453 if (entry == NULL)
454 fprintf(stdout, "\n");
455 else
456 fprintf(stdout, ": %s\n", entry->desc);
457
458 if (attr_num != -1)
459 break;
460 }
461 break;
462 }
463 case SRA_SA_PART_LIST:
464 case SRA_SA_LOG_VOL_LIST: {
465 struct scsi_attrib_lv_list *lv_list;
466 const char *partition_name = "Partition";
467 const char *lv_name = "Logical Volume";
468
469 if (valid_len < sizeof(*lv_list)) {
470 fprintf(stdout, "No %s list returned\n",
471 (read_service_action == SRA_SA_PART_LIST) ?
472 partition_name : lv_name);
473 error = 0;
474 goto bailout;
475 }
476
477 lv_list = (struct scsi_attrib_lv_list *)data_buf;
478
479 fprintf(stdout, "First %s: %d\n",
480 (read_service_action == SRA_SA_PART_LIST) ?
481 partition_name : lv_name,
482 lv_list->first_lv_number);
483 fprintf(stdout, "Number of %ss: %d\n",
484 (read_service_action == SRA_SA_PART_LIST) ?
485 partition_name : lv_name,
486 lv_list->num_logical_volumes);
487 break;
488 }
489 default:
490 break;
491 }
492 bailout:
493 if (ccb != NULL)
494 cam_freeccb(ccb);
495
496 free(data_buf);
497
498 return (error);
499 }
500