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
62static int set_restore_flags(struct cam_device *device, uint8_t *flags,
63			     int set_flag, int task_attr, int retry_count,
64			     int timeout);
65static int report_timestamp(struct cam_device *device, uint64_t *ts,
66			    int task_attr, int retry_count, int timeout);
67static int set_timestamp(struct cam_device *device, char *format_string,
68			 char *timestamp_string, int task_attr, int retry_count,
69			 int timeout);
70
71static int
72set_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
177bailout:
178	if (ccb != NULL)
179		cam_freeccb(ccb);
180
181	return error;
182}
183
184static int
185report_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
234bailout:
235	if (ccb != NULL)
236		cam_freeccb(ccb);
237	free(report_buf);
238
239	return error;
240}
241
242static int
243set_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
317bailout:
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
327int
328timestamp(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
493bailout:
494	free(format_string);
495	free(timestamp_string);
496
497	return (error);
498}
499