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