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