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