1 /*-
2 * Copyright (c) 2016 Spectra Logic Corporation
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions, and the following disclaimer,
10 * without modification.
11 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12 * substantially similar to the "NO WARRANTY" disclaimer below
13 * ("Disclaimer") and any redistribution must be conditioned upon
14 * including a substantially similar Disclaimer requirement for further
15 * binary redistribution.
16 *
17 * NO WARRANTY
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
27 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGES.
29 *
30 * Authors: Ken Merry (Spectra Logic Corporation)
31 * Reid Linnemann (Spectra Logic Corporation)
32 * Samuel Klopsch (Spectra Logic Corporation)
33 */
34 /*
35 * SCSI tape drive timestamp support
36 */
37
38 #include <sys/types.h>
39
40 #include <assert.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <unistd.h>
44 #include <string.h>
45 #include <err.h>
46 #include <time.h>
47 #include <locale.h>
48
49 #include <cam/cam.h>
50 #include <cam/cam_debug.h>
51 #include <cam/cam_ccb.h>
52 #include <cam/scsi/scsi_all.h>
53 #include <cam/scsi/scsi_message.h>
54 #include <camlib.h>
55 #include "camcontrol.h"
56
57 #define TIMESTAMP_REPORT 0
58 #define TIMESTAMP_SET 1
59 #define MIL "milliseconds"
60 #define UTC "utc"
61
62 static int set_restore_flags(struct cam_device *device, uint8_t *flags,
63 int set_flag, int task_attr, int retry_count,
64 int timeout);
65 static int report_timestamp(struct cam_device *device, uint64_t *ts,
66 int task_attr, int retry_count, int timeout);
67 static int set_timestamp(struct cam_device *device, char *format_string,
68 char *timestamp_string, int task_attr, int retry_count,
69 int timeout);
70
71 static int
set_restore_flags(struct cam_device * device,uint8_t * flags,int set_flag,int task_attr,int retry_count,int timeout)72 set_restore_flags(struct cam_device *device, uint8_t *flags, int set_flag,
73 int task_attr, int retry_count, int timeout)
74 {
75 unsigned long blk_desc_length, hdr_and_blk_length;
76 int error = 0;
77 struct scsi_control_ext_page *control_page = NULL;
78 struct scsi_mode_header_10 *mode_hdr = NULL;
79 union ccb *ccb = NULL;
80 unsigned long mode_buf_size = sizeof(struct scsi_mode_header_10) +
81 sizeof(struct scsi_mode_blk_desc) +
82 sizeof(struct scsi_control_ext_page);
83 uint8_t mode_buf[mode_buf_size];
84
85 ccb = cam_getccb(device);
86 if (ccb == NULL) {
87 warnx("%s: error allocating CCB", __func__);
88 error = 1;
89 goto bailout;
90 }
91 /*
92 * Get the control extension subpage, we'll send it back modified to
93 * enable SCSI control over the tape drive's timestamp
94 */
95 scsi_mode_sense_subpage(&ccb->csio,
96 /*retries*/ retry_count,
97 /*cbfcnp*/ NULL,
98 /*tag_action*/ task_attr,
99 /*dbd*/ 0,
100 /*page_control*/ SMS_PAGE_CTRL_CURRENT,
101 /*page*/ SCEP_PAGE_CODE,
102 /*subpage*/ SCEP_SUBPAGE_CODE,
103 /*param_buf*/ &mode_buf[0],
104 /*param_len*/ mode_buf_size,
105 /*minimum_cmd_size*/ 10,
106 /*sense_len*/ SSD_FULL_SIZE,
107 /*timeout*/ timeout ? timeout : 5000);
108
109 ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
110 if (retry_count > 0)
111 ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
112
113 error = cam_send_ccb(device, ccb);
114 if (error != 0) {
115 warn("error sending Mode Sense");
116 goto bailout;
117 }
118
119 if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
120 cam_error_print(device, ccb, CAM_ESF_ALL,
121 CAM_EPF_ALL, stderr);
122 error = 1;
123 goto bailout;
124 }
125
126 mode_hdr = (struct scsi_mode_header_10 *)&mode_buf[0];
127 blk_desc_length = scsi_2btoul(mode_hdr->blk_desc_len);
128 hdr_and_blk_length = sizeof(struct scsi_mode_header_10)+blk_desc_length;
129 /*
130 * Create the control page at the correct point in the mode_buf, it
131 * starts after the header and the blk description.
132 */
133 assert(hdr_and_blk_length <=
134 sizeof(mode_buf) - sizeof(struct scsi_control_ext_page));
135 control_page = (struct scsi_control_ext_page *)&mode_buf
136 [hdr_and_blk_length];
137 if (set_flag != 0) {
138 *flags = control_page->flags;
139 /*
140 * Set the SCSIP flag to enable SCSI to change the
141 * tape drive's timestamp.
142 */
143 control_page->flags |= SCEP_SCSIP;
144 } else {
145 control_page->flags = *flags;
146 }
147
148 scsi_mode_select_len(&ccb->csio,
149 /*retries*/ retry_count,
150 /*cbfcnp*/ NULL,
151 /*tag_action*/ task_attr,
152 /*scsi_page_fmt*/ 1,
153 /*save_pages*/ 0,
154 /*param_buf*/ &mode_buf[0],
155 /*param_len*/ mode_buf_size,
156 /*minimum_cmd_size*/ 10,
157 /*sense_len*/ SSD_FULL_SIZE,
158 /*timeout*/ timeout ? timeout : 5000);
159
160 ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
161 if (retry_count > 0)
162 ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
163
164 error = cam_send_ccb(device, ccb);
165 if (error != 0) {
166 warn("error sending Mode Select");
167 goto bailout;
168 }
169
170 if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
171 cam_error_print(device, ccb, CAM_ESF_ALL,
172 CAM_EPF_ALL, stderr);
173 error = 1;
174 goto bailout;
175 }
176
177 bailout:
178 if (ccb != NULL)
179 cam_freeccb(ccb);
180
181 return error;
182 }
183
184 static int
report_timestamp(struct cam_device * device,uint64_t * ts,int task_attr,int retry_count,int timeout)185 report_timestamp(struct cam_device *device, uint64_t *ts, int task_attr,
186 int retry_count, int timeout)
187 {
188 int error = 0;
189 struct scsi_report_timestamp_data *report_buf = malloc(
190 sizeof(struct scsi_report_timestamp_data));
191 uint8_t temp_timestamp[8];
192 uint32_t report_buf_size = sizeof(
193 struct scsi_report_timestamp_data);
194 union ccb *ccb = NULL;
195
196 ccb = cam_getccb(device);
197 if (ccb == NULL) {
198 warnx("%s: error allocating CCB", __func__);
199 error = 1;
200 goto bailout;
201 }
202
203 scsi_report_timestamp(&ccb->csio,
204 /*retries*/ retry_count,
205 /*cbfcnp*/ NULL,
206 /*tag_action*/ task_attr,
207 /*pdf*/ 0,
208 /*buf*/ report_buf,
209 /*buf_len*/ report_buf_size,
210 /*sense_len*/ SSD_FULL_SIZE,
211 /*timeout*/ timeout ? timeout : 5000);
212
213 ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
214 if (retry_count > 0)
215 ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
216
217 error = cam_send_ccb(device, ccb);
218 if (error != 0) {
219 warn("error sending Report Timestamp");
220 goto bailout;
221 }
222 if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
223 cam_error_print(device, ccb, CAM_ESF_ALL,
224 CAM_EPF_ALL, stderr);
225 error = 1;
226 goto bailout;
227 }
228
229 bzero(temp_timestamp, sizeof(temp_timestamp));
230 memcpy(&temp_timestamp[2], &report_buf->timestamp, 6);
231
232 *ts = scsi_8btou64(temp_timestamp);
233
234 bailout:
235 if (ccb != NULL)
236 cam_freeccb(ccb);
237 free(report_buf);
238
239 return error;
240 }
241
242 static int
set_timestamp(struct cam_device * device,char * format_string,char * timestamp_string,int task_attr,int retry_count,int timeout)243 set_timestamp(struct cam_device *device, char *format_string,
244 char *timestamp_string, int task_attr, int retry_count,
245 int timeout)
246 {
247 struct scsi_set_timestamp_parameters ts_p;
248 time_t time_value;
249 struct tm time_struct;
250 uint8_t flags = 0;
251 int error = 0;
252 uint64_t ts = 0;
253 union ccb *ccb = NULL;
254 int do_restore_flags = 0;
255
256 error = set_restore_flags(device, &flags, /*set_flag*/ 1, task_attr,
257 retry_count, timeout);
258 if (error != 0)
259 goto bailout;
260
261 do_restore_flags = 1;
262
263 ccb = cam_getccb(device);
264 if (ccb == NULL) {
265 warnx("%s: error allocating CCB", __func__);
266 error = 1;
267 goto bailout;
268 }
269
270 if (strcmp(format_string, UTC) == 0) {
271 time(&time_value);
272 ts = (uint64_t) time_value;
273 } else {
274 bzero(&time_struct, sizeof(struct tm));
275 if (strptime(timestamp_string, format_string,
276 &time_struct) == NULL) {
277 warnx("%s: strptime(3) failed", __func__);
278 error = 1;
279 goto bailout;
280 }
281 time_value = mktime(&time_struct);
282 ts = (uint64_t) time_value;
283 }
284 /* Convert time from seconds to milliseconds */
285 ts *= 1000;
286 bzero(&ts_p, sizeof(ts_p));
287 scsi_create_timestamp(ts_p.timestamp, ts);
288
289 scsi_set_timestamp(&ccb->csio,
290 /*retries*/ retry_count,
291 /*cbfcnp*/ NULL,
292 /*tag_action*/ task_attr,
293 /*buf*/ &ts_p,
294 /*buf_len*/ sizeof(ts_p),
295 /*sense_len*/ SSD_FULL_SIZE,
296 /*timeout*/ timeout ? timeout : 5000);
297
298 ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
299 if (retry_count > 0)
300 ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
301
302 error = cam_send_ccb(device, ccb);
303 if (error != 0) {
304 warn("error sending Set Timestamp");
305 goto bailout;
306 }
307
308 if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
309 cam_error_print(device, ccb, CAM_ESF_ALL,
310 CAM_EPF_ALL, stderr);
311 error = 1;
312 goto bailout;
313 }
314
315 printf("Timestamp set to %ju\n", (uintmax_t)ts);
316
317 bailout:
318 if (do_restore_flags != 0)
319 error = set_restore_flags(device, &flags, /*set_flag*/ 0,
320 task_attr, retry_count, timeout);
321 if (ccb != NULL)
322 cam_freeccb(ccb);
323
324 return error;
325 }
326
327 int
timestamp(struct cam_device * device,int argc,char ** argv,char * combinedopt,int task_attr,int retry_count,int timeout,int verbosemode __unused)328 timestamp(struct cam_device *device, int argc, char **argv, char *combinedopt,
329 int task_attr, int retry_count, int timeout, int verbosemode __unused)
330 {
331 int c;
332 uint64_t ts = 0;
333 char *format_string = NULL;
334 char *timestamp_string = NULL;
335 int action = -1;
336 int error = 0;
337 int single_arg = 0;
338 int do_utc = 0;
339
340 while ((c = getopt(argc, argv, combinedopt)) != -1) {
341 switch (c) {
342 case 'r': {
343 if (action != -1) {
344 warnx("Use only one -r or only one -s");
345 error =1;
346 goto bailout;
347 }
348 action = TIMESTAMP_REPORT;
349 break;
350 }
351 case 's': {
352 if (action != -1) {
353 warnx("Use only one -r or only one -s");
354 error = 1;
355 goto bailout;
356 }
357 action = TIMESTAMP_SET;
358 break;
359 }
360 case 'f': {
361 single_arg++;
362 free(format_string);
363 format_string = strdup(optarg);
364 if (format_string == NULL) {
365 warn("Error allocating memory for format "
366 "argument");
367 error = 1;
368 goto bailout;
369 }
370 break;
371 }
372 case 'm': {
373 single_arg++;
374 free(format_string);
375 format_string = strdup(MIL);
376 if (format_string == NULL) {
377 warn("Error allocating memory");
378 error = 1;
379 goto bailout;
380 }
381 break;
382 }
383 case 'U': {
384 do_utc = 1;
385 break;
386 }
387 case 'T':
388 free(timestamp_string);
389 timestamp_string = strdup(optarg);
390 if (timestamp_string == NULL) {
391 warn("Error allocating memory for format "
392 "argument");
393 error = 1;
394 goto bailout;
395 }
396 break;
397 default:
398 break;
399 }
400 }
401
402 if (action == -1) {
403 warnx("Must specify an action, either -r or -s");
404 error = 1;
405 goto bailout;
406 }
407
408 if (single_arg > 1) {
409 warnx("Select only one: %s",
410 (action == TIMESTAMP_REPORT) ?
411 "-f format or -m for the -r flag" :
412 "-f format -T time or -U for the -s flag");
413 error = 1;
414 goto bailout;
415 }
416
417 if (action == TIMESTAMP_SET) {
418 if ((format_string == NULL)
419 && (do_utc == 0)) {
420 warnx("Must specify either -f format or -U for "
421 "setting the timestamp");
422 error = 1;
423 } else if ((format_string != NULL)
424 && (do_utc != 0)) {
425 warnx("Must specify only one of -f or -U to set "
426 "the timestamp");
427 error = 1;
428 } else if ((format_string != NULL)
429 && (strcmp(format_string, MIL) == 0)) {
430 warnx("-m is not allowed for setting the "
431 "timestamp");
432 error = 1;
433 } else if ((do_utc == 0)
434 && (timestamp_string == NULL)) {
435 warnx("Must specify the time (-T) to set as the "
436 "timestamp");
437 error = 1;
438 }
439 if (error != 0)
440 goto bailout;
441 } else if (action == TIMESTAMP_REPORT) {
442 if (format_string == NULL) {
443 format_string = strdup("%c %Z");
444 if (format_string == NULL) {
445 warn("Error allocating memory for format "
446 "string");
447 error = 1;
448 goto bailout;
449 }
450 }
451 }
452
453 if (action == TIMESTAMP_REPORT) {
454 error = report_timestamp(device, &ts, task_attr, retry_count,
455 timeout);
456 if (error != 0) {
457 goto bailout;
458 } else if (strcmp(format_string, MIL) == 0) {
459 printf("Timestamp in milliseconds: %ju\n",
460 (uintmax_t)ts);
461 } else {
462 char temp_timestamp_string[100];
463 time_t time_var = ts / 1000;
464 const struct tm *restrict cur_time;
465
466 setlocale(LC_TIME, "");
467 if (do_utc != 0)
468 cur_time = gmtime(&time_var);
469 else
470 cur_time = localtime(&time_var);
471
472 strftime(temp_timestamp_string,
473 sizeof(temp_timestamp_string), format_string,
474 cur_time);
475 printf("Formatted timestamp: %s\n",
476 temp_timestamp_string);
477 }
478 } else if (action == TIMESTAMP_SET) {
479 if (do_utc != 0) {
480 format_string = strdup(UTC);
481 if (format_string == NULL) {
482 warn("Error allocating memory for format "
483 "string");
484 error = 1;
485 goto bailout;
486 }
487 }
488
489 error = set_timestamp(device, format_string, timestamp_string,
490 task_attr, retry_count, timeout);
491 }
492
493 bailout:
494 free(format_string);
495 free(timestamp_string);
496
497 return (error);
498 }
499