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