1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
23  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 #include <sys/types.h>
27 #include <sys/kmem.h>
28 #include <sys/ddi.h>
29 #include <sys/sunddi.h>
30 #include <sys/cmn_err.h>
31 #include <sys/door.h>
32 #include <smbsrv/smb_kproto.h>
33 #include <smbsrv/smb_door.h>
34 
35 static int smb_kdoor_send(smb_server_t *, smb_doorarg_t *);
36 static int smb_kdoor_receive(smb_server_t *, smb_doorarg_t *);
37 static int smb_kdoor_upcall_private(smb_server_t *, smb_doorarg_t *);
38 static int smb_kdoor_encode(smb_doorarg_t *);
39 static int smb_kdoor_decode(smb_doorarg_t *);
40 static void smb_kdoor_sethdr(smb_doorarg_t *, uint32_t);
41 static boolean_t smb_kdoor_chkhdr(smb_doorarg_t *, smb_doorhdr_t *);
42 static void smb_kdoor_free(door_arg_t *);
43 
44 void
45 smb_kdoor_init(smb_server_t *sv)
46 {
47 	sv->sv_kdoor_id = -1;
48 	mutex_init(&sv->sv_kdoor_mutex, NULL, MUTEX_DEFAULT, NULL);
49 	cv_init(&sv->sv_kdoor_cv, NULL, CV_DEFAULT, NULL);
50 }
51 
52 void
53 smb_kdoor_fini(smb_server_t *sv)
54 {
55 	smb_kdoor_close(sv);
56 	cv_destroy(&sv->sv_kdoor_cv);
57 	mutex_destroy(&sv->sv_kdoor_mutex);
58 }
59 
60 /*
61  * Open the door.  If the door is already open, close it first
62  * because the door-id has probably changed.
63  */
64 int
65 smb_kdoor_open(smb_server_t *sv, int door_id)
66 {
67 	int rc;
68 
69 	smb_kdoor_close(sv);
70 
71 	mutex_enter(&sv->sv_kdoor_mutex);
72 	sv->sv_kdoor_ncall = 0;
73 
74 	if (sv->sv_kdoor_hd == NULL) {
75 		sv->sv_kdoor_id = door_id;
76 		sv->sv_kdoor_hd = door_ki_lookup(door_id);
77 	}
78 
79 	rc = (sv->sv_kdoor_hd == NULL)  ? -1 : 0;
80 	mutex_exit(&sv->sv_kdoor_mutex);
81 	return (rc);
82 }
83 
84 /*
85  * Close the door.
86  */
87 void
88 smb_kdoor_close(smb_server_t *sv)
89 {
90 	mutex_enter(&sv->sv_kdoor_mutex);
91 
92 	if (sv->sv_kdoor_hd != NULL) {
93 		while (sv->sv_kdoor_ncall > 0)
94 			cv_wait(&sv->sv_kdoor_cv, &sv->sv_kdoor_mutex);
95 
96 		door_ki_rele(sv->sv_kdoor_hd);
97 		sv->sv_kdoor_hd = NULL;
98 		sv->sv_kdoor_id = -1;
99 	}
100 
101 	mutex_exit(&sv->sv_kdoor_mutex);
102 }
103 
104 /*
105  * Wrapper to handle door call reference counting.
106  */
107 int
108 smb_kdoor_upcall(smb_server_t *sv, uint32_t cmd,
109     void *req_data, xdrproc_t req_xdr,
110     void *rsp_data, xdrproc_t rsp_xdr)
111 {
112 	smb_doorarg_t	da;
113 	int		rc;
114 
115 	bzero(&da, sizeof (smb_doorarg_t));
116 	da.da_opcode = cmd;
117 	da.da_opname = smb_doorhdr_opname(cmd);
118 	da.da_req_xdr = req_xdr;
119 	da.da_rsp_xdr = rsp_xdr;
120 	da.da_req_data = req_data;
121 	da.da_rsp_data = rsp_data;
122 
123 	if ((req_data == NULL && req_xdr != NULL) ||
124 	    (rsp_data == NULL && rsp_xdr != NULL)) {
125 		cmn_err(CE_WARN, "smb_kdoor_upcall[%s]: invalid param",
126 		    da.da_opname);
127 		return (-1);
128 	}
129 
130 	if (rsp_data != NULL && rsp_xdr != NULL)
131 		da.da_flags = SMB_DF_ASYNC;
132 
133 	if ((da.da_event = smb_event_create(sv, SMB_EVENT_TIMEOUT)) == NULL)
134 		return (-1);
135 
136 	mutex_enter(&sv->sv_kdoor_mutex);
137 
138 	if (sv->sv_kdoor_hd == NULL) {
139 		mutex_exit(&sv->sv_kdoor_mutex);
140 
141 		if (smb_kdoor_open(sv, sv->sv_kdoor_id) != 0) {
142 			smb_event_destroy(da.da_event);
143 			return (-1);
144 		}
145 
146 		mutex_enter(&sv->sv_kdoor_mutex);
147 	}
148 
149 	sv->sv_kdoor_ncall++;
150 	mutex_exit(&sv->sv_kdoor_mutex);
151 
152 	if (da.da_flags & SMB_DF_ASYNC) {
153 		if ((rc = smb_kdoor_send(sv, &da)) == 0) {
154 			if (smb_event_wait(da.da_event) != 0)
155 				rc = -1;
156 			else
157 				rc = smb_kdoor_receive(sv, &da);
158 		}
159 	} else {
160 		if ((rc = smb_kdoor_encode(&da)) == 0) {
161 			if ((rc = smb_kdoor_upcall_private(sv, &da)) == 0)
162 				rc = smb_kdoor_decode(&da);
163 		}
164 		smb_kdoor_free(&da.da_arg);
165 	}
166 
167 	smb_event_destroy(da.da_event);
168 
169 	mutex_enter(&sv->sv_kdoor_mutex);
170 	if ((--sv->sv_kdoor_ncall) == 0)
171 		cv_signal(&sv->sv_kdoor_cv);
172 	mutex_exit(&sv->sv_kdoor_mutex);
173 	return (rc);
174 }
175 
176 /*
177  * Send the request half of the consumer's door call.
178  */
179 static int
180 smb_kdoor_send(smb_server_t *sv, smb_doorarg_t *outer_da)
181 {
182 	smb_doorarg_t	da;
183 	int		rc;
184 
185 	bcopy(outer_da, &da, sizeof (smb_doorarg_t));
186 	da.da_rsp_xdr = NULL;
187 	da.da_rsp_data = NULL;
188 
189 	if (smb_kdoor_encode(&da) != 0)
190 		return (-1);
191 
192 	if ((rc = smb_kdoor_upcall_private(sv, &da)) == 0)
193 		rc = smb_kdoor_decode(&da);
194 
195 	smb_kdoor_free(&da.da_arg);
196 	return (rc);
197 }
198 
199 /*
200  * Get the response half for the consumer's door call.
201  */
202 static int
203 smb_kdoor_receive(smb_server_t *sv, smb_doorarg_t *outer_da)
204 {
205 	smb_doorarg_t	da;
206 	int		rc;
207 
208 	bcopy(outer_da, &da, sizeof (smb_doorarg_t));
209 	da.da_opcode = SMB_DR_ASYNC_RESPONSE;
210 	da.da_opname = smb_doorhdr_opname(da.da_opcode);
211 	da.da_flags &= ~SMB_DF_ASYNC;
212 	da.da_req_xdr = NULL;
213 	da.da_req_data = NULL;
214 
215 	if (smb_kdoor_encode(&da) != 0)
216 		return (-1);
217 
218 	if ((rc = smb_kdoor_upcall_private(sv, &da)) == 0)
219 		rc = smb_kdoor_decode(&da);
220 
221 	smb_kdoor_free(&da.da_arg);
222 	return (rc);
223 }
224 
225 /*
226  * We use a copy of the door arg because doorfs may change data_ptr
227  * and we want to detect that when freeing the door buffers.  After
228  * this call, response data must be referenced via rbuf and rsize.
229  */
230 static int
231 smb_kdoor_upcall_private(smb_server_t *sv, smb_doorarg_t *da)
232 {
233 	door_arg_t	door_arg;
234 	int		i;
235 	int		rc;
236 
237 	bcopy(&da->da_arg, &door_arg, sizeof (door_arg_t));
238 
239 	for (i = 0; i < SMB_DOOR_CALL_RETRIES; ++i) {
240 		if (smb_server_is_stopping(sv))
241 			return (-1);
242 
243 		if ((rc = door_ki_upcall_limited(sv->sv_kdoor_hd, &door_arg,
244 		    NULL, SIZE_MAX, 0)) == 0)
245 			break;
246 
247 		if (rc != EAGAIN && rc != EINTR)
248 			return (-1);
249 	}
250 
251 	if (rc != 0 || door_arg.data_size == 0 || door_arg.rsize == 0)
252 		return (-1);
253 
254 	da->da_arg.rbuf = door_arg.data_ptr;
255 	da->da_arg.rsize = door_arg.rsize;
256 	return (0);
257 }
258 
259 static int
260 smb_kdoor_encode(smb_doorarg_t *da)
261 {
262 	XDR		xdrs;
263 	char		*buf;
264 	uint32_t	len;
265 
266 	len = xdr_sizeof(smb_doorhdr_xdr, &da->da_hdr);
267 	if (da->da_req_xdr != NULL)
268 		len += xdr_sizeof(da->da_req_xdr, da->da_req_data);
269 
270 	smb_kdoor_sethdr(da, len);
271 
272 	buf = kmem_zalloc(len, KM_SLEEP);
273 	xdrmem_create(&xdrs, buf, len, XDR_ENCODE);
274 
275 	if (!smb_doorhdr_xdr(&xdrs, &da->da_hdr)) {
276 		cmn_err(CE_WARN, "smb_kdoor_encode[%s]: header encode failed",
277 		    da->da_opname);
278 		kmem_free(buf, len);
279 		xdr_destroy(&xdrs);
280 		return (-1);
281 	}
282 
283 	if (da->da_req_xdr != NULL) {
284 		if (!da->da_req_xdr(&xdrs, da->da_req_data)) {
285 			cmn_err(CE_WARN, "smb_kdoor_encode[%s]: encode failed",
286 			    da->da_opname);
287 			kmem_free(buf, len);
288 			xdr_destroy(&xdrs);
289 			return (-1);
290 		}
291 	}
292 
293 	da->da_arg.data_ptr = buf;
294 	da->da_arg.data_size = len;
295 	da->da_arg.desc_ptr = NULL;
296 	da->da_arg.desc_num = 0;
297 	da->da_arg.rbuf = buf;
298 	da->da_arg.rsize = len;
299 
300 	xdr_destroy(&xdrs);
301 	return (0);
302 }
303 
304 /*
305  * Decode the response in rbuf and rsize.
306  */
307 static int
308 smb_kdoor_decode(smb_doorarg_t *da)
309 {
310 	XDR		xdrs;
311 	smb_doorhdr_t	hdr;
312 	char		*rbuf = da->da_arg.rbuf;
313 	uint32_t	rsize = da->da_arg.rsize;
314 
315 	if (rbuf == NULL || rsize == 0) {
316 		cmn_err(CE_WARN, "smb_kdoor_decode[%s]: invalid param",
317 		    da->da_opname);
318 		return (-1);
319 	}
320 
321 	xdrmem_create(&xdrs, rbuf, rsize, XDR_DECODE);
322 
323 	if (!smb_doorhdr_xdr(&xdrs, &hdr)) {
324 		cmn_err(CE_WARN, "smb_kdoor_decode[%s]: header decode failed",
325 		    da->da_opname);
326 		xdr_destroy(&xdrs);
327 		return (-1);
328 	}
329 
330 	if (!smb_kdoor_chkhdr(da, &hdr)) {
331 		xdr_destroy(&xdrs);
332 		return (-1);
333 	}
334 
335 	if (hdr.dh_datalen != 0 && da->da_rsp_xdr != NULL) {
336 		if (!da->da_rsp_xdr(&xdrs, da->da_rsp_data)) {
337 			cmn_err(CE_WARN, "smb_kdoor_decode[%s]: decode failed",
338 			    da->da_opname);
339 			xdr_destroy(&xdrs);
340 			return (-1);
341 		}
342 	}
343 
344 	xdr_destroy(&xdrs);
345 	return (0);
346 }
347 
348 static void
349 smb_kdoor_sethdr(smb_doorarg_t *da, uint32_t datalen)
350 {
351 	smb_doorhdr_t	*hdr = &da->da_hdr;
352 
353 	bzero(hdr, sizeof (smb_doorhdr_t));
354 	hdr->dh_magic = SMB_DOOR_HDR_MAGIC;
355 	hdr->dh_flags = da->da_flags | SMB_DF_SYSSPACE;
356 	hdr->dh_op = da->da_opcode;
357 	hdr->dh_txid = smb_event_txid(da->da_event);
358 	hdr->dh_datalen = datalen;
359 	hdr->dh_door_rc = SMB_DOP_NOT_CALLED;
360 }
361 
362 static boolean_t
363 smb_kdoor_chkhdr(smb_doorarg_t *da, smb_doorhdr_t *hdr)
364 {
365 	if ((hdr->dh_magic != SMB_DOOR_HDR_MAGIC) ||
366 	    (hdr->dh_op != da->da_hdr.dh_op) ||
367 	    (hdr->dh_txid != da->da_hdr.dh_txid)) {
368 		cmn_err(CE_WARN, "smb_kdoor_chkhdr[%s]: invalid header",
369 		    da->da_opname);
370 		return (B_FALSE);
371 	}
372 
373 	switch (hdr->dh_door_rc) {
374 	case SMB_DOP_SUCCESS:
375 		break;
376 
377 	/* SMB_DOP_EMPTYBUF is a "normal" error (silent). */
378 	case SMB_DOP_EMPTYBUF:
379 		return (B_FALSE);
380 
381 	default:
382 		cmn_err(CE_WARN, "smb_kdoor_chkhdr[%s]: call failed: %u",
383 		    da->da_opname, hdr->dh_door_rc);
384 		return (B_FALSE);
385 	}
386 
387 	return (B_TRUE);
388 }
389 
390 /*
391  * Free both the argument and result door buffers regardless of the status
392  * of the up-call.  The doorfs allocates a new buffer if the result buffer
393  * passed by the client is too small.
394  */
395 static void
396 smb_kdoor_free(door_arg_t *arg)
397 {
398 	if (arg->rbuf != NULL && arg->rbuf != arg->data_ptr)
399 		kmem_free(arg->rbuf, arg->rsize);
400 
401 	if (arg->data_ptr != NULL)
402 		kmem_free(arg->data_ptr, arg->data_size);
403 }
404