xref: /freebsd/sbin/camcontrol/timestamp.c (revision a4aa656aa587a4bad15e15bf45e149e176c22293)
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 
65 static int set_restore_flags(struct cam_device *device, uint8_t *flags,
66 			     int set_flag, int retry_count, int timeout);
67 static int report_timestamp(struct cam_device *device, uint64_t *ts,
68 			    int retry_count, int timeout);
69 static int set_timestamp(struct cam_device *device, char *format_string,
70 			 char *timestamp_string,
71 			 int retry_count, int timeout);
72 
73 static int
74 set_restore_flags(struct cam_device *device, uint8_t *flags, int set_flag,
75 		  int retry_count, int timeout)
76 {
77 	unsigned long blk_desc_length, hdr_and_blk_length;
78 	int error = 0;
79 	struct scsi_control_ext_page *control_page = NULL;
80 	struct scsi_mode_header_10 *mode_hdr = NULL;
81 	struct scsi_mode_sense_10 *cdb = 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_len(&ccb->csio,
99 	    /*retries*/ retry_count,
100 	    /*cbfcnp*/ NULL,
101 	    /*tag_action*/ MSG_SIMPLE_Q_TAG,
102 	    /*dbd*/ 0,
103 	    /*page_control*/ SMS_PAGE_CTRL_CURRENT,
104 	    /*page*/ SCEP_PAGE_CODE,
105 	    /*param_buf*/ &mode_buf[0],
106 	    /*param_len*/ mode_buf_size,
107 	    /*minimum_cmd_size*/ 10,
108 	    /*sense_len*/ SSD_FULL_SIZE,
109 	    /*timeout*/ timeout ? timeout : 5000);
110 	/*
111 	 * scsi_mode_sense_len does not have a subpage argument at the moment,
112 	 * so we have to manually set the subpage code before calling
113 	 * cam_send_ccb().
114 	 */
115 	cdb = (struct scsi_mode_sense_10 *)ccb->csio.cdb_io.cdb_bytes;
116 	cdb->subpage = SCEP_SUBPAGE_CODE;
117 
118 	ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
119 	if (retry_count > 0)
120 		ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
121 
122 	error = cam_send_ccb(device, ccb);
123 	if (error != 0) {
124 		warn("error sending Mode Sense");
125 		goto bailout;
126 	}
127 
128 	if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
129 		cam_error_print(device, ccb, CAM_ESF_ALL,
130 				CAM_EPF_ALL, stderr);
131 		error = 1;
132 		goto bailout;
133 	}
134 
135 	mode_hdr = (struct scsi_mode_header_10 *)&mode_buf[0];
136 	blk_desc_length = scsi_2btoul(mode_hdr->blk_desc_len);
137 	hdr_and_blk_length = sizeof(struct scsi_mode_header_10)+blk_desc_length;
138 	/*
139 	 * Create the control page at the correct point in the mode_buf, it
140 	 * starts after the header and the blk description.
141 	 */
142 	assert(hdr_and_blk_length <=
143 	    sizeof(mode_buf) - sizeof(struct scsi_control_ext_page));
144 	control_page = (struct scsi_control_ext_page *)&mode_buf
145 	    [hdr_and_blk_length];
146 	if (set_flag != 0) {
147 		*flags = control_page->flags;
148 		/*
149 		 * Set the SCSIP flag to enable SCSI to change the
150 		 * tape drive's timestamp.
151 		 */
152 		control_page->flags |= SCEP_SCSIP;
153 	} else {
154 		control_page->flags = *flags;
155 	}
156 
157 	scsi_mode_select_len(&ccb->csio,
158 	    /*retries*/ retry_count,
159 	    /*cbfcnp*/ NULL,
160 	    /*tag_action*/ MSG_SIMPLE_Q_TAG,
161 	    /*scsi_page_fmt*/ 1,
162 	    /*save_pages*/ 0,
163 	    /*param_buf*/ &mode_buf[0],
164 	    /*param_len*/ mode_buf_size,
165 	    /*minimum_cmd_size*/ 10,
166 	    /*sense_len*/ SSD_FULL_SIZE,
167 	    /*timeout*/ timeout ? timeout : 5000);
168 
169 	ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
170 	if (retry_count > 0)
171 		ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
172 
173 	error = cam_send_ccb(device, ccb);
174 	if (error != 0) {
175 		warn("error sending Mode Select");
176 		goto bailout;
177 	}
178 
179 	if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
180 		cam_error_print(device, ccb, CAM_ESF_ALL,
181 				CAM_EPF_ALL, stderr);
182 		error = 1;
183 		goto bailout;
184 	}
185 
186 bailout:
187 	if (ccb != NULL)
188 		cam_freeccb(ccb);
189 
190 	return error;
191 }
192 
193 static int
194 report_timestamp(struct cam_device *device, uint64_t *ts,
195 		 int retry_count, int timeout)
196 {
197 	int error = 0;
198 	struct scsi_report_timestamp_data *report_buf = malloc(
199 		sizeof(struct scsi_report_timestamp_data));
200 	uint8_t temp_timestamp[8];
201 	uint32_t report_buf_size = sizeof(
202 	    struct scsi_report_timestamp_data);
203 	union ccb *ccb = NULL;
204 
205 	ccb = cam_getccb(device);
206 	if (ccb == NULL) {
207 		warnx("%s: error allocating CCB", __func__);
208 		error = 1;
209 		goto bailout;
210 	}
211 
212 	scsi_report_timestamp(&ccb->csio,
213 	    /*retries*/ retry_count,
214 	    /*cbfcnp*/ NULL,
215 	    /*tag_action*/ MSG_SIMPLE_Q_TAG,
216 	    /*pdf*/ 0,
217 	    /*buf*/ report_buf,
218 	    /*buf_len*/ report_buf_size,
219 	    /*sense_len*/ SSD_FULL_SIZE,
220 	    /*timeout*/ timeout ? timeout : 5000);
221 
222 	ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
223 	if (retry_count > 0)
224 		ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
225 
226 	error = cam_send_ccb(device, ccb);
227 	if (error != 0) {
228 		warn("error sending Report Timestamp");
229 		goto bailout;
230 	}
231 	if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
232 		cam_error_print(device, ccb, CAM_ESF_ALL,
233 				CAM_EPF_ALL, stderr);
234 		error = 1;
235 		goto bailout;
236 	}
237 
238 	bzero(temp_timestamp, sizeof(temp_timestamp));
239 	memcpy(&temp_timestamp[2], &report_buf->timestamp, 6);
240 
241 	*ts = scsi_8btou64(temp_timestamp);
242 
243 bailout:
244 	if (ccb != NULL)
245 		cam_freeccb(ccb);
246 	free(report_buf);
247 
248 	return error;
249 }
250 
251 static int
252 set_timestamp(struct cam_device *device, char *format_string,
253 	      char *timestamp_string, int retry_count,
254 	      int timeout)
255 {
256 	struct scsi_set_timestamp_parameters ts_p;
257 	time_t time_value;
258 	struct tm time_struct;
259 	uint8_t flags = 0;
260 	int error = 0;
261 	uint64_t ts = 0;
262 	union ccb *ccb = NULL;
263 	int do_restore_flags = 0;
264 
265 	error = set_restore_flags(device, &flags, /*set_flag*/ 1, retry_count,
266 				  timeout);
267 	if (error != 0)
268 		goto bailout;
269 
270 	do_restore_flags = 1;
271 
272 	ccb = cam_getccb(device);
273 	if (ccb == NULL) {
274 		warnx("%s: error allocating CCB", __func__);
275 		error = 1;
276 		goto bailout;
277 	}
278 
279 	if (strcmp(format_string, UTC) == 0) {
280 		time(&time_value);
281 		ts = (uint64_t) time_value;
282 	} else {
283 		bzero(&time_struct, sizeof(struct tm));
284 		strptime(timestamp_string, format_string, &time_struct);
285 		time_value = mktime(&time_struct);
286 		ts = (uint64_t) time_value;
287 	}
288 	/* Convert time from seconds to milliseconds */
289 	ts *= 1000;
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*/ MSG_SIMPLE_Q_TAG,
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 
320 bailout:
321 	if (do_restore_flags != 0)
322 		error = set_restore_flags(device, &flags, /*set_flag*/ 0,
323 					  retry_count, timeout);
324 	if (ccb != NULL)
325 		cam_freeccb(ccb);
326 
327 	return error;
328 }
329 
330 int
331 timestamp(struct cam_device *device, int argc, char **argv, char *combinedopt,
332 	  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, 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 		    retry_count, timeout);
494 	}
495 
496 bailout:
497 	free(format_string);
498 	free(timestamp_string);
499 
500 	return (error);
501 }
502