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 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 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 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 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