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