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