modeedit.c revision 114513
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: head/sbin/camcontrol/modeedit.c 114513 2003-05-02 06:49:10Z obrien $");
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
51int 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
75struct 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};
86STAILQ_HEAD(, editentry) editlist;	/* List of page entries. */
87int editlist_changed = 0;		/* Whether any entries were changed. */
88
89struct pagename {
90	SLIST_ENTRY(pagename) link;
91	int pagenum;
92	char *name;
93};
94SLIST_HEAD(, pagename) namelist;	/* Page number to name mappings. */
95
96static char format[MAX_FORMAT_SPEC];	/* Buffer for scsi cdb format def. */
97
98static FILE *edit_file = NULL;		/* File handle for edit file. */
99static char edit_path[] = "/tmp/camXXXXXX";
100
101
102/* Function prototypes. */
103static void		 editentry_create(void *hook, int letter, void *arg,
104					  int count, char *name);
105static void		 editentry_update(void *hook, int letter, void *arg,
106					  int count, char *name);
107static int		 editentry_save(void *hook, char *name);
108static struct editentry	*editentry_lookup(char *name);
109static int		 editentry_set(char *name, char *newvalue,
110				       int editonly);
111static void		 editlist_populate(struct cam_device *device,
112					   int modepage, int page_control,
113					   int dbd, int retries, int timeout);
114static void		 editlist_save(struct cam_device *device, int modepage,
115				       int page_control, int dbd, int retries,
116				       int timeout);
117static void		 nameentry_create(int pagenum, char *name);
118static struct pagename	*nameentry_lookup(int pagenum);
119static int		 load_format(char *pagedb_path, int page);
120static int		 modepage_write(FILE *file, int editonly);
121static int		 modepage_read(FILE *file);
122static void		 modepage_edit(void);
123static void		 modepage_dump(struct cam_device *device, int page,
124				       int page_control, int dbd, int retries,
125				       int timeout);
126static void		 cleanup_editfile(void);
127void			 mode_edit(struct cam_device *device, int page,
128				   int page_control, int dbd, int edit,
129				   int binary, int retry_count, int timeout);
130void			 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
147static void
148editentry_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
168static void
169editentry_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
195static int
196editentry_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
222static struct editentry *
223editentry_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
238static int
239editentry_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
325static void
326nameentry_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
344static struct pagename *
345nameentry_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
357static int
358load_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 %zd %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 %zd %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 %zd %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
529static void
530editlist_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
559static void
560editlist_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
614static int
615modepage_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
635static int
636modepage_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	size_t 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
717static void
718modepage_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
779static void
780modepage_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
804static void
805cleanup_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
814void
815mode_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
867void
868mode_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