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