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