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