164382Skbyanc/*-
264382Skbyanc * Copyright (c) 2000 Kelly Yancey <kbyanc@posi.net>
364382Skbyanc * Derived from work done by Julian Elischer <julian@tfs.com,
464382Skbyanc * julian@dialix.oz.au>, 1993, and Peter Dufault <dufault@hda.com>, 1994.
539214Sgibbs * All rights reserved.
639214Sgibbs *
739214Sgibbs * Redistribution and use in source and binary forms, with or without
839214Sgibbs * modification, are permitted provided that the following conditions
939214Sgibbs * are met:
1039214Sgibbs * 1. Redistributions of source code must retain the above copyright
1164382Skbyanc *    notice, this list of conditions and the following disclaimer,
1264382Skbyanc *    without modification, immediately at the beginning of the file.
1339214Sgibbs * 2. Redistributions in binary form must reproduce the above copyright
1439214Sgibbs *    notice, this list of conditions and the following disclaimer in the
1564382Skbyanc *    documentation and/or other materials provided with the distribution.
1664382Skbyanc *
1764382Skbyanc * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1864382Skbyanc * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1964382Skbyanc * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2064382Skbyanc * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2164382Skbyanc * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2264382Skbyanc * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2364382Skbyanc * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2464382Skbyanc * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2564382Skbyanc * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2664382Skbyanc * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2739214Sgibbs */
2864382Skbyanc
29114513Sobrien#include <sys/cdefs.h>
30114513Sobrien__FBSDID("$FreeBSD: stable/10/sbin/camcontrol/modeedit.c 317966 2017-05-08 18:30:56Z ken $");
3139214Sgibbs
3264382Skbyanc#include <sys/queue.h>
3364382Skbyanc#include <sys/types.h>
3464382Skbyanc
3564382Skbyanc#include <assert.h>
3639214Sgibbs#include <ctype.h>
3739214Sgibbs#include <err.h>
3839214Sgibbs#include <errno.h>
3964382Skbyanc#include <stdlib.h>
4039214Sgibbs#include <string.h>
4139214Sgibbs#include <stdio.h>
4264382Skbyanc#include <sysexits.h>
4339214Sgibbs#include <unistd.h>
4439214Sgibbs
4564382Skbyanc#include <cam/scsi/scsi_all.h>
4639214Sgibbs#include <cam/cam.h>
4739214Sgibbs#include <cam/cam_ccb.h>
4839214Sgibbs#include <camlib.h>
4939214Sgibbs#include "camcontrol.h"
5039214Sgibbs
5164382Skbyanc#define	DEFAULT_SCSI_MODE_DB	"/usr/share/misc/scsi_modes"
5264382Skbyanc#define	DEFAULT_EDITOR		"vi"
5364382Skbyanc#define	MAX_FORMAT_SPEC		4096	/* Max CDB format specifier. */
5464382Skbyanc#define	MAX_PAGENUM_LEN		10	/* Max characters in page num. */
5564382Skbyanc#define	MAX_PAGENAME_LEN	64	/* Max characters in page name. */
5664382Skbyanc#define	PAGEDEF_START		'{'	/* Page definition delimiter. */
5764382Skbyanc#define	PAGEDEF_END		'}'	/* Page definition delimiter. */
5864382Skbyanc#define	PAGENAME_START		'"'	/* Page name delimiter. */
5964382Skbyanc#define	PAGENAME_END		'"'	/* Page name delimiter. */
6064382Skbyanc#define	PAGEENTRY_END		';'	/* Page entry terminator (optional). */
6164382Skbyanc#define	MAX_COMMAND_SIZE	255	/* Mode/Log sense data buffer size. */
6264474Skbyanc#define PAGE_CTRL_SHIFT		6	/* Bit offset to page control field. */
6364382Skbyanc
6464382Skbyanc
6564382Skbyanc/* Macros for working with mode pages. */
6664382Skbyanc#define	MODE_PAGE_HEADER(mh)						\
6764382Skbyanc	(struct scsi_mode_page_header *)find_mode_page_6(mh)
6864382Skbyanc
6964382Skbyanc
7064382Skbyancstruct editentry {
7164382Skbyanc	STAILQ_ENTRY(editentry) link;
7264382Skbyanc	char	*name;
7364382Skbyanc	char	type;
7464382Skbyanc	int	editable;
7564382Skbyanc	int	size;
7664382Skbyanc	union {
7764382Skbyanc		int	ivalue;
7864382Skbyanc		char	*svalue;
7964382Skbyanc	} value;
8064382Skbyanc};
81228407Sedstatic STAILQ_HEAD(, editentry) editlist; /* List of page entries. */
82228407Sedstatic int editlist_changed = 0;	/* Whether any entries were changed. */
8364382Skbyanc
8464382Skbyancstruct pagename {
8564382Skbyanc	SLIST_ENTRY(pagename) link;
86312567Smav	int page;
87312567Smav	int subpage;
8864382Skbyanc	char *name;
8964382Skbyanc};
90228407Sedstatic SLIST_HEAD(, pagename) namelist;	/* Page number to name mappings. */
9164382Skbyanc
9264382Skbyancstatic char format[MAX_FORMAT_SPEC];	/* Buffer for scsi cdb format def. */
9364382Skbyanc
9464382Skbyancstatic FILE *edit_file = NULL;		/* File handle for edit file. */
9564382Skbyancstatic char edit_path[] = "/tmp/camXXXXXX";
9664382Skbyanc
9764382Skbyanc
9864382Skbyanc/* Function prototypes. */
9964382Skbyancstatic void		 editentry_create(void *hook, int letter, void *arg,
10064382Skbyanc					  int count, char *name);
10164382Skbyancstatic void		 editentry_update(void *hook, int letter, void *arg,
10264382Skbyanc					  int count, char *name);
10364382Skbyancstatic int		 editentry_save(void *hook, char *name);
10464382Skbyancstatic struct editentry	*editentry_lookup(char *name);
10564382Skbyancstatic int		 editentry_set(char *name, char *newvalue,
10664382Skbyanc				       int editonly);
107312567Smavstatic void		 editlist_populate(struct cam_device *device, int dbd,
108312567Smav					   int pc, int page, int subpage,
109314221Sken					   int task_attr, int retries,
110314221Sken					   int timeout);
111312567Smavstatic void		 editlist_save(struct cam_device *device, int dbd,
112312567Smav				       int pc, int page, int subpage,
113314221Sken				       int task_attr, int retries, int timeout);
114312567Smavstatic void		 nameentry_create(int page, int subpage, char *name);
115312567Smavstatic struct pagename	*nameentry_lookup(int page, int subpage);
116312567Smavstatic int		 load_format(const char *pagedb_path, int lpage,
117312567Smav			    int lsubpage);
11864382Skbyancstatic int		 modepage_write(FILE *file, int editonly);
11964382Skbyancstatic int		 modepage_read(FILE *file);
12064382Skbyancstatic void		 modepage_edit(void);
121312567Smavstatic void		 modepage_dump(struct cam_device *device, int dbd,
122314221Sken			    int pc, int page, int subpage, int task_attr,
123314221Sken			    int retries, int timeout);
12464382Skbyancstatic void		 cleanup_editfile(void);
12564382Skbyanc
12664382Skbyanc
12764382Skbyanc#define	returnerr(code) do {						\
12864382Skbyanc	errno = code;							\
12964382Skbyanc	return (-1);							\
13064382Skbyanc} while (0)
13164382Skbyanc
13264382Skbyanc
13364382Skbyanc#define	RTRIM(string) do {						\
134119252Simp	int _length;							\
13564382Skbyanc	while (isspace(string[_length = strlen(string) - 1]))		\
13664382Skbyanc		string[_length] = '\0';					\
13764382Skbyanc} while (0)
13864382Skbyanc
13964382Skbyanc
14064382Skbyancstatic void
141118478Sjohaneditentry_create(void *hook __unused, int letter, void *arg, int count,
142118478Sjohan		 char *name)
14339214Sgibbs{
14464382Skbyanc	struct editentry *newentry;	/* Buffer to hold new entry. */
14539214Sgibbs
14664382Skbyanc	/* Allocate memory for the new entry and a copy of the entry name. */
14764382Skbyanc	if ((newentry = malloc(sizeof(struct editentry))) == NULL ||
14864382Skbyanc	    (newentry->name = strdup(name)) == NULL)
14964382Skbyanc		err(EX_OSERR, NULL);
15039214Sgibbs
15164382Skbyanc	/* Trim any trailing whitespace for the entry name. */
15264382Skbyanc	RTRIM(newentry->name);
15339214Sgibbs
15464382Skbyanc	newentry->editable = (arg != NULL);
15564382Skbyanc	newentry->type = letter;
15664382Skbyanc	newentry->size = count;		/* Placeholder; not accurate. */
15764382Skbyanc	newentry->value.svalue = NULL;
15864382Skbyanc
15964382Skbyanc	STAILQ_INSERT_TAIL(&editlist, newentry, link);
16039214Sgibbs}
16139214Sgibbs
16264382Skbyancstatic void
163118478Sjohaneditentry_update(void *hook __unused, int letter, void *arg, int count,
164118478Sjohan		 char *name)
16539214Sgibbs{
16664382Skbyanc	struct editentry *dest;		/* Buffer to hold entry to update. */
16739214Sgibbs
16864382Skbyanc	dest = editentry_lookup(name);
16964382Skbyanc	assert(dest != NULL);
17064382Skbyanc
17164382Skbyanc	dest->type = letter;
17264382Skbyanc	dest->size = count;		/* We get the real size now. */
17364382Skbyanc
17464382Skbyanc	switch (dest->type) {
17564382Skbyanc	case 'i':			/* Byte-sized integral type. */
17664382Skbyanc	case 'b':			/* Bit-sized integral types. */
17764382Skbyanc	case 't':
17864382Skbyanc		dest->value.ivalue = (intptr_t)arg;
17964382Skbyanc		break;
18064382Skbyanc
18164382Skbyanc	case 'c':			/* Character array. */
18264382Skbyanc	case 'z':			/* Null-padded string. */
18364382Skbyanc		editentry_set(name, (char *)arg, 0);
18464382Skbyanc		break;
18564382Skbyanc	default:
18697636Swollman		; /* NOTREACHED */
18739214Sgibbs	}
18864382Skbyanc}
18939214Sgibbs
19064382Skbyancstatic int
191118478Sjohaneditentry_save(void *hook __unused, char *name)
19264382Skbyanc{
19364382Skbyanc	struct editentry *src;		/* Entry value to save. */
19439214Sgibbs
19564382Skbyanc	src = editentry_lookup(name);
196312565Smav	if (src == 0) {
197312565Smav		/*
198312565Smav		 * This happens if field does not fit into read page size.
199312565Smav		 * It also means that this field won't be written, so the
200312565Smav		 * returned value does not really matter.
201312565Smav		 */
202312565Smav		return (0);
203312565Smav	}
20464382Skbyanc
20564382Skbyanc	switch (src->type) {
20664382Skbyanc	case 'i':			/* Byte-sized integral type. */
20764382Skbyanc	case 'b':			/* Bit-sized integral types. */
20864382Skbyanc	case 't':
20964382Skbyanc		return (src->value.ivalue);
21064382Skbyanc		/* NOTREACHED */
21164382Skbyanc
21264382Skbyanc	case 'c':			/* Character array. */
21364382Skbyanc	case 'z':			/* Null-padded string. */
21464382Skbyanc		return ((intptr_t)src->value.svalue);
21564382Skbyanc		/* NOTREACHED */
21664382Skbyanc
21764382Skbyanc	default:
21897636Swollman		; /* NOTREACHED */
21964382Skbyanc	}
22064382Skbyanc
22164382Skbyanc	return (0);			/* This should never happen. */
22239214Sgibbs}
22339214Sgibbs
22464382Skbyancstatic struct editentry *
22564382Skbyanceditentry_lookup(char *name)
22639214Sgibbs{
22764382Skbyanc	struct editentry *scan;
22839214Sgibbs
22964382Skbyanc	assert(name != NULL);
23039214Sgibbs
23164382Skbyanc	STAILQ_FOREACH(scan, &editlist, link) {
23264382Skbyanc		if (strcasecmp(scan->name, name) == 0)
23364382Skbyanc			return (scan);
23464382Skbyanc	}
23539214Sgibbs
23664382Skbyanc	/* Not found during list traversal. */
23764382Skbyanc	return (NULL);
23864382Skbyanc}
23939214Sgibbs
24064382Skbyancstatic int
24164382Skbyanceditentry_set(char *name, char *newvalue, int editonly)
24264382Skbyanc{
24364382Skbyanc	struct editentry *dest;	/* Modepage entry to update. */
24464382Skbyanc	char *cval;		/* Pointer to new string value. */
24564382Skbyanc	char *convertend;	/* End-of-conversion pointer. */
24664382Skbyanc	int ival;		/* New integral value. */
24764382Skbyanc	int resolution;		/* Resolution in bits for integer conversion. */
24839214Sgibbs
24964382Skbyanc/*
25064382Skbyanc * Macro to determine the maximum value of the given size for the current
25164382Skbyanc * resolution.
25264382Skbyanc * XXX Lovely x86's optimize out the case of shifting by 32 and gcc doesn't
25364382Skbyanc *     currently workaround it (even for int64's), so we have to kludge it.
25464382Skbyanc */
25564382Skbyanc#define	RESOLUTION_MAX(size) ((resolution * (size) == 32)? 		\
256290385Sngie	INT_MAX: (1 << (resolution * (size))) - 1)
25764382Skbyanc
25864382Skbyanc	assert(newvalue != NULL);
25964382Skbyanc	if (*newvalue == '\0')
26064382Skbyanc		return (0);	/* Nothing to do. */
26164382Skbyanc
26264382Skbyanc	if ((dest = editentry_lookup(name)) == NULL)
26364382Skbyanc		returnerr(ENOENT);
26464382Skbyanc	if (!dest->editable && editonly)
26564382Skbyanc		returnerr(EPERM);
26664382Skbyanc
26764382Skbyanc	switch (dest->type) {
26864382Skbyanc	case 'i':		/* Byte-sized integral type. */
26964382Skbyanc	case 'b':		/* Bit-sized integral types. */
27064382Skbyanc	case 't':
27164382Skbyanc		/* Convert the value string to an integer. */
27264382Skbyanc		resolution = (dest->type == 'i')? 8: 1;
27364382Skbyanc		ival = (int)strtol(newvalue, &convertend, 0);
27464382Skbyanc		if (*convertend != '\0')
27564382Skbyanc			returnerr(EINVAL);
27664382Skbyanc		if (ival > RESOLUTION_MAX(dest->size) || ival < 0) {
27764382Skbyanc			int newival = (ival < 0)? 0: RESOLUTION_MAX(dest->size);
27864382Skbyanc			warnx("value %d is out of range for entry %s; clipping "
27964382Skbyanc			    "to %d", ival, name, newival);
28064382Skbyanc			ival = newival;
28139214Sgibbs		}
28264382Skbyanc		if (dest->value.ivalue != ival)
28364382Skbyanc			editlist_changed = 1;
28464382Skbyanc		dest->value.ivalue = ival;
28564382Skbyanc		break;
28639214Sgibbs
28764382Skbyanc	case 'c':		/* Character array. */
28864382Skbyanc	case 'z':		/* Null-padded string. */
28964382Skbyanc		if ((cval = malloc(dest->size + 1)) == NULL)
29064382Skbyanc			err(EX_OSERR, NULL);
29164382Skbyanc		bzero(cval, dest->size + 1);
29264382Skbyanc		strncpy(cval, newvalue, dest->size);
29364382Skbyanc		if (dest->type == 'z') {
29464382Skbyanc			/* Convert trailing spaces to nulls. */
295118478Sjohan			char *convertend2;
29664382Skbyanc
297118478Sjohan			for (convertend2 = cval + dest->size;
298118478Sjohan			    convertend2 >= cval; convertend2--) {
299118478Sjohan				if (*convertend2 == ' ')
300118478Sjohan					*convertend2 = '\0';
301118478Sjohan				else if (*convertend2 != '\0')
30264382Skbyanc					break;
30364382Skbyanc			}
30464382Skbyanc		}
30564382Skbyanc		if (strncmp(dest->value.svalue, cval, dest->size) == 0) {
30664382Skbyanc			/* Nothing changed, free the newly allocated string. */
30764382Skbyanc			free(cval);
30864382Skbyanc			break;
30964382Skbyanc		}
31064382Skbyanc		if (dest->value.svalue != NULL) {
31164382Skbyanc			/* Free the current string buffer. */
31264382Skbyanc			free(dest->value.svalue);
31364382Skbyanc			dest->value.svalue = NULL;
31464382Skbyanc		}
31564382Skbyanc		dest->value.svalue = cval;
31664382Skbyanc		editlist_changed = 1;
31739214Sgibbs		break;
31839214Sgibbs
31964382Skbyanc	default:
32097636Swollman		; /* NOTREACHED */
32139214Sgibbs	}
32264382Skbyanc
32364382Skbyanc	return (0);
32464382Skbyanc#undef RESOLUTION_MAX
32539214Sgibbs}
32639214Sgibbs
32739214Sgibbsstatic void
328312567Smavnameentry_create(int page, int subpage, char *name) {
32964382Skbyanc	struct pagename *newentry;
33039214Sgibbs
331312567Smav	if (page < 0 || subpage < 0 || name == NULL || name[0] == '\0')
33264382Skbyanc		return;
33339214Sgibbs
33464382Skbyanc	/* Allocate memory for the new entry and a copy of the entry name. */
33564382Skbyanc	if ((newentry = malloc(sizeof(struct pagename))) == NULL ||
33664382Skbyanc	    (newentry->name = strdup(name)) == NULL)
33764382Skbyanc		err(EX_OSERR, NULL);
33839214Sgibbs
33964382Skbyanc	/* Trim any trailing whitespace for the page name. */
34064382Skbyanc	RTRIM(newentry->name);
34164382Skbyanc
342312567Smav	newentry->page = page;
343312567Smav	newentry->subpage = subpage;
34464382Skbyanc	SLIST_INSERT_HEAD(&namelist, newentry, link);
34564382Skbyanc}
34664382Skbyanc
34764382Skbyancstatic struct pagename *
348312567Smavnameentry_lookup(int page, int subpage) {
34964382Skbyanc	struct pagename *scan;
35064382Skbyanc
35164382Skbyanc	SLIST_FOREACH(scan, &namelist, link) {
352312567Smav		if (page == scan->page && subpage == scan->subpage)
35364382Skbyanc			return (scan);
35439214Sgibbs	}
35539214Sgibbs
35664382Skbyanc	/* Not found during list traversal. */
35764382Skbyanc	return (NULL);
35839214Sgibbs}
35939214Sgibbs
36064382Skbyancstatic int
361312567Smavload_format(const char *pagedb_path, int lpage, int lsubpage)
36239214Sgibbs{
36364382Skbyanc	FILE *pagedb;
364312567Smav	char str_page[MAX_PAGENUM_LEN];
365312567Smav	char *str_subpage;
36664382Skbyanc	char str_pagename[MAX_PAGENAME_LEN];
367312567Smav	int page;
368312567Smav	int subpage;
36964382Skbyanc	int depth;			/* Quoting depth. */
37064382Skbyanc	int found;
37164382Skbyanc	int lineno;
37264382Skbyanc	enum { LOCATE, PAGENAME, PAGEDEF } state;
373124830Sgrehan	int ch;
37464382Skbyanc	char c;
37539214Sgibbs
37664382Skbyanc#define	SETSTATE_LOCATE do {						\
377312567Smav	str_page[0] = '\0';						\
37864382Skbyanc	str_pagename[0] = '\0';						\
379312567Smav	page = -1;							\
380312567Smav	subpage = -1;							\
38164382Skbyanc	state = LOCATE;							\
38264382Skbyanc} while (0)
38339214Sgibbs
38464382Skbyanc#define	SETSTATE_PAGENAME do {						\
38564382Skbyanc	str_pagename[0] = '\0';						\
38664382Skbyanc	state = PAGENAME;						\
38764382Skbyanc} while (0)
38839214Sgibbs
38964382Skbyanc#define	SETSTATE_PAGEDEF do {						\
39064382Skbyanc	format[0] = '\0';						\
39164382Skbyanc	state = PAGEDEF;						\
39264382Skbyanc} while (0)
39339214Sgibbs
39464382Skbyanc#define	UPDATE_LINENO do {						\
39564382Skbyanc	if (c == '\n')							\
39664382Skbyanc		lineno++;						\
39764382Skbyanc} while (0)
39839214Sgibbs
39964382Skbyanc#define	BUFFERFULL(buffer)	(strlen(buffer) + 1 >= sizeof(buffer))
40039214Sgibbs
40164382Skbyanc	if ((pagedb = fopen(pagedb_path, "r")) == NULL)
40264382Skbyanc		returnerr(ENOENT);
40339214Sgibbs
40464382Skbyanc	SLIST_INIT(&namelist);
40539214Sgibbs
406209051Suqs	c = '\0';
40764382Skbyanc	depth = 0;
40864382Skbyanc	lineno = 0;
40964382Skbyanc	found = 0;
41064382Skbyanc	SETSTATE_LOCATE;
411124830Sgrehan	while ((ch = fgetc(pagedb)) != EOF) {
41239214Sgibbs
41364382Skbyanc		/* Keep a line count to make error messages more useful. */
41464382Skbyanc		UPDATE_LINENO;
41564382Skbyanc
41664382Skbyanc		/* Skip over comments anywhere in the mode database. */
417124830Sgrehan		if (ch == '#') {
41864382Skbyanc			do {
419124830Sgrehan				ch = fgetc(pagedb);
420124830Sgrehan			} while (ch != '\n' && ch != EOF);
42164382Skbyanc			UPDATE_LINENO;
42264382Skbyanc			continue;
42364382Skbyanc		}
424124830Sgrehan		c = ch;
42564382Skbyanc
42664382Skbyanc		/* Strip out newline characters. */
42764382Skbyanc		if (c == '\n')
42864382Skbyanc			continue;
42964382Skbyanc
43064382Skbyanc		/* Keep track of the nesting depth for braces. */
43164382Skbyanc		if (c == PAGEDEF_START)
43264382Skbyanc			depth++;
43364382Skbyanc		else if (c == PAGEDEF_END) {
43464382Skbyanc			depth--;
43564382Skbyanc			if (depth < 0) {
43664382Skbyanc				errx(EX_OSFILE, "%s:%d: %s", pagedb_path,
43764382Skbyanc				    lineno, "mismatched bracket");
43839214Sgibbs			}
43964382Skbyanc		}
44039214Sgibbs
44164382Skbyanc		switch (state) {
44264382Skbyanc		case LOCATE:
44364382Skbyanc			/*
44464382Skbyanc			 * Locate the page the user is interested in, skipping
44564382Skbyanc			 * all others.
44664382Skbyanc			 */
44764382Skbyanc			if (isspace(c)) {
44864382Skbyanc				/* Ignore all whitespace between pages. */
44964382Skbyanc				break;
45064382Skbyanc			} else if (depth == 0 && c == PAGEENTRY_END) {
45164382Skbyanc				/*
45264382Skbyanc				 * A page entry terminator will reset page
45364382Skbyanc				 * scanning (useful for assigning names to
45464382Skbyanc				 * modes without providing a mode definition).
45564382Skbyanc				 */
45664382Skbyanc				/* Record the name of this page. */
457312567Smav				str_subpage = str_page;
458312567Smav				strsep(&str_subpage, ",");
459312567Smav				page = strtol(str_page, NULL, 0);
460312567Smav				if (str_subpage)
461312567Smav				    subpage = strtol(str_subpage, NULL, 0);
462312567Smav				else
463312567Smav				    subpage = 0;
464312567Smav				nameentry_create(page, subpage, str_pagename);
46564382Skbyanc				SETSTATE_LOCATE;
46664382Skbyanc			} else if (depth == 0 && c == PAGENAME_START) {
46764382Skbyanc				SETSTATE_PAGENAME;
46864382Skbyanc			} else if (c == PAGEDEF_START) {
469312567Smav				str_subpage = str_page;
470312567Smav				strsep(&str_subpage, ",");
471312567Smav				page = strtol(str_page, NULL, 0);
472312567Smav				if (str_subpage)
473312567Smav				    subpage = strtol(str_subpage, NULL, 0);
474312567Smav				else
475312567Smav				    subpage = 0;
47664382Skbyanc				if (depth == 1) {
47764382Skbyanc					/* Record the name of this page. */
478312567Smav					nameentry_create(page, subpage,
479312567Smav					    str_pagename);
48064382Skbyanc					/*
48164382Skbyanc					 * Only record the format if this is
48264382Skbyanc					 * the page we are interested in.
48364382Skbyanc					 */
484312567Smav					if (lpage == page &&
485312567Smav					    lsubpage == subpage && !found)
48664382Skbyanc						SETSTATE_PAGEDEF;
48764382Skbyanc				}
48864382Skbyanc			} else if (c == PAGEDEF_END) {
48964382Skbyanc				/* Reset the processor state. */
49064382Skbyanc				SETSTATE_LOCATE;
491312567Smav			} else if (depth == 0 && ! BUFFERFULL(str_page)) {
492312567Smav				strncat(str_page, &c, 1);
49364382Skbyanc			} else if (depth == 0) {
494112254Sru				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
49564382Skbyanc				    lineno, "page identifier exceeds",
496312567Smav				    sizeof(str_page) - 1, "characters");
49739214Sgibbs			}
49864382Skbyanc			break;
49964382Skbyanc
50064382Skbyanc		case PAGENAME:
50164382Skbyanc			if (c == PAGENAME_END) {
50264382Skbyanc				/*
50364382Skbyanc				 * Return to LOCATE state without resetting the
50464382Skbyanc				 * page number buffer.
50564382Skbyanc				 */
50664382Skbyanc				state = LOCATE;
50764382Skbyanc			} else if (! BUFFERFULL(str_pagename)) {
50864382Skbyanc				strncat(str_pagename, &c, 1);
50964382Skbyanc			} else {
510112254Sru				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
51164382Skbyanc				    lineno, "page name exceeds",
512312567Smav				    sizeof(str_page) - 1, "characters");
51339214Sgibbs			}
51464382Skbyanc			break;
51539214Sgibbs
51664382Skbyanc		case PAGEDEF:
51764382Skbyanc			/*
51864382Skbyanc			 * Transfer the page definition into a format buffer
51964382Skbyanc			 * suitable for use with CDB encoding/decoding routines.
52064382Skbyanc			 */
52164382Skbyanc			if (depth == 0) {
52264382Skbyanc				found = 1;
52364382Skbyanc				SETSTATE_LOCATE;
52464382Skbyanc			} else if (! BUFFERFULL(format)) {
52564382Skbyanc				strncat(format, &c, 1);
52664382Skbyanc			} else {
527112254Sru				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
52864382Skbyanc				    lineno, "page definition exceeds",
52964382Skbyanc				    sizeof(format) - 1, "characters");
53039214Sgibbs			}
53164382Skbyanc			break;
53264382Skbyanc
53364382Skbyanc		default:
53497636Swollman			; /* NOTREACHED */
53539214Sgibbs		}
53664382Skbyanc
53764382Skbyanc		/* Repeat processing loop with next character. */
53839214Sgibbs	}
53939214Sgibbs
54064382Skbyanc	if (ferror(pagedb))
54164382Skbyanc		err(EX_OSFILE, "%s", pagedb_path);
54239214Sgibbs
54364382Skbyanc	/* Close the SCSI page database. */
54464382Skbyanc	fclose(pagedb);
54539214Sgibbs
54664382Skbyanc	if (!found)			/* Never found a matching page. */
54764382Skbyanc		returnerr(ESRCH);
54839214Sgibbs
54964382Skbyanc	return (0);
55039214Sgibbs}
55139214Sgibbs
55239214Sgibbsstatic void
553312567Smaveditlist_populate(struct cam_device *device, int dbd, int pc, int page,
554314221Sken    int subpage, int task_attr, int retries, int timeout)
55539214Sgibbs{
55664382Skbyanc	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
55764382Skbyanc	u_int8_t *mode_pars;		/* Pointer to modepage params. */
55864382Skbyanc	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
55964382Skbyanc	struct scsi_mode_page_header *mph;
560312567Smav	struct scsi_mode_page_header_sp *mphsp;
561312569Smav	size_t len;
56239214Sgibbs
56364382Skbyanc	STAILQ_INIT(&editlist);
56439214Sgibbs
56564382Skbyanc	/* Fetch changeable values; use to build initial editlist. */
566314221Sken	mode_sense(device, dbd, 1, page, subpage, task_attr, retries, timeout,
567314221Sken		   data, sizeof(data));
56839214Sgibbs
56964382Skbyanc	mh = (struct scsi_mode_header_6 *)data;
57064382Skbyanc	mph = MODE_PAGE_HEADER(mh);
571312567Smav	if ((mph->page_code & SMPH_SPF) == 0) {
572312567Smav		mode_pars = (uint8_t *)(mph + 1);
573312567Smav		len = mph->page_length;
574312567Smav	} else {
575312567Smav		mphsp = (struct scsi_mode_page_header_sp *)mph;
576312567Smav		mode_pars = (uint8_t *)(mphsp + 1);
577312567Smav		len = scsi_2btoul(mphsp->page_length);
578312567Smav	}
579312569Smav	len = MIN(len, sizeof(data) - (mode_pars - data));
58039214Sgibbs
58164382Skbyanc	/* Decode the value data, creating edit_entries for each value. */
582312567Smav	buff_decode_visit(mode_pars, len, format, editentry_create, 0);
58339214Sgibbs
58464382Skbyanc	/* Fetch the current/saved values; use to set editentry values. */
585314221Sken	mode_sense(device, dbd, pc, page, subpage, task_attr, retries, timeout,
586312567Smav	    data, sizeof(data));
587312567Smav	buff_decode_visit(mode_pars, len, format, editentry_update, 0);
58839214Sgibbs}
58939214Sgibbs
59039214Sgibbsstatic void
591312567Smaveditlist_save(struct cam_device *device, int dbd, int pc, int page,
592314221Sken    int subpage, int task_attr, int retries, int timeout)
59339214Sgibbs{
59464382Skbyanc	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
59564382Skbyanc	u_int8_t *mode_pars;		/* Pointer to modepage params. */
59664382Skbyanc	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
59764382Skbyanc	struct scsi_mode_page_header *mph;
598312567Smav	struct scsi_mode_page_header_sp *mphsp;
599312569Smav	size_t len, hlen;
60051737Sken
60164382Skbyanc	/* Make sure that something changed before continuing. */
60264382Skbyanc	if (! editlist_changed)
60364382Skbyanc		return;
60439214Sgibbs
605312567Smav	/* Preload the CDB buffer with the current mode page data. */
606314221Sken	mode_sense(device, dbd, pc, page, subpage, task_attr, retries, timeout,
607312567Smav	    data, sizeof(data));
60839214Sgibbs
60964382Skbyanc	/* Initial headers & offsets. */
61064382Skbyanc	mh = (struct scsi_mode_header_6 *)data;
61164393Skbyanc	mph = MODE_PAGE_HEADER(mh);
612312567Smav	if ((mph->page_code & SMPH_SPF) == 0) {
613312567Smav		hlen = sizeof(*mph);
614312567Smav		mode_pars = (uint8_t *)(mph + 1);
615312567Smav		len = mph->page_length;
616312567Smav	} else {
617312567Smav		mphsp = (struct scsi_mode_page_header_sp *)mph;
618312567Smav		hlen = sizeof(*mphsp);
619312567Smav		mode_pars = (uint8_t *)(mphsp + 1);
620312567Smav		len = scsi_2btoul(mphsp->page_length);
621312567Smav	}
622312569Smav	len = MIN(len, sizeof(data) - (mode_pars - data));
62339214Sgibbs
62464393Skbyanc	/* Encode the value data to be passed back to the device. */
625312567Smav	buff_encode_visit(mode_pars, len, format, editentry_save, 0);
62639214Sgibbs
62764393Skbyanc	/* Eliminate block descriptors. */
628312567Smav	bcopy(mph, mh + 1, hlen + len);
62939214Sgibbs
63064382Skbyanc	/* Recalculate headers & offsets. */
631312567Smav	mh->data_length = 0;		/* Reserved for MODE SELECT command. */
63264382Skbyanc	mh->blk_desc_len = 0;		/* No block descriptors. */
633317966Sken	/*
634317966Sken	 * Tape drives include write protect (WP), Buffered Mode and Speed
635317966Sken	 * settings in the device-specific parameter.  Clearing this
636317966Sken	 * parameter on a mode select can have the effect of turning off
637317966Sken	 * write protect or buffered mode, or changing the speed setting of
638317966Sken	 * the tape drive.
639317966Sken	 *
640317966Sken	 * Disks report DPO/FUA support via the device specific parameter
641317966Sken	 * for MODE SENSE, but the bit is reserved for MODE SELECT.  So we
642317966Sken	 * clear this for disks (and other non-tape devices) to avoid
643317966Sken	 * potential errors from the target device.
644317966Sken	 */
645317966Sken	if (device->pd_type != T_SEQUENTIAL)
646317966Sken		mh->dev_spec = 0;
64764393Skbyanc	mph = MODE_PAGE_HEADER(mh);
648312567Smav	mph->page_code &= ~SMPH_PS;	/* Reserved for MODE SELECT command. */
64939214Sgibbs
65064382Skbyanc	/*
65164382Skbyanc	 * Write the changes back to the device. If the user editted control
65264382Skbyanc	 * page 3 (saved values) then request the changes be permanently
65364382Skbyanc	 * recorded.
65464382Skbyanc	 */
655312567Smav	mode_select(device, (pc << PAGE_CTRL_SHIFT == SMS_PAGE_CTRL_SAVED),
656314221Sken	    task_attr, retries, timeout, (u_int8_t *)mh,
657314221Sken	    sizeof(*mh) + hlen + len);
65839214Sgibbs}
65939214Sgibbs
66064382Skbyancstatic int
66164382Skbyancmodepage_write(FILE *file, int editonly)
66239214Sgibbs{
66364382Skbyanc	struct editentry *scan;
66464382Skbyanc	int written = 0;
66539214Sgibbs
66664382Skbyanc	STAILQ_FOREACH(scan, &editlist, link) {
66764382Skbyanc		if (scan->editable || !editonly) {
66864382Skbyanc			written++;
66964382Skbyanc			if (scan->type == 'c' || scan->type == 'z') {
67064382Skbyanc				fprintf(file, "%s:  %s\n", scan->name,
67164382Skbyanc				    scan->value.svalue);
67264382Skbyanc			} else {
67364382Skbyanc				fprintf(file, "%s:  %d\n", scan->name,
67464382Skbyanc				    scan->value.ivalue);
67564382Skbyanc			}
67664382Skbyanc		}
67739214Sgibbs	}
67864382Skbyanc	return (written);
67939214Sgibbs}
68039214Sgibbs
68139214Sgibbsstatic int
68264382Skbyancmodepage_read(FILE *file)
68339214Sgibbs{
68464382Skbyanc	char *buffer;			/* Pointer to dynamic line buffer.  */
68564382Skbyanc	char *line;			/* Pointer to static fgetln buffer. */
68664382Skbyanc	char *name;			/* Name portion of the line buffer. */
68764382Skbyanc	char *value;			/* Value portion of line buffer.    */
688111195Sjohan	size_t length;			/* Length of static fgetln buffer.  */
68939214Sgibbs
69064382Skbyanc#define	ABORT_READ(message, param) do {					\
69164382Skbyanc	warnx(message, param);						\
69264382Skbyanc	free(buffer);							\
69364382Skbyanc	returnerr(EAGAIN);						\
69464382Skbyanc} while (0)
69539214Sgibbs
69664382Skbyanc	while ((line = fgetln(file, &length)) != NULL) {
69764382Skbyanc		/* Trim trailing whitespace (including optional newline). */
69864382Skbyanc		while (length > 0 && isspace(line[length - 1]))
69964382Skbyanc			length--;
70039214Sgibbs
70164382Skbyanc	    	/* Allocate a buffer to hold the line + terminating null. */
70264382Skbyanc	    	if ((buffer = malloc(length + 1)) == NULL)
70364382Skbyanc			err(EX_OSERR, NULL);
70464382Skbyanc		memcpy(buffer, line, length);
70564382Skbyanc		buffer[length] = '\0';
70639214Sgibbs
70764382Skbyanc		/* Strip out comments. */
70864382Skbyanc		if ((value = strchr(buffer, '#')) != NULL)
70964382Skbyanc			*value = '\0';
71064382Skbyanc
71164382Skbyanc		/* The name is first in the buffer. Trim whitespace.*/
71264382Skbyanc		name = buffer;
71364382Skbyanc		RTRIM(name);
71464382Skbyanc		while (isspace(*name))
71564382Skbyanc			name++;
71664382Skbyanc
71764382Skbyanc		/* Skip empty lines. */
71864382Skbyanc		if (strlen(name) == 0)
71964382Skbyanc			continue;
72064382Skbyanc
72164382Skbyanc		/* The name ends at the colon; the value starts there. */
72264382Skbyanc		if ((value = strrchr(buffer, ':')) == NULL)
72364382Skbyanc			ABORT_READ("no value associated with %s", name);
72464382Skbyanc		*value = '\0';			/* Null-terminate name. */
72564382Skbyanc		value++;			/* Value starts afterwards. */
72664382Skbyanc
72764382Skbyanc		/* Trim leading and trailing whitespace. */
72864382Skbyanc		RTRIM(value);
72964382Skbyanc		while (isspace(*value))
73064382Skbyanc			value++;
73164382Skbyanc
73264382Skbyanc		/* Make sure there is a value left. */
73364382Skbyanc		if (strlen(value) == 0)
73464382Skbyanc			ABORT_READ("no value associated with %s", name);
73564382Skbyanc
73664382Skbyanc		/* Update our in-memory copy of the modepage entry value. */
73764382Skbyanc		if (editentry_set(name, value, 1) != 0) {
73864382Skbyanc			if (errno == ENOENT) {
73964382Skbyanc				/* No entry by the name. */
74064382Skbyanc				ABORT_READ("no such modepage entry \"%s\"",
74164382Skbyanc				    name);
74264382Skbyanc			} else if (errno == EINVAL) {
74364382Skbyanc				/* Invalid value. */
74464382Skbyanc				ABORT_READ("Invalid value for entry \"%s\"",
74564382Skbyanc				    name);
74664382Skbyanc			} else if (errno == ERANGE) {
74764382Skbyanc				/* Value out of range for entry type. */
74864382Skbyanc				ABORT_READ("value out of range for %s", name);
74964382Skbyanc			} else if (errno == EPERM) {
75064382Skbyanc				/* Entry is not editable; not fatal. */
75164382Skbyanc				warnx("modepage entry \"%s\" is read-only; "
75264382Skbyanc				    "skipping.", name);
75364382Skbyanc			}
75464382Skbyanc		}
75564382Skbyanc
75664382Skbyanc		free(buffer);
75739214Sgibbs	}
75864382Skbyanc	return (ferror(file)? -1: 0);
75939214Sgibbs
76064382Skbyanc#undef ABORT_READ
76139214Sgibbs}
76239214Sgibbs
76339214Sgibbsstatic void
76464382Skbyancmodepage_edit(void)
76539214Sgibbs{
766118478Sjohan	const char *editor;
76764382Skbyanc	char *commandline;
76864382Skbyanc	int fd;
76964382Skbyanc	int written;
77039214Sgibbs
77164382Skbyanc	if (!isatty(fileno(stdin))) {
77264382Skbyanc		/* Not a tty, read changes from stdin. */
77364382Skbyanc		modepage_read(stdin);
77464382Skbyanc		return;
77564382Skbyanc	}
77639214Sgibbs
77764382Skbyanc	/* Lookup editor to invoke. */
77864382Skbyanc	if ((editor = getenv("EDITOR")) == NULL)
77964382Skbyanc		editor = DEFAULT_EDITOR;
78039214Sgibbs
78164382Skbyanc	/* Create temp file for editor to modify. */
78264382Skbyanc	if ((fd = mkstemp(edit_path)) == -1)
78364382Skbyanc		errx(EX_CANTCREAT, "mkstemp failed");
78439214Sgibbs
78564382Skbyanc	atexit(cleanup_editfile);
78639214Sgibbs
78764382Skbyanc	if ((edit_file = fdopen(fd, "w")) == NULL)
78864382Skbyanc		err(EX_NOINPUT, "%s", edit_path);
78939214Sgibbs
79064382Skbyanc	written = modepage_write(edit_file, 1);
79139214Sgibbs
79264382Skbyanc	fclose(edit_file);
79364382Skbyanc	edit_file = NULL;
79464382Skbyanc
79564382Skbyanc	if (written == 0) {
79664382Skbyanc		warnx("no editable entries");
79764382Skbyanc		cleanup_editfile();
79864382Skbyanc		return;
79939214Sgibbs	}
80039214Sgibbs
80164382Skbyanc	/*
80264382Skbyanc	 * Allocate memory to hold the command line (the 2 extra characters
80364382Skbyanc	 * are to hold the argument separator (a space), and the terminating
80464382Skbyanc	 * null character.
80564382Skbyanc	 */
80664382Skbyanc	commandline = malloc(strlen(editor) + strlen(edit_path) + 2);
80764382Skbyanc	if (commandline == NULL)
80864382Skbyanc		err(EX_OSERR, NULL);
80964382Skbyanc	sprintf(commandline, "%s %s", editor, edit_path);
81039214Sgibbs
81164382Skbyanc	/* Invoke the editor on the temp file. */
81264382Skbyanc	if (system(commandline) == -1)
81364382Skbyanc		err(EX_UNAVAILABLE, "could not invoke %s", editor);
81464382Skbyanc	free(commandline);
81539214Sgibbs
81664382Skbyanc	if ((edit_file = fopen(edit_path, "r")) == NULL)
81764382Skbyanc		err(EX_NOINPUT, "%s", edit_path);
81839214Sgibbs
81964382Skbyanc	/* Read any changes made to the temp file. */
82064382Skbyanc	modepage_read(edit_file);
82139214Sgibbs
82264382Skbyanc	cleanup_editfile();
82364382Skbyanc}
82439214Sgibbs
82564382Skbyancstatic void
826312567Smavmodepage_dump(struct cam_device *device, int dbd, int pc, int page, int subpage,
827314221Sken	      int task_attr, int retries, int timeout)
82864382Skbyanc{
82964382Skbyanc	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
83064382Skbyanc	u_int8_t *mode_pars;		/* Pointer to modepage params. */
83164382Skbyanc	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
83264382Skbyanc	struct scsi_mode_page_header *mph;
833312567Smav	struct scsi_mode_page_header_sp *mphsp;
834312569Smav	size_t indx, len;
83539214Sgibbs
836314221Sken	mode_sense(device, dbd, pc, page, subpage, task_attr, retries, timeout,
837312567Smav	    data, sizeof(data));
83839214Sgibbs
83964382Skbyanc	mh = (struct scsi_mode_header_6 *)data;
84064382Skbyanc	mph = MODE_PAGE_HEADER(mh);
841312567Smav	if ((mph->page_code & SMPH_SPF) == 0) {
842312567Smav		mode_pars = (uint8_t *)(mph + 1);
843312567Smav		len = mph->page_length;
844312567Smav	} else {
845312567Smav		mphsp = (struct scsi_mode_page_header_sp *)mph;
846312567Smav		mode_pars = (uint8_t *)(mphsp + 1);
847312567Smav		len = scsi_2btoul(mphsp->page_length);
848312567Smav	}
849312569Smav	len = MIN(len, sizeof(data) - (mode_pars - data));
85039214Sgibbs
85164382Skbyanc	/* Print the raw mode page data with newlines each 8 bytes. */
852312567Smav	for (indx = 0; indx < len; indx++) {
853118478Sjohan		printf("%02x%c",mode_pars[indx],
854118478Sjohan		    (((indx + 1) % 8) == 0) ? '\n' : ' ');
85564382Skbyanc	}
85664382Skbyanc	putchar('\n');
85764382Skbyanc}
85839214Sgibbs
85964382Skbyancstatic void
86064382Skbyanccleanup_editfile(void)
86164382Skbyanc{
86264382Skbyanc	if (edit_file == NULL)
86364382Skbyanc		return;
86464382Skbyanc	if (fclose(edit_file) != 0 || unlink(edit_path) != 0)
86564382Skbyanc		warn("%s", edit_path);
86664382Skbyanc	edit_file = NULL;
86764382Skbyanc}
86839214Sgibbs
86964382Skbyancvoid
870312567Smavmode_edit(struct cam_device *device, int dbd, int pc, int page, int subpage,
871314221Sken	  int edit, int binary, int task_attr, int retry_count, int timeout)
87264382Skbyanc{
873118478Sjohan	const char *pagedb_path;	/* Path to modepage database. */
87439214Sgibbs
87564382Skbyanc	if (edit && binary)
87664382Skbyanc		errx(EX_USAGE, "cannot edit in binary mode.");
87739214Sgibbs
87864382Skbyanc	if (! binary) {
87964382Skbyanc		if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
88064382Skbyanc			pagedb_path = DEFAULT_SCSI_MODE_DB;
88139214Sgibbs
882312567Smav		if (load_format(pagedb_path, page, subpage) != 0 &&
883312567Smav		    (edit || verbose)) {
88464382Skbyanc			if (errno == ENOENT) {
88564382Skbyanc				/* Modepage database file not found. */
88664382Skbyanc				warn("cannot open modepage database \"%s\"",
88764382Skbyanc				    pagedb_path);
88864382Skbyanc			} else if (errno == ESRCH) {
88964382Skbyanc				/* Modepage entry not found in database. */
890312567Smav				warnx("modepage 0x%02x,0x%02x not found in "
891312567Smav				    "database \"%s\"", page, subpage,
892312567Smav				    pagedb_path);
89364382Skbyanc			}
89464382Skbyanc			/* We can recover in display mode, otherwise we exit. */
89564382Skbyanc			if (!edit) {
89664382Skbyanc				warnx("reverting to binary display only");
89764382Skbyanc				binary = 1;
89864382Skbyanc			} else
89964382Skbyanc				exit(EX_OSFILE);
90064382Skbyanc		}
90139214Sgibbs
902314221Sken		editlist_populate(device, dbd, pc, page, subpage, task_attr,
903314221Sken		    retry_count, timeout);
90464382Skbyanc	}
90539214Sgibbs
90664382Skbyanc	if (edit) {
907312567Smav		if (pc << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_CURRENT &&
908312567Smav		    pc << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_SAVED)
90964382Skbyanc			errx(EX_USAGE, "it only makes sense to edit page 0 "
91064382Skbyanc			    "(current) or page 3 (saved values)");
91164382Skbyanc		modepage_edit();
912314221Sken		editlist_save(device, dbd, pc, page, subpage, task_attr,
913314221Sken		    retry_count, timeout);
91464382Skbyanc	} else if (binary || STAILQ_EMPTY(&editlist)) {
91564382Skbyanc		/* Display without formatting information. */
916314221Sken		modepage_dump(device, dbd, pc, page, subpage, task_attr,
917314221Sken		    retry_count, timeout);
91864382Skbyanc	} else {
91964382Skbyanc		/* Display with format. */
92064382Skbyanc		modepage_write(stdout, 0);
92164382Skbyanc	}
92264382Skbyanc}
92339214Sgibbs
92464382Skbyancvoid
925312567Smavmode_list(struct cam_device *device, int dbd, int pc, int subpages,
926314221Sken	  int task_attr, int retry_count, int timeout)
92764382Skbyanc{
92864382Skbyanc	u_int8_t data[MAX_COMMAND_SIZE];/* Buffer to hold sense data. */
92964382Skbyanc	struct scsi_mode_header_6 *mh;	/* Location of mode header. */
93064382Skbyanc	struct scsi_mode_page_header *mph;
931312567Smav	struct scsi_mode_page_header_sp *mphsp;
93264382Skbyanc	struct pagename *nameentry;
933118478Sjohan	const char *pagedb_path;
934312567Smav	int len, page, subpage;
93539214Sgibbs
93664382Skbyanc	if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
93764382Skbyanc		pagedb_path = DEFAULT_SCSI_MODE_DB;
93839214Sgibbs
939312567Smav	if (load_format(pagedb_path, 0, 0) != 0 && verbose && errno == ENOENT) {
94064382Skbyanc		/* Modepage database file not found. */
94164382Skbyanc		warn("cannot open modepage database \"%s\"", pagedb_path);
94239214Sgibbs	}
94339214Sgibbs
94464382Skbyanc	/* Build the list of all mode pages by querying the "all pages" page. */
945312567Smav	mode_sense(device, dbd, pc, SMS_ALL_PAGES_PAGE,
946312567Smav	    subpages ? SMS_SUBPAGE_ALL : 0,
947314221Sken	    task_attr, retry_count, timeout, data, sizeof(data));
94839214Sgibbs
94964382Skbyanc	mh = (struct scsi_mode_header_6 *)data;
950256318Smav	len = sizeof(*mh) + mh->blk_desc_len;	/* Skip block descriptors. */
95164382Skbyanc	/* Iterate through the pages in the reply. */
95264382Skbyanc	while (len < mh->data_length) {
95364382Skbyanc		/* Locate the next mode page header. */
954312567Smav		mph = (struct scsi_mode_page_header *)((intptr_t)mh + len);
95539214Sgibbs
956312567Smav		if ((mph->page_code & SMPH_SPF) == 0) {
957312567Smav			page = mph->page_code & SMS_PAGE_CODE;
958312567Smav			subpage = 0;
959312567Smav			len += sizeof(*mph) + mph->page_length;
960312567Smav		} else {
961312567Smav			mphsp = (struct scsi_mode_page_header_sp *)mph;
962312567Smav			page = mphsp->page_code & SMS_PAGE_CODE;
963312567Smav			subpage = mphsp->subpage;
964312567Smav			len += sizeof(*mphsp) + scsi_2btoul(mphsp->page_length);
965312567Smav		}
96664382Skbyanc
967312567Smav		nameentry = nameentry_lookup(page, subpage);
968312567Smav		if (subpage == 0) {
969312567Smav			printf("0x%02x\t%s\n", page,
970312567Smav			    nameentry ? nameentry->name : "");
971312567Smav		} else {
972312567Smav			printf("0x%02x,0x%02x\t%s\n", page, subpage,
973312567Smav			    nameentry ? nameentry->name : "");
974312567Smav		}
97539214Sgibbs	}
97639214Sgibbs}
977