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