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