xref: /aosp_15_r20/external/sg3_utils/src/sg_sat_set_features.c (revision 44704f698541f6367e81f991ef8bb54ccbf3fc18)
1 /*
2  * Copyright (c) 2006-2022 Douglas Gilbert.
3  * All rights reserved.
4  * Use of this source code is governed by a BSD-style
5  * license that can be found in the BSD_LICENSE file.
6  *
7  * SPDX-License-Identifier: BSD-2-Clause
8  */
9 
10 #include <unistd.h>
11 #include <fcntl.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <stdarg.h>
15 #include <stdbool.h>
16 #include <string.h>
17 #include <errno.h>
18 #include <getopt.h>
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include "sg_lib.h"
25 #include "sg_cmds_basic.h"
26 #include "sg_cmds_extra.h"
27 #include "sg_pr2serr.h"
28 
29 /* This program uses a ATA PASS-THROUGH SCSI command. This usage is
30  * defined in the SCSI to ATA Translation (SAT) drafts and standards.
31  * See https://www.t10.org for drafts. SAT is a standard: SAT ANSI INCITS
32  * 431-2007 (draft prior to that is sat-r09.pdf). SAT-2 is also a
33  * standard: SAT-2 ANSI INCITS 465-2010 and the draft prior to that is
34  * sat2r09.pdf . The SAT-3 project has started and the most recent draft
35  * is sat3r01.pdf .
36  */
37 
38 /* This program performs a ATA PASS-THROUGH (16) SCSI command in order
39  * to perform an ATA SET FEATURES command.
40  *
41  * See man page (sg_sat_set_features.8) for details.
42  */
43 
44 #define SAT_ATA_PASS_THROUGH16 0x85
45 #define SAT_ATA_PASS_THROUGH16_LEN 16
46 #define SAT_ATA_PASS_THROUGH12 0xa1     /* clashes with MMC BLANK command */
47 #define SAT_ATA_PASS_THROUGH12_LEN 12
48 #define SAT_ATA_RETURN_DESC 9  /* ATA Return (sense) Descriptor */
49 #define ASCQ_ATA_PT_INFO_AVAILABLE 0x1d
50 
51 #define ATA_SET_FEATURES 0xef
52 
53 #define DEF_TIMEOUT 20
54 
55 static const char * version_str = "1.19 20220425";
56 
57 static struct option long_options[] = {
58     {"count", required_argument, 0, 'c'},
59     {"ck_cond", no_argument, 0, 'C'},
60     {"ck-cond", no_argument, 0, 'C'},
61     {"extended", no_argument, 0, 'e'},
62     {"feature", required_argument, 0, 'f'},
63     {"help", no_argument, 0, 'h'},
64     {"len", required_argument, 0, 'l'},
65     {"lba", required_argument, 0, 'L'},
66     {"readonly", no_argument, 0, 'r'},
67     {"verbose", no_argument, 0, 'v'},
68     {"version", no_argument, 0, 'V'},
69     {0, 0, 0, 0},
70 };
71 
72 
73 static void
usage()74 usage()
75 {
76     pr2serr("Usage: sg_sat_set_features [--count=CO] [--ck_cond] [--extended] "
77             "[--feature=FEA]\n"
78             "                           [--help] [--lba=LBA] [--len=16|12] "
79             "[--readonly]\n"
80             "                           [--verbose] [--version] DEVICE\n"
81             "  where:\n"
82             "    --count=CO | -c CO      count field contents (def: 0)\n"
83             "    --ck_cond | -C          set ck_cond field in pass-through "
84             "(def: 0)\n"
85             "    --extended | -e         enable extended lba values\n"
86             "    --feature=FEA|-f FEA    feature field contents\n"
87             "                            (def: 0 (which is reserved))\n"
88             "    --help | -h             output this usage message\n"
89             "    --lba=LBA | -L LBA      LBA field contents (def: 0)\n"
90             "                            meaning depends on sub-command "
91             "(feature)\n"
92             "    --len=16|12 | -l 16|12    cdb length: 16 or 12 bytes "
93             "(def: 16)\n"
94             "    --verbose | -v          increase verbosity\n"
95             "    --readonly | -r         open DEVICE read-only (def: "
96             "read-write)\n"
97             "                            recommended if DEVICE is ATA disk\n"
98             "    --version | -V          print version string and exit\n\n"
99             "Sends an ATA SET FEATURES command via a SAT pass through.\n"
100             "Primary feature code is placed in '--feature=FEA' with "
101             "'--count=CO' and\n"
102             "'--lba=LBA' being auxiliaries for some features.  The arguments "
103             "CO, FEA\n"
104             "and LBA are decimal unless prefixed by '0x' or have a trailing "
105             "'h'.\n"
106             "Example enabling write cache: 'sg_sat_set_feature --feature=2 "
107             "/dev/sdc'\n");
108 }
109 
110 static int
do_set_features(int sg_fd,int feature,int count,uint64_t lba,int cdb_len,bool ck_cond,bool extend,int verbose)111 do_set_features(int sg_fd, int feature, int count, uint64_t lba,
112                 int cdb_len, bool ck_cond, bool extend, int verbose)
113 {
114     const bool t_type = false;  /* false -> 512 byte blocks, true -> device's
115                                    LB size */
116     const bool t_dir = true;    /* false -> to device, true -> from device */
117     const bool byte_block = true; /* false -> bytes, true -> 512 byte blocks
118                                      (if t_type=false) */
119     bool got_ard = false;       /* got ATA result descriptor */
120     int res, ret;
121     /* Following for ATA READ/WRITE MULTIPLE (EXT) cmds, normally 0 */
122     int multiple_count = 0;
123     int protocol = 3;   /* non-data */
124     int t_length = 0;   /* 0 -> no data transferred, 2 -> sector count */
125     int resid = 0;
126     int sb_sz;
127     struct sg_scsi_sense_hdr ssh;
128     uint8_t sense_buffer[64] SG_C_CPP_ZERO_INIT;
129     uint8_t ata_return_desc[16] SG_C_CPP_ZERO_INIT;
130     uint8_t apt_cdb[SAT_ATA_PASS_THROUGH16_LEN] =
131                 {SAT_ATA_PASS_THROUGH16, 0, 0, 0, 0, 0, 0, 0,
132                  0, 0, 0, 0, 0, 0, 0, 0};
133     uint8_t apt12_cdb[SAT_ATA_PASS_THROUGH12_LEN] =
134                 {SAT_ATA_PASS_THROUGH12, 0, 0, 0, 0, 0, 0, 0,
135                  0, 0, 0, 0};
136 
137     sb_sz = sizeof(sense_buffer);
138     if (16 == cdb_len) {
139         /* Prepare ATA PASS-THROUGH COMMAND (16) command */
140         apt_cdb[14] = ATA_SET_FEATURES;
141         apt_cdb[4] = feature;
142         apt_cdb[6] = count;
143         apt_cdb[8] = lba & 0xff;
144         apt_cdb[10] = (lba >> 8) & 0xff;
145         apt_cdb[12] = (lba >> 16) & 0xff;
146         apt_cdb[7] = (lba >> 24) & 0xff;
147         apt_cdb[9] = (lba >> 32) & 0xff;
148         apt_cdb[11] = (lba >> 40) & 0xff;
149         apt_cdb[1] = (multiple_count << 5) | (protocol << 1);
150         if (extend)
151            apt_cdb[1] |= 0x1;
152         apt_cdb[2] = t_length;
153         if (ck_cond)
154             apt_cdb[2] |= 0x20;
155         if (t_type)
156             apt_cdb[2] |= 0x10;
157         if (t_dir)
158             apt_cdb[2] |= 0x8;
159         if (byte_block)
160             apt_cdb[2] |= 0x4;
161         res = sg_ll_ata_pt(sg_fd, apt_cdb, cdb_len, DEF_TIMEOUT, NULL,
162                            NULL /* doutp */, 0, sense_buffer,
163                            sb_sz, ata_return_desc,
164                            sizeof(ata_return_desc), &resid, verbose);
165     } else {
166         /* Prepare ATA PASS-THROUGH COMMAND (12) command */
167         apt12_cdb[9] = ATA_SET_FEATURES;
168         apt12_cdb[3] = feature;
169         apt12_cdb[4] = count;
170         apt12_cdb[5] = lba & 0xff;
171         apt12_cdb[6] = (lba >> 8) & 0xff;
172         apt12_cdb[7] = (lba >> 16) & 0xff;
173         apt12_cdb[1] = (multiple_count << 5) | (protocol << 1);
174         apt12_cdb[2] = t_length;
175         if (ck_cond)
176             apt12_cdb[2] |= 0x20;
177         if (t_type)
178             apt12_cdb[2] |= 0x10;
179         if (t_dir)
180             apt12_cdb[2] |= 0x8;
181         if (byte_block)
182             apt12_cdb[2] |= 0x4;
183         res = sg_ll_ata_pt(sg_fd, apt12_cdb, cdb_len, DEF_TIMEOUT, NULL,
184                            NULL /* doutp */, 0, sense_buffer,
185                            sb_sz, ata_return_desc,
186                            sizeof(ata_return_desc), &resid, verbose);
187     }
188     if (0 == res) {
189         if (verbose > 2)
190             pr2serr("command completed with SCSI GOOD status\n");
191     } else if ((res > 0) && (res & SAM_STAT_CHECK_CONDITION)) {
192         if (verbose > 1) {
193             pr2serr("ATA pass through:\n");
194             sg_print_sense(NULL, sense_buffer, sb_sz,
195                            ((verbose > 2) ? 1 : 0));
196         }
197         if (sg_scsi_normalize_sense(sense_buffer, sb_sz, &ssh)) {
198             switch (ssh.sense_key) {
199             case SPC_SK_ILLEGAL_REQUEST:
200                 if ((0x20 == ssh.asc) && (0x0 == ssh.ascq)) {
201                     ret = SG_LIB_CAT_INVALID_OP;
202                     if (verbose < 2)
203                         pr2serr("ATA PASS-THROUGH (%d) not supported\n",
204                                 cdb_len);
205                 } else {
206                     ret = SG_LIB_CAT_ILLEGAL_REQ;
207                     if (verbose < 2)
208                         pr2serr("ATA PASS-THROUGH (%d), bad field in cdb\n",
209                                 cdb_len);
210                 }
211                 return ret;
212             case SPC_SK_NO_SENSE:
213             case SPC_SK_RECOVERED_ERROR:
214                 if ((0x0 == ssh.asc) &&
215                     (ASCQ_ATA_PT_INFO_AVAILABLE == ssh.ascq)) {
216                     if (SAT_ATA_RETURN_DESC != ata_return_desc[0]) {
217                         if (verbose)
218                             pr2serr("did not find ATA Return (sense) "
219                                     "Descriptor\n");
220                         return SG_LIB_CAT_RECOVERED;
221                     }
222                     got_ard = true;
223                     break;
224                 } else if (SPC_SK_RECOVERED_ERROR == ssh.sense_key)
225                     return SG_LIB_CAT_RECOVERED;
226                 else {
227                     if ((0x0 == ssh.asc) && (0x0 == ssh.ascq))
228                         break;
229                     return SG_LIB_CAT_SENSE;
230                 }
231             case SPC_SK_UNIT_ATTENTION:
232                 if (verbose < 2)
233                     pr2serr("ATA PASS-THROUGH (%d), Unit Attention detected\n",
234                             cdb_len);
235                 return SG_LIB_CAT_UNIT_ATTENTION;
236             case SPC_SK_NOT_READY:
237                 if (verbose < 2)
238                     pr2serr("ATA PASS-THROUGH (%d), device not ready\n",
239                             cdb_len);
240                 return SG_LIB_CAT_NOT_READY;
241             case SPC_SK_MEDIUM_ERROR:
242             case SPC_SK_HARDWARE_ERROR:
243                 if (verbose < 2)
244                     pr2serr("ATA PASS-THROUGH (%d), medium or hardware "
245                             "error\n", cdb_len);
246                 return SG_LIB_CAT_MEDIUM_HARD;
247             case SPC_SK_ABORTED_COMMAND:
248                 if (0x10 == ssh.asc) {
249                     pr2serr("Aborted command: protection information\n");
250                     return SG_LIB_CAT_PROTECTION;
251                 } else {
252                     pr2serr("Aborted command\n");
253                     return SG_LIB_CAT_ABORTED_COMMAND;
254                 }
255             case SPC_SK_DATA_PROTECT:
256                 pr2serr("ATA PASS-THROUGH (%d): data protect, read only "
257                         "media?\n", cdb_len);
258                 return SG_LIB_CAT_DATA_PROTECT;
259             default:
260                 if (verbose < 2)
261                     pr2serr("ATA PASS-THROUGH (%d), some sense data, use "
262                             "'-v' for more information\n", cdb_len);
263                 return SG_LIB_CAT_SENSE;
264             }
265         } else {
266             pr2serr("CHECK CONDITION without response code ??\n");
267             return SG_LIB_CAT_SENSE;
268         }
269         if (0x72 != (sense_buffer[0] & 0x7f)) {
270             pr2serr("expected descriptor sense format, response code=0x%x\n",
271                     sense_buffer[0]);
272             return SG_LIB_CAT_MALFORMED;
273         }
274     } else if (res > 0) {
275         if (SAM_STAT_RESERVATION_CONFLICT == res) {
276             pr2serr("SCSI status: RESERVATION CONFLICT\n");
277             return SG_LIB_CAT_RES_CONFLICT;
278         } else {
279             pr2serr("Unexpected SCSI status=0x%x\n", res);
280             return SG_LIB_CAT_MALFORMED;
281         }
282     } else {
283         pr2serr("ATA pass through (%d) failed\n", cdb_len);
284         if (verbose < 2)
285             pr2serr("    try adding '-v' for more information\n");
286         return -1;
287     }
288 
289     if ((SAT_ATA_RETURN_DESC == ata_return_desc[0]) && (! got_ard))
290         pr2serr("Seem to have got ATA Result Descriptor but it was not "
291                 "indicated\n");
292     if (got_ard) {
293         if (ata_return_desc[3] & 0x4) {
294                 pr2serr("error indication in returned FIS: aborted command\n");
295                 return SG_LIB_CAT_ABORTED_COMMAND;
296         }
297     }
298     return 0;
299 }
300 
301 
302 int
main(int argc,char * argv[])303 main(int argc, char * argv[])
304 {
305     bool ck_cond = false;
306     bool extend = false;
307     bool rdonly = false;
308     bool verbose_given = false;
309     bool version_given = false;
310     int c, ret, res;
311     int sg_fd = -1;
312     int count = 0;
313     int feature = 0;
314     int verbose = 0;
315     int cdb_len = SAT_ATA_PASS_THROUGH16_LEN;
316     uint64_t lba = 0;
317     const char * device_name = NULL;
318 
319     while (1) {
320         int option_index = 0;
321 
322         c = getopt_long(argc, argv, "c:Cef:hl:L:rvV", long_options,
323                         &option_index);
324         if (c == -1)
325             break;
326 
327         switch (c) {
328         case 'c':
329             count = sg_get_num(optarg);
330             if ((count < 0) || (count > 255)) {
331                 pr2serr("bad argument for '--count'\n");
332                 return SG_LIB_SYNTAX_ERROR;
333             }
334             break;
335         case 'C':
336             ck_cond = true;
337             break;
338         case 'e':
339             extend = true;
340             break;
341         case 'f':
342             feature = sg_get_num(optarg);
343             if ((feature < 0) || (feature > 255)) {
344                 pr2serr("bad argument for '--feature'\n");
345                 return SG_LIB_SYNTAX_ERROR;
346             }
347             break;
348         case 'h':
349         case '?':
350             usage();
351             return 0;
352         case 'l':
353             cdb_len = sg_get_num(optarg);
354             if (! ((cdb_len == 12) || (cdb_len == 16))) {
355                 pr2serr("argument to '--len' should be 12 or 16\n");
356                 return SG_LIB_SYNTAX_ERROR;
357             }
358             break;
359         case 'L':       /* up to 32 bits, allow for 48 bits (less -1) */
360             lba = sg_get_llnum(optarg);
361             if ((uint64_t)-1 == lba) {
362                 pr2serr("bad argument for '--lba'\n");
363                 return SG_LIB_SYNTAX_ERROR;
364             }
365             break;
366         case 'r':
367             rdonly = true;
368             break;
369         case 'v':
370             verbose_given = true;
371             ++verbose;
372             break;
373         case 'V':
374             version_given = true;
375             break;
376         default:
377             pr2serr("unrecognised option code 0x%x ??\n", c);
378             usage();
379             return SG_LIB_SYNTAX_ERROR;
380         }
381     }
382     if (optind < argc) {
383         if (NULL == device_name) {
384             device_name = argv[optind];
385             ++optind;
386         }
387         if (optind < argc) {
388             for (; optind < argc; ++optind)
389                 pr2serr("Unexpected extra argument: %s\n", argv[optind]);
390             usage();
391             return SG_LIB_SYNTAX_ERROR;
392         }
393     }
394 
395 #ifdef DEBUG
396     pr2serr("In DEBUG mode, ");
397     if (verbose_given && version_given) {
398         pr2serr("but override: '-vV' given, zero verbose and continue\n");
399         verbose_given = false;
400         version_given = false;
401         verbose = 0;
402     } else if (! verbose_given) {
403         pr2serr("set '-vv'\n");
404         verbose = 2;
405     } else
406         pr2serr("keep verbose=%d\n", verbose);
407 #else
408     if (verbose_given && version_given)
409         pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
410 #endif
411     if (version_given) {
412         pr2serr("version: %s\n", version_str);
413         return 0;
414     }
415 
416     if (NULL == device_name) {
417         pr2serr("Missing device name!\n\n");
418         usage();
419         return 1;
420     }
421 
422     if (lba > 0xffffff) {
423         if (12 == cdb_len) {
424             cdb_len = 16;
425             if (verbose)
426                 pr2serr("Since lba > 0xffffff, forcing cdb length to 16\n");
427         }
428         if (16 == cdb_len) {
429             if (! extend) {
430                 extend = true;
431                 if (verbose)
432                     pr2serr("Since lba > 0xffffff, set extend bit\n");
433             }
434         }
435     }
436 
437     if ((sg_fd = sg_cmds_open_device(device_name, rdonly, verbose)) < 0) {
438         if (verbose)
439             pr2serr("error opening file: %s: %s\n", device_name,
440                     safe_strerror(-sg_fd));
441         ret = sg_convert_errno(-sg_fd);
442         goto fini;
443     }
444 
445     ret = do_set_features(sg_fd, feature, count, lba, cdb_len, ck_cond,
446                           extend, verbose);
447 
448 fini:
449     if (sg_fd >= 0) {
450         res = sg_cmds_close_device(sg_fd);
451         if (res < 0) {
452             pr2serr("close error: %s\n", safe_strerror(-res));
453             if (0 == ret)
454                 ret = sg_convert_errno(-res);
455         }
456     }
457     if (0 == verbose) {
458         if (! sg_if_can2stderr("sg_sat_set_feature failed: ", ret))
459             pr2serr("Some error occurred, try again with '-v' "
460                     "or '-vv' for more information\n");
461     }
462     return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
463 }
464