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