xref: /freebsd/sbin/camcontrol/modeedit.c (revision 1d386b48a555f61cb7325543adbbb5c3f3407a66)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2000 Kelly Yancey <kbyanc@posi.net>
5  * Derived from work done by Julian Elischer <julian@tfs.com,
6  * julian@dialix.oz.au>, 1993, and Peter Dufault <dufault@hda.com>, 1994.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer,
14  *    without modification, immediately at the beginning of the file.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include <sys/cdefs.h>
32 #include <sys/queue.h>
33 #include <sys/types.h>
34 #include <sys/sbuf.h>
35 
36 #include <assert.h>
37 #include <ctype.h>
38 #include <err.h>
39 #include <errno.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <stdio.h>
43 #include <sysexits.h>
44 #include <unistd.h>
45 
46 #include <cam/scsi/scsi_all.h>
47 #include <cam/cam.h>
48 #include <cam/cam_ccb.h>
49 #include <camlib.h>
50 #include "camcontrol.h"
51 
52 #define	DEFAULT_SCSI_MODE_DB	"/usr/share/misc/scsi_modes"
53 #define	DEFAULT_EDITOR		"vi"
54 #define	MAX_FORMAT_SPEC		4096	/* Max CDB format specifier. */
55 #define	MAX_PAGENUM_LEN		10	/* Max characters in page num. */
56 #define	MAX_PAGENAME_LEN	64	/* Max characters in page name. */
57 #define	PAGEDEF_START		'{'	/* Page definition delimiter. */
58 #define	PAGEDEF_END		'}'	/* Page definition delimiter. */
59 #define	PAGENAME_START		'"'	/* Page name delimiter. */
60 #define	PAGENAME_END		'"'	/* Page name delimiter. */
61 #define	PAGEENTRY_END		';'	/* Page entry terminator (optional). */
62 #define	MAX_DATA_SIZE		4096	/* Mode/Log sense data buffer size. */
63 #define PAGE_CTRL_SHIFT		6	/* Bit offset to page control field. */
64 
65 struct editentry {
66 	STAILQ_ENTRY(editentry) link;
67 	char	*name;
68 	char	type;
69 	int	editable;
70 	int	size;
71 	union {
72 		int	ivalue;
73 		char	*svalue;
74 	} value;
75 };
76 static STAILQ_HEAD(, editentry) editlist; /* List of page entries. */
77 static int editlist_changed = 0;	/* Whether any entries were changed. */
78 
79 struct pagename {
80 	SLIST_ENTRY(pagename) link;
81 	int page;
82 	int subpage;
83 	char *name;
84 };
85 static SLIST_HEAD(, pagename) namelist;	/* Page number to name mappings. */
86 
87 static char format[MAX_FORMAT_SPEC];	/* Buffer for scsi cdb format def. */
88 
89 static FILE *edit_file = NULL;		/* File handle for edit file. */
90 static char edit_path[] = "/tmp/camXXXXXX";
91 
92 
93 /* Function prototypes. */
94 static void		 editentry_create(void *hook, int letter, void *arg,
95 					  int count, char *name);
96 static void		 editentry_update(void *hook, int letter, void *arg,
97 					  int count, char *name);
98 static void		 editentry_create_desc(void *hook, int letter, void *arg,
99 					  int count, char *name);
100 static int		 editentry_save(void *hook, char *name);
101 static struct editentry	*editentry_lookup(char *name);
102 static int		 editentry_set(char *name, char *newvalue,
103 				       int editonly);
104 static void		 editlist_populate(struct cam_device *device,
105 			    int cdb_len, int dbd, int pc, int page, int subpage,
106 			    int task_attr, int retries, int timeout);
107 static void		 editlist_populate_desc(struct cam_device *device,
108 			    int cdb_len, int llbaa, int pc, int page,
109 			    int subpage, int task_attr, int retries,
110 			    int timeout);
111 static void		 editlist_save(struct cam_device *device, int cdb_len,
112 			    int dbd, int pc, int page, int subpage,
113 			    int task_attr, int retries, int timeout);
114 static void		 editlist_save_desc(struct cam_device *device, int cdb_len,
115 			    int llbaa, int pc, int page, int subpage,
116 			    int task_attr, int retries, int timeout);
117 static void		 nameentry_create(int page, int subpage, char *name);
118 static struct pagename	*nameentry_lookup(int page, int subpage);
119 static int		 load_format(const char *pagedb_path, int lpage,
120 			    int lsubpage);
121 static int		 modepage_write(FILE *file, int editonly);
122 static int		 modepage_read(FILE *file);
123 static void		 modepage_edit(void);
124 static void		 modepage_dump(struct cam_device *device, int cdb_len,
125 			    int dbd, int pc, int page, int subpage,
126 			    int task_attr, int retries, int timeout);
127 static void		 modepage_dump_desc(struct cam_device *device,
128 			    int cdb_len, int llbaa, int pc, int page,
129 			    int subpage, int task_attr, int retries,
130 			    int timeout);
131 static void		 cleanup_editfile(void);
132 
133 
134 #define	returnerr(code) do {						\
135 	errno = code;							\
136 	return (-1);							\
137 } while (0)
138 
139 
140 #define	RTRIM(string) do {						\
141 	int _length;							\
142 	while (isspace(string[_length = strlen(string) - 1]))		\
143 		string[_length] = '\0';					\
144 } while (0)
145 
146 
147 static void
editentry_create(void * hook __unused,int letter,void * arg,int count,char * name)148 editentry_create(void *hook __unused, int letter, void *arg, int count,
149 		 char *name)
150 {
151 	struct editentry *newentry;	/* Buffer to hold new entry. */
152 
153 	/* Allocate memory for the new entry and a copy of the entry name. */
154 	if ((newentry = malloc(sizeof(struct editentry))) == NULL ||
155 	    (newentry->name = strdup(name)) == NULL)
156 		err(EX_OSERR, NULL);
157 
158 	/* Trim any trailing whitespace for the entry name. */
159 	RTRIM(newentry->name);
160 
161 	newentry->editable = (arg != NULL);
162 	newentry->type = letter;
163 	newentry->size = count;		/* Placeholder; not accurate. */
164 	newentry->value.svalue = NULL;
165 
166 	STAILQ_INSERT_TAIL(&editlist, newentry, link);
167 }
168 
169 static void
editentry_update(void * hook __unused,int letter,void * arg,int count,char * name)170 editentry_update(void *hook __unused, int letter, void *arg, int count,
171 		 char *name)
172 {
173 	struct editentry *dest;		/* Buffer to hold entry to update. */
174 
175 	dest = editentry_lookup(name);
176 	assert(dest != NULL);
177 
178 	dest->type = letter;
179 	dest->size = count;		/* We get the real size now. */
180 
181 	switch (dest->type) {
182 	case 'i':			/* Byte-sized integral type. */
183 	case 'b':			/* Bit-sized integral types. */
184 	case 't':
185 		dest->value.ivalue = (intptr_t)arg;
186 		break;
187 
188 	case 'c':			/* Character array. */
189 	case 'z':			/* Null-padded string. */
190 		editentry_set(name, (char *)arg, 0);
191 		break;
192 	default:
193 		; /* NOTREACHED */
194 	}
195 }
196 
197 static void
editentry_create_desc(void * hook __unused,int letter,void * arg,int count,char * name)198 editentry_create_desc(void *hook __unused, int letter, void *arg, int count,
199 		 char *name)
200 {
201 	struct editentry *newentry;	/* Buffer to hold new entry. */
202 
203 	/* Allocate memory for the new entry and a copy of the entry name. */
204 	if ((newentry = malloc(sizeof(struct editentry))) == NULL ||
205 	    (newentry->name = strdup(name)) == NULL)
206 		err(EX_OSERR, NULL);
207 
208 	/* Trim any trailing whitespace for the entry name. */
209 	RTRIM(newentry->name);
210 
211 	newentry->editable = 1;
212 	newentry->type = letter;
213 	newentry->size = count;
214 	newentry->value.svalue = NULL;
215 
216 	STAILQ_INSERT_TAIL(&editlist, newentry, link);
217 
218 	switch (letter) {
219 	case 'i':			/* Byte-sized integral type. */
220 	case 'b':			/* Bit-sized integral types. */
221 	case 't':
222 		newentry->value.ivalue = (intptr_t)arg;
223 		break;
224 
225 	case 'c':			/* Character array. */
226 	case 'z':			/* Null-padded string. */
227 		editentry_set(name, (char *)arg, 0);
228 		break;
229 	default:
230 		; /* NOTREACHED */
231 	}
232 }
233 
234 static int
editentry_save(void * hook __unused,char * name)235 editentry_save(void *hook __unused, char *name)
236 {
237 	struct editentry *src;		/* Entry value to save. */
238 
239 	src = editentry_lookup(name);
240 	if (src == 0) {
241 		/*
242 		 * This happens if field does not fit into read page size.
243 		 * It also means that this field won't be written, so the
244 		 * returned value does not really matter.
245 		 */
246 		return (0);
247 	}
248 
249 	switch (src->type) {
250 	case 'i':			/* Byte-sized integral type. */
251 	case 'b':			/* Bit-sized integral types. */
252 	case 't':
253 		return (src->value.ivalue);
254 		/* NOTREACHED */
255 
256 	case 'c':			/* Character array. */
257 	case 'z':			/* Null-padded string. */
258 		return ((intptr_t)src->value.svalue);
259 		/* NOTREACHED */
260 
261 	default:
262 		; /* NOTREACHED */
263 	}
264 
265 	return (0);			/* This should never happen. */
266 }
267 
268 static struct editentry *
editentry_lookup(char * name)269 editentry_lookup(char *name)
270 {
271 	struct editentry *scan;
272 
273 	assert(name != NULL);
274 
275 	STAILQ_FOREACH(scan, &editlist, link) {
276 		if (strcasecmp(scan->name, name) == 0)
277 			return (scan);
278 	}
279 
280 	/* Not found during list traversal. */
281 	return (NULL);
282 }
283 
284 static int
editentry_set(char * name,char * newvalue,int editonly)285 editentry_set(char *name, char *newvalue, int editonly)
286 {
287 	struct editentry *dest;	/* Modepage entry to update. */
288 	char *cval;		/* Pointer to new string value. */
289 	char *convertend;	/* End-of-conversion pointer. */
290 	long long ival, newival; /* New integral value. */
291 	int resolution;		/* Resolution in bits for integer conversion. */
292 
293 /*
294  * Macro to determine the maximum value of the given size for the current
295  * resolution.
296  */
297 #define	RESOLUTION_MAX(size)	((1LL << (resolution * (size))) - 1)
298 
299 	assert(newvalue != NULL);
300 	if (*newvalue == '\0')
301 		return (0);	/* Nothing to do. */
302 
303 	if ((dest = editentry_lookup(name)) == NULL)
304 		returnerr(ENOENT);
305 	if (!dest->editable && editonly)
306 		returnerr(EPERM);
307 
308 	switch (dest->type) {
309 	case 'i':		/* Byte-sized integral type. */
310 	case 'b':		/* Bit-sized integral types. */
311 	case 't':
312 		/* Convert the value string to an integer. */
313 		resolution = (dest->type == 'i')? 8: 1;
314 		ival = strtoll(newvalue, &convertend, 0);
315 		if (*convertend != '\0')
316 			returnerr(EINVAL);
317 		if (ival > RESOLUTION_MAX(dest->size) || ival < 0) {
318 			newival = (ival < 0) ? 0 : RESOLUTION_MAX(dest->size);
319 			warnx("value %lld is out of range for entry %s; "
320 			    "clipping to %lld", ival, name, newival);
321 			ival = newival;
322 		}
323 		if (dest->value.ivalue != ival)
324 			editlist_changed = 1;
325 		dest->value.ivalue = ival;
326 		break;
327 
328 	case 'c':		/* Character array. */
329 	case 'z':		/* Null-padded string. */
330 		if ((cval = calloc(1, dest->size + 1)) == NULL)
331 			err(EX_OSERR, NULL);
332 		strlcpy(cval, newvalue, dest->size + 1);
333 		if (dest->type == 'z') {
334 			/* Convert trailing spaces to nulls. */
335 			char *convertend2;
336 
337 			for (convertend2 = cval + dest->size;
338 			    convertend2 >= cval; convertend2--) {
339 				if (*convertend2 == ' ')
340 					*convertend2 = '\0';
341 				else if (*convertend2 != '\0')
342 					break;
343 			}
344 		}
345 		if (strncmp(dest->value.svalue, cval, dest->size) == 0) {
346 			/* Nothing changed, free the newly allocated string. */
347 			free(cval);
348 			break;
349 		}
350 		if (dest->value.svalue != NULL) {
351 			/* Free the current string buffer. */
352 			free(dest->value.svalue);
353 			dest->value.svalue = NULL;
354 		}
355 		dest->value.svalue = cval;
356 		editlist_changed = 1;
357 		break;
358 
359 	default:
360 		; /* NOTREACHED */
361 	}
362 
363 	return (0);
364 #undef RESOLUTION_MAX
365 }
366 
367 static void
nameentry_create(int page,int subpage,char * name)368 nameentry_create(int page, int subpage, char *name) {
369 	struct pagename *newentry;
370 
371 	if (page < 0 || subpage < 0 || name == NULL || name[0] == '\0')
372 		return;
373 
374 	/* Allocate memory for the new entry and a copy of the entry name. */
375 	if ((newentry = malloc(sizeof(struct pagename))) == NULL ||
376 	    (newentry->name = strdup(name)) == NULL)
377 		err(EX_OSERR, NULL);
378 
379 	/* Trim any trailing whitespace for the page name. */
380 	RTRIM(newentry->name);
381 
382 	newentry->page = page;
383 	newentry->subpage = subpage;
384 	SLIST_INSERT_HEAD(&namelist, newentry, link);
385 }
386 
387 static struct pagename *
nameentry_lookup(int page,int subpage)388 nameentry_lookup(int page, int subpage) {
389 	struct pagename *scan;
390 
391 	SLIST_FOREACH(scan, &namelist, link) {
392 		if (page == scan->page && subpage == scan->subpage)
393 			return (scan);
394 	}
395 
396 	/* Not found during list traversal. */
397 	return (NULL);
398 }
399 
400 static int
load_format(const char * pagedb_path,int lpage,int lsubpage)401 load_format(const char *pagedb_path, int lpage, int lsubpage)
402 {
403 	FILE *pagedb;
404 	char str_page[MAX_PAGENUM_LEN];
405 	char *str_subpage;
406 	char str_pagename[MAX_PAGENAME_LEN];
407 	int page;
408 	int subpage;
409 	int depth;			/* Quoting depth. */
410 	int found;
411 	int lineno;
412 	enum { LOCATE, PAGENAME, PAGEDEF } state;
413 	int ch;
414 	char c;
415 
416 #define	SETSTATE_LOCATE do {						\
417 	str_page[0] = '\0';						\
418 	str_pagename[0] = '\0';						\
419 	page = -1;							\
420 	subpage = -1;							\
421 	state = LOCATE;							\
422 } while (0)
423 
424 #define	SETSTATE_PAGENAME do {						\
425 	str_pagename[0] = '\0';						\
426 	state = PAGENAME;						\
427 } while (0)
428 
429 #define	SETSTATE_PAGEDEF do {						\
430 	format[0] = '\0';						\
431 	state = PAGEDEF;						\
432 } while (0)
433 
434 #define	UPDATE_LINENO do {						\
435 	if (c == '\n')							\
436 		lineno++;						\
437 } while (0)
438 
439 #define	BUFFERFULL(buffer)	(strlen(buffer) + 1 >= sizeof(buffer))
440 
441 	if ((pagedb = fopen(pagedb_path, "r")) == NULL)
442 		returnerr(ENOENT);
443 
444 	SLIST_INIT(&namelist);
445 
446 	c = '\0';
447 	depth = 0;
448 	lineno = 0;
449 	found = 0;
450 	SETSTATE_LOCATE;
451 	while ((ch = fgetc(pagedb)) != EOF) {
452 
453 		/* Keep a line count to make error messages more useful. */
454 		UPDATE_LINENO;
455 
456 		/* Skip over comments anywhere in the mode database. */
457 		if (ch == '#') {
458 			do {
459 				ch = fgetc(pagedb);
460 			} while (ch != '\n' && ch != EOF);
461 			UPDATE_LINENO;
462 			continue;
463 		}
464 		c = ch;
465 
466 		/* Strip out newline characters. */
467 		if (c == '\n')
468 			continue;
469 
470 		/* Keep track of the nesting depth for braces. */
471 		if (c == PAGEDEF_START)
472 			depth++;
473 		else if (c == PAGEDEF_END) {
474 			depth--;
475 			if (depth < 0) {
476 				errx(EX_OSFILE, "%s:%d: %s", pagedb_path,
477 				    lineno, "mismatched bracket");
478 			}
479 		}
480 
481 		switch (state) {
482 		case LOCATE:
483 			/*
484 			 * Locate the page the user is interested in, skipping
485 			 * all others.
486 			 */
487 			if (isspace(c)) {
488 				/* Ignore all whitespace between pages. */
489 				break;
490 			} else if (depth == 0 && c == PAGEENTRY_END) {
491 				/*
492 				 * A page entry terminator will reset page
493 				 * scanning (useful for assigning names to
494 				 * modes without providing a mode definition).
495 				 */
496 				/* Record the name of this page. */
497 				str_subpage = str_page;
498 				strsep(&str_subpage, ",");
499 				page = strtol(str_page, NULL, 0);
500 				if (str_subpage)
501 				    subpage = strtol(str_subpage, NULL, 0);
502 				else
503 				    subpage = 0;
504 				nameentry_create(page, subpage, str_pagename);
505 				SETSTATE_LOCATE;
506 			} else if (depth == 0 && c == PAGENAME_START) {
507 				SETSTATE_PAGENAME;
508 			} else if (c == PAGEDEF_START) {
509 				str_subpage = str_page;
510 				strsep(&str_subpage, ",");
511 				page = strtol(str_page, NULL, 0);
512 				if (str_subpage)
513 				    subpage = strtol(str_subpage, NULL, 0);
514 				else
515 				    subpage = 0;
516 				if (depth == 1) {
517 					/* Record the name of this page. */
518 					nameentry_create(page, subpage,
519 					    str_pagename);
520 					/*
521 					 * Only record the format if this is
522 					 * the page we are interested in.
523 					 */
524 					if (lpage == page &&
525 					    lsubpage == subpage && !found)
526 						SETSTATE_PAGEDEF;
527 				}
528 			} else if (c == PAGEDEF_END) {
529 				/* Reset the processor state. */
530 				SETSTATE_LOCATE;
531 			} else if (depth == 0 && ! BUFFERFULL(str_page)) {
532 				strncat(str_page, &c, 1);
533 			} else if (depth == 0) {
534 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
535 				    lineno, "page identifier exceeds",
536 				    sizeof(str_page) - 1, "characters");
537 			}
538 			break;
539 
540 		case PAGENAME:
541 			if (c == PAGENAME_END) {
542 				/*
543 				 * Return to LOCATE state without resetting the
544 				 * page number buffer.
545 				 */
546 				state = LOCATE;
547 			} else if (! BUFFERFULL(str_pagename)) {
548 				strncat(str_pagename, &c, 1);
549 			} else {
550 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
551 				    lineno, "page name exceeds",
552 				    sizeof(str_page) - 1, "characters");
553 			}
554 			break;
555 
556 		case PAGEDEF:
557 			/*
558 			 * Transfer the page definition into a format buffer
559 			 * suitable for use with CDB encoding/decoding routines.
560 			 */
561 			if (depth == 0) {
562 				found = 1;
563 				SETSTATE_LOCATE;
564 			} else if (! BUFFERFULL(format)) {
565 				strncat(format, &c, 1);
566 			} else {
567 				errx(EX_OSFILE, "%s:%d: %s %zd %s", pagedb_path,
568 				    lineno, "page definition exceeds",
569 				    sizeof(format) - 1, "characters");
570 			}
571 			break;
572 
573 		default:
574 			; /* NOTREACHED */
575 		}
576 
577 		/* Repeat processing loop with next character. */
578 	}
579 
580 	if (ferror(pagedb))
581 		err(EX_OSFILE, "%s", pagedb_path);
582 
583 	/* Close the SCSI page database. */
584 	fclose(pagedb);
585 
586 	if (!found)			/* Never found a matching page. */
587 		returnerr(ESRCH);
588 
589 	return (0);
590 }
591 
592 static void
editlist_populate(struct cam_device * device,int cdb_len,int dbd,int pc,int page,int subpage,int task_attr,int retries,int timeout)593 editlist_populate(struct cam_device *device, int cdb_len, int dbd, int pc,
594     int page, int subpage, int task_attr, int retries, int timeout)
595 {
596 	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
597 	uint8_t *mode_pars;		/* Pointer to modepage params. */
598 	struct scsi_mode_page_header *mph;
599 	struct scsi_mode_page_header_sp *mphsp;
600 	size_t len;
601 
602 	STAILQ_INIT(&editlist);
603 
604 	/* Fetch changeable values; use to build initial editlist. */
605 	mode_sense(device, &cdb_len, dbd, 0, 1, page, subpage, task_attr,
606 		   retries, timeout, data, sizeof(data));
607 
608 	if (cdb_len == 6) {
609 		struct scsi_mode_header_6 *mh =
610 		    (struct scsi_mode_header_6 *)data;
611 		mph = find_mode_page_6(mh);
612 	} else {
613 		struct scsi_mode_header_10 *mh =
614 		    (struct scsi_mode_header_10 *)data;
615 		mph = find_mode_page_10(mh);
616 	}
617 	if ((mph->page_code & SMPH_SPF) == 0) {
618 		mode_pars = (uint8_t *)(mph + 1);
619 		len = mph->page_length;
620 	} else {
621 		mphsp = (struct scsi_mode_page_header_sp *)mph;
622 		mode_pars = (uint8_t *)(mphsp + 1);
623 		len = scsi_2btoul(mphsp->page_length);
624 	}
625 	len = MIN(len, sizeof(data) - (mode_pars - data));
626 
627 	/* Decode the value data, creating edit_entries for each value. */
628 	buff_decode_visit(mode_pars, len, format, editentry_create, 0);
629 
630 	/* Fetch the current/saved values; use to set editentry values. */
631 	mode_sense(device, &cdb_len, dbd, 0, pc, page, subpage, task_attr,
632 	    retries, timeout, data, sizeof(data));
633 	buff_decode_visit(mode_pars, len, format, editentry_update, 0);
634 }
635 
636 static void
editlist_populate_desc(struct cam_device * device,int cdb_len,int llbaa,int pc,int page,int subpage,int task_attr,int retries,int timeout)637 editlist_populate_desc(struct cam_device *device, int cdb_len, int llbaa, int pc,
638     int page, int subpage, int task_attr, int retries, int timeout)
639 {
640 	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
641 	uint8_t *desc;			/* Pointer to block descriptor. */
642 	char num[8];
643 	struct sbuf sb;
644 	size_t len;
645 	u_int longlba, dlen, i;
646 
647 	STAILQ_INIT(&editlist);
648 
649 	/* Fetch the current/saved values. */
650 	mode_sense(device, &cdb_len, 0, llbaa, pc, page, subpage, task_attr,
651 	    retries, timeout, data, sizeof(data));
652 
653 	if (cdb_len == 6) {
654 		struct scsi_mode_header_6 *mh =
655 		    (struct scsi_mode_header_6 *)data;
656 		desc = (uint8_t *)(mh + 1);
657 		len = mh->blk_desc_len;
658 		longlba = 0;
659 	} else {
660 		struct scsi_mode_header_10 *mh =
661 		    (struct scsi_mode_header_10 *)data;
662 		desc = (uint8_t *)(mh + 1);
663 		len = scsi_2btoul(mh->blk_desc_len);
664 		longlba = (mh->flags & SMH_LONGLBA) != 0;
665 	}
666 	dlen = longlba ? 16 : 8;
667 	len = MIN(len, sizeof(data) - (desc - data));
668 
669 	sbuf_new(&sb, format, sizeof(format), SBUF_FIXEDLEN);
670 	num[0] = 0;
671 	for (i = 0; i * dlen < len; i++) {
672 		if (i > 0)
673 			snprintf(num, sizeof(num), " %d", i + 1);
674 		if (longlba) {
675 			sbuf_printf(&sb, "{Number of Logical Blocks%s High} i4\n", num);
676 			sbuf_printf(&sb, "{Number of Logical Blocks%s} i4\n", num);
677 			sbuf_cat(&sb, "{Reserved} *i4\n");
678 			sbuf_printf(&sb, "{Logical Block Length%s} i4\n", num);
679 		} else if (device->pd_type == T_DIRECT) {
680 			sbuf_printf(&sb, "{Number of Logical Blocks%s} i4\n", num);
681 			sbuf_cat(&sb, "{Reserved} *i1\n");
682 			sbuf_printf(&sb, "{Logical Block Length%s} i3\n", num);
683 		} else {
684 			sbuf_printf(&sb, "{Density Code%s} i1\n", num);
685 			sbuf_printf(&sb, "{Number of Logical Blocks%s} i3\n", num);
686 			sbuf_cat(&sb, "{Reserved} *i1\n");
687 			sbuf_printf(&sb, "{Logical Block Length%s} i3\n", num);
688 		}
689 	}
690 	sbuf_finish(&sb);
691 	sbuf_delete(&sb);
692 
693 	/* Decode the value data, creating edit_entries for each value. */
694 	buff_decode_visit(desc, len, format, editentry_create_desc, 0);
695 }
696 
697 static void
editlist_save(struct cam_device * device,int cdb_len,int dbd,int pc,int page,int subpage,int task_attr,int retries,int timeout)698 editlist_save(struct cam_device *device, int cdb_len, int dbd, int pc,
699     int page, int subpage, int task_attr, int retries, int timeout)
700 {
701 	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
702 	uint8_t *mode_pars;		/* Pointer to modepage params. */
703 	struct scsi_mode_page_header *mph;
704 	struct scsi_mode_page_header_sp *mphsp;
705 	size_t len, hlen, mphlen;
706 
707 	/* Make sure that something changed before continuing. */
708 	if (! editlist_changed)
709 		return;
710 
711 	/* Preload the CDB buffer with the current mode page data. */
712 	mode_sense(device, &cdb_len, dbd, 0, pc, page, subpage, task_attr,
713 	    retries, timeout, data, sizeof(data));
714 
715 	/* Initial headers & offsets. */
716 	/*
717 	 * Tape drives include write protect (WP), Buffered Mode and Speed
718 	 * settings in the device-specific parameter.  Clearing this
719 	 * parameter on a mode select can have the effect of turning off
720 	 * write protect or buffered mode, or changing the speed setting of
721 	 * the tape drive.
722 	 *
723 	 * Disks report DPO/FUA support via the device specific parameter
724 	 * for MODE SENSE, but the bit is reserved for MODE SELECT.  So we
725 	 * clear this for disks (and other non-tape devices) to avoid
726 	 * potential errors from the target device.
727 	 */
728 	if (cdb_len == 6) {
729 		struct scsi_mode_header_6 *mh =
730 		    (struct scsi_mode_header_6 *)data;
731 		hlen = sizeof(*mh);
732 		/* Eliminate block descriptors. */
733 		if (mh->blk_desc_len > 0) {
734 			bcopy(find_mode_page_6(mh), mh + 1,
735 			    mh->data_length + 1 - hlen -
736 			    mh->blk_desc_len);
737 			mh->blk_desc_len = 0;
738 		}
739 		mh->data_length = 0;	/* Reserved for MODE SELECT command. */
740 		if (device->pd_type != T_SEQUENTIAL)
741 			mh->dev_spec = 0;	/* See comment above */
742 		mph = find_mode_page_6(mh);
743 	} else {
744 		struct scsi_mode_header_10 *mh =
745 		    (struct scsi_mode_header_10 *)data;
746 		hlen = sizeof(*mh);
747 		/* Eliminate block descriptors. */
748 		if (scsi_2btoul(mh->blk_desc_len) > 0) {
749 			bcopy(find_mode_page_10(mh), mh + 1,
750 			    scsi_2btoul(mh->data_length) + 1 - hlen -
751 			    scsi_2btoul(mh->blk_desc_len));
752 			scsi_ulto2b(0, mh->blk_desc_len);
753 		}
754 		scsi_ulto2b(0, mh->data_length); /* Reserved for MODE SELECT. */
755 		if (device->pd_type != T_SEQUENTIAL)
756 			mh->dev_spec = 0;	/* See comment above */
757 		mph = find_mode_page_10(mh);
758 	}
759 	if ((mph->page_code & SMPH_SPF) == 0) {
760 		mphlen = sizeof(*mph);
761 		mode_pars = (uint8_t *)(mph + 1);
762 		len = mph->page_length;
763 	} else {
764 		mphsp = (struct scsi_mode_page_header_sp *)mph;
765 		mphlen = sizeof(*mphsp);
766 		mode_pars = (uint8_t *)(mphsp + 1);
767 		len = scsi_2btoul(mphsp->page_length);
768 	}
769 	len = MIN(len, sizeof(data) - (mode_pars - data));
770 
771 	/* Encode the value data to be passed back to the device. */
772 	buff_encode_visit(mode_pars, len, format, editentry_save, 0);
773 
774 	mph->page_code &= ~SMPH_PS;	/* Reserved for MODE SELECT command. */
775 
776 	/*
777 	 * Write the changes back to the device. If the user editted control
778 	 * page 3 (saved values) then request the changes be permanently
779 	 * recorded.
780 	 */
781 	mode_select(device, cdb_len, (pc << PAGE_CTRL_SHIFT == SMS_PAGE_CTRL_SAVED),
782 	    task_attr, retries, timeout, data, hlen + mphlen + len);
783 }
784 
785 static void
editlist_save_desc(struct cam_device * device,int cdb_len,int llbaa,int pc,int page,int subpage,int task_attr,int retries,int timeout)786 editlist_save_desc(struct cam_device *device, int cdb_len, int llbaa, int pc,
787     int page, int subpage, int task_attr, int retries, int timeout)
788 {
789 	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
790 	uint8_t *desc;			/* Pointer to block descriptor. */
791 	size_t len, hlen;
792 
793 	/* Make sure that something changed before continuing. */
794 	if (! editlist_changed)
795 		return;
796 
797 	/* Preload the CDB buffer with the current mode page data. */
798 	mode_sense(device, &cdb_len, 0, llbaa, pc, page, subpage, task_attr,
799 	    retries, timeout, data, sizeof(data));
800 
801 	/* Initial headers & offsets. */
802 	if (cdb_len == 6) {
803 		struct scsi_mode_header_6 *mh =
804 		    (struct scsi_mode_header_6 *)data;
805 		hlen = sizeof(*mh);
806 		desc = (uint8_t *)(mh + 1);
807 		len = mh->blk_desc_len;
808 		mh->data_length = 0;	/* Reserved for MODE SELECT command. */
809 		if (device->pd_type != T_SEQUENTIAL)
810 			mh->dev_spec = 0;	/* See comment above */
811 	} else {
812 		struct scsi_mode_header_10 *mh =
813 		    (struct scsi_mode_header_10 *)data;
814 		hlen = sizeof(*mh);
815 		desc = (uint8_t *)(mh + 1);
816 		len = scsi_2btoul(mh->blk_desc_len);
817 		scsi_ulto2b(0, mh->data_length); /* Reserved for MODE SELECT. */
818 		if (device->pd_type != T_SEQUENTIAL)
819 			mh->dev_spec = 0;	/* See comment above */
820 	}
821 	len = MIN(len, sizeof(data) - (desc - data));
822 
823 	/* Encode the value data to be passed back to the device. */
824 	buff_encode_visit(desc, len, format, editentry_save, 0);
825 
826 	/*
827 	 * Write the changes back to the device. If the user editted control
828 	 * page 3 (saved values) then request the changes be permanently
829 	 * recorded.
830 	 */
831 	mode_select(device, cdb_len, (pc << PAGE_CTRL_SHIFT == SMS_PAGE_CTRL_SAVED),
832 	    task_attr, retries, timeout, data, hlen + len);
833 }
834 
835 static int
modepage_write(FILE * file,int editonly)836 modepage_write(FILE *file, int editonly)
837 {
838 	struct editentry *scan;
839 	int written = 0;
840 
841 	STAILQ_FOREACH(scan, &editlist, link) {
842 		if (scan->editable || !editonly) {
843 			written++;
844 			if (scan->type == 'c' || scan->type == 'z') {
845 				fprintf(file, "%s:  %s\n", scan->name,
846 				    scan->value.svalue);
847 			} else {
848 				fprintf(file, "%s:  %u\n", scan->name,
849 				    scan->value.ivalue);
850 			}
851 		}
852 	}
853 	return (written);
854 }
855 
856 static int
modepage_read(FILE * file)857 modepage_read(FILE *file)
858 {
859 	char *buffer;			/* Pointer to dynamic line buffer.  */
860 	char *line;			/* Pointer to static fgetln buffer. */
861 	char *name;			/* Name portion of the line buffer. */
862 	char *value;			/* Value portion of line buffer.    */
863 	size_t length;			/* Length of static fgetln buffer.  */
864 
865 #define	ABORT_READ(message, param) do {					\
866 	warnx(message, param);						\
867 	free(buffer);							\
868 	returnerr(EAGAIN);						\
869 } while (0)
870 
871 	while ((line = fgetln(file, &length)) != NULL) {
872 		/* Trim trailing whitespace (including optional newline). */
873 		while (length > 0 && isspace(line[length - 1]))
874 			length--;
875 
876 	    	/* Allocate a buffer to hold the line + terminating null. */
877 	    	if ((buffer = malloc(length + 1)) == NULL)
878 			err(EX_OSERR, NULL);
879 		memcpy(buffer, line, length);
880 		buffer[length] = '\0';
881 
882 		/* Strip out comments. */
883 		if ((value = strchr(buffer, '#')) != NULL)
884 			*value = '\0';
885 
886 		/* The name is first in the buffer. Trim whitespace.*/
887 		name = buffer;
888 		RTRIM(name);
889 		while (isspace(*name))
890 			name++;
891 
892 		/* Skip empty lines. */
893 		if (strlen(name) == 0)
894 			continue;
895 
896 		/* The name ends at the colon; the value starts there. */
897 		if ((value = strrchr(buffer, ':')) == NULL)
898 			ABORT_READ("no value associated with %s", name);
899 		*value = '\0';			/* Null-terminate name. */
900 		value++;			/* Value starts afterwards. */
901 
902 		/* Trim leading and trailing whitespace. */
903 		RTRIM(value);
904 		while (isspace(*value))
905 			value++;
906 
907 		/* Make sure there is a value left. */
908 		if (strlen(value) == 0)
909 			ABORT_READ("no value associated with %s", name);
910 
911 		/* Update our in-memory copy of the modepage entry value. */
912 		if (editentry_set(name, value, 1) != 0) {
913 			if (errno == ENOENT) {
914 				/* No entry by the name. */
915 				ABORT_READ("no such modepage entry \"%s\"",
916 				    name);
917 			} else if (errno == EINVAL) {
918 				/* Invalid value. */
919 				ABORT_READ("Invalid value for entry \"%s\"",
920 				    name);
921 			} else if (errno == ERANGE) {
922 				/* Value out of range for entry type. */
923 				ABORT_READ("value out of range for %s", name);
924 			} else if (errno == EPERM) {
925 				/* Entry is not editable; not fatal. */
926 				warnx("modepage entry \"%s\" is read-only; "
927 				    "skipping.", name);
928 			}
929 		}
930 
931 		free(buffer);
932 	}
933 	return (ferror(file)? -1: 0);
934 
935 #undef ABORT_READ
936 }
937 
938 static void
modepage_edit(void)939 modepage_edit(void)
940 {
941 	const char *editor;
942 	char *commandline;
943 	int fd;
944 	int written;
945 
946 	if (!isatty(fileno(stdin))) {
947 		/* Not a tty, read changes from stdin. */
948 		modepage_read(stdin);
949 		return;
950 	}
951 
952 	/* Lookup editor to invoke. */
953 	if ((editor = getenv("EDITOR")) == NULL)
954 		editor = DEFAULT_EDITOR;
955 
956 	/* Create temp file for editor to modify. */
957 	if ((fd = mkstemp(edit_path)) == -1)
958 		errx(EX_CANTCREAT, "mkstemp failed");
959 
960 	atexit(cleanup_editfile);
961 
962 	if ((edit_file = fdopen(fd, "w")) == NULL)
963 		err(EX_NOINPUT, "%s", edit_path);
964 
965 	written = modepage_write(edit_file, 1);
966 
967 	fclose(edit_file);
968 	edit_file = NULL;
969 
970 	if (written == 0) {
971 		warnx("no editable entries");
972 		cleanup_editfile();
973 		return;
974 	}
975 
976 	/*
977 	 * Allocate memory to hold the command line (the 2 extra characters
978 	 * are to hold the argument separator (a space), and the terminating
979 	 * null character.
980 	 */
981 	commandline = malloc(strlen(editor) + strlen(edit_path) + 2);
982 	if (commandline == NULL)
983 		err(EX_OSERR, NULL);
984 	sprintf(commandline, "%s %s", editor, edit_path);
985 
986 	/* Invoke the editor on the temp file. */
987 	if (system(commandline) == -1)
988 		err(EX_UNAVAILABLE, "could not invoke %s", editor);
989 	free(commandline);
990 
991 	if ((edit_file = fopen(edit_path, "r")) == NULL)
992 		err(EX_NOINPUT, "%s", edit_path);
993 
994 	/* Read any changes made to the temp file. */
995 	modepage_read(edit_file);
996 
997 	cleanup_editfile();
998 }
999 
1000 static void
modepage_dump(struct cam_device * device,int cdb_len,int dbd,int pc,int page,int subpage,int task_attr,int retries,int timeout)1001 modepage_dump(struct cam_device *device, int cdb_len, int dbd, int pc,
1002 	      int page, int subpage, int task_attr, int retries, int timeout)
1003 {
1004 	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
1005 	uint8_t *mode_pars;		/* Pointer to modepage params. */
1006 	struct scsi_mode_page_header *mph;
1007 	struct scsi_mode_page_header_sp *mphsp;
1008 	size_t indx, len;
1009 
1010 	mode_sense(device, &cdb_len, dbd, 0, pc, page, subpage, task_attr,
1011 	    retries, timeout, data, sizeof(data));
1012 
1013 	if (cdb_len == 6) {
1014 		struct scsi_mode_header_6 *mh =
1015 		    (struct scsi_mode_header_6 *)data;
1016 		mph = find_mode_page_6(mh);
1017 	} else {
1018 		struct scsi_mode_header_10 *mh =
1019 		    (struct scsi_mode_header_10 *)data;
1020 		mph = find_mode_page_10(mh);
1021 	}
1022 	if ((mph->page_code & SMPH_SPF) == 0) {
1023 		mode_pars = (uint8_t *)(mph + 1);
1024 		len = mph->page_length;
1025 	} else {
1026 		mphsp = (struct scsi_mode_page_header_sp *)mph;
1027 		mode_pars = (uint8_t *)(mphsp + 1);
1028 		len = scsi_2btoul(mphsp->page_length);
1029 	}
1030 	len = MIN(len, sizeof(data) - (mode_pars - data));
1031 
1032 	/* Print the raw mode page data with newlines each 8 bytes. */
1033 	for (indx = 0; indx < len; indx++) {
1034 		printf("%02x%c",mode_pars[indx],
1035 		    (((indx + 1) % 8) == 0) ? '\n' : ' ');
1036 	}
1037 	putchar('\n');
1038 }
1039 static void
modepage_dump_desc(struct cam_device * device,int cdb_len,int llbaa,int pc,int page,int subpage,int task_attr,int retries,int timeout)1040 modepage_dump_desc(struct cam_device *device, int cdb_len, int llbaa, int pc,
1041 	      int page, int subpage, int task_attr, int retries, int timeout)
1042 {
1043 	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
1044 	uint8_t *desc;			/* Pointer to block descriptor. */
1045 	size_t indx, len;
1046 
1047 	mode_sense(device, &cdb_len, 0, llbaa, pc, page, subpage, task_attr,
1048 	    retries, timeout, data, sizeof(data));
1049 
1050 	if (cdb_len == 6) {
1051 		struct scsi_mode_header_6 *mh =
1052 		    (struct scsi_mode_header_6 *)data;
1053 		desc = (uint8_t *)(mh + 1);
1054 		len = mh->blk_desc_len;
1055 	} else {
1056 		struct scsi_mode_header_10 *mh =
1057 		    (struct scsi_mode_header_10 *)data;
1058 		desc = (uint8_t *)(mh + 1);
1059 		len = scsi_2btoul(mh->blk_desc_len);
1060 	}
1061 	len = MIN(len, sizeof(data) - (desc - data));
1062 
1063 	/* Print the raw mode page data with newlines each 8 bytes. */
1064 	for (indx = 0; indx < len; indx++) {
1065 		printf("%02x%c", desc[indx],
1066 		    (((indx + 1) % 8) == 0) ? '\n' : ' ');
1067 	}
1068 	putchar('\n');
1069 }
1070 
1071 static void
cleanup_editfile(void)1072 cleanup_editfile(void)
1073 {
1074 	if (edit_file == NULL)
1075 		return;
1076 	if (fclose(edit_file) != 0 || unlink(edit_path) != 0)
1077 		warn("%s", edit_path);
1078 	edit_file = NULL;
1079 }
1080 
1081 void
mode_edit(struct cam_device * device,int cdb_len,int desc,int dbd,int llbaa,int pc,int page,int subpage,int edit,int binary,int task_attr,int retry_count,int timeout)1082 mode_edit(struct cam_device *device, int cdb_len, int desc, int dbd, int llbaa,
1083     int pc, int page, int subpage, int edit, int binary, int task_attr,
1084     int retry_count, int timeout)
1085 {
1086 	const char *pagedb_path;	/* Path to modepage database. */
1087 
1088 	if (binary) {
1089 		if (edit)
1090 			errx(EX_USAGE, "cannot edit in binary mode.");
1091 	} else if (desc) {
1092 		editlist_populate_desc(device, cdb_len, llbaa, pc, page,
1093 		    subpage, task_attr, retry_count, timeout);
1094 	} else {
1095 		if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
1096 			pagedb_path = DEFAULT_SCSI_MODE_DB;
1097 
1098 		if (load_format(pagedb_path, page, subpage) != 0 &&
1099 		    (edit || verbose)) {
1100 			if (errno == ENOENT) {
1101 				/* Modepage database file not found. */
1102 				warn("cannot open modepage database \"%s\"",
1103 				    pagedb_path);
1104 			} else if (errno == ESRCH) {
1105 				/* Modepage entry not found in database. */
1106 				warnx("modepage 0x%02x,0x%02x not found in "
1107 				    "database \"%s\"", page, subpage,
1108 				    pagedb_path);
1109 			}
1110 			/* We can recover in display mode, otherwise we exit. */
1111 			if (!edit) {
1112 				warnx("reverting to binary display only");
1113 				binary = 1;
1114 			} else
1115 				exit(EX_OSFILE);
1116 		}
1117 
1118 		editlist_populate(device, cdb_len, dbd, pc, page, subpage,
1119 		    task_attr, retry_count, timeout);
1120 	}
1121 
1122 	if (edit) {
1123 		if (pc << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_CURRENT &&
1124 		    pc << PAGE_CTRL_SHIFT != SMS_PAGE_CTRL_SAVED)
1125 			errx(EX_USAGE, "it only makes sense to edit page 0 "
1126 			    "(current) or page 3 (saved values)");
1127 		modepage_edit();
1128 		if (desc) {
1129 			editlist_save_desc(device, cdb_len, llbaa, pc, page,
1130 			    subpage, task_attr, retry_count, timeout);
1131 		} else {
1132 			editlist_save(device, cdb_len, dbd, pc, page, subpage,
1133 			    task_attr, retry_count, timeout);
1134 		}
1135 	} else if (binary || STAILQ_EMPTY(&editlist)) {
1136 		/* Display without formatting information. */
1137 		if (desc) {
1138 			modepage_dump_desc(device, cdb_len, llbaa, pc, page,
1139 			    subpage, task_attr, retry_count, timeout);
1140 		} else {
1141 			modepage_dump(device, cdb_len, dbd, pc, page, subpage,
1142 			    task_attr, retry_count, timeout);
1143 		}
1144 	} else {
1145 		/* Display with format. */
1146 		modepage_write(stdout, 0);
1147 	}
1148 }
1149 
1150 void
mode_list(struct cam_device * device,int cdb_len,int dbd,int pc,int subpages,int task_attr,int retry_count,int timeout)1151 mode_list(struct cam_device *device, int cdb_len, int dbd, int pc, int subpages,
1152 	  int task_attr, int retry_count, int timeout)
1153 {
1154 	uint8_t data[MAX_DATA_SIZE];	/* Buffer to hold mode parameters. */
1155 	struct scsi_mode_page_header *mph;
1156 	struct scsi_mode_page_header_sp *mphsp;
1157 	struct pagename *nameentry;
1158 	const char *pagedb_path;
1159 	int len, off, page, subpage;
1160 
1161 	if ((pagedb_path = getenv("SCSI_MODES")) == NULL)
1162 		pagedb_path = DEFAULT_SCSI_MODE_DB;
1163 
1164 	if (load_format(pagedb_path, 0, 0) != 0 && verbose && errno == ENOENT) {
1165 		/* Modepage database file not found. */
1166 		warn("cannot open modepage database \"%s\"", pagedb_path);
1167 	}
1168 
1169 	/* Build the list of all mode pages by querying the "all pages" page. */
1170 	mode_sense(device, &cdb_len, dbd, 0, pc, SMS_ALL_PAGES_PAGE,
1171 	    subpages ? SMS_SUBPAGE_ALL : 0,
1172 	    task_attr, retry_count, timeout, data, sizeof(data));
1173 
1174 	/* Skip block descriptors. */
1175 	if (cdb_len == 6) {
1176 		struct scsi_mode_header_6 *mh =
1177 		    (struct scsi_mode_header_6 *)data;
1178 		len = mh->data_length;
1179 		off = sizeof(*mh) + mh->blk_desc_len;
1180 	} else {
1181 		struct scsi_mode_header_10 *mh =
1182 		    (struct scsi_mode_header_10 *)data;
1183 		len = scsi_2btoul(mh->data_length);
1184 		off = sizeof(*mh) + scsi_2btoul(mh->blk_desc_len);
1185 	}
1186 	/* Iterate through the pages in the reply. */
1187 	while (off < len) {
1188 		/* Locate the next mode page header. */
1189 		mph = (struct scsi_mode_page_header *)(data + off);
1190 
1191 		if ((mph->page_code & SMPH_SPF) == 0) {
1192 			page = mph->page_code & SMS_PAGE_CODE;
1193 			subpage = 0;
1194 			off += sizeof(*mph) + mph->page_length;
1195 		} else {
1196 			mphsp = (struct scsi_mode_page_header_sp *)mph;
1197 			page = mphsp->page_code & SMS_PAGE_CODE;
1198 			subpage = mphsp->subpage;
1199 			off += sizeof(*mphsp) + scsi_2btoul(mphsp->page_length);
1200 		}
1201 
1202 		nameentry = nameentry_lookup(page, subpage);
1203 		if (subpage == 0) {
1204 			printf("0x%02x\t%s\n", page,
1205 			    nameentry ? nameentry->name : "");
1206 		} else {
1207 			printf("0x%02x,0x%02x\t%s\n", page, subpage,
1208 			    nameentry ? nameentry->name : "");
1209 		}
1210 	}
1211 }
1212