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