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