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