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