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 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 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 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 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 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 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 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 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 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 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 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 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 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