99713fcaad
MFC after: 2 weeks
502 lines
13 KiB
C
502 lines
13 KiB
C
/*-
|
|
* Copyright (c) 2016 Spectra Logic Corporation
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions, and the following disclaimer,
|
|
* without modification.
|
|
* 2. Redistributions in binary form must reproduce at minimum a disclaimer
|
|
* substantially similar to the "NO WARRANTY" disclaimer below
|
|
* ("Disclaimer") and any redistribution must be conditioned upon
|
|
* including a substantially similar Disclaimer requirement for further
|
|
* binary redistribution.
|
|
*
|
|
* NO WARRANTY
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGES.
|
|
*
|
|
* Authors: Ken Merry (Spectra Logic Corporation)
|
|
* Reid Linnemann (Spectra Logic Corporation)
|
|
* Samuel Klopsch (Spectra Logic Corporation)
|
|
*/
|
|
/*
|
|
* SCSI tape drive timestamp support
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <err.h>
|
|
#include <time.h>
|
|
#include <locale.h>
|
|
|
|
#include <cam/cam.h>
|
|
#include <cam/cam_debug.h>
|
|
#include <cam/cam_ccb.h>
|
|
#include <cam/scsi/scsi_all.h>
|
|
#include <cam/scsi/scsi_message.h>
|
|
#include <camlib.h>
|
|
#include "camcontrol.h"
|
|
|
|
#define TIMESTAMP_REPORT 0
|
|
#define TIMESTAMP_SET 1
|
|
#define MIL "milliseconds"
|
|
#define UTC "utc"
|
|
|
|
static int set_restore_flags(struct cam_device *device, uint8_t *flags,
|
|
int set_flag, int task_attr, int retry_count,
|
|
int timeout);
|
|
static int report_timestamp(struct cam_device *device, uint64_t *ts,
|
|
int task_attr, int retry_count, int timeout);
|
|
static int set_timestamp(struct cam_device *device, char *format_string,
|
|
char *timestamp_string, int task_attr, int retry_count,
|
|
int timeout);
|
|
|
|
static int
|
|
set_restore_flags(struct cam_device *device, uint8_t *flags, int set_flag,
|
|
int task_attr, int retry_count, int timeout)
|
|
{
|
|
unsigned long blk_desc_length, hdr_and_blk_length;
|
|
int error = 0;
|
|
struct scsi_control_ext_page *control_page = NULL;
|
|
struct scsi_mode_header_10 *mode_hdr = NULL;
|
|
union ccb *ccb = NULL;
|
|
unsigned long mode_buf_size = sizeof(struct scsi_mode_header_10) +
|
|
sizeof(struct scsi_mode_blk_desc) +
|
|
sizeof(struct scsi_control_ext_page);
|
|
uint8_t mode_buf[mode_buf_size];
|
|
|
|
ccb = cam_getccb(device);
|
|
if (ccb == NULL) {
|
|
warnx("%s: error allocating CCB", __func__);
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
/*
|
|
* Get the control extension subpage, we'll send it back modified to
|
|
* enable SCSI control over the tape drive's timestamp
|
|
*/
|
|
scsi_mode_sense_subpage(&ccb->csio,
|
|
/*retries*/ retry_count,
|
|
/*cbfcnp*/ NULL,
|
|
/*tag_action*/ task_attr,
|
|
/*dbd*/ 0,
|
|
/*page_control*/ SMS_PAGE_CTRL_CURRENT,
|
|
/*page*/ SCEP_PAGE_CODE,
|
|
/*subpage*/ SCEP_SUBPAGE_CODE,
|
|
/*param_buf*/ &mode_buf[0],
|
|
/*param_len*/ mode_buf_size,
|
|
/*minimum_cmd_size*/ 10,
|
|
/*sense_len*/ SSD_FULL_SIZE,
|
|
/*timeout*/ timeout ? timeout : 5000);
|
|
|
|
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
|
|
if (retry_count > 0)
|
|
ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
|
|
|
|
error = cam_send_ccb(device, ccb);
|
|
if (error != 0) {
|
|
warn("error sending Mode Sense");
|
|
goto bailout;
|
|
}
|
|
|
|
if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
|
|
cam_error_print(device, ccb, CAM_ESF_ALL,
|
|
CAM_EPF_ALL, stderr);
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
|
|
mode_hdr = (struct scsi_mode_header_10 *)&mode_buf[0];
|
|
blk_desc_length = scsi_2btoul(mode_hdr->blk_desc_len);
|
|
hdr_and_blk_length = sizeof(struct scsi_mode_header_10)+blk_desc_length;
|
|
/*
|
|
* Create the control page at the correct point in the mode_buf, it
|
|
* starts after the header and the blk description.
|
|
*/
|
|
assert(hdr_and_blk_length <=
|
|
sizeof(mode_buf) - sizeof(struct scsi_control_ext_page));
|
|
control_page = (struct scsi_control_ext_page *)&mode_buf
|
|
[hdr_and_blk_length];
|
|
if (set_flag != 0) {
|
|
*flags = control_page->flags;
|
|
/*
|
|
* Set the SCSIP flag to enable SCSI to change the
|
|
* tape drive's timestamp.
|
|
*/
|
|
control_page->flags |= SCEP_SCSIP;
|
|
} else {
|
|
control_page->flags = *flags;
|
|
}
|
|
|
|
scsi_mode_select_len(&ccb->csio,
|
|
/*retries*/ retry_count,
|
|
/*cbfcnp*/ NULL,
|
|
/*tag_action*/ task_attr,
|
|
/*scsi_page_fmt*/ 1,
|
|
/*save_pages*/ 0,
|
|
/*param_buf*/ &mode_buf[0],
|
|
/*param_len*/ mode_buf_size,
|
|
/*minimum_cmd_size*/ 10,
|
|
/*sense_len*/ SSD_FULL_SIZE,
|
|
/*timeout*/ timeout ? timeout : 5000);
|
|
|
|
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
|
|
if (retry_count > 0)
|
|
ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
|
|
|
|
error = cam_send_ccb(device, ccb);
|
|
if (error != 0) {
|
|
warn("error sending Mode Select");
|
|
goto bailout;
|
|
}
|
|
|
|
if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
|
|
cam_error_print(device, ccb, CAM_ESF_ALL,
|
|
CAM_EPF_ALL, stderr);
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
|
|
bailout:
|
|
if (ccb != NULL)
|
|
cam_freeccb(ccb);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
report_timestamp(struct cam_device *device, uint64_t *ts, int task_attr,
|
|
int retry_count, int timeout)
|
|
{
|
|
int error = 0;
|
|
struct scsi_report_timestamp_data *report_buf = malloc(
|
|
sizeof(struct scsi_report_timestamp_data));
|
|
uint8_t temp_timestamp[8];
|
|
uint32_t report_buf_size = sizeof(
|
|
struct scsi_report_timestamp_data);
|
|
union ccb *ccb = NULL;
|
|
|
|
ccb = cam_getccb(device);
|
|
if (ccb == NULL) {
|
|
warnx("%s: error allocating CCB", __func__);
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
|
|
scsi_report_timestamp(&ccb->csio,
|
|
/*retries*/ retry_count,
|
|
/*cbfcnp*/ NULL,
|
|
/*tag_action*/ task_attr,
|
|
/*pdf*/ 0,
|
|
/*buf*/ report_buf,
|
|
/*buf_len*/ report_buf_size,
|
|
/*sense_len*/ SSD_FULL_SIZE,
|
|
/*timeout*/ timeout ? timeout : 5000);
|
|
|
|
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
|
|
if (retry_count > 0)
|
|
ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
|
|
|
|
error = cam_send_ccb(device, ccb);
|
|
if (error != 0) {
|
|
warn("error sending Report Timestamp");
|
|
goto bailout;
|
|
}
|
|
if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
|
|
cam_error_print(device, ccb, CAM_ESF_ALL,
|
|
CAM_EPF_ALL, stderr);
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
|
|
bzero(temp_timestamp, sizeof(temp_timestamp));
|
|
memcpy(&temp_timestamp[2], &report_buf->timestamp, 6);
|
|
|
|
*ts = scsi_8btou64(temp_timestamp);
|
|
|
|
bailout:
|
|
if (ccb != NULL)
|
|
cam_freeccb(ccb);
|
|
free(report_buf);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
set_timestamp(struct cam_device *device, char *format_string,
|
|
char *timestamp_string, int task_attr, int retry_count,
|
|
int timeout)
|
|
{
|
|
struct scsi_set_timestamp_parameters ts_p;
|
|
time_t time_value;
|
|
struct tm time_struct;
|
|
uint8_t flags = 0;
|
|
int error = 0;
|
|
uint64_t ts = 0;
|
|
union ccb *ccb = NULL;
|
|
int do_restore_flags = 0;
|
|
|
|
error = set_restore_flags(device, &flags, /*set_flag*/ 1, task_attr,
|
|
retry_count, timeout);
|
|
if (error != 0)
|
|
goto bailout;
|
|
|
|
do_restore_flags = 1;
|
|
|
|
ccb = cam_getccb(device);
|
|
if (ccb == NULL) {
|
|
warnx("%s: error allocating CCB", __func__);
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
|
|
if (strcmp(format_string, UTC) == 0) {
|
|
time(&time_value);
|
|
ts = (uint64_t) time_value;
|
|
} else {
|
|
bzero(&time_struct, sizeof(struct tm));
|
|
if (strptime(timestamp_string, format_string,
|
|
&time_struct) == NULL) {
|
|
warnx("%s: strptime(3) failed", __func__);
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
time_value = mktime(&time_struct);
|
|
ts = (uint64_t) time_value;
|
|
}
|
|
/* Convert time from seconds to milliseconds */
|
|
ts *= 1000;
|
|
bzero(&ts_p, sizeof(ts_p));
|
|
scsi_create_timestamp(ts_p.timestamp, ts);
|
|
|
|
scsi_set_timestamp(&ccb->csio,
|
|
/*retries*/ retry_count,
|
|
/*cbfcnp*/ NULL,
|
|
/*tag_action*/ task_attr,
|
|
/*buf*/ &ts_p,
|
|
/*buf_len*/ sizeof(ts_p),
|
|
/*sense_len*/ SSD_FULL_SIZE,
|
|
/*timeout*/ timeout ? timeout : 5000);
|
|
|
|
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
|
|
if (retry_count > 0)
|
|
ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
|
|
|
|
error = cam_send_ccb(device, ccb);
|
|
if (error != 0) {
|
|
warn("error sending Set Timestamp");
|
|
goto bailout;
|
|
}
|
|
|
|
if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
|
|
cam_error_print(device, ccb, CAM_ESF_ALL,
|
|
CAM_EPF_ALL, stderr);
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
|
|
printf("Timestamp set to %ju\n", (uintmax_t)ts);
|
|
|
|
bailout:
|
|
if (do_restore_flags != 0)
|
|
error = set_restore_flags(device, &flags, /*set_flag*/ 0,
|
|
task_attr, retry_count, timeout);
|
|
if (ccb != NULL)
|
|
cam_freeccb(ccb);
|
|
|
|
return error;
|
|
}
|
|
|
|
int
|
|
timestamp(struct cam_device *device, int argc, char **argv, char *combinedopt,
|
|
int task_attr, int retry_count, int timeout, int verbosemode __unused)
|
|
{
|
|
int c;
|
|
uint64_t ts = 0;
|
|
char *format_string = NULL;
|
|
char *timestamp_string = NULL;
|
|
int action = -1;
|
|
int error = 0;
|
|
int single_arg = 0;
|
|
int do_utc = 0;
|
|
|
|
while ((c = getopt(argc, argv, combinedopt)) != -1) {
|
|
switch (c) {
|
|
case 'r': {
|
|
if (action != -1) {
|
|
warnx("Use only one -r or only one -s");
|
|
error =1;
|
|
goto bailout;
|
|
}
|
|
action = TIMESTAMP_REPORT;
|
|
break;
|
|
}
|
|
case 's': {
|
|
if (action != -1) {
|
|
warnx("Use only one -r or only one -s");
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
action = TIMESTAMP_SET;
|
|
break;
|
|
}
|
|
case 'f': {
|
|
single_arg++;
|
|
free(format_string);
|
|
format_string = strdup(optarg);
|
|
if (format_string == NULL) {
|
|
warn("Error allocating memory for format "
|
|
"argument");
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
break;
|
|
}
|
|
case 'm': {
|
|
single_arg++;
|
|
free(format_string);
|
|
format_string = strdup(MIL);
|
|
if (format_string == NULL) {
|
|
warn("Error allocating memory");
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
break;
|
|
}
|
|
case 'U': {
|
|
do_utc = 1;
|
|
break;
|
|
}
|
|
case 'T':
|
|
free(timestamp_string);
|
|
timestamp_string = strdup(optarg);
|
|
if (timestamp_string == NULL) {
|
|
warn("Error allocating memory for format "
|
|
"argument");
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (action == -1) {
|
|
warnx("Must specify an action, either -r or -s");
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
|
|
if (single_arg > 1) {
|
|
warnx("Select only one: %s",
|
|
(action == TIMESTAMP_REPORT) ?
|
|
"-f format or -m for the -r flag" :
|
|
"-f format -T time or -U for the -s flag");
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
|
|
if (action == TIMESTAMP_SET) {
|
|
if ((format_string == NULL)
|
|
&& (do_utc == 0)) {
|
|
warnx("Must specify either -f format or -U for "
|
|
"setting the timestamp");
|
|
error = 1;
|
|
} else if ((format_string != NULL)
|
|
&& (do_utc != 0)) {
|
|
warnx("Must specify only one of -f or -U to set "
|
|
"the timestamp");
|
|
error = 1;
|
|
} else if ((format_string != NULL)
|
|
&& (strcmp(format_string, MIL) == 0)) {
|
|
warnx("-m is not allowed for setting the "
|
|
"timestamp");
|
|
error = 1;
|
|
} else if ((do_utc == 0)
|
|
&& (timestamp_string == NULL)) {
|
|
warnx("Must specify the time (-T) to set as the "
|
|
"timestamp");
|
|
error = 1;
|
|
}
|
|
if (error != 0)
|
|
goto bailout;
|
|
} else if (action == TIMESTAMP_REPORT) {
|
|
if (format_string == NULL) {
|
|
format_string = strdup("%c %Z");
|
|
if (format_string == NULL) {
|
|
warn("Error allocating memory for format "
|
|
"string");
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (action == TIMESTAMP_REPORT) {
|
|
error = report_timestamp(device, &ts, task_attr, retry_count,
|
|
timeout);
|
|
if (error != 0) {
|
|
goto bailout;
|
|
} else if (strcmp(format_string, MIL) == 0) {
|
|
printf("Timestamp in milliseconds: %ju\n",
|
|
(uintmax_t)ts);
|
|
} else {
|
|
char temp_timestamp_string[100];
|
|
time_t time_var = ts / 1000;
|
|
const struct tm *restrict cur_time;
|
|
|
|
setlocale(LC_TIME, "");
|
|
if (do_utc != 0)
|
|
cur_time = gmtime(&time_var);
|
|
else
|
|
cur_time = localtime(&time_var);
|
|
|
|
strftime(temp_timestamp_string,
|
|
sizeof(temp_timestamp_string), format_string,
|
|
cur_time);
|
|
printf("Formatted timestamp: %s\n",
|
|
temp_timestamp_string);
|
|
}
|
|
} else if (action == TIMESTAMP_SET) {
|
|
if (do_utc != 0) {
|
|
format_string = strdup(UTC);
|
|
if (format_string == NULL) {
|
|
warn("Error allocating memory for format "
|
|
"string");
|
|
error = 1;
|
|
goto bailout;
|
|
}
|
|
}
|
|
|
|
error = set_timestamp(device, format_string, timestamp_string,
|
|
task_attr, retry_count, timeout);
|
|
}
|
|
|
|
bailout:
|
|
free(format_string);
|
|
free(timestamp_string);
|
|
|
|
return (error);
|
|
}
|