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