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