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 strptime(timestamp_string, format_string, &time_struct); 286 time_value = mktime(&time_struct); 287 ts = (uint64_t) time_value; 288 } 289 /* Convert time from seconds to milliseconds */ 290 ts *= 1000; 291 scsi_create_timestamp(ts_p.timestamp, ts); 292 293 scsi_set_timestamp(&ccb->csio, 294 /*retries*/ retry_count, 295 /*cbfcnp*/ NULL, 296 /*tag_action*/ task_attr, 297 /*buf*/ &ts_p, 298 /*buf_len*/ sizeof(ts_p), 299 /*sense_len*/ SSD_FULL_SIZE, 300 /*timeout*/ timeout ? timeout : 5000); 301 302 ccb->ccb_h.flags |= CAM_DEV_QFRZDIS; 303 if (retry_count > 0) 304 ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER; 305 306 error = cam_send_ccb(device, ccb); 307 if (error != 0) { 308 warn("error sending Set Timestamp"); 309 goto bailout; 310 } 311 312 if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) { 313 cam_error_print(device, ccb, CAM_ESF_ALL, 314 CAM_EPF_ALL, stderr); 315 error = 1; 316 goto bailout; 317 } 318 319 printf("Timestamp set to %ju\n", (uintmax_t)ts); 320 321 bailout: 322 if (do_restore_flags != 0) 323 error = set_restore_flags(device, &flags, /*set_flag*/ 0, 324 task_attr, retry_count, timeout); 325 if (ccb != NULL) 326 cam_freeccb(ccb); 327 328 return error; 329 } 330 331 int 332 timestamp(struct cam_device *device, int argc, char **argv, char *combinedopt, 333 int task_attr, int retry_count, int timeout, int verbosemode __unused) 334 { 335 int c; 336 uint64_t ts = 0; 337 char *format_string = NULL; 338 char *timestamp_string = NULL; 339 int action = -1; 340 int error = 0; 341 int single_arg = 0; 342 int do_utc = 0; 343 344 while ((c = getopt(argc, argv, combinedopt)) != -1) { 345 switch (c) { 346 case 'r': { 347 if (action != -1) { 348 warnx("Use only one -r or only one -s"); 349 error =1; 350 goto bailout; 351 } 352 action = TIMESTAMP_REPORT; 353 break; 354 } 355 case 's': { 356 if (action != -1) { 357 warnx("Use only one -r or only one -s"); 358 error = 1; 359 goto bailout; 360 } 361 action = TIMESTAMP_SET; 362 break; 363 } 364 case 'f': { 365 single_arg++; 366 free(format_string); 367 format_string = strdup(optarg); 368 if (format_string == NULL) { 369 warn("Error allocating memory for format " 370 "argument"); 371 error = 1; 372 goto bailout; 373 } 374 break; 375 } 376 case 'm': { 377 single_arg++; 378 free(format_string); 379 format_string = strdup(MIL); 380 if (format_string == NULL) { 381 warn("Error allocating memory"); 382 error = 1; 383 goto bailout; 384 } 385 break; 386 } 387 case 'U': { 388 do_utc = 1; 389 break; 390 } 391 case 'T': 392 free(timestamp_string); 393 timestamp_string = strdup(optarg); 394 if (timestamp_string == NULL) { 395 warn("Error allocating memory for format " 396 "argument"); 397 error = 1; 398 goto bailout; 399 } 400 break; 401 default: 402 break; 403 } 404 } 405 406 if (action == -1) { 407 warnx("Must specify an action, either -r or -s"); 408 error = 1; 409 goto bailout; 410 } 411 412 if (single_arg > 1) { 413 warnx("Select only one: %s", 414 (action == TIMESTAMP_REPORT) ? 415 "-f format or -m for the -r flag" : 416 "-f format -T time or -U for the -s flag"); 417 error = 1; 418 goto bailout; 419 } 420 421 if (action == TIMESTAMP_SET) { 422 if ((format_string == NULL) 423 && (do_utc == 0)) { 424 warnx("Must specify either -f format or -U for " 425 "setting the timestamp"); 426 error = 1; 427 } else if ((format_string != NULL) 428 && (do_utc != 0)) { 429 warnx("Must specify only one of -f or -U to set " 430 "the timestamp"); 431 error = 1; 432 } else if ((format_string != NULL) 433 && (strcmp(format_string, MIL) == 0)) { 434 warnx("-m is not allowed for setting the " 435 "timestamp"); 436 error = 1; 437 } else if ((do_utc == 0) 438 && (timestamp_string == NULL)) { 439 warnx("Must specify the time (-T) to set as the " 440 "timestamp"); 441 error = 1; 442 } 443 if (error != 0) 444 goto bailout; 445 } else if (action == TIMESTAMP_REPORT) { 446 if (format_string == NULL) { 447 format_string = strdup("%c %Z"); 448 if (format_string == NULL) { 449 warn("Error allocating memory for format " 450 "string"); 451 error = 1; 452 goto bailout; 453 } 454 } 455 } 456 457 if (action == TIMESTAMP_REPORT) { 458 error = report_timestamp(device, &ts, task_attr, retry_count, 459 timeout); 460 if (error != 0) { 461 goto bailout; 462 } else if (strcmp(format_string, MIL) == 0) { 463 printf("Timestamp in milliseconds: %ju\n", 464 (uintmax_t)ts); 465 } else { 466 char temp_timestamp_string[100]; 467 time_t time_var = ts / 1000; 468 const struct tm *restrict cur_time; 469 470 setlocale(LC_TIME, ""); 471 if (do_utc != 0) 472 cur_time = gmtime(&time_var); 473 else 474 cur_time = localtime(&time_var); 475 476 strftime(temp_timestamp_string, 477 sizeof(temp_timestamp_string), format_string, 478 cur_time); 479 printf("Formatted timestamp: %s\n", 480 temp_timestamp_string); 481 } 482 } else if (action == TIMESTAMP_SET) { 483 if (do_utc != 0) { 484 format_string = strdup(UTC); 485 if (format_string == NULL) { 486 warn("Error allocating memory for format " 487 "string"); 488 error = 1; 489 goto bailout; 490 } 491 } 492 493 error = set_timestamp(device, format_string, timestamp_string, 494 task_attr, retry_count, timeout); 495 } 496 497 bailout: 498 free(format_string); 499 free(timestamp_string); 500 501 return (error); 502 } 503