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