xref: /freebsd/sbin/camcontrol/modeedit.c (revision 2f513db72b034fd5ef7f080b11be5c711c15186a)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2000 Kelly Yancey <kbyanc@posi.net>
5  * Derived from work done by Julian Elischer <julian@tfs.com,
6  * julian@dialix.oz.au>, 1993, and Peter Dufault <dufault@hda.com>, 1994.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer,
14  *    without modification, immediately at the beginning of the file.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
33 
34 #include <sys/queue.h>
35 #include <sys/types.h>
36 #include <sys/sbuf.h>
37 
38 #include <assert.h>
39 #include <ctype.h>
40 #include <err.h>
41 #include <errno.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <stdio.h>
45 #include <sysexits.h>
46 #include <unistd.h>
47 
48 #include <cam/scsi/scsi_all.h>
49 #include <cam/cam.h>
50 #include <cam/cam_ccb.h>
51 #include <camlib.h>
52 #include "camcontrol.h"
53 
54 #define	DEFAULT_SCSI_MODE_DB	"/usr/share/misc/scsi_modes"
55 #define	DEFAULT_EDITOR		"vi"
56 #define	MAX_FORMAT_SPEC		4096	/* Max CDB format specifier. */
57 #define	MAX_PAGENUM_LEN		10	/* Max characters in page num. */
58 #define	MAX_PAGENAME_LEN	64	/* Max characters in page name. */
59 #define	PAGEDEF_START		'{'	/* Page definition delimiter. */
60 #define	PAGEDEF_END		'}'	/* Page definition delimiter. */
61 #define	PAGENAME_START		'"'	/* Page name delimiter. */
62 #define	PAGENAME_END		'"'	/* Page name delimiter. */
63 #define	PAGEENTRY_END		';'	/* Page entry terminator (optional). */
64 #define	MAX_DATA_SIZE		4096	/* Mode/Log sense data buffer size. */
65 #define PAGE_CTRL_SHIFT		6	/* Bit offset to page control field. */
66 
67 struct editentry {
68 	STAILQ_ENTRY(editentry) link;
69 	char	*name;
70 	char	type;
71 	int	editable;
72 	int	size;
73 	union {
74 		int	ivalue;
75 		char	*svalue;
76 	} value;
77 };
78 static STAILQ_HEAD(, editentry) editlist; /* List of page entries. */
79 static int editlist_changed = 0;	/* Whether any entries were changed. */
80 
81 struct pagename {
82 	SLIST_ENTRY(pagename) link;
83 	int page;
84 	int subpage;
85 	char *name;
86 };
87 static SLIST_HEAD(, pagename) namelist;	/* Page number to name mappings. */
88 
89 static char format[MAX_FORMAT_SPEC];	/* Buffer for scsi cdb format def. */
90 
91 static FILE *edit_file = NULL;		/* File handle for edit file. */
92 static char edit_path[] = "/tmp/camXXXXXX";
93 
94 
95 /* Function prototypes. */
96 static void		 editentry_create(void *hook, int letter, void *arg,
97 					  int count, char *name);
98 static void		 editentry_update(void *hook, int letter, void *arg,
99 					  int count, char *name);
100 static void		 editentry_create_desc(void *hook, int letter, void *arg,
101 					  int count, char *name);
102 static int		 editentry_save(void *hook, char *name);
103 static struct editentry	*editentry_lookup(char *name);
104 static int		 editentry_set(char *name, char *newvalue,
105 				       int editonly);
106 static void		 editlist_populate(struct cam_device *device,
107 			    int cdb_len, int dbd, int pc, int page, int subpage,
108 			    int task_attr, int retries, int timeout);
109 static void		 editlist_populate_desc(struct cam_device *device,
110 			    int cdb_len, int llbaa, int pc, int page,
111 			    int subpage, int task_attr, int retries,
112 			    int timeout);
113 static void		 editlist_save(struct cam_device *device, int cdb_len,
114 			    int dbd, int pc, int page, int subpage,
115 			    int task_attr, int retries, int timeout);
116 static void		 editlist_save_desc(struct cam_device *device, int cdb_len,
117 			    int llbaa, int pc, int page, int subpage,
118 			    int task_attr, int retries, int timeout);
119 static void		 nameentry_create(int page, int subpage, char *name);
120 static struct pagename	*nameentry_lookup(int page, int subpage);
121 static int		 load_format(const char *pagedb_path, int lpage,
122 			    int lsubpage);
123 static int		 modepage_write(FILE *file, int editonly);
124 static int		 modepage_read(FILE *file);
125 static void		 modepage_edit(void);
126 static void		 modepage_dump(struct cam_device *device, int cdb_len,
127 			    int dbd, int pc, int page, int subpage,
128 			    int task_attr, int retries, int timeout);
129 static void		 modepage_dump_desc(struct cam_device *device,
130 			    int cdb_len, int llbaa, int pc, int page,
131 			    int subpage, int task_attr, int retries,
132 			    int timeout);
133 static void		 cleanup_editfile(void);
134 
135 
136 #define	returnerr(code) do {						\
137 	errno = code;							\
138 	return (-1);							\
139 } while (0)
140 
141 
142 #define	RTRIM(string) do {						\
143 	int _length;							\
144 	while (isspace(string[_length = strlen(string) - 1]))		\
145 		string[_length] = '\0';					\
146 } while (0)
147 
148 
149 static void
150 editentry_create(void *hook __unused, int letter, void *arg, int count,
151 		 char *name)
152 {
153 	struct editentry *newentry;	/* Buffer to hold new entry. */
154 
155 	/* Allocate memory for the new entry and a copy of the entry name. */
156 	if ((newentry = malloc(sizeof(struct editentry))) == NULL ||
157 	    (newentry->name = strdup(name)) == NULL)
158 		err(EX_OSERR, NULL);
159 
160 	/* Trim any trailing whitespace for the entry name. */
161 	RTRIM(newentry->name);
162 
163 	newentry->editable = (arg != NULL);
164 	newentry->type = letter;
165 	newentry->size = count;		/* Placeholder; not accurate. */
166 	newentry->value.svalue = NULL;
167 
168 	STAILQ_INSERT_TAIL(&editlist, newentry, link);
169 }
170 
171 static void
172 editentry_update(void *hook __unused, int letter, void *arg, int count,
173 		 char *name)
174 {
175 	struct editentry *dest;		/* Buffer to hold entry to update. */
176 
177 	dest = editentry_lookup(name);
178 	assert(dest != NULL);
179 
180 	dest->type = letter;
181 	dest->size = count;		/* We get the real size now. */
182 
183 	switch (dest->type) {
184 	case 'i':			/* Byte-sized integral type. */
185 	case 'b':			/* Bit-sized integral types. */
186 	case 't':
187 		dest->value.ivalue = (intptr_t)arg;
188 		break;
189 
190 	case 'c':			/* Character array. */
191 	case 'z':			/* Null-padded string. */
192 		editentry_set(name, (char *)arg, 0);
193 		break;
194 	default:
195 		; /* NOTREACHED */
196 	}
197 }
198 
199 static void
200 editentry_create_desc(void *hook __unused, int letter, void *arg, int count,
201 		 char *name)
202 {
203 	struct editentry *newentry;	/* Buffer to hold new entry. */
204 
205 	/* Allocate memory for the new entry and a copy of the entry name. */
206 	if ((newentry = malloc(sizeof(struct editentry))) == NULL ||
207 	    (newentry->name = strdup(name)) == NULL)
208 		err(EX_OSERR, NULL);
209 
210 	/* Trim any trailing whitespace for the entry name. */
211 	RTRIM(newentry->name);
212 
213 	newentry->editable = 1;
214 	newentry->type = letter;
215 	newentry->size = count;
216 	newentry->value.svalue = NULL;
217 
218 	STAILQ_INSERT_TAIL(&editlist, newentry, link);
219 
220 	switch (letter) {
221 	case 'i':			/* Byte-sized integral type. */
222 	case 'b':			/* Bit-sized integral types. */
223 	case 't':
224 		newentry->value.ivalue = (intptr_t)arg;
225 		break;
226 
227 	case 'c':			/* Character array. */
228 	case 'z':			/* Null-padded string. */
229 		editentry_set(name, (char *)arg, 0);
230 		break;
231 	default:
232 		; /* NOTREACHED */
233 	}
234 }
235 
236 static int
237 editentry_save(void *hook __unused, char *name)
238 {
239 	struct editentry *src;		/* Entry value to save. */
240 
241 	src = editentry_lookup(name);
242 	if (src == 0) {
243 		/*
244 		 * This happens if field does not fit into read page size.
245 		 * It also means that this field won't be written, so the
246 		 * returned value does not really matter.
247 		 */
248 		return (0);
249 	}
250 
251 	switch (src->type) {
252 	case 'i':			/* Byte-sized integral type. */
253 	case 'b':			/* Bit-sized integral types. */
254 	case 't':
255 		return (src->value.ivalue);
256 		/* NOTREACHED */
257 
258 	case 'c':			/* Character array. */
259 	case 'z':			/* Null-padded string. */
260 		return ((intptr_t)src->value.svalue);
261 		/* NOTREACHED */
262 
263 	default:
264 		; /* NOTREACHED */
265 	}
266 
267 	return (0);			/* This should never happen. */
268 }
269 
270 static struct editentry *
271 editentry_lookup(char *name)
272 {
273 	struct editentry *scan;
274 
275 	assert(name != NULL);
276 
277 	STAILQ_FOREACH(scan, &editlist, link) {
278 		if (strcasecmp(scan->name, name) == 0)
279 			return (scan);
280 	}
281 
282 	/* Not found during list traversal. */
283 	return (NULL);
284 }
285 
286 static int
287 editentry_set(char *name, char *newvalue, int editonly)
288 {
289 	struct editentry *dest;	/* Modepage entry to update. */
290 	char *cval;		/* Pointer to new string value. */
291 	char *convertend;	/* End-of-conversion pointer. */
292 	long long ival, newival; /* New integral value. */
293 	int resolution;		/* Resolution in bits for integer conversion. */
294 
295 /*
296  * Macro to determine the maximum value of the given size for the current
297  * resolution.
298  */
299 #define	RESOLUTION_MAX(size)	((1LL << (resolution * (size))) - 1)
300 
301 	assert(newvalue != NULL);
302 	if (*newvalue == '\0')
303 		return (0);	/* Nothing to do. */
304 
305 	if ((dest = editentry_lookup(name)) == NULL)
306 		returnerr(ENOENT);
307 	if (!dest->editable && editonly)
308 		returnerr(EPERM);
309 
310 	switch (dest->type) {
311 	case 'i':		/* Byte-sized integral type. */
312 	case 'b':		/* Bit-sized integral types. */
313 	case 't':
314 		/* Convert the value string to an integer. */
315 		resolution = (dest->type == 'i')? 8: 1;
316 		ival = strtoll(newvalue, &convertend, 0);
317 		if (*convertend != '\0')
318 			returnerr(EINVAL);
319 		if (ival > RESOLUTION_MAX(dest->size) || ival < 0) {
320 			newival = (ival < 0) ? 0 : RESOLUTION_MAX(dest->size);
321 			warnx("value %lld is out of range for entry %s; "
322 			    "clipping to %lld", ival, name, newival);
323 			ival = newival;
324 		}
325 		if (dest->value.ivalue != ival)
326 			editlist_changed = 1;
327 		dest->value.ivalue = ival;
328 		break;
329 
330 	case 'c':		/* Character array. */
331 	case 'z':		/* Null-padded string. */
332 		if ((cval = malloc(dest->size + 1)) == NULL)
333 			err(EX_OSERR, NULL);
334 		bzero(cval, dest->size + 1);
335 		strncpy(cval, newvalue, dest->size);
336 		if (dest->type == 'z') {
337 			/* Convert trailing spaces to nulls. */
338 			char *convertend2;
339 
340 			for (convertend2 = cval + dest->size;
341 			    convertend2 >= cval; convertend2--) {
342 				if (*convertend2 == ' ')
343 					*convertend2 = '\0';
344 				else if (*convertend2 != '\0')
345 					break;
346 			}
347 		}
348 		if (strncmp(dest->value.svalue, cval, dest->size) == 0) {
349 			/* Nothing changed, free the newly allocated string. */
350 			free(cval);
351 			break;
352 		}
353 		if (dest->value.svalue != NULL) {
354 			/* Free the current string buffer. */
355 			free(dest->value.svalue);
356 			dest->value.svalue = NULL;
357 		}
358 		dest->value.svalue = cval;
359 		editlist_changed = 1;
360 		break;
361 
362 	default:
363 		; /* NOTREACHED */
364 	}
365 
366 	return (0);
367 #undef RESOLUTION_MAX
368 }
369 
370 static void
371 nameentry_create(int page, int subpage, char *name) {
372 	struct pagename *newentry;
373 
374 	if (page < 0 || subpage < 0 || name == NULL || name[0] == '\0')
375 		return;
376 
377 	/* Allocate memory for the new entry and a copy of the entry name. */
378 	if ((newentry = malloc(sizeof(struct pagename))) == NULL ||
379 	    (newentry->name = strdup(name)) == NULL)
380 		err(EX_OSERR, NULL);
381 
382 	/* Trim any trailing whitespace for the page name. */
383 	RTRIM(newentry->name);
384 
385 	newentry->page = page;
386 	newentry->subpage = subpage;
387 	SLIST_INSERT_HEAD(&namelist, newentry, link);
388 }
389 
390 static struct pagename *
391 nameentry_lookup(int page, int subpage) {
392 	struct pagename *scan;
393 
394 	SLIST_FOREACH(scan, &namelist, link) {
395 		if (page == scan->page && subpage == scan->subpage)
396 			return (scan);
397 	}
398 
399 	/* Not found during list traversal. */
400 	return (NULL);
401 }
402 
403 static int
404 load_format(const char *pagedb_path, int lpage, int lsubpage)
405 {
406 	FILE *pagedb;
407 	char str_page[MAX_PAGENUM_LEN];
408 	char *str_subpage;
409 	char str_pagename[MAX_PAGENAME_LEN];
410 	int page;
411 	int subpage;
412 	int depth;			/* Quoting depth. */
413 	int found;
414 	int lineno;
415 	enum { LOCATE, PAGENAME, PAGEDEF } state;
416 	int ch;
417 	char c;
418 
419 #define	SETSTATE_LOCATE do {						\
420 	str_page[0] = '\0';						\
421 	str_pagename[0] = '\0';						\
422 	page = -1;							\
423 	subpage = -1;							\
424 	state = LOCATE;							\
425 } while (0)
426 
427 #define	SETSTATE_PAGENAME do {						\
428 	str_pagename[0] = '\0';						\
429 	state = PAGENAME;						\
430 } while (0)
431 
432 #define	SETSTATE_PAGEDEF do {						\
433 	format[0] = '\0';						\
434 	state = PAGEDEF;						\
435 } while (0)
436 
437 #define	UPDATE_LINENO do {						\
438 	if (c == '\n')							\
439 		lineno++;						\
440 } while (0)
441 
442 #define	BUFFERFULL(buffer)	(strlen(buffer) + 1 >= sizeof(buffer))
443 
444 	if ((pagedb = fopen(pagedb_path, "r")) == NULL)
445 		returnerr(ENOENT);
446 
447 	SLIST_INIT(&namelist);
448 
449 	c = '\0';
450 	depth = 0;
451 	lineno = 0;
452 	found = 0;
453 	SETSTATE_LOCATE;
454 	while ((ch = fgetc(pagedb)) != EOF) {
455 
456 		/* Keep a line count to make error messages more useful. */
457 		UPDATE_LINENO;
458 
459 		/* Skip over comments anywhere in the mode database. */
460 		if (ch == '#') {
461 			do {
462 				ch = fgetc(pagedb);
463 			} while (ch != '\n' && ch != EOF);
464 			UPDATE_LINENO;
465 			continue;
466 		}
467 		c = ch;
468 
469 		/* Strip out newline characters. */
470 		if (c == '\n')
471 			continue;
472 
473 		/* Keep track of the nesting depth for braces. */
474 		if (c == PAGEDEF_START)
475 			depth++;
476 		else if (c == PAGEDEF_END) {
477 			depth--;
478 			if (depth < 0) {
479 				errx(EX_OSFILE, "%s:%d: %s", pagedb_path,
480 				    lineno, "mismatched bracket");
481 			}
482 		}
483 
484 		switch (state) {
485 		case LOCATE:
486 			/*
487 			 * Locate the page the user is interested in, skipping
488 			 * all others.
489 			 */
490 			if (isspace(c)) {
491 				/* Ignore all whitespace between pages. */
492 				break;
493 			} else if (depth == 0 && c == PAGEENTRY_END) {
494 				/*
495 				 * A page entry terminator will reset page
496 				 * scanning (useful for assigning names to
497 				 * modes without providing a mode definition).
498 				 */
499 				/* Record the name of this page. */
500 				str_subpage = str_page;
501 				strsep(&str_subpage, ",");
502 				page = strtol(str_page, NULL, 0);
503 				if (str_subpage)
504 				    subpage = strtol(str_subpage, NULL, 0);
505 				else
506 				    subpage = 0;
507 				nameentry_create(page, subpage, str_pagename);
508 				SETSTATE_LOCATE;
509 			} else if (depth == 0 && c == PAGENAME_START) {
510 				SETSTATE_PAGENAME;
511 			} else if (c == PAGEDEF_START) {
512 				str_subpage = str_page;
513 				strsep(&str_subpage, ",");
514 				page = strtol(str_page, NULL, 0);
515 				if (str_subpage)
516 				    subpage = strtol(str_subpage, NULL, 0);
517 				else
518 				    subpage = 0;
519 				if (depth == 1) {
520 					/* Record the name of this page. */
521 					nameentry_create(page, subpage,
522 					    str_pagename);
523 					/*
524 					 * Only record the format if this is
525 					 * the page we are interested in.
526 					 */
527 					if (lpage == page &&
528 					    lsubpage == subpage && !found)
529 						SETSTATE_PAGEDEF;
530 				}
531 			} else if (c == PAGEDEF_END) {
532 				/* Reset the processor state. */
533 				SETSTATE_LOCATE;
534 			} else if (depth == 0 && ! BUFFERFULL(str_page)) {
535 				strncat(str_page, &c, 1);
536 			} else if (depth == 0) {
537 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
538 				    lineno, "page identifier exceeds",
539 				    sizeof(str_page) - 1, "characters");
540 			}
541 			break;
542 
543 		case PAGENAME:
544 			if (c == PAGENAME_END) {
545 				/*
546 				 * Return to LOCATE state without resetting the
547 				 * page number buffer.
548 				 */
549 				state = LOCATE;
550 			} else if (! BUFFERFULL(str_pagename)) {
551 				strncat(str_pagename, &c, 1);
552 			} else {
553 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
554 				    lineno, "page name exceeds",
555 				    sizeof(str_page) - 1, "characters");
556 			}
557 			break;
558 
559 		case PAGEDEF:
560 			/*
561 			 * Transfer the page definition into a format buffer
562 			 * suitable for use with CDB encoding/decoding routines.
563 			 */
564 			if (depth == 0) {
565 				found = 1;
566 				SETSTATE_LOCATE;
567 			} else if (! BUFFERFULL(format)) {
568 				strncat(format, &c, 1);
569 			} else {
570 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
571 				    lineno, "page definition exceeds",
572 				    sizeof(format) - 1, "characters");
573 			}
574 			break;
575 
576 		default:
577 			; /* NOTREACHED */
578 		}
579 
580 		/* Repeat processing loop with next character. */
581 	}
582 
583 	if (ferror(pagedb))
584 		err(EX_OSFILE, "%s", pagedb_path);
585 
586 	/* Close the SCSI page database. */
587 	fclose(pagedb);
588 
589 	if (!found)			/* Never found a matching page. */
590 		returnerr(ESRCH);
591 
592 	return (0);
593 }
594 
595 static void
596 editlist_populate(struct cam_device *device, int cdb_len, int dbd, int pc,
597     int page, int subpage, int task_attr, int retries, int timeout)
598 {
599 	u_int8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
600 	u_int8_t *mode_pars;		/* Pointer to modepage params. */
601 	struct scsi_mode_page_header *mph;
602 	struct scsi_mode_page_header_sp *mphsp;
603 	size_t len;
604 
605 	STAILQ_INIT(&editlist);
606 
607 	/* Fetch changeable values; use to build initial editlist. */
608 	mode_sense(device, &cdb_len, dbd, 0, 1, page, subpage, task_attr,
609 		   retries, timeout, data, sizeof(data));
610 
611 	if (cdb_len == 6) {
612 		struct scsi_mode_header_6 *mh =
613 		    (struct scsi_mode_header_6 *)data;
614 		mph = find_mode_page_6(mh);
615 	} else {
616 		struct scsi_mode_header_10 *mh =
617 		    (struct scsi_mode_header_10 *)data;
618 		mph = find_mode_page_10(mh);
619 	}
620 	if ((mph->page_code & SMPH_SPF) == 0) {
621 		mode_pars = (uint8_t *)(mph + 1);
622 		len = mph->page_length;
623 	} else {
624 		mphsp = (struct scsi_mode_page_header_sp *)mph;
625 		mode_pars = (uint8_t *)(mphsp + 1);
626 		len = scsi_2btoul(mphsp->page_length);
627 	}
628 	len = MIN(len, sizeof(data) - (mode_pars - data));
629 
630 	/* Decode the value data, creating edit_entries for each value. */
631 	buff_decode_visit(mode_pars, len, format, editentry_create, 0);
632 
633 	/* Fetch the current/saved values; use to set editentry values. */
634 	mode_sense(device, &cdb_len, dbd, 0, pc, page, subpage, task_attr,
635 	    retries, timeout, data, sizeof(data));
636 	buff_decode_visit(mode_pars, len, format, editentry_update, 0);
637 }
638 
639 static void
640 editlist_populate_desc(struct cam_device *device, int cdb_len, int llbaa, int pc,
641     int page, int subpage, int task_attr, int retries, int timeout)
642 {
643 	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
644 	uint8_t *desc;			/* Pointer to block descriptor. */
645 	char num[8];
646 	struct sbuf sb;
647 	size_t len;
648 	u_int longlba, dlen, i;
649 
650 	STAILQ_INIT(&editlist);
651 
652 	/* Fetch the current/saved values. */
653 	mode_sense(device, &cdb_len, 0, llbaa, pc, page, subpage, task_attr,
654 	    retries, timeout, data, sizeof(data));
655 
656 	if (cdb_len == 6) {
657 		struct scsi_mode_header_6 *mh =
658 		    (struct scsi_mode_header_6 *)data;
659 		desc = (uint8_t *)(mh + 1);
660 		len = mh->blk_desc_len;
661 		longlba = 0;
662 	} else {
663 		struct scsi_mode_header_10 *mh =
664 		    (struct scsi_mode_header_10 *)data;
665 		desc = (uint8_t *)(mh + 1);
666 		len = scsi_2btoul(mh->blk_desc_len);
667 		longlba = (mh->flags & SMH_LONGLBA) != 0;
668 	}
669 	dlen = longlba ? 16 : 8;
670 	len = MIN(len, sizeof(data) - (desc - data));
671 
672 	sbuf_new(&sb, format, sizeof(format), SBUF_FIXEDLEN);
673 	num[0] = 0;
674 	for (i = 0; i * dlen < len; i++) {
675 		if (i > 0)
676 			snprintf(num, sizeof(num), " %d", i + 1);
677 		if (longlba) {
678 			sbuf_printf(&sb, "{Number of Logical Blocks%s High} i4\n", num);
679 			sbuf_printf(&sb, "{Number of Logical Blocks%s} i4\n", num);
680 			sbuf_cat(&sb, "{Reserved} *i4\n");
681 			sbuf_printf(&sb, "{Logical Block Length%s} i4\n", num);
682 		} else if (device->pd_type == T_DIRECT) {
683 			sbuf_printf(&sb, "{Number of Logical Blocks%s} i4\n", num);
684 			sbuf_cat(&sb, "{Reserved} *i1\n");
685 			sbuf_printf(&sb, "{Logical Block Length%s} i3\n", num);
686 		} else {
687 			sbuf_printf(&sb, "{Density Code%s} i1\n", num);
688 			sbuf_printf(&sb, "{Number of Logical Blocks%s} i3\n", num);
689 			sbuf_cat(&sb, "{Reserved} *i1\n");
690 			sbuf_printf(&sb, "{Logical Block Length%s} i3\n", num);
691 		}
692 	}
693 	sbuf_finish(&sb);
694 	sbuf_delete(&sb);
695 
696 	/* Decode the value data, creating edit_entries for each value. */
697 	buff_decode_visit(desc, len, format, editentry_create_desc, 0);
698 }
699 
700 static void
701 editlist_save(struct cam_device *device, int cdb_len, int dbd, int pc,
702     int page, int subpage, int task_attr, int retries, int timeout)
703 {
704 	u_int8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
705 	u_int8_t *mode_pars;		/* Pointer to modepage params. */
706 	struct scsi_mode_page_header *mph;
707 	struct scsi_mode_page_header_sp *mphsp;
708 	size_t len, hlen, mphlen;
709 
710 	/* Make sure that something changed before continuing. */
711 	if (! editlist_changed)
712 		return;
713 
714 	/* Preload the CDB buffer with the current mode page data. */
715 	mode_sense(device, &cdb_len, dbd, 0, pc, page, subpage, task_attr,
716 	    retries, timeout, data, sizeof(data));
717 
718 	/* Initial headers & offsets. */
719 	/*
720 	 * Tape drives include write protect (WP), Buffered Mode and Speed
721 	 * settings in the device-specific parameter.  Clearing this
722 	 * parameter on a mode select can have the effect of turning off
723 	 * write protect or buffered mode, or changing the speed setting of
724 	 * the tape drive.
725 	 *
726 	 * Disks report DPO/FUA support via the device specific parameter
727 	 * for MODE SENSE, but the bit is reserved for MODE SELECT.  So we
728 	 * clear this for disks (and other non-tape devices) to avoid
729 	 * potential errors from the target device.
730 	 */
731 	if (cdb_len == 6) {
732 		struct scsi_mode_header_6 *mh =
733 		    (struct scsi_mode_header_6 *)data;
734 		hlen = sizeof(*mh);
735 		/* Eliminate block descriptors. */
736 		if (mh->blk_desc_len > 0) {
737 			bcopy(find_mode_page_6(mh), mh + 1,
738 			    mh->data_length + 1 - hlen -
739 			    mh->blk_desc_len);
740 			mh->blk_desc_len = 0;
741 		}
742 		mh->data_length = 0;	/* Reserved for MODE SELECT command. */
743 		if (device->pd_type != T_SEQUENTIAL)
744 			mh->dev_spec = 0;	/* See comment above */
745 		mph = find_mode_page_6(mh);
746 	} else {
747 		struct scsi_mode_header_10 *mh =
748 		    (struct scsi_mode_header_10 *)data;
749 		hlen = sizeof(*mh);
750 		/* Eliminate block descriptors. */
751 		if (scsi_2btoul(mh->blk_desc_len) > 0) {
752 			bcopy(find_mode_page_10(mh), mh + 1,
753 			    scsi_2btoul(mh->data_length) + 1 - hlen -
754 			    scsi_2btoul(mh->blk_desc_len));
755 			scsi_ulto2b(0, mh->blk_desc_len);
756 		}
757 		scsi_ulto2b(0, mh->data_length); /* Reserved for MODE SELECT. */
758 		if (device->pd_type != T_SEQUENTIAL)
759 			mh->dev_spec = 0;	/* See comment above */
760 		mph = find_mode_page_10(mh);
761 	}
762 	if ((mph->page_code & SMPH_SPF) == 0) {
763 		mphlen = sizeof(*mph);
764 		mode_pars = (uint8_t *)(mph + 1);
765 		len = mph->page_length;
766 	} else {
767 		mphsp = (struct scsi_mode_page_header_sp *)mph;
768 		mphlen = sizeof(*mphsp);
769 		mode_pars = (uint8_t *)(mphsp + 1);
770 		len = scsi_2btoul(mphsp->page_length);
771 	}
772 	len = MIN(len, sizeof(data) - (mode_pars - data));
773 
774 	/* Encode the value data to be passed back to the device. */
775 	buff_encode_visit(mode_pars, len, format, editentry_save, 0);
776 
777 	mph->page_code &= ~SMPH_PS;	/* Reserved for MODE SELECT command. */
778 
779 	/*
780 	 * Write the changes back to the device. If the user editted control
781 	 * page 3 (saved values) then request the changes be permanently
782 	 * recorded.
783 	 */
784 	mode_select(device, cdb_len, (pc << PAGE_CTRL_SHIFT == SMS_PAGE_CTRL_SAVED),
785 	    task_attr, retries, timeout, data, hlen + mphlen + len);
786 }
787 
788 static void
789 editlist_save_desc(struct cam_device *device, int cdb_len, int llbaa, int pc,
790     int page, int subpage, int task_attr, int retries, int timeout)
791 {
792 	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
793 	uint8_t *desc;			/* Pointer to block descriptor. */
794 	size_t len, hlen;
795 
796 	/* Make sure that something changed before continuing. */
797 	if (! editlist_changed)
798 		return;
799 
800 	/* Preload the CDB buffer with the current mode page data. */
801 	mode_sense(device, &cdb_len, 0, llbaa, pc, page, subpage, task_attr,
802 	    retries, timeout, data, sizeof(data));
803 
804 	/* Initial headers & offsets. */
805 	if (cdb_len == 6) {
806 		struct scsi_mode_header_6 *mh =
807 		    (struct scsi_mode_header_6 *)data;
808 		hlen = sizeof(*mh);
809 		desc = (uint8_t *)(mh + 1);
810 		len = mh->blk_desc_len;
811 		mh->data_length = 0;	/* Reserved for MODE SELECT command. */
812 		if (device->pd_type != T_SEQUENTIAL)
813 			mh->dev_spec = 0;	/* See comment above */
814 	} else {
815 		struct scsi_mode_header_10 *mh =
816 		    (struct scsi_mode_header_10 *)data;
817 		hlen = sizeof(*mh);
818 		desc = (uint8_t *)(mh + 1);
819 		len = scsi_2btoul(mh->blk_desc_len);
820 		scsi_ulto2b(0, mh->data_length); /* Reserved for MODE SELECT. */
821 		if (device->pd_type != T_SEQUENTIAL)
822 			mh->dev_spec = 0;	/* See comment above */
823 	}
824 	len = MIN(len, sizeof(data) - (desc - data));
825 
826 	/* Encode the value data to be passed back to the device. */
827 	buff_encode_visit(desc, len, format, editentry_save, 0);
828 
829 	/*
830 	 * Write the changes back to the device. If the user editted control
831 	 * page 3 (saved values) then request the changes be permanently
832 	 * recorded.
833 	 */
834 	mode_select(device, cdb_len, (pc << PAGE_CTRL_SHIFT == SMS_PAGE_CTRL_SAVED),
835 	    task_attr, retries, timeout, data, hlen + len);
836 }
837 
838 static int
839 modepage_write(FILE *file, int editonly)
840 {
841 	struct editentry *scan;
842 	int written = 0;
843 
844 	STAILQ_FOREACH(scan, &editlist, link) {
845 		if (scan->editable || !editonly) {
846 			written++;
847 			if (scan->type == 'c' || scan->type == 'z') {
848 				fprintf(file, "%s:  %s\n", scan->name,
849 				    scan->value.svalue);
850 			} else {
851 				fprintf(file, "%s:  %u\n", scan->name,
852 				    scan->value.ivalue);
853 			}
854 		}
855 	}
856 	return (written);
857 }
858 
859 static int
860 modepage_read(FILE *file)
861 {
862 	char *buffer;			/* Pointer to dynamic line buffer.  */
863 	char *line;			/* Pointer to static fgetln buffer. */
864 	char *name;			/* Name portion of the line buffer. */
865 	char *value;			/* Value portion of line buffer.    */
866 	size_t length;			/* Length of static fgetln buffer.  */
867 
868 #define	ABORT_READ(message, param) do {					\
869 	warnx(message, param);						\
870 	free(buffer);							\
871 	returnerr(EAGAIN);						\
872 } while (0)
873 
874 	while ((line = fgetln(file, &length)) != NULL) {
875 		/* Trim trailing whitespace (including optional newline). */
876 		while (length > 0 && isspace(line[length - 1]))
877 			length--;
878 
879 	    	/* Allocate a buffer to hold the line + terminating null. */
880 	    	if ((buffer = malloc(length + 1)) == NULL)
881 			err(EX_OSERR, NULL);
882 		memcpy(buffer, line, length);
883 		buffer[length] = '\0';
884 
885 		/* Strip out comments. */
886 		if ((value = strchr(buffer, '#')) != NULL)
887 			*value = '\0';
888 
889 		/* The name is first in the buffer. Trim whitespace.*/
890 		name = buffer;
891 		RTRIM(name);
892 		while (isspace(*name))
893 			name++;
894 
895 		/* Skip empty lines. */
896 		if (strlen(name) == 0)
897 			continue;
898 
899 		/* The name ends at the colon; the value starts there. */
900 		if ((value = strrchr(buffer, ':')) == NULL)
901 			ABORT_READ("no value associated with %s", name);
902 		*value = '\0';			/* Null-terminate name. */
903 		value++;			/* Value starts afterwards. */
904 
905 		/* Trim leading and trailing whitespace. */
906 		RTRIM(value);
907 		while (isspace(*value))
908 			value++;
909 
910 		/* Make sure there is a value left. */
911 		if (strlen(value) == 0)
912 			ABORT_READ("no value associated with %s", name);
913 
914 		/* Update our in-memory copy of the modepage entry value. */
915 		if (editentry_set(name, value, 1) != 0) {
916 			if (errno == ENOENT) {
917 				/* No entry by the name. */
918 				ABORT_READ("no such modepage entry \"%s\"",
919 				    name);
920 			} else if (errno == EINVAL) {
921 				/* Invalid value. */
922 				ABORT_READ("Invalid value for entry \"%s\"",
923 				    name);
924 			} else if (errno == ERANGE) {
925 				/* Value out of range for entry type. */
926 				ABORT_READ("value out of range for %s", name);
927 			} else if (errno == EPERM) {
928 				/* Entry is not editable; not fatal. */
929 				warnx("modepage entry \"%s\" is read-only; "
930 				    "skipping.", name);
931 			}
932 		}
933 
934 		free(buffer);
935 	}
936 	return (ferror(file)? -1: 0);
937 
938 #undef ABORT_READ
939 }
940 
941 static void
942 modepage_edit(void)
943 {
944 	const char *editor;
945 	char *commandline;
946 	int fd;
947 	int written;
948 
949 	if (!isatty(fileno(stdin))) {
950 		/* Not a tty, read changes from stdin. */
951 		modepage_read(stdin);
952 		return;
953 	}
954 
955 	/* Lookup editor to invoke. */
956 	if ((editor = getenv("EDITOR")) == NULL)
957 		editor = DEFAULT_EDITOR;
958 
959 	/* Create temp file for editor to modify. */
960 	if ((fd = mkstemp(edit_path)) == -1)
961 		errx(EX_CANTCREAT, "mkstemp failed");
962 
963 	atexit(cleanup_editfile);
964 
965 	if ((edit_file = fdopen(fd, "w")) == NULL)
966 		err(EX_NOINPUT, "%s", edit_path);
967 
968 	written = modepage_write(edit_file, 1);
969 
970 	fclose(edit_file);
971 	edit_file = NULL;
972 
973 	if (written == 0) {
974 		warnx("no editable entries");
975 		cleanup_editfile();
976 		return;
977 	}
978 
979 	/*
980 	 * Allocate memory to hold the command line (the 2 extra characters
981 	 * are to hold the argument separator (a space), and the terminating
982 	 * null character.
983 	 */
984 	commandline = malloc(strlen(editor) + strlen(edit_path) + 2);
985 	if (commandline == NULL)
986 		err(EX_OSERR, NULL);
987 	sprintf(commandline, "%s %s", editor, edit_path);
988 
989 	/* Invoke the editor on the temp file. */
990 	if (system(commandline) == -1)
991 		err(EX_UNAVAILABLE, "could not invoke %s", editor);
992 	free(commandline);
993 
994 	if ((edit_file = fopen(edit_path, "r")) == NULL)
995 		err(EX_NOINPUT, "%s", edit_path);
996 
997 	/* Read any changes made to the temp file. */
998 	modepage_read(edit_file);
999 
1000 	cleanup_editfile();
1001 }
1002 
1003 static void
1004 modepage_dump(struct cam_device *device, int cdb_len, int dbd, int pc,
1005 	      int page, int subpage, int task_attr, int retries, int timeout)
1006 {
1007 	u_int8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
1008 	u_int8_t *mode_pars;		/* Pointer to modepage params. */
1009 	struct scsi_mode_page_header *mph;
1010 	struct scsi_mode_page_header_sp *mphsp;
1011 	size_t indx, len;
1012 
1013 	mode_sense(device, &cdb_len, dbd, 0, pc, page, subpage, task_attr,
1014 	    retries, timeout, data, sizeof(data));
1015 
1016 	if (cdb_len == 6) {
1017 		struct scsi_mode_header_6 *mh =
1018 		    (struct scsi_mode_header_6 *)data;
1019 		mph = find_mode_page_6(mh);
1020 	} else {
1021 		struct scsi_mode_header_10 *mh =
1022 		    (struct scsi_mode_header_10 *)data;
1023 		mph = find_mode_page_10(mh);
1024 	}
1025 	if ((mph->page_code & SMPH_SPF) == 0) {
1026 		mode_pars = (uint8_t *)(mph + 1);
1027 		len = mph->page_length;
1028 	} else {
1029 		mphsp = (struct scsi_mode_page_header_sp *)mph;
1030 		mode_pars = (uint8_t *)(mphsp + 1);
1031 		len = scsi_2btoul(mphsp->page_length);
1032 	}
1033 	len = MIN(len, sizeof(data) - (mode_pars - data));
1034 
1035 	/* Print the raw mode page data with newlines each 8 bytes. */
1036 	for (indx = 0; indx < len; indx++) {
1037 		printf("%02x%c",mode_pars[indx],
1038 		    (((indx + 1) % 8) == 0) ? '\n' : ' ');
1039 	}
1040 	putchar('\n');
1041 }
1042 static void
1043 modepage_dump_desc(struct cam_device *device, int cdb_len, int llbaa, int pc,
1044 	      int page, int subpage, int task_attr, int retries, int timeout)
1045 {
1046 	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
1047 	uint8_t *desc;			/* Pointer to block descriptor. */
1048 	size_t indx, len;
1049 
1050 	mode_sense(device, &cdb_len, 0, llbaa, pc, page, subpage, task_attr,
1051 	    retries, timeout, data, sizeof(data));
1052 
1053 	if (cdb_len == 6) {
1054 		struct scsi_mode_header_6 *mh =
1055 		    (struct scsi_mode_header_6 *)data;
1056 		desc = (uint8_t *)(mh + 1);
1057 		len = mh->blk_desc_len;
1058 	} else {
1059 		struct scsi_mode_header_10 *mh =
1060 		    (struct scsi_mode_header_10 *)data;
1061 		desc = (uint8_t *)(mh + 1);
1062 		len = scsi_2btoul(mh->blk_desc_len);
1063 	}
1064 	len = MIN(len, sizeof(data) - (desc - data));
1065 
1066 	/* Print the raw mode page data with newlines each 8 bytes. */
1067 	for (indx = 0; indx < len; indx++) {
1068 		printf("%02x%c", desc[indx],
1069 		    (((indx + 1) % 8) == 0) ? '\n' : ' ');
1070 	}
1071 	putchar('\n');
1072 }
1073 
1074 static void
1075 cleanup_editfile(void)
1076 {
1077 	if (edit_file == NULL)
1078 		return;
1079 	if (fclose(edit_file) != 0 || unlink(edit_path) != 0)
1080 		warn("%s", edit_path);
1081 	edit_file = NULL;
1082 }
1083 
1084 void
1085 mode_edit(struct cam_device *device, int cdb_len, int desc, int dbd, int llbaa,
1086     int pc, int page, int subpage, int edit, int binary, int task_attr,
1087     int retry_count, int timeout)
1088 {
1089 	const char *pagedb_path;	/* Path to modepage database. */
1090 
1091 	if (binary) {
1092 		if (edit)
1093 			errx(EX_USAGE, "cannot edit in binary mode.");
1094 	} else if (desc) {
1095 		editlist_populate_desc(device, cdb_len, llbaa, pc, page,
1096 		    subpage, task_attr, retry_count, timeout);
1097 	} else {
1098 		if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
1099 			pagedb_path = DEFAULT_SCSI_MODE_DB;
1100 
1101 		if (load_format(pagedb_path, page, subpage) != 0 &&
1102 		    (edit || verbose)) {
1103 			if (errno == ENOENT) {
1104 				/* Modepage database file not found. */
1105 				warn("cannot open modepage database \"%s\"",
1106 				    pagedb_path);
1107 			} else if (errno == ESRCH) {
1108 				/* Modepage entry not found in database. */
1109 				warnx("modepage 0x%02x,0x%02x not found in "
1110 				    "database \"%s\"", page, subpage,
1111 				    pagedb_path);
1112 			}
1113 			/* We can recover in display mode, otherwise we exit. */
1114 			if (!edit) {
1115 				warnx("reverting to binary display only");
1116 				binary = 1;
1117 			} else
1118 				exit(EX_OSFILE);
1119 		}
1120 
1121 		editlist_populate(device, cdb_len, dbd, pc, page, subpage,
1122 		    task_attr, retry_count, timeout);
1123 	}
1124 
1125 	if (edit) {
1126 		if (pc << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_CURRENT &&
1127 		    pc << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_SAVED)
1128 			errx(EX_USAGE, "it only makes sense to edit page 0 "
1129 			    "(current) or page 3 (saved values)");
1130 		modepage_edit();
1131 		if (desc) {
1132 			editlist_save_desc(device, cdb_len, llbaa, pc, page,
1133 			    subpage, task_attr, retry_count, timeout);
1134 		} else {
1135 			editlist_save(device, cdb_len, dbd, pc, page, subpage,
1136 			    task_attr, retry_count, timeout);
1137 		}
1138 	} else if (binary || STAILQ_EMPTY(&editlist)) {
1139 		/* Display without formatting information. */
1140 		if (desc) {
1141 			modepage_dump_desc(device, cdb_len, llbaa, pc, page,
1142 			    subpage, task_attr, retry_count, timeout);
1143 		} else {
1144 			modepage_dump(device, cdb_len, dbd, pc, page, subpage,
1145 			    task_attr, retry_count, timeout);
1146 		}
1147 	} else {
1148 		/* Display with format. */
1149 		modepage_write(stdout, 0);
1150 	}
1151 }
1152 
1153 void
1154 mode_list(struct cam_device *device, int cdb_len, int dbd, int pc, int subpages,
1155 	  int task_attr, int retry_count, int timeout)
1156 {
1157 	u_int8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
1158 	struct scsi_mode_page_header *mph;
1159 	struct scsi_mode_page_header_sp *mphsp;
1160 	struct pagename *nameentry;
1161 	const char *pagedb_path;
1162 	int len, off, page, subpage;
1163 
1164 	if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
1165 		pagedb_path = DEFAULT_SCSI_MODE_DB;
1166 
1167 	if (load_format(pagedb_path, 0, 0) != 0 && verbose && errno == ENOENT) {
1168 		/* Modepage database file not found. */
1169 		warn("cannot open modepage database \"%s\"", pagedb_path);
1170 	}
1171 
1172 	/* Build the list of all mode pages by querying the "all pages" page. */
1173 	mode_sense(device, &cdb_len, dbd, 0, pc, SMS_ALL_PAGES_PAGE,
1174 	    subpages ? SMS_SUBPAGE_ALL : 0,
1175 	    task_attr, retry_count, timeout, data, sizeof(data));
1176 
1177 	/* Skip block descriptors. */
1178 	if (cdb_len == 6) {
1179 		struct scsi_mode_header_6 *mh =
1180 		    (struct scsi_mode_header_6 *)data;
1181 		len = mh->data_length;
1182 		off = sizeof(*mh) + mh->blk_desc_len;
1183 	} else {
1184 		struct scsi_mode_header_10 *mh =
1185 		    (struct scsi_mode_header_10 *)data;
1186 		len = scsi_2btoul(mh->data_length);
1187 		off = sizeof(*mh) + scsi_2btoul(mh->blk_desc_len);
1188 	}
1189 	/* Iterate through the pages in the reply. */
1190 	while (off < len) {
1191 		/* Locate the next mode page header. */
1192 		mph = (struct scsi_mode_page_header *)(data + off);
1193 
1194 		if ((mph->page_code & SMPH_SPF) == 0) {
1195 			page = mph->page_code & SMS_PAGE_CODE;
1196 			subpage = 0;
1197 			off += sizeof(*mph) + mph->page_length;
1198 		} else {
1199 			mphsp = (struct scsi_mode_page_header_sp *)mph;
1200 			page = mphsp->page_code & SMS_PAGE_CODE;
1201 			subpage = mphsp->subpage;
1202 			off += sizeof(*mphsp) + scsi_2btoul(mphsp->page_length);
1203 		}
1204 
1205 		nameentry = nameentry_lookup(page, subpage);
1206 		if (subpage == 0) {
1207 			printf("0x%02x\t%s\n", page,
1208 			    nameentry ? nameentry->name : "");
1209 		} else {
1210 			printf("0x%02x,0x%02x\t%s\n", page, subpage,
1211 			    nameentry ? nameentry->name : "");
1212 		}
1213 	}
1214 }
1215