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