xref: /illumos-gate/usr/src/cmd/ndmpd/ndmp/ndmpd_scsi.c (revision 46b592853d0f4f11781b6b0a7533f267c6aee132)
1 /*
2  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 /*
7  * BSD 3 Clause License
8  *
9  * Copyright (c) 2007, The Storage Networking Industry Association.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 	- Redistributions of source code must retain the above copyright
15  *	  notice, this list of conditions and the following disclaimer.
16  *
17  * 	- Redistributions in binary form must reproduce the above copyright
18  *	  notice, this list of conditions and the following disclaimer in
19  *	  the documentation and/or other materials provided with the
20  *	  distribution.
21  *
22  *	- Neither the name of The Storage Networking Industry Association (SNIA)
23  *	  nor the names of its contributors may be used to endorse or promote
24  *	  products derived from this software without specific prior written
25  *	  permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
28  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
31  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGE.
38  */
39 /* Copyright (c) 2007, The Storage Networking Industry Association. */
40 /* Copyright (c) 1996, 1997 PDC, Network Appliance. All Rights Reserved */
41 
42 #include <sys/types.h>
43 #include <ctype.h>
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <stdlib.h>
47 #include "ndmpd_common.h"
48 #include "ndmpd.h"
49 #include <string.h>
50 #include <sys/scsi/impl/uscsi.h>
51 #include <sys/scsi/scsi.h>
52 
53 static void scsi_open_send_reply(ndmp_connection_t *connection, int err);
54 static void common_open(ndmp_connection_t *connection, char *devname);
55 static void common_set_target(ndmp_connection_t *connection, char *device,
56     ushort_t controller, ushort_t sid, ushort_t lun);
57 
58 
59 /*
60  * ************************************************************************
61  * NDMP V2 HANDLERS
62  * ************************************************************************
63  */
64 
65 /*
66  * ndmpd_scsi_open_v2
67  *
68  * This handler opens the specified SCSI device.
69  *
70  * Parameters:
71  *   connection (input) - connection handle.
72  *   body       (input) - request message body.
73  *
74  * Returns:
75  *   void
76  */
77 void
78 ndmpd_scsi_open_v2(ndmp_connection_t *connection, void *body)
79 {
80 	ndmp_scsi_open_request_v2 *request = (ndmp_scsi_open_request_v2 *)body;
81 
82 	common_open(connection, request->device.name);
83 }
84 
85 
86 /*
87  * ndmpd_scsi_close_v2
88  *
89  * This handler closes the currently open SCSI device.
90  *
91  * Parameters:
92  *   connection (input) - connection handle.
93  *   body       (input) - request message body.
94  *
95  * Returns:
96  *   void
97  */
98 /*ARGSUSED*/
99 void
100 ndmpd_scsi_close_v2(ndmp_connection_t *connection, void *body)
101 {
102 	ndmp_scsi_close_reply reply;
103 	ndmpd_session_t *session = ndmp_get_client_data(connection);
104 
105 	if (session->ns_scsi.sd_is_open == -1) {
106 		NDMP_LOG(LOG_ERR, "SCSI device is not open.");
107 		reply.error = NDMP_DEV_NOT_OPEN_ERR;
108 		ndmp_send_reply(connection, (void *) &reply,
109 		    "sending scsi_close reply");
110 		return;
111 	}
112 	(void) ndmp_open_list_del(session->ns_scsi.sd_adapter_name,
113 	    session->ns_scsi.sd_sid,
114 	    session->ns_scsi.sd_lun);
115 	(void) close(session->ns_scsi.sd_devid);
116 
117 	session->ns_scsi.sd_is_open = -1;
118 	session->ns_scsi.sd_devid = -1;
119 	session->ns_scsi.sd_sid = 0;
120 	session->ns_scsi.sd_lun = 0;
121 	session->ns_scsi.sd_valid_target_set = FALSE;
122 	(void) memset(session->ns_scsi.sd_adapter_name, 0,
123 	    sizeof (session->ns_scsi.sd_adapter_name));
124 
125 	reply.error = NDMP_NO_ERR;
126 	ndmp_send_reply(connection, (void *) &reply,
127 	    "sending scsi_close reply");
128 }
129 
130 
131 /*
132  * ndmpd_scsi_get_state_v2
133  *
134  * This handler returns state information for the currently open SCSI device.
135  * Since the implementation only supports the opening of a specific SCSI
136  * device, as opposed to a device that can talk to multiple SCSI targets,
137  * this request is not supported. This request is only appropriate for
138  * implementations that support device files that can target multiple
139  * SCSI devices.
140  *
141  * Parameters:
142  *   connection (input) - connection handle.
143  *   body       (input) - request message body.
144  *
145  * Returns:
146  *   void
147  */
148 /*ARGSUSED*/
149 void
150 ndmpd_scsi_get_state_v2(ndmp_connection_t *connection, void *body)
151 {
152 	ndmp_scsi_get_state_reply reply;
153 	ndmpd_session_t *session = ndmp_get_client_data(connection);
154 
155 	if (session->ns_scsi.sd_is_open == -1)
156 		reply.error = NDMP_DEV_NOT_OPEN_ERR;
157 	else if (!session->ns_scsi.sd_valid_target_set) {
158 		reply.error = NDMP_NO_ERR;
159 		reply.target_controller = -1;
160 		reply.target_id = -1;
161 		reply.target_lun = -1;
162 	} else {
163 		reply.error = NDMP_NO_ERR;
164 		reply.target_controller = 0;
165 		reply.target_id = session->ns_scsi.sd_sid;
166 		reply.target_lun = session->ns_scsi.sd_lun;
167 	}
168 
169 	ndmp_send_reply(connection, (void *) &reply,
170 	    "sending scsi_get_state reply");
171 }
172 
173 
174 /*
175  * ndmpd_scsi_set_target_v2
176  *
177  * This handler sets the SCSI target of the SCSI device.
178  * It is only valid to use this request if the opened SCSI device
179  * is capable of talking to multiple SCSI targets.
180  * Since the implementation only supports the opening of a specific SCSI
181  * device, as opposed to a device that can talk to multiple SCSI targets,
182  * this request is not supported. This request is only appropriate for
183  * implementations that support device files that can target multiple
184  * SCSI devices.
185  *
186  * Parameters:
187  *   connection (input) - connection handle.
188  *   body       (input) - request message body.
189  *
190  * Returns:
191  *   void
192  */
193 void
194 ndmpd_scsi_set_target_v2(ndmp_connection_t *connection, void *body)
195 {
196 	ndmp_scsi_set_target_request_v2 *request;
197 
198 	request = (ndmp_scsi_set_target_request_v2 *) body;
199 
200 	common_set_target(connection, request->device.name,
201 	    request->target_controller, request->target_id,
202 	    request->target_lun);
203 }
204 
205 
206 /*
207  * ndmpd_scsi_reset_device_v2
208  *
209  * This handler resets the currently targeted SCSI device.
210  *
211  * Parameters:
212  *   connection (input) - connection handle.
213  *   body       (input) - request message body.
214  *
215  * Returns:
216  *   void
217  */
218 /*ARGSUSED*/
219 void
220 ndmpd_scsi_reset_device_v2(ndmp_connection_t *connection, void *body)
221 {
222 	ndmp_scsi_reset_device_reply reply;
223 
224 
225 	ndmpd_session_t *session = ndmp_get_client_data(connection);
226 	struct uscsi_cmd  cmd;
227 
228 	if (session->ns_scsi.sd_devid == -1) {
229 		NDMP_LOG(LOG_ERR, "SCSI device is not open.");
230 		reply.error = NDMP_DEV_NOT_OPEN_ERR;
231 	} else {
232 		reply.error = NDMP_NO_ERR;
233 		(void) memset((void*)&cmd, 0, sizeof (cmd));
234 		cmd.uscsi_flags |= USCSI_RESET;
235 		if (ioctl(session->ns_scsi.sd_devid, USCSICMD, &cmd) < 0) {
236 			NDMP_LOG(LOG_ERR, "USCSI reset failed: %m.");
237 			NDMP_LOG(LOG_DEBUG,
238 			    "ioctl(USCSICMD) USCSI_RESET failed: %m.");
239 			reply.error = NDMP_IO_ERR;
240 		}
241 	}
242 
243 	ndmp_send_reply(connection, (void *) &reply,
244 	    "sending scsi_reset_device reply");
245 }
246 
247 
248 /*
249  * ndmpd_scsi_reset_bus_v2
250  *
251  * This handler resets the currently targeted SCSI bus.
252  *
253  * Request not yet supported.
254  *
255  * Parameters:
256  *   connection (input) - connection handle.
257  *   body       (input) - request message body.
258  *
259  * Returns:
260  *   void
261  */
262 /*ARGSUSED*/
263 void
264 ndmpd_scsi_reset_bus_v2(ndmp_connection_t *connection, void *body)
265 {
266 	ndmp_scsi_reset_bus_reply reply;
267 
268 	NDMP_LOG(LOG_DEBUG, "request not supported");
269 	reply.error = NDMP_NOT_SUPPORTED_ERR;
270 
271 	ndmp_send_reply(connection, (void *) &reply,
272 	    "sending scsi_reset_bus reply");
273 }
274 
275 
276 /*
277  * ndmpd_scsi_execute_cdb_v2
278  *
279  * This handler sends the CDB to the currently targeted SCSI device.
280  *
281  * Parameters:
282  *   connection (input) - connection handle.
283  *   body       (input) - request message body.
284  *
285  * Returns:
286  *   void
287  */
288 void
289 ndmpd_scsi_execute_cdb_v2(ndmp_connection_t *connection, void *body)
290 {
291 	ndmp_execute_cdb_request *request = (ndmp_execute_cdb_request *) body;
292 	ndmp_execute_cdb_reply reply;
293 	ndmpd_session_t *session = ndmp_get_client_data(connection);
294 
295 	if (session->ns_scsi.sd_is_open == -1 ||
296 	    !session->ns_scsi.sd_valid_target_set) {
297 		(void) memset((void *) &reply, 0, sizeof (reply));
298 
299 		NDMP_LOG(LOG_ERR, "SCSI device is not open.");
300 		reply.error = NDMP_DEV_NOT_OPEN_ERR;
301 		ndmp_send_reply(connection, (void *) &reply,
302 		    "sending scsi_execute_cdb reply");
303 	} else {
304 		ndmp_execute_cdb(session, session->ns_scsi.sd_adapter_name,
305 		    session->ns_scsi.sd_sid, session->ns_scsi.sd_lun, request);
306 	}
307 }
308 
309 
310 /*
311  * ************************************************************************
312  * NDMP V3 HANDLERS
313  * ************************************************************************
314  */
315 
316 /*
317  * ndmpd_scsi_open_v3
318  *
319  * This handler opens the specified SCSI device.
320  *
321  * Parameters:
322  *   connection (input) - connection handle.
323  *   body       (input) - request message body.
324  *
325  * Returns:
326  *   void
327  */
328 void
329 ndmpd_scsi_open_v3(ndmp_connection_t *connection, void *body)
330 {
331 	ndmp_scsi_open_request_v3 *request = (ndmp_scsi_open_request_v3 *)body;
332 
333 	common_open(connection, request->device);
334 }
335 
336 
337 /*
338  * ndmpd_scsi_set_target_v3
339  *
340  * This handler sets the SCSI target of the SCSI device.
341  * It is only valid to use this request if the opened SCSI device
342  * is capable of talking to multiple SCSI targets.
343  *
344  * Parameters:
345  *   connection (input) - connection handle.
346  *   body       (input) - request message body.
347  *
348  * Returns:
349  *   void
350  */
351 void
352 ndmpd_scsi_set_target_v3(ndmp_connection_t *connection, void *body)
353 {
354 	ndmp_scsi_set_target_request_v3 *request;
355 
356 	request = (ndmp_scsi_set_target_request_v3 *) body;
357 
358 	common_set_target(connection, request->device,
359 	    request->target_controller, request->target_id,
360 	    request->target_lun);
361 }
362 
363 
364 /*
365  * ************************************************************************
366  * NDMP V4 HANDLERS
367  * ************************************************************************
368  */
369 
370 /*
371  * ************************************************************************
372  * LOCALS
373  * ************************************************************************
374  */
375 
376 
377 /*
378  * scsi_open_send_reply
379  *
380  * Send a reply for SCSI open command
381  *
382  * Parameters:
383  *   connection (input) - connection handle.
384  *   err        (input) - ndmp error code
385  *
386  * Returns:
387  *   void
388  */
389 static void
390 scsi_open_send_reply(ndmp_connection_t *connection, int err)
391 {
392 	ndmp_scsi_open_reply reply;
393 
394 	reply.error = err;
395 	ndmp_send_reply(connection, (void *) &reply, "sending scsi_open reply");
396 }
397 
398 
399 /*
400  * common_open
401  *
402  * Common SCSI open function for all NDMP versions
403  *
404  * Parameters:
405  *   connection (input) - connection handle.
406  *   devname (input) - device name to open.
407  *
408  * Returns:
409  *   void
410  */
411 static void
412 common_open(ndmp_connection_t *connection, char *devname)
413 {
414 	ndmpd_session_t *session = ndmp_get_client_data(connection);
415 	char adptnm[SCSI_MAX_NAME];
416 	int sid, lun;
417 	int err;
418 	scsi_adapter_t *sa;
419 	int devid;
420 
421 	err = NDMP_NO_ERR;
422 
423 	if (session->ns_tape.td_fd != -1 || session->ns_scsi.sd_is_open != -1) {
424 		NDMP_LOG(LOG_ERR,
425 		    "Session already has a tape or scsi device open.");
426 		err = NDMP_DEVICE_OPENED_ERR;
427 	} else if ((sa = scsi_get_adapter(0)) != NULL) {
428 		NDMP_LOG(LOG_DEBUG, "Adapter device found: %s", devname);
429 		(void) strlcpy(adptnm, devname, SCSI_MAX_NAME-2);
430 		adptnm[SCSI_MAX_NAME-1] = '\0';
431 		sid = lun = -1;
432 
433 		scsi_find_sid_lun(sa, devname, &sid, &lun);
434 		if (ndmp_open_list_find(devname, sid, lun) == NULL &&
435 		    (devid = open(devname, O_RDWR | O_NDELAY)) < 0) {
436 			NDMP_LOG(LOG_ERR, "Failed to open device %s: %m.",
437 			    devname);
438 			err = NDMP_NO_DEVICE_ERR;
439 		}
440 	} else {
441 		NDMP_LOG(LOG_ERR, "%s: No such SCSI adapter.", devname);
442 		err = NDMP_NO_DEVICE_ERR;
443 	}
444 
445 	if (err != NDMP_NO_ERR) {
446 		scsi_open_send_reply(connection, err);
447 		return;
448 	}
449 
450 	switch (ndmp_open_list_add(connection, adptnm, sid, lun, devid)) {
451 	case 0:
452 		/* OK */
453 		break;
454 	case EBUSY:
455 		err = NDMP_DEVICE_BUSY_ERR;
456 		break;
457 	case ENOMEM:
458 		err = NDMP_NO_MEM_ERR;
459 		break;
460 	default:
461 		err = NDMP_IO_ERR;
462 	}
463 	if (err != NDMP_NO_ERR) {
464 		scsi_open_send_reply(connection, err);
465 		return;
466 	}
467 
468 	(void) strlcpy(session->ns_scsi.sd_adapter_name, adptnm, SCSI_MAX_NAME);
469 	session->ns_scsi.sd_is_open = 1;
470 	session->ns_scsi.sd_devid = devid;
471 	if (sid != -1) {
472 		session->ns_scsi.sd_sid = sid;
473 		session->ns_scsi.sd_lun = lun;
474 		session->ns_scsi.sd_valid_target_set = TRUE;
475 	} else {
476 		session->ns_scsi.sd_sid = session->ns_scsi.sd_lun = -1;
477 		session->ns_scsi.sd_valid_target_set = FALSE;
478 	}
479 
480 	scsi_open_send_reply(connection, err);
481 }
482 
483 
484 /*
485  * common_set_target
486  *
487  * Set the SCSI target (SCSI number, LUN number, controller number)
488  *
489  * Parameters:
490  *   connection (input) - connection handle.
491  *   device (input) - device name.
492  *   controller (input) - controller number.
493  *   sid (input) - SCSI target ID.
494  *   lun (input) - LUN number.
495  *
496  * Returns:
497  *   0: on success
498  *  -1: otherwise
499  */
500 /*ARGSUSED*/
501 static void
502 common_set_target(ndmp_connection_t *connection, char *device,
503     ushort_t controller, ushort_t sid, ushort_t lun)
504 {
505 	ndmp_scsi_set_target_reply reply;
506 	ndmpd_session_t *session = ndmp_get_client_data(connection);
507 	int type;
508 
509 	reply.error = NDMP_NO_ERR;
510 
511 	if (session->ns_scsi.sd_is_open == -1) {
512 		reply.error = NDMP_DEV_NOT_OPEN_ERR;
513 	} else if (!scsi_dev_exists(session->ns_scsi.sd_adapter_name, sid,
514 	    lun)) {
515 		NDMP_LOG(LOG_ERR, "No such SCSI device: target %d lun %d.",
516 		    sid, lun);
517 		reply.error = NDMP_NO_DEVICE_ERR;
518 	} else {
519 		type = scsi_get_devtype(session->ns_scsi.sd_adapter_name, sid,
520 		    lun);
521 		if (type != DTYPE_SEQUENTIAL && type != DTYPE_CHANGER) {
522 			NDMP_LOG(LOG_ERR,
523 			    "Not a tape or robot device: target %d lun %d.",
524 			    sid, lun);
525 			reply.error = NDMP_ILLEGAL_ARGS_ERR;
526 		}
527 	}
528 
529 	if (reply.error != NDMP_NO_ERR) {
530 		ndmp_send_reply(connection, (void *) &reply,
531 		    "sending scsi_set_target reply");
532 		return;
533 	}
534 
535 	/*
536 	 * The open_list must be updated if the SID or LUN are going to be
537 	 * changed.  Close uses the same SID & LUN for removing the entry
538 	 * from the open_list.
539 	 */
540 	if (sid != session->ns_scsi.sd_sid || lun != session->ns_scsi.sd_lun) {
541 		switch (ndmp_open_list_add(connection,
542 		    session->ns_scsi.sd_adapter_name, sid, lun, 0)) {
543 		case 0:
544 			(void) ndmp_open_list_del(session->
545 			    ns_scsi.sd_adapter_name, session->ns_scsi.sd_sid,
546 			    session->ns_scsi.sd_lun);
547 			break;
548 		case EBUSY:
549 			reply.error = NDMP_DEVICE_BUSY_ERR;
550 			break;
551 		case ENOMEM:
552 			reply.error = NDMP_NO_MEM_ERR;
553 			break;
554 		default:
555 			reply.error = NDMP_IO_ERR;
556 		}
557 	}
558 
559 	if (reply.error == NDMP_NO_ERR) {
560 		NDMP_LOG(LOG_DEBUG, "Updated sid %d lun %d", sid, lun);
561 		session->ns_scsi.sd_sid = sid;
562 		session->ns_scsi.sd_lun = lun;
563 		session->ns_scsi.sd_valid_target_set = TRUE;
564 	}
565 
566 	ndmp_send_reply(connection, (void *) &reply,
567 	    "sending scsi_set_target reply");
568 }
569 
570 /*
571  * scsi_find_sid_lun
572  *
573  * gets the adapter, and returns the sid and lun number
574  */
575 void
576 scsi_find_sid_lun(scsi_adapter_t *sa, char *devname, int *sid, int *lun)
577 {
578 	scsi_link_t *sl;
579 	char *name;
580 
581 	for (sl = sa->sa_link_head.sl_next; sl && sl != &sa->sa_link_head;
582 	    sl = sl->sl_next) {
583 		name = sasd_slink_name(sl);
584 		if (strcmp(devname, name) == 0) {
585 			*sid = sl->sl_sid;
586 			*lun = sl->sl_lun;
587 			return;
588 		}
589 	}
590 
591 	*sid = -1;
592 	*lun = -1;
593 }
594