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