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
39 static int smb_kdoor_send(smb_server_t *, smb_doorarg_t *);
40 static int smb_kdoor_receive(smb_server_t *, smb_doorarg_t *);
41 static int smb_kdoor_upcall_private(smb_server_t *, smb_doorarg_t *);
42 static int smb_kdoor_encode(smb_doorarg_t *);
43 static int smb_kdoor_decode(smb_doorarg_t *);
44 static void smb_kdoor_sethdr(smb_doorarg_t *, uint32_t);
45 static boolean_t smb_kdoor_chkhdr(smb_doorarg_t *, smb_doorhdr_t *);
46 static void smb_kdoor_free(door_arg_t *);
47
48 void
smb_kdoor_init(smb_server_t * sv)49 smb_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
56 void
smb_kdoor_fini(smb_server_t * sv)57 smb_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 */
68 int
smb_kdoor_open(smb_server_t * sv,int door_id)69 smb_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 */
91 void
smb_kdoor_close(smb_server_t * sv)92 smb_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 */
111 int
smb_kdoor_upcall(smb_server_t * sv,uint32_t cmd,void * req_data,xdrproc_t req_xdr,void * rsp_data,xdrproc_t rsp_xdr)112 smb_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 */
183 static int
smb_kdoor_send(smb_server_t * sv,smb_doorarg_t * outer_da)184 smb_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 */
206 static int
smb_kdoor_receive(smb_server_t * sv,smb_doorarg_t * outer_da)207 smb_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 */
234 static int
smb_kdoor_upcall_private(smb_server_t * sv,smb_doorarg_t * da)235 smb_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
263 static int
smb_kdoor_encode(smb_doorarg_t * da)264 smb_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 */
311 static int
smb_kdoor_decode(smb_doorarg_t * da)312 smb_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
352 static void
smb_kdoor_sethdr(smb_doorarg_t * da,uint32_t datalen)353 smb_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
366 static boolean_t
smb_kdoor_chkhdr(smb_doorarg_t * da,smb_doorhdr_t * hdr)367 smb_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 */
399 static void
smb_kdoor_free(door_arg_t * arg)400 smb_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