xref: /aosp_15_r20/external/sg3_utils/src/sg_timestamp.c (revision 44704f698541f6367e81f991ef8bb54ccbf3fc18)
1 /*
2  * Copyright (c) 2015-2021 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 <ctype.h>
18 #include <getopt.h>
19 #define __STDC_FORMAT_MACROS 1
20 #include <inttypes.h>
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include "sg_lib.h"
27 #include "sg_lib_data.h"
28 #include "sg_pt.h"
29 #include "sg_cmds_basic.h"
30 #include "sg_unaligned.h"
31 #include "sg_pr2serr.h"
32 
33 /* A utility program originally written for the Linux OS SCSI subsystem.
34  *
35  *
36  * This program issues a SCSI REPORT TIMESTAMP and SET TIMESTAMP commands
37  * to the given SCSI device. Based on spc5r07.pdf .
38  */
39 
40 static const char * version_str = "1.14 20210830";
41 
42 #define REP_TIMESTAMP_CMDLEN 12
43 #define SET_TIMESTAMP_CMDLEN 12
44 #define REP_TIMESTAMP_SA 0xf
45 #define SET_TIMESTAMP_SA 0xf
46 
47 #define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
48 #define DEF_PT_TIMEOUT  60      /* 60 seconds */
49 
50 uint8_t d_buff[256];
51 
52 /* example Report timestamp parameter data */
53 /* uint8_t test[12] = {0, 0xa, 2, 0, 0x1, 0x51, 0x5b, 0xe2, 0xc1, 0x30,
54  *                     0, 0}; */
55 
56 
57 static struct option long_options[] = {
58         {"elapsed", no_argument, 0, 'e'},
59         {"help", no_argument, 0, 'h'},
60         {"hex", no_argument, 0, 'H'},
61         {"milliseconds", required_argument, 0, 'm'},
62         {"no_timestamp", no_argument, 0, 'N'},
63         {"no-timestamp", no_argument, 0, 'N'},
64         {"origin", no_argument, 0, 'o'},
65         {"raw", no_argument, 0, 'r'},
66         {"readonly", no_argument, 0, 'R'},
67         {"seconds", required_argument, 0, 's'},
68         {"srep", no_argument, 0, 'S'},
69         {"verbose", no_argument, 0, 'v'},
70         {"version", no_argument, 0, 'V'},
71         {0, 0, 0, 0},
72 };
73 
74 /* Indexed by 'timestamp origin' field value */
75 static const char * ts_origin_arr[] = {
76     "initialized to zero at power on or by hard reset",
77     "reserved [0x1]",
78     "initialized by SET TIMESTAMP command",
79     "initialized by other method",
80     "reserved [0x4]",
81     "reserved [0x5]",
82     "reserved [0x6]",
83     "reserved [0x7]",
84 };
85 
86 
87 static void
usage(int num)88 usage(int num)
89 {
90     if (num > 1)
91         goto page2;
92 
93     pr2serr("Usage: "
94             "sg_timestamp  [--elapsed] [--help] [--hex] [--milliseconds=MS]\n"
95             "                     [--no-timestamp] [--origin] [--raw] "
96             "[--readonly]\n"
97             "                     [--seconds=SECS] [--srep] [--verbose] "
98             "[--version]\n"
99             "                     DEVICE\n"
100            );
101     pr2serr("  where:\n"
102             "    --elapsed|-e       show time as '<n> days hh:mm:ss.xxx' "
103             "where\n"
104             "                       '.xxx' is the remainder milliseconds. "
105             "Don't show\n"
106             "                       '<n> days' if <n> is 0 (unless '-e' "
107             "given twice)\n"
108             "    --help|-h          print out usage message, use twice for "
109             "examples\n"
110             "    --hex|-H           output response in ASCII hexadecimal\n"
111             "    --milliseconds=MS|-m MS    set timestamp to MS "
112             "milliseconds since\n"
113             "                               1970-01-01 00:00:00 UTC\n"
114             "    --no-timestamp|-N    suppress output of timestamp\n"
115             "    --origin|-o        show Report timestamp origin "
116             "(def: don't)\n"
117             "                       used twice outputs value of field\n"
118             "                       0: power up or hard reset; 2: SET "
119             "TIMESTAMP\n"
120             "    --raw|-r           output Report timestamp response to "
121             "stdout in\n"
122             "                       binary\n"
123             "    --readonly|-R      open DEVICE read only (def: "
124             "read/write)\n"
125             "    --seconds=SECS|-s SECS    set timestamp to SECS "
126             "seconds since\n"
127             "                            1970-01-01 00:00:00 UTC\n"
128             "    --srep|-S          output Report timestamp in seconds "
129             "(def:\n"
130             "                       milliseconds)\n"
131             "    --verbose|-v       increase verbosity\n"
132             "    --version|-V       print version string and exit\n\n"
133           );
134     pr2serr("Performs a SCSI REPORT TIMESTAMP or SET TIMESTAMP command. "
135             "The timestamp\nis SET if either the --milliseconds=MS or "
136             "--seconds=SECS option is given,\notherwise the existing "
137             "timestamp is reported in milliseconds. The\nDEVICE stores "
138             "the timestamp as the number of milliseconds since power up\n"
139             "(or reset) or since 1970-01-01 00:00:00 UTC which also "
140             "happens to\nbe the time 'epoch'of Unix machines.\n\n"
141             "Use '-hh' (the '-h' option twice) for examples.\n"
142 #if 0
143  "The 'date +%%s' command in "
144             "Unix returns the number of\nseconds since the epoch. To "
145             "convert a reported timestamp (in seconds since\nthe epoch) "
146             "to a more readable form use "
147             "'date --date=@<secs_since_epoch>' .\n"
148 #endif
149            );
150     return;
151 page2:
152     pr2serr("sg_timestamp examples:\n"
153             "It is possible that the target device containing a SCSI "
154             "Logical Unit (LU)\nhas a battery (or supercapacitor) to "
155             "keep its RTC (real time clock)\nticking during a power "
156             "outage. More likely it doesn't and its RTC is\ncleared to "
157             "zero after a power cycle or hard reset.\n\n"
158             "Either way REPORT TIMESTAMP returns a 48 bit counter value "
159             "whose unit is\na millisecond. A heuristic to determine if a "
160             "date or elapsed time is\nbeing returned is to choose a date "
161             "like 1 January 2000 which is 30 years\nafter the Unix epoch "
162             "(946,684,800,000 milliseconds) and values less than\nthat are "
163             "elapsed times and greater are timestamps. Observing the "
164             "TIMESTAMP\nORIGIN field of REPORT TIMESTAMP is a better "
165             "method:\n\n"
166            );
167     pr2serr(" $ sg_timestamp -o -N /dev/sg1\n"
168             "Device clock initialized to zero at power on or by hard "
169             "reset\n"
170             " $ sg_timestamp -oo -N /dev/sg1\n"
171             "0\n\n"
172             " $ sg_timestamp /dev/sg1\n"
173             "3984499\n"
174             " $ sg_timestamp --elapsed /dev/sg1\n"
175             "01:06:28.802\n\n"
176             "The last output indicates an elapsed time of 1 hour, 6 minutes "
177             "and 28.802\nseconds. Next set the clock to the current time:\n\n"
178             " $ sg_timestamp --seconds=`date +%%s` /dev/sg1\n\n"
179             " $ sg_timestamp -o -N /dev/sg1\n"
180             "Device clock initialized by SET TIMESTAMP command\n\n"
181             "Now show that as an elapsed time:\n\n"
182             " $ sg_timestamp -e /dev/sg1\n"
183             "17652 days 20:53:22.545\n\n"
184             "That is over 48 years worth of days. Lets try again as a "
185             "data-time\nstamp in UTC:\n\n"
186             " $ date -u -R --date=@`sg_timestamp -S /dev/sg1`\n"
187             "Tue, 01 May 2018 20:56:38 +0000\n"
188            );
189 }
190 
191 /* Invokes a SCSI REPORT TIMESTAMP command.  Return of 0 -> success,
192  * various SG_LIB_CAT_* positive values or -1 -> other errors */
193 static int
sg_ll_rep_timestamp(int sg_fd,void * resp,int mx_resp_len,int * residp,bool noisy,int verbose)194 sg_ll_rep_timestamp(int sg_fd, void * resp, int mx_resp_len, int * residp,
195                     bool noisy, int verbose)
196 {
197     int k, ret, res, sense_cat;
198     uint8_t rt_cdb[REP_TIMESTAMP_CMDLEN] =
199           {SG_MAINTENANCE_IN, REP_TIMESTAMP_SA, 0, 0,  0, 0, 0, 0, 0, 0, 0, 0};
200     uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
201     struct sg_pt_base * ptvp;
202 
203     sg_put_unaligned_be32((uint32_t)mx_resp_len, rt_cdb + 6);
204     if (verbose) {
205         char b[128];
206 
207         pr2serr("    Report timestamp cdb: %s\n",
208                 sg_get_command_str(rt_cdb, REP_TIMESTAMP_CMDLEN, false,
209                                    sizeof(b), b));
210     }
211 
212     ptvp = construct_scsi_pt_obj();
213     if (NULL == ptvp) {
214         pr2serr("%s: out of memory\n", __func__);
215         return -1;
216     }
217     set_scsi_pt_cdb(ptvp, rt_cdb, sizeof(rt_cdb));
218     set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
219     set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
220     res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
221     ret = sg_cmds_process_resp(ptvp, "report timestamp", res, noisy, verbose,
222                                &sense_cat);
223     if (-1 == ret) {
224         if (get_scsi_pt_transport_err(ptvp))
225             ret = SG_LIB_TRANSPORT_ERROR;
226         else
227             ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
228     } else if (-2 == ret) {
229         switch (sense_cat) {
230         case SG_LIB_CAT_RECOVERED:
231         case SG_LIB_CAT_NO_SENSE:
232             ret = 0;
233             break;
234         default:
235             ret = sense_cat;
236             break;
237         }
238     } else
239         ret = 0;
240     k = get_scsi_pt_resid(ptvp);
241     if (residp)
242         *residp = k;
243     if ((verbose > 2) && ((mx_resp_len - k) > 0)) {
244         pr2serr("Parameter data returned:\n");
245         hex2stderr((const uint8_t *)resp, mx_resp_len - k,
246                    ((verbose > 3) ? -1 : 1));
247     }
248     destruct_scsi_pt_obj(ptvp);
249     return ret;
250 }
251 
252 
253 /* Invokes the SET TIMESTAMP command.  Return of 0 -> success, various
254  * SG_LIB_CAT_* positive values or -1 -> other errors */
255 static int
sg_ll_set_timestamp(int sg_fd,void * paramp,int param_len,bool noisy,int verbose)256 sg_ll_set_timestamp(int sg_fd, void * paramp, int param_len, bool noisy,
257                     int verbose)
258 {
259     int ret, res, sense_cat;
260     uint8_t st_cdb[SET_TIMESTAMP_CMDLEN] =
261           {SG_MAINTENANCE_OUT, SET_TIMESTAMP_SA, 0, 0,  0, 0, 0, 0,
262            0, 0, 0, 0};
263     uint8_t sense_b[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
264     struct sg_pt_base * ptvp;
265 
266     sg_put_unaligned_be32(param_len, st_cdb + 6);
267     if (verbose) {
268         char b[128];
269 
270         pr2serr("    Set timestamp cdb: %s\n",
271                 sg_get_command_str(st_cdb, SET_TIMESTAMP_CMDLEN, false,
272                                    sizeof(b), b));
273         if ((verbose > 1) && paramp && param_len) {
274             pr2serr("    set timestamp parameter list:\n");
275             hex2stderr((const uint8_t *)paramp, param_len, -1);
276         }
277     }
278 
279     ptvp = construct_scsi_pt_obj();
280     if (NULL == ptvp) {
281         pr2serr("%s: out of memory\n", __func__);
282         return -1;
283     }
284     set_scsi_pt_cdb(ptvp, st_cdb, sizeof(st_cdb));
285     set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
286     set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
287     res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
288     ret = sg_cmds_process_resp(ptvp, "set timestamp", res, noisy, verbose,
289                                &sense_cat);
290     if (-1 == ret) {
291         if (get_scsi_pt_transport_err(ptvp))
292             ret = SG_LIB_TRANSPORT_ERROR;
293         else
294             ret = sg_convert_errno(get_scsi_pt_os_err(ptvp));
295     } else if (-2 == ret) {
296         switch (sense_cat) {
297         case SG_LIB_CAT_RECOVERED:
298         case SG_LIB_CAT_NO_SENSE:
299             ret = 0;
300             break;
301         default:
302             ret = sense_cat;
303             break;
304         }
305     } else
306         ret = 0;
307     destruct_scsi_pt_obj(ptvp);
308     return ret;
309 }
310 
311 static void
dStrRaw(const uint8_t * str,int len)312 dStrRaw(const uint8_t * str, int len)
313 {
314     int k;
315 
316     for (k = 0; k < len; ++k)
317         printf("%c", str[k]);
318 }
319 
320 
321 int
main(int argc,char * argv[])322 main(int argc, char * argv[])
323 {
324     bool do_srep = false;
325     bool do_raw = false;
326     bool no_timestamp = false;
327     bool readonly = false;
328     bool secs_given = false;
329     bool verbose_given = false;
330     bool version_given = false;
331     int res, c;
332     int sg_fd = 1;
333     int elapsed = 0;
334     int do_origin = 0;
335     int do_help = 0;
336     int do_hex = 0;
337     int do_set = 0;
338     int ret = 0;
339     int verbose = 0;
340     uint64_t secs = 0;
341     uint64_t msecs = 0;
342     int64_t ll;
343     const char * device_name = NULL;
344     const char * cmd_name;
345 
346     while (1) {
347         int option_index = 0;
348 
349         c = getopt_long(argc, argv, "ehHm:NorRs:SvV", long_options,
350                         &option_index);
351         if (c == -1)
352             break;
353 
354         switch (c) {
355         case 'e':
356             ++elapsed;
357             break;
358         case 'h':
359         case '?':
360             ++do_help;
361             break;
362         case 'H':
363             ++do_hex;
364             break;
365         case 'm':
366             ll = sg_get_llnum(optarg);
367             if (-1 == ll) {
368                 pr2serr("bad argument to '--milliseconds=MS'\n");
369                 return SG_LIB_SYNTAX_ERROR;
370             }
371             msecs = (uint64_t)ll;
372             ++do_set;
373             break;
374         case 'N':
375             no_timestamp = true;
376             break;
377         case 'o':
378             ++do_origin;
379             break;
380         case 'r':
381             do_raw = true;
382             break;
383         case 'R':
384             readonly = true;
385             break;
386         case 's':
387             ll = sg_get_llnum(optarg);
388             if (-1 == ll) {
389                 pr2serr("bad argument to '--seconds=SECS'\n");
390                 return SG_LIB_SYNTAX_ERROR;
391             }
392             secs = (uint64_t)ll;
393             ++do_set;
394             secs_given = true;
395             break;
396         case 'S':
397             do_srep = true;
398             break;
399         case 'v':
400             verbose_given = true;
401             ++verbose;
402             break;
403         case 'V':
404             version_given = true;
405             break;
406         default:
407             pr2serr("unrecognised option code 0x%x ??\n", c);
408             usage(1);
409             return SG_LIB_SYNTAX_ERROR;
410         }
411     }
412     if (optind < argc) {
413         if (NULL == device_name) {
414             device_name = argv[optind];
415             ++optind;
416         }
417         if (optind < argc) {
418             for (; optind < argc; ++optind)
419                 pr2serr("Unexpected extra argument: %s\n", argv[optind]);
420             usage(1);
421             return SG_LIB_SYNTAX_ERROR;
422         }
423     }
424     if (do_help) {
425         usage(do_help);
426         return 0;
427     }
428 
429 #ifdef DEBUG
430     pr2serr("In DEBUG mode, ");
431     if (verbose_given && version_given) {
432         pr2serr("but override: '-vV' given, zero verbose and continue\n");
433         verbose_given = false;
434         version_given = false;
435         verbose = 0;
436     } else if (! verbose_given) {
437         pr2serr("set '-vv'\n");
438         verbose = 2;
439     } else
440         pr2serr("keep verbose=%d\n", verbose);
441 #else
442     if (verbose_given && version_given)
443         pr2serr("Not in DEBUG mode, so '-vV' has no special action\n");
444 #endif
445     if (version_given) {
446         pr2serr("version: %s\n", version_str);
447         return 0;
448     }
449 
450     if (do_set > 1) {
451         pr2serr("either --milliseconds=MS or --seconds=SECS may be given, "
452                 "not both\n");
453         usage(1);
454         return SG_LIB_CONTRADICT;
455     }
456 
457     if (NULL == device_name) {
458         pr2serr("missing device name!\n\n");
459         usage(1);
460         return SG_LIB_SYNTAX_ERROR;
461     }
462 
463     sg_fd = sg_cmds_open_device(device_name, readonly, verbose);
464     if (sg_fd < 0) {
465         if (verbose)
466             pr2serr("open error: %s: %s\n", device_name,
467                     safe_strerror(-sg_fd));
468         ret = sg_convert_errno(-sg_fd);
469         goto fini;
470     }
471 
472     memset(d_buff, 0, 12);
473     if (do_set) {
474         cmd_name = "Set timestamp";
475         sg_put_unaligned_be48(secs_given ? (secs * 1000) : msecs, d_buff + 4);
476         res = sg_ll_set_timestamp(sg_fd, d_buff, 12, true, verbose);
477     } else {
478         cmd_name = "Report timestamp";
479         res = sg_ll_rep_timestamp(sg_fd, d_buff, 12, NULL, true, verbose);
480         if (0 == res) {
481             if (do_raw)
482                 dStrRaw(d_buff, 12);
483             else if (do_hex)
484                 hex2stderr(d_buff, 12, 1);
485             else {
486                 int len = sg_get_unaligned_be16(d_buff + 0);
487 
488                 if (len < 8)
489                     pr2serr("timestamp parameter data length too short, "
490                             "expect >= 10, got %d\n", len + 2);
491                 else {
492                     if (do_origin) {
493                         if (1 == do_origin)
494                             printf("Device clock %s\n",
495                                    ts_origin_arr[0x7 & d_buff[2]]);
496                         else if (2 == do_origin)
497                             printf("%d\n", 0x7 & d_buff[2]);
498                         else
499                             printf("TIMESTAMP_ORIGIN=%d\n", 0x7 & d_buff[2]);
500                     }
501                     if (! no_timestamp) {
502                         msecs = sg_get_unaligned_be48(d_buff + 4);
503                         if (elapsed) {
504                             int days = (int)(msecs / 1000 / 60 / 60 / 24);
505                             int hours = (int)(msecs / 1000 / 60 / 60 % 24);
506                             int mins = (int)(msecs / 1000 / 60 % 60);
507                             int secs_in_min =(int)( msecs / 1000 % 60);
508                             int rem_msecs = (int)(msecs % 1000);
509 
510                             if ((elapsed > 1) || (days > 0))
511                                 printf("%d day%s ", days,
512                                        ((1 == days) ? "" : "s"));
513                             printf("%02d:%02d:%02d.%03d\n", hours, mins,
514                                    secs_in_min, rem_msecs);
515                         } else
516                             printf("%" PRIu64 "\n", do_srep ?
517                                                     (msecs / 1000) : msecs);
518                     }
519                 }
520             }
521         }
522     }
523     ret = res;
524     if (res) {
525         if (SG_LIB_CAT_INVALID_OP == res)
526             pr2serr("%s command not supported\n", cmd_name);
527         else {
528             char b[80];
529 
530             sg_get_category_sense_str(res, sizeof(b), b, verbose);
531             pr2serr("%s command: %s\n", cmd_name, b);
532         }
533     }
534 
535 fini:
536     if (sg_fd >= 0) {
537         res = sg_cmds_close_device(sg_fd);
538         if (res < 0) {
539             pr2serr("close error: %s\n", safe_strerror(-res));
540             if (0 == ret)
541                 ret = sg_convert_errno(-res);
542         }
543     }
544     if (0 == verbose) {
545         if (! sg_if_can2stderr("sg_timestamp failed: ", ret))
546             pr2serr("Some error occurred, try again with '-v' "
547                     "or '-vv' for more information\n");
548     }
549     return (ret >= 0) ? ret : SG_LIB_CAT_OTHER;
550 }
551