xref: /freebsd/sbin/camcontrol/modeedit.c (revision 0b3105a37d7adcadcb720112fed4dc4e8040be99)
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 #define	MODE_PAGE_DATA(mph)						\
70 	(u_int8_t *)(mph) + sizeof(struct scsi_mode_page_header)
71 
72 
73 struct editentry {
74 	STAILQ_ENTRY(editentry) link;
75 	char	*name;
76 	char	type;
77 	int	editable;
78 	int	size;
79 	union {
80 		int	ivalue;
81 		char	*svalue;
82 	} value;
83 };
84 static STAILQ_HEAD(, editentry) editlist; /* List of page entries. */
85 static int editlist_changed = 0;	/* Whether any entries were changed. */
86 
87 struct pagename {
88 	SLIST_ENTRY(pagename) link;
89 	int pagenum;
90 	char *name;
91 };
92 static SLIST_HEAD(, pagename) namelist;	/* Page number to name mappings. */
93 
94 static char format[MAX_FORMAT_SPEC];	/* Buffer for scsi cdb format def. */
95 
96 static FILE *edit_file = NULL;		/* File handle for edit file. */
97 static char edit_path[] = "/tmp/camXXXXXX";
98 
99 
100 /* Function prototypes. */
101 static void		 editentry_create(void *hook, int letter, void *arg,
102 					  int count, char *name);
103 static void		 editentry_update(void *hook, int letter, void *arg,
104 					  int count, char *name);
105 static int		 editentry_save(void *hook, char *name);
106 static struct editentry	*editentry_lookup(char *name);
107 static int		 editentry_set(char *name, char *newvalue,
108 				       int editonly);
109 static void		 editlist_populate(struct cam_device *device,
110 					   int modepage, int page_control,
111 					   int dbd, int retries, int timeout);
112 static void		 editlist_save(struct cam_device *device, int modepage,
113 				       int page_control, int dbd, int retries,
114 				       int timeout);
115 static void		 nameentry_create(int pagenum, char *name);
116 static struct pagename	*nameentry_lookup(int pagenum);
117 static int		 load_format(const char *pagedb_path, int page);
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 page,
122 				       int page_control, int dbd, int retries,
123 				       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 	assert(src != NULL);
197 
198 	switch (src->type) {
199 	case 'i':			/* Byte-sized integral type. */
200 	case 'b':			/* Bit-sized integral types. */
201 	case 't':
202 		return (src->value.ivalue);
203 		/* NOTREACHED */
204 
205 	case 'c':			/* Character array. */
206 	case 'z':			/* Null-padded string. */
207 		return ((intptr_t)src->value.svalue);
208 		/* NOTREACHED */
209 
210 	default:
211 		; /* NOTREACHED */
212 	}
213 
214 	return (0);			/* This should never happen. */
215 }
216 
217 static struct editentry *
218 editentry_lookup(char *name)
219 {
220 	struct editentry *scan;
221 
222 	assert(name != NULL);
223 
224 	STAILQ_FOREACH(scan, &editlist, link) {
225 		if (strcasecmp(scan->name, name) == 0)
226 			return (scan);
227 	}
228 
229 	/* Not found during list traversal. */
230 	return (NULL);
231 }
232 
233 static int
234 editentry_set(char *name, char *newvalue, int editonly)
235 {
236 	struct editentry *dest;	/* Modepage entry to update. */
237 	char *cval;		/* Pointer to new string value. */
238 	char *convertend;	/* End-of-conversion pointer. */
239 	int ival;		/* New integral value. */
240 	int resolution;		/* Resolution in bits for integer conversion. */
241 
242 /*
243  * Macro to determine the maximum value of the given size for the current
244  * resolution.
245  * XXX Lovely x86's optimize out the case of shifting by 32 and gcc doesn't
246  *     currently workaround it (even for int64's), so we have to kludge it.
247  */
248 #define	RESOLUTION_MAX(size) ((resolution * (size) == 32)? 		\
249 	INT_MAX: (1 << (resolution * (size))) - 1)
250 
251 	assert(newvalue != NULL);
252 	if (*newvalue == '\0')
253 		return (0);	/* Nothing to do. */
254 
255 	if ((dest = editentry_lookup(name)) == NULL)
256 		returnerr(ENOENT);
257 	if (!dest->editable && editonly)
258 		returnerr(EPERM);
259 
260 	switch (dest->type) {
261 	case 'i':		/* Byte-sized integral type. */
262 	case 'b':		/* Bit-sized integral types. */
263 	case 't':
264 		/* Convert the value string to an integer. */
265 		resolution = (dest->type == 'i')? 8: 1;
266 		ival = (int)strtol(newvalue, &convertend, 0);
267 		if (*convertend != '\0')
268 			returnerr(EINVAL);
269 		if (ival > RESOLUTION_MAX(dest->size) || ival < 0) {
270 			int newival = (ival < 0)? 0: RESOLUTION_MAX(dest->size);
271 			warnx("value %d is out of range for entry %s; clipping "
272 			    "to %d", ival, name, newival);
273 			ival = newival;
274 		}
275 		if (dest->value.ivalue != ival)
276 			editlist_changed = 1;
277 		dest->value.ivalue = ival;
278 		break;
279 
280 	case 'c':		/* Character array. */
281 	case 'z':		/* Null-padded string. */
282 		if ((cval = malloc(dest->size + 1)) == NULL)
283 			err(EX_OSERR, NULL);
284 		bzero(cval, dest->size + 1);
285 		strncpy(cval, newvalue, dest->size);
286 		if (dest->type == 'z') {
287 			/* Convert trailing spaces to nulls. */
288 			char *convertend2;
289 
290 			for (convertend2 = cval + dest->size;
291 			    convertend2 >= cval; convertend2--) {
292 				if (*convertend2 == ' ')
293 					*convertend2 = '\0';
294 				else if (*convertend2 != '\0')
295 					break;
296 			}
297 		}
298 		if (strncmp(dest->value.svalue, cval, dest->size) == 0) {
299 			/* Nothing changed, free the newly allocated string. */
300 			free(cval);
301 			break;
302 		}
303 		if (dest->value.svalue != NULL) {
304 			/* Free the current string buffer. */
305 			free(dest->value.svalue);
306 			dest->value.svalue = NULL;
307 		}
308 		dest->value.svalue = cval;
309 		editlist_changed = 1;
310 		break;
311 
312 	default:
313 		; /* NOTREACHED */
314 	}
315 
316 	return (0);
317 #undef RESOLUTION_MAX
318 }
319 
320 static void
321 nameentry_create(int pagenum, char *name) {
322 	struct pagename *newentry;
323 
324 	if (pagenum < 0 || name == NULL || name[0] == '\0')
325 		return;
326 
327 	/* Allocate memory for the new entry and a copy of the entry name. */
328 	if ((newentry = malloc(sizeof(struct pagename))) == NULL ||
329 	    (newentry->name = strdup(name)) == NULL)
330 		err(EX_OSERR, NULL);
331 
332 	/* Trim any trailing whitespace for the page name. */
333 	RTRIM(newentry->name);
334 
335 	newentry->pagenum = pagenum;
336 	SLIST_INSERT_HEAD(&namelist, newentry, link);
337 }
338 
339 static struct pagename *
340 nameentry_lookup(int pagenum) {
341 	struct pagename *scan;
342 
343 	SLIST_FOREACH(scan, &namelist, link) {
344 		if (pagenum == scan->pagenum)
345 			return (scan);
346 	}
347 
348 	/* Not found during list traversal. */
349 	return (NULL);
350 }
351 
352 static int
353 load_format(const char *pagedb_path, int page)
354 {
355 	FILE *pagedb;
356 	char str_pagenum[MAX_PAGENUM_LEN];
357 	char str_pagename[MAX_PAGENAME_LEN];
358 	int pagenum;
359 	int depth;			/* Quoting depth. */
360 	int found;
361 	int lineno;
362 	enum { LOCATE, PAGENAME, PAGEDEF } state;
363 	int ch;
364 	char c;
365 
366 #define	SETSTATE_LOCATE do {						\
367 	str_pagenum[0] = '\0';						\
368 	str_pagename[0] = '\0';						\
369 	pagenum = -1;							\
370 	state = LOCATE;							\
371 } while (0)
372 
373 #define	SETSTATE_PAGENAME do {						\
374 	str_pagename[0] = '\0';						\
375 	state = PAGENAME;						\
376 } while (0)
377 
378 #define	SETSTATE_PAGEDEF do {						\
379 	format[0] = '\0';						\
380 	state = PAGEDEF;						\
381 } while (0)
382 
383 #define	UPDATE_LINENO do {						\
384 	if (c == '\n')							\
385 		lineno++;						\
386 } while (0)
387 
388 #define	BUFFERFULL(buffer)	(strlen(buffer) + 1 >= sizeof(buffer))
389 
390 	if ((pagedb = fopen(pagedb_path, "r")) == NULL)
391 		returnerr(ENOENT);
392 
393 	SLIST_INIT(&namelist);
394 
395 	c = '\0';
396 	depth = 0;
397 	lineno = 0;
398 	found = 0;
399 	SETSTATE_LOCATE;
400 	while ((ch = fgetc(pagedb)) != EOF) {
401 
402 		/* Keep a line count to make error messages more useful. */
403 		UPDATE_LINENO;
404 
405 		/* Skip over comments anywhere in the mode database. */
406 		if (ch == '#') {
407 			do {
408 				ch = fgetc(pagedb);
409 			} while (ch != '\n' && ch != EOF);
410 			UPDATE_LINENO;
411 			continue;
412 		}
413 		c = ch;
414 
415 		/* Strip out newline characters. */
416 		if (c == '\n')
417 			continue;
418 
419 		/* Keep track of the nesting depth for braces. */
420 		if (c == PAGEDEF_START)
421 			depth++;
422 		else if (c == PAGEDEF_END) {
423 			depth--;
424 			if (depth < 0) {
425 				errx(EX_OSFILE, "%s:%d: %s", pagedb_path,
426 				    lineno, "mismatched bracket");
427 			}
428 		}
429 
430 		switch (state) {
431 		case LOCATE:
432 			/*
433 			 * Locate the page the user is interested in, skipping
434 			 * all others.
435 			 */
436 			if (isspace(c)) {
437 				/* Ignore all whitespace between pages. */
438 				break;
439 			} else if (depth == 0 && c == PAGEENTRY_END) {
440 				/*
441 				 * A page entry terminator will reset page
442 				 * scanning (useful for assigning names to
443 				 * modes without providing a mode definition).
444 				 */
445 				/* Record the name of this page. */
446 				pagenum = strtol(str_pagenum, NULL, 0);
447 				nameentry_create(pagenum, str_pagename);
448 				SETSTATE_LOCATE;
449 			} else if (depth == 0 && c == PAGENAME_START) {
450 				SETSTATE_PAGENAME;
451 			} else if (c == PAGEDEF_START) {
452 				pagenum = strtol(str_pagenum, NULL, 0);
453 				if (depth == 1) {
454 					/* Record the name of this page. */
455 					nameentry_create(pagenum, str_pagename);
456 					/*
457 					 * Only record the format if this is
458 					 * the page we are interested in.
459 					 */
460 					if (page == pagenum && !found)
461 						SETSTATE_PAGEDEF;
462 				}
463 			} else if (c == PAGEDEF_END) {
464 				/* Reset the processor state. */
465 				SETSTATE_LOCATE;
466 			} else if (depth == 0 && ! BUFFERFULL(str_pagenum)) {
467 				strncat(str_pagenum, &c, 1);
468 			} else if (depth == 0) {
469 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
470 				    lineno, "page identifier exceeds",
471 				    sizeof(str_pagenum) - 1, "characters");
472 			}
473 			break;
474 
475 		case PAGENAME:
476 			if (c == PAGENAME_END) {
477 				/*
478 				 * Return to LOCATE state without resetting the
479 				 * page number buffer.
480 				 */
481 				state = LOCATE;
482 			} else if (! BUFFERFULL(str_pagename)) {
483 				strncat(str_pagename, &c, 1);
484 			} else {
485 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
486 				    lineno, "page name exceeds",
487 				    sizeof(str_pagenum) - 1, "characters");
488 			}
489 			break;
490 
491 		case PAGEDEF:
492 			/*
493 			 * Transfer the page definition into a format buffer
494 			 * suitable for use with CDB encoding/decoding routines.
495 			 */
496 			if (depth == 0) {
497 				found = 1;
498 				SETSTATE_LOCATE;
499 			} else if (! BUFFERFULL(format)) {
500 				strncat(format, &c, 1);
501 			} else {
502 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
503 				    lineno, "page definition exceeds",
504 				    sizeof(format) - 1, "characters");
505 			}
506 			break;
507 
508 		default:
509 			; /* NOTREACHED */
510 		}
511 
512 		/* Repeat processing loop with next character. */
513 	}
514 
515 	if (ferror(pagedb))
516 		err(EX_OSFILE, "%s", pagedb_path);
517 
518 	/* Close the SCSI page database. */
519 	fclose(pagedb);
520 
521 	if (!found)			/* Never found a matching page. */
522 		returnerr(ESRCH);
523 
524 	return (0);
525 }
526 
527 static void
528 editlist_populate(struct cam_device *device, int modepage, int page_control,
529 		  int dbd, int retries, int timeout)
530 {
531 	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
532 	u_int8_t *mode_pars;		/* Pointer to modepage params. */
533 	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
534 	struct scsi_mode_page_header *mph;
535 
536 	STAILQ_INIT(&editlist);
537 
538 	/* Fetch changeable values; use to build initial editlist. */
539 	mode_sense(device, modepage, 1, dbd, retries, timeout, data,
540 		   sizeof(data));
541 
542 	mh = (struct scsi_mode_header_6 *)data;
543 	mph = MODE_PAGE_HEADER(mh);
544 	mode_pars = MODE_PAGE_DATA(mph);
545 
546 	/* Decode the value data, creating edit_entries for each value. */
547 	buff_decode_visit(mode_pars, mh->data_length, format,
548 	    editentry_create, 0);
549 
550 	/* Fetch the current/saved values; use to set editentry values. */
551 	mode_sense(device, modepage, page_control, dbd, retries, timeout, data,
552 		   sizeof(data));
553 	buff_decode_visit(mode_pars, mh->data_length, format,
554 	    editentry_update, 0);
555 }
556 
557 static void
558 editlist_save(struct cam_device *device, int modepage, int page_control,
559 	      int dbd, int retries, int timeout)
560 {
561 	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
562 	u_int8_t *mode_pars;		/* Pointer to modepage params. */
563 	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
564 	struct scsi_mode_page_header *mph;
565 
566 	/* Make sure that something changed before continuing. */
567 	if (! editlist_changed)
568 		return;
569 
570 	/*
571 	 * Preload the CDB buffer with the current mode page data.
572 	 * XXX If buff_encode_visit would return the number of bytes encoded
573 	 *     we *should* use that to build a header from scratch. As it is
574 	 *     now, we need mode_sense to find out the page length.
575 	 */
576 	mode_sense(device, modepage, page_control, dbd, retries, timeout, data,
577 		   sizeof(data));
578 
579 	/* Initial headers & offsets. */
580 	mh = (struct scsi_mode_header_6 *)data;
581 	mph = MODE_PAGE_HEADER(mh);
582 	mode_pars = MODE_PAGE_DATA(mph);
583 
584 	/* Encode the value data to be passed back to the device. */
585 	buff_encode_visit(mode_pars, mh->data_length, format,
586 	    editentry_save, 0);
587 
588 	/* Eliminate block descriptors. */
589 	bcopy(mph, ((u_int8_t *)mh) + sizeof(*mh),
590 	    sizeof(*mph) + mph->page_length);
591 
592 	/* Recalculate headers & offsets. */
593 	mh->blk_desc_len = 0;		/* No block descriptors. */
594 	mh->dev_spec = 0;		/* Clear device-specific parameters. */
595 	mph = MODE_PAGE_HEADER(mh);
596 	mode_pars = MODE_PAGE_DATA(mph);
597 
598 	mph->page_code &= SMS_PAGE_CODE;/* Isolate just the page code. */
599 	mh->data_length = 0;		/* Reserved for MODE SELECT command. */
600 
601 	/*
602 	 * Write the changes back to the device. If the user editted control
603 	 * page 3 (saved values) then request the changes be permanently
604 	 * recorded.
605 	 */
606 	mode_select(device,
607 	    (page_control << PAGE_CTRL_SHIFT == SMS_PAGE_CTRL_SAVED),
608 	    retries, timeout, (u_int8_t *)mh,
609 	    sizeof(*mh) + mh->blk_desc_len + sizeof(*mph) + mph->page_length);
610 }
611 
612 static int
613 modepage_write(FILE *file, int editonly)
614 {
615 	struct editentry *scan;
616 	int written = 0;
617 
618 	STAILQ_FOREACH(scan, &editlist, link) {
619 		if (scan->editable || !editonly) {
620 			written++;
621 			if (scan->type == 'c' || scan->type == 'z') {
622 				fprintf(file, "%s:  %s\n", scan->name,
623 				    scan->value.svalue);
624 			} else {
625 				fprintf(file, "%s:  %d\n", scan->name,
626 				    scan->value.ivalue);
627 			}
628 		}
629 	}
630 	return (written);
631 }
632 
633 static int
634 modepage_read(FILE *file)
635 {
636 	char *buffer;			/* Pointer to dynamic line buffer.  */
637 	char *line;			/* Pointer to static fgetln buffer. */
638 	char *name;			/* Name portion of the line buffer. */
639 	char *value;			/* Value portion of line buffer.    */
640 	size_t length;			/* Length of static fgetln buffer.  */
641 
642 #define	ABORT_READ(message, param) do {					\
643 	warnx(message, param);						\
644 	free(buffer);							\
645 	returnerr(EAGAIN);						\
646 } while (0)
647 
648 	while ((line = fgetln(file, &length)) != NULL) {
649 		/* Trim trailing whitespace (including optional newline). */
650 		while (length > 0 && isspace(line[length - 1]))
651 			length--;
652 
653 	    	/* Allocate a buffer to hold the line + terminating null. */
654 	    	if ((buffer = malloc(length + 1)) == NULL)
655 			err(EX_OSERR, NULL);
656 		memcpy(buffer, line, length);
657 		buffer[length] = '\0';
658 
659 		/* Strip out comments. */
660 		if ((value = strchr(buffer, '#')) != NULL)
661 			*value = '\0';
662 
663 		/* The name is first in the buffer. Trim whitespace.*/
664 		name = buffer;
665 		RTRIM(name);
666 		while (isspace(*name))
667 			name++;
668 
669 		/* Skip empty lines. */
670 		if (strlen(name) == 0)
671 			continue;
672 
673 		/* The name ends at the colon; the value starts there. */
674 		if ((value = strrchr(buffer, ':')) == NULL)
675 			ABORT_READ("no value associated with %s", name);
676 		*value = '\0';			/* Null-terminate name. */
677 		value++;			/* Value starts afterwards. */
678 
679 		/* Trim leading and trailing whitespace. */
680 		RTRIM(value);
681 		while (isspace(*value))
682 			value++;
683 
684 		/* Make sure there is a value left. */
685 		if (strlen(value) == 0)
686 			ABORT_READ("no value associated with %s", name);
687 
688 		/* Update our in-memory copy of the modepage entry value. */
689 		if (editentry_set(name, value, 1) != 0) {
690 			if (errno == ENOENT) {
691 				/* No entry by the name. */
692 				ABORT_READ("no such modepage entry \"%s\"",
693 				    name);
694 			} else if (errno == EINVAL) {
695 				/* Invalid value. */
696 				ABORT_READ("Invalid value for entry \"%s\"",
697 				    name);
698 			} else if (errno == ERANGE) {
699 				/* Value out of range for entry type. */
700 				ABORT_READ("value out of range for %s", name);
701 			} else if (errno == EPERM) {
702 				/* Entry is not editable; not fatal. */
703 				warnx("modepage entry \"%s\" is read-only; "
704 				    "skipping.", name);
705 			}
706 		}
707 
708 		free(buffer);
709 	}
710 	return (ferror(file)? -1: 0);
711 
712 #undef ABORT_READ
713 }
714 
715 static void
716 modepage_edit(void)
717 {
718 	const char *editor;
719 	char *commandline;
720 	int fd;
721 	int written;
722 
723 	if (!isatty(fileno(stdin))) {
724 		/* Not a tty, read changes from stdin. */
725 		modepage_read(stdin);
726 		return;
727 	}
728 
729 	/* Lookup editor to invoke. */
730 	if ((editor = getenv("EDITOR")) == NULL)
731 		editor = DEFAULT_EDITOR;
732 
733 	/* Create temp file for editor to modify. */
734 	if ((fd = mkstemp(edit_path)) == -1)
735 		errx(EX_CANTCREAT, "mkstemp failed");
736 
737 	atexit(cleanup_editfile);
738 
739 	if ((edit_file = fdopen(fd, "w")) == NULL)
740 		err(EX_NOINPUT, "%s", edit_path);
741 
742 	written = modepage_write(edit_file, 1);
743 
744 	fclose(edit_file);
745 	edit_file = NULL;
746 
747 	if (written == 0) {
748 		warnx("no editable entries");
749 		cleanup_editfile();
750 		return;
751 	}
752 
753 	/*
754 	 * Allocate memory to hold the command line (the 2 extra characters
755 	 * are to hold the argument separator (a space), and the terminating
756 	 * null character.
757 	 */
758 	commandline = malloc(strlen(editor) + strlen(edit_path) + 2);
759 	if (commandline == NULL)
760 		err(EX_OSERR, NULL);
761 	sprintf(commandline, "%s %s", editor, edit_path);
762 
763 	/* Invoke the editor on the temp file. */
764 	if (system(commandline) == -1)
765 		err(EX_UNAVAILABLE, "could not invoke %s", editor);
766 	free(commandline);
767 
768 	if ((edit_file = fopen(edit_path, "r")) == NULL)
769 		err(EX_NOINPUT, "%s", edit_path);
770 
771 	/* Read any changes made to the temp file. */
772 	modepage_read(edit_file);
773 
774 	cleanup_editfile();
775 }
776 
777 static void
778 modepage_dump(struct cam_device *device, int page, int page_control, int dbd,
779 	      int retries, int timeout)
780 {
781 	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
782 	u_int8_t *mode_pars;		/* Pointer to modepage params. */
783 	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
784 	struct scsi_mode_page_header *mph;
785 	int indx;			/* Index for scanning mode params. */
786 
787 	mode_sense(device, page, page_control, dbd, retries, timeout, data,
788 		   sizeof(data));
789 
790 	mh = (struct scsi_mode_header_6 *)data;
791 	mph = MODE_PAGE_HEADER(mh);
792 	mode_pars = MODE_PAGE_DATA(mph);
793 
794 	/* Print the raw mode page data with newlines each 8 bytes. */
795 	for (indx = 0; indx < mph->page_length; indx++) {
796 		printf("%02x%c",mode_pars[indx],
797 		    (((indx + 1) % 8) == 0) ? '\n' : ' ');
798 	}
799 	putchar('\n');
800 }
801 
802 static void
803 cleanup_editfile(void)
804 {
805 	if (edit_file == NULL)
806 		return;
807 	if (fclose(edit_file) != 0 || unlink(edit_path) != 0)
808 		warn("%s", edit_path);
809 	edit_file = NULL;
810 }
811 
812 void
813 mode_edit(struct cam_device *device, int page, int page_control, int dbd,
814 	  int edit, int binary, int retry_count, int timeout)
815 {
816 	const char *pagedb_path;	/* Path to modepage database. */
817 
818 	if (edit && binary)
819 		errx(EX_USAGE, "cannot edit in binary mode.");
820 
821 	if (! binary) {
822 		if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
823 			pagedb_path = DEFAULT_SCSI_MODE_DB;
824 
825 		if (load_format(pagedb_path, page) != 0 && (edit || verbose)) {
826 			if (errno == ENOENT) {
827 				/* Modepage database file not found. */
828 				warn("cannot open modepage database \"%s\"",
829 				    pagedb_path);
830 			} else if (errno == ESRCH) {
831 				/* Modepage entry not found in database. */
832 				warnx("modepage %d not found in database"
833 				    "\"%s\"", page, pagedb_path);
834 			}
835 			/* We can recover in display mode, otherwise we exit. */
836 			if (!edit) {
837 				warnx("reverting to binary display only");
838 				binary = 1;
839 			} else
840 				exit(EX_OSFILE);
841 		}
842 
843 		editlist_populate(device, page, page_control, dbd, retry_count,
844 			timeout);
845 	}
846 
847 	if (edit) {
848 		if (page_control << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_CURRENT &&
849 		    page_control << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_SAVED)
850 			errx(EX_USAGE, "it only makes sense to edit page 0 "
851 			    "(current) or page 3 (saved values)");
852 		modepage_edit();
853 		editlist_save(device, page, page_control, dbd, retry_count,
854 			timeout);
855 	} else if (binary || STAILQ_EMPTY(&editlist)) {
856 		/* Display without formatting information. */
857 		modepage_dump(device, page, page_control, dbd, retry_count,
858 		    timeout);
859 	} else {
860 		/* Display with format. */
861 		modepage_write(stdout, 0);
862 	}
863 }
864 
865 void
866 mode_list(struct cam_device *device, int page_control, int dbd,
867 	  int retry_count, int timeout)
868 {
869 	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
870 	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
871 	struct scsi_mode_page_header *mph;
872 	struct pagename *nameentry;
873 	const char *pagedb_path;
874 	int len;
875 
876 	if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
877 		pagedb_path = DEFAULT_SCSI_MODE_DB;
878 
879 	if (load_format(pagedb_path, 0) != 0 && verbose && errno == ENOENT) {
880 		/* Modepage database file not found. */
881 		warn("cannot open modepage database \"%s\"", pagedb_path);
882 	}
883 
884 	/* Build the list of all mode pages by querying the "all pages" page. */
885 	mode_sense(device, SMS_ALL_PAGES_PAGE, page_control, dbd, retry_count,
886 	    timeout, data, sizeof(data));
887 
888 	mh = (struct scsi_mode_header_6 *)data;
889 	len = sizeof(*mh) + mh->blk_desc_len;	/* Skip block descriptors. */
890 	/* Iterate through the pages in the reply. */
891 	while (len < mh->data_length) {
892 		/* Locate the next mode page header. */
893 		mph = (struct scsi_mode_page_header *)
894 		    ((intptr_t)mh + len);
895 
896 		mph->page_code &= SMS_PAGE_CODE;
897 		nameentry = nameentry_lookup(mph->page_code);
898 
899 		if (nameentry == NULL || nameentry->name == NULL)
900 			printf("0x%02x\n", mph->page_code);
901 		else
902 			printf("0x%02x\t%s\n", mph->page_code,
903 			    nameentry->name);
904 		len += mph->page_length + sizeof(*mph);
905 	}
906 }
907