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