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 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 348 if (verbosemode != 0) { 349 cam_error_print(device, ccb, CAM_ESF_ALL, 350 CAM_EPF_ALL, stderr); 351 } 352 353 error = 1; 354 goto bailout; 355 } 356 357 if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { 358 if (verbosemode != 0) { 359 cam_error_print(device, ccb, CAM_ESF_ALL, 360 CAM_EPF_ALL, stderr); 361 } 362 error = 1; 363 goto bailout; 364 } 365 366 if (read_attr == 0) 367 goto bailout; 368 369 valid_len = dxfer_len - ccb->csio.resid; 370 371 switch (read_service_action) { 372 case SRA_SA_ATTR_VALUES: { 373 uint32_t len_left, hdr_len, cur_len; 374 struct scsi_read_attribute_values *hdr; 375 struct scsi_mam_attribute_header *cur_id; 376 char error_str[512]; 377 uint8_t *cur_pos; 378 struct sbuf *sb; 379 380 hdr = (struct scsi_read_attribute_values *)data_buf; 381 382 if (valid_len < sizeof(*hdr)) { 383 fprintf(stdout, "No attributes returned.\n"); 384 error = 0; 385 goto bailout; 386 } 387 388 sb = sbuf_new_auto(); 389 if (sb == NULL) { 390 warn("%s: Unable to allocate sbuf", __func__); 391 error = 1; 392 goto bailout; 393 } 394 /* 395 * XXX KDM grab more data if it is available. 396 */ 397 hdr_len = scsi_4btoul(hdr->length); 398 399 for (len_left = MIN(valid_len, hdr_len), 400 cur_pos = &hdr->attribute_0[0]; len_left > sizeof(*cur_id); 401 len_left -= cur_len, cur_pos += cur_len) { 402 int cur_attr_num; 403 cur_id = (struct scsi_mam_attribute_header *)cur_pos; 404 cur_len = scsi_2btoul(cur_id->length) + sizeof(*cur_id); 405 cur_attr_num = scsi_2btoul(cur_id->id); 406 407 if ((attr_num != -1) 408 && (cur_attr_num != attr_num)) 409 continue; 410 411 error = scsi_attrib_sbuf(sb, cur_id, len_left, 412 /*user_table*/ NULL, /*num_user_entries*/ 0, 413 /*prefer_user_table*/ 0, output_format, error_str, 414 sizeof(error_str)); 415 if (error != 0) { 416 warnx("%s: %s", __func__, error_str); 417 sbuf_delete(sb); 418 error = 1; 419 goto bailout; 420 } 421 if (attr_num != -1) 422 break; 423 } 424 425 sbuf_finish(sb); 426 fprintf(stdout, "%s", sbuf_data(sb)); 427 sbuf_delete(sb); 428 break; 429 } 430 case SRA_SA_SUPPORTED_ATTRS: 431 case SRA_SA_ATTR_LIST: { 432 uint32_t len_left, hdr_len; 433 struct scsi_attrib_list_header *hdr; 434 struct scsi_attrib_table_entry *entry = NULL; 435 const char *sa_name = "Supported Attributes"; 436 const char *at_name = "Available Attributes"; 437 int attr_id; 438 uint8_t *cur_id; 439 440 hdr = (struct scsi_attrib_list_header *)data_buf; 441 if (valid_len < sizeof(*hdr)) { 442 fprintf(stdout, "No %s\n", 443 (read_service_action == SRA_SA_SUPPORTED_ATTRS)? 444 sa_name : at_name); 445 error = 0; 446 goto bailout; 447 } 448 fprintf(stdout, "%s:\n", 449 (read_service_action == SRA_SA_SUPPORTED_ATTRS) ? 450 sa_name : at_name); 451 hdr_len = scsi_4btoul(hdr->length); 452 for (len_left = MIN(valid_len, hdr_len), 453 cur_id = &hdr->first_attr_0[0]; len_left > 1; 454 len_left -= sizeof(uint16_t), cur_id += sizeof(uint16_t)) { 455 attr_id = scsi_2btoul(cur_id); 456 457 if ((attr_num != -1) 458 && (attr_id != attr_num)) 459 continue; 460 461 entry = scsi_get_attrib_entry(attr_id); 462 fprintf(stdout, "0x%.4x", attr_id); 463 if (entry == NULL) 464 fprintf(stdout, "\n"); 465 else 466 fprintf(stdout, ": %s\n", entry->desc); 467 468 if (attr_num != -1) 469 break; 470 } 471 break; 472 } 473 case SRA_SA_PART_LIST: 474 case SRA_SA_LOG_VOL_LIST: { 475 struct scsi_attrib_lv_list *lv_list; 476 const char *partition_name = "Partition"; 477 const char *lv_name = "Logical Volume"; 478 479 if (valid_len < sizeof(*lv_list)) { 480 fprintf(stdout, "No %s list returned\n", 481 (read_service_action == SRA_SA_PART_LIST) ? 482 partition_name : lv_name); 483 error = 0; 484 goto bailout; 485 } 486 487 lv_list = (struct scsi_attrib_lv_list *)data_buf; 488 489 fprintf(stdout, "First %s: %d\n", 490 (read_service_action == SRA_SA_PART_LIST) ? 491 partition_name : lv_name, 492 lv_list->first_lv_number); 493 fprintf(stdout, "Number of %ss: %d\n", 494 (read_service_action == SRA_SA_PART_LIST) ? 495 partition_name : lv_name, 496 lv_list->num_logical_volumes); 497 break; 498 } 499 default: 500 break; 501 } 502 bailout: 503 if (ccb != NULL) 504 cam_freeccb(ccb); 505 506 free(data_buf); 507 508 return (error); 509 } 510