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