110023SGordon.Ross@Sun.COM /* 210023SGordon.Ross@Sun.COM * CDDL HEADER START 310023SGordon.Ross@Sun.COM * 410023SGordon.Ross@Sun.COM * The contents of this file are subject to the terms of the 510023SGordon.Ross@Sun.COM * Common Development and Distribution License (the "License"). 610023SGordon.Ross@Sun.COM * You may not use this file except in compliance with the License. 710023SGordon.Ross@Sun.COM * 810023SGordon.Ross@Sun.COM * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 910023SGordon.Ross@Sun.COM * or http://www.opensolaris.org/os/licensing. 1010023SGordon.Ross@Sun.COM * See the License for the specific language governing permissions 1110023SGordon.Ross@Sun.COM * and limitations under the License. 1210023SGordon.Ross@Sun.COM * 1310023SGordon.Ross@Sun.COM * When distributing Covered Code, include this CDDL HEADER in each 1410023SGordon.Ross@Sun.COM * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 1510023SGordon.Ross@Sun.COM * If applicable, add the following below this CDDL HEADER, with the 1610023SGordon.Ross@Sun.COM * fields enclosed by brackets "[]" replaced with your own identifying 1710023SGordon.Ross@Sun.COM * information: Portions Copyright [yyyy] [name of copyright owner] 1810023SGordon.Ross@Sun.COM * 1910023SGordon.Ross@Sun.COM * CDDL HEADER END 2010023SGordon.Ross@Sun.COM */ 2110023SGordon.Ross@Sun.COM 2210023SGordon.Ross@Sun.COM /* 23*12140SGordon.Ross@Sun.COM * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. 2410023SGordon.Ross@Sun.COM */ 2510023SGordon.Ross@Sun.COM 2610023SGordon.Ross@Sun.COM /* 2710023SGordon.Ross@Sun.COM * SMBFS I/O Deamon (smbiod) 2810023SGordon.Ross@Sun.COM */ 2910023SGordon.Ross@Sun.COM 3010023SGordon.Ross@Sun.COM #include <sys/types.h> 3110023SGordon.Ross@Sun.COM #include <sys/stat.h> 3210023SGordon.Ross@Sun.COM #include <sys/note.h> 3310023SGordon.Ross@Sun.COM 3410023SGordon.Ross@Sun.COM #include <errno.h> 3510023SGordon.Ross@Sun.COM #include <fcntl.h> 3610023SGordon.Ross@Sun.COM #include <signal.h> 3710023SGordon.Ross@Sun.COM #include <stdarg.h> 3810023SGordon.Ross@Sun.COM #include <stdio.h> 3910023SGordon.Ross@Sun.COM #include <string.h> 4010023SGordon.Ross@Sun.COM #include <strings.h> 4110023SGordon.Ross@Sun.COM #include <stdlib.h> 4210023SGordon.Ross@Sun.COM #include <synch.h> 4310023SGordon.Ross@Sun.COM #include <time.h> 4410023SGordon.Ross@Sun.COM #include <unistd.h> 4510023SGordon.Ross@Sun.COM #include <ucred.h> 4610023SGordon.Ross@Sun.COM 4710023SGordon.Ross@Sun.COM #include <err.h> 4810023SGordon.Ross@Sun.COM #include <door.h> 4910023SGordon.Ross@Sun.COM #include <thread.h> 5010023SGordon.Ross@Sun.COM 5110023SGordon.Ross@Sun.COM #include <netsmb/smb_lib.h> 5210023SGordon.Ross@Sun.COM 5310023SGordon.Ross@Sun.COM #define EXIT_FAIL 1 5410023SGordon.Ross@Sun.COM #define EXIT_OK 0 5510023SGordon.Ross@Sun.COM 5610023SGordon.Ross@Sun.COM #if defined(DEBUG) || defined(__lint) 5710023SGordon.Ross@Sun.COM #define DPRINT(...) do \ 5810023SGordon.Ross@Sun.COM { \ 5910023SGordon.Ross@Sun.COM if (smb_debug) \ 6010023SGordon.Ross@Sun.COM fprintf(stderr, __VA_ARGS__); \ 6110023SGordon.Ross@Sun.COM _NOTE(CONSTCOND) \ 6210023SGordon.Ross@Sun.COM } while (0) 6310023SGordon.Ross@Sun.COM #else 6410023SGordon.Ross@Sun.COM #define DPRINT(...) ((void)0) 6510023SGordon.Ross@Sun.COM #endif 6610023SGordon.Ross@Sun.COM 6710023SGordon.Ross@Sun.COM mutex_t iod_mutex = DEFAULTMUTEX; 6810023SGordon.Ross@Sun.COM int iod_thr_count; /* threads, excluding main */ 6910023SGordon.Ross@Sun.COM int iod_terminating; 70*12140SGordon.Ross@Sun.COM int iod_alarm_time = 30; /* sec. */ 7110023SGordon.Ross@Sun.COM 7210023SGordon.Ross@Sun.COM void iod_dispatch(void *cookie, char *argp, size_t argsz, 7310023SGordon.Ross@Sun.COM door_desc_t *dp, uint_t n_desc); 7410023SGordon.Ross@Sun.COM int iod_newvc(smb_iod_ssn_t *clnt_ssn); 7510023SGordon.Ross@Sun.COM void * iod_work(void *arg); 7610023SGordon.Ross@Sun.COM 7710023SGordon.Ross@Sun.COM int 7810023SGordon.Ross@Sun.COM main(int argc, char **argv) 7910023SGordon.Ross@Sun.COM { 8010023SGordon.Ross@Sun.COM static const int door_attrs = 8110023SGordon.Ross@Sun.COM DOOR_REFUSE_DESC | DOOR_NO_CANCEL; 8210023SGordon.Ross@Sun.COM sigset_t oldmask, tmpmask; 8310023SGordon.Ross@Sun.COM char *env, *door_path = NULL; 8410023SGordon.Ross@Sun.COM int door_fd = -1, tmp_fd = -1; 8510023SGordon.Ross@Sun.COM int err, i, sig; 8610023SGordon.Ross@Sun.COM int rc = EXIT_FAIL; 8710023SGordon.Ross@Sun.COM 8810023SGordon.Ross@Sun.COM /* Debugging support. */ 8910023SGordon.Ross@Sun.COM if ((env = getenv("SMBFS_DEBUG")) != NULL) { 9010023SGordon.Ross@Sun.COM smb_debug = atoi(env); 9110023SGordon.Ross@Sun.COM if (smb_debug < 1) 9210023SGordon.Ross@Sun.COM smb_debug = 1; 93*12140SGordon.Ross@Sun.COM iod_alarm_time = 300; 9410023SGordon.Ross@Sun.COM } 9510023SGordon.Ross@Sun.COM 9610023SGordon.Ross@Sun.COM /* 9710023SGordon.Ross@Sun.COM * Find out if an IOD is already running. 9810023SGordon.Ross@Sun.COM * If so, we lost a harmless startup race. 9910023SGordon.Ross@Sun.COM * An IOD did start, so exit success. 10010023SGordon.Ross@Sun.COM */ 10110023SGordon.Ross@Sun.COM err = smb_iod_open_door(&door_fd); 10210023SGordon.Ross@Sun.COM if (err == 0) { 10310023SGordon.Ross@Sun.COM close(door_fd); 10410023SGordon.Ross@Sun.COM door_fd = -1; 10510023SGordon.Ross@Sun.COM DPRINT("main: already running\n"); 10610023SGordon.Ross@Sun.COM exit(EXIT_OK); 10710023SGordon.Ross@Sun.COM } 10810023SGordon.Ross@Sun.COM 10910023SGordon.Ross@Sun.COM /* 11010023SGordon.Ross@Sun.COM * Create a file for the door. 11110023SGordon.Ross@Sun.COM */ 11210023SGordon.Ross@Sun.COM door_path = smb_iod_door_path(); 11310023SGordon.Ross@Sun.COM unlink(door_path); 11410023SGordon.Ross@Sun.COM tmp_fd = open(door_path, O_RDWR|O_CREAT|O_EXCL, 0600); 11510023SGordon.Ross@Sun.COM if (tmp_fd < 0) { 11610023SGordon.Ross@Sun.COM perror(door_path); 11710023SGordon.Ross@Sun.COM exit(EXIT_FAIL); 11810023SGordon.Ross@Sun.COM } 11910023SGordon.Ross@Sun.COM close(tmp_fd); 12010023SGordon.Ross@Sun.COM tmp_fd = -1; 12110023SGordon.Ross@Sun.COM 12210023SGordon.Ross@Sun.COM 12310023SGordon.Ross@Sun.COM /* 12410023SGordon.Ross@Sun.COM * Close FDs 0,1,2 so we don't have a TTY, and 12510023SGordon.Ross@Sun.COM * re-open them on /dev/null so they won't be 12610023SGordon.Ross@Sun.COM * used for device handles (etc.) later, and 12710023SGordon.Ross@Sun.COM * we don't have to worry about printf calls 12810023SGordon.Ross@Sun.COM * or whatever going to these FDs. 12910023SGordon.Ross@Sun.COM */ 13010023SGordon.Ross@Sun.COM for (i = 0; i < 3; i++) { 13110023SGordon.Ross@Sun.COM /* Exception: If smb_debug, keep stderr */ 13210023SGordon.Ross@Sun.COM if (smb_debug && i == 2) 13310023SGordon.Ross@Sun.COM break; 13410023SGordon.Ross@Sun.COM close(i); 13510023SGordon.Ross@Sun.COM tmp_fd = open("/dev/null", O_RDWR); 13610023SGordon.Ross@Sun.COM if (tmp_fd < 0) 13710023SGordon.Ross@Sun.COM perror("/dev/null"); 13810023SGordon.Ross@Sun.COM if (tmp_fd != i) 13910023SGordon.Ross@Sun.COM DPRINT("Open /dev/null - wrong fd?\n"); 14010023SGordon.Ross@Sun.COM } 14110023SGordon.Ross@Sun.COM 14210023SGordon.Ross@Sun.COM /* 14310023SGordon.Ross@Sun.COM * Become session leader. 14410023SGordon.Ross@Sun.COM */ 14510023SGordon.Ross@Sun.COM setsid(); 14610023SGordon.Ross@Sun.COM 14710023SGordon.Ross@Sun.COM /* 14810023SGordon.Ross@Sun.COM * Create door service threads with signals blocked. 14910023SGordon.Ross@Sun.COM */ 15010023SGordon.Ross@Sun.COM sigfillset(&tmpmask); 15110023SGordon.Ross@Sun.COM sigprocmask(SIG_BLOCK, &tmpmask, &oldmask); 15210023SGordon.Ross@Sun.COM 15310023SGordon.Ross@Sun.COM /* Setup the door service. */ 15410023SGordon.Ross@Sun.COM door_fd = door_create(iod_dispatch, NULL, door_attrs); 15510023SGordon.Ross@Sun.COM if (door_fd < 0) { 15610023SGordon.Ross@Sun.COM fprintf(stderr, "%s: door_create failed\n", argv[0]); 15710023SGordon.Ross@Sun.COM rc = EXIT_FAIL; 15810023SGordon.Ross@Sun.COM goto errout; 15910023SGordon.Ross@Sun.COM } 16010023SGordon.Ross@Sun.COM fdetach(door_path); 16110023SGordon.Ross@Sun.COM if (fattach(door_fd, door_path) < 0) { 16210023SGordon.Ross@Sun.COM fprintf(stderr, "%s: fattach failed\n", argv[0]); 16310023SGordon.Ross@Sun.COM rc = EXIT_FAIL; 16410023SGordon.Ross@Sun.COM goto errout; 16510023SGordon.Ross@Sun.COM } 16610023SGordon.Ross@Sun.COM 16710023SGordon.Ross@Sun.COM /* 16810023SGordon.Ross@Sun.COM * Post the initial alarm, and then just 16910023SGordon.Ross@Sun.COM * wait for signals. 17010023SGordon.Ross@Sun.COM */ 171*12140SGordon.Ross@Sun.COM alarm(iod_alarm_time); 17210023SGordon.Ross@Sun.COM again: 17310023SGordon.Ross@Sun.COM sig = sigwait(&tmpmask); 17410023SGordon.Ross@Sun.COM DPRINT("main: sig=%d\n", sig); 17510023SGordon.Ross@Sun.COM 17610023SGordon.Ross@Sun.COM /* 17710023SGordon.Ross@Sun.COM * If a door call races with the alarm, ignore the alarm. 17810023SGordon.Ross@Sun.COM * It will be rescheduled when the threads go away. 17910023SGordon.Ross@Sun.COM */ 18010023SGordon.Ross@Sun.COM mutex_lock(&iod_mutex); 18110023SGordon.Ross@Sun.COM if (sig == SIGALRM && iod_thr_count > 0) { 18210023SGordon.Ross@Sun.COM mutex_unlock(&iod_mutex); 18310023SGordon.Ross@Sun.COM goto again; 18410023SGordon.Ross@Sun.COM } 18510023SGordon.Ross@Sun.COM iod_terminating = 1; 18610023SGordon.Ross@Sun.COM mutex_unlock(&iod_mutex); 18710023SGordon.Ross@Sun.COM rc = EXIT_OK; 18810023SGordon.Ross@Sun.COM 18910023SGordon.Ross@Sun.COM errout: 19010023SGordon.Ross@Sun.COM fdetach(door_path); 19110023SGordon.Ross@Sun.COM door_revoke(door_fd); 19210023SGordon.Ross@Sun.COM door_fd = -1; 19310023SGordon.Ross@Sun.COM unlink(door_path); 19410023SGordon.Ross@Sun.COM 19510023SGordon.Ross@Sun.COM return (rc); 19610023SGordon.Ross@Sun.COM } 19710023SGordon.Ross@Sun.COM 19810023SGordon.Ross@Sun.COM /*ARGSUSED*/ 19910023SGordon.Ross@Sun.COM void 20010023SGordon.Ross@Sun.COM iod_dispatch(void *cookie, char *argp, size_t argsz, 20110023SGordon.Ross@Sun.COM door_desc_t *dp, uint_t n_desc) 20210023SGordon.Ross@Sun.COM { 20310023SGordon.Ross@Sun.COM smb_iod_ssn_t *ssn; 20410023SGordon.Ross@Sun.COM ucred_t *ucred; 20510023SGordon.Ross@Sun.COM uid_t cl_uid; 20610023SGordon.Ross@Sun.COM int rc; 20710023SGordon.Ross@Sun.COM 20810023SGordon.Ross@Sun.COM /* 20910023SGordon.Ross@Sun.COM * Verify that the calling process has the same UID. 21010023SGordon.Ross@Sun.COM * Paranoia: The door we created has mode 0600, so 21110023SGordon.Ross@Sun.COM * this check is probably redundant. 21210023SGordon.Ross@Sun.COM */ 21310023SGordon.Ross@Sun.COM ucred = NULL; 21410023SGordon.Ross@Sun.COM if (door_ucred(&ucred) != 0) { 21510023SGordon.Ross@Sun.COM rc = EACCES; 21610023SGordon.Ross@Sun.COM goto out; 21710023SGordon.Ross@Sun.COM } 21810023SGordon.Ross@Sun.COM cl_uid = ucred_getruid(ucred); 21910023SGordon.Ross@Sun.COM ucred_free(ucred); 22010023SGordon.Ross@Sun.COM ucred = NULL; 22110023SGordon.Ross@Sun.COM if (cl_uid != getuid()) { 22210023SGordon.Ross@Sun.COM DPRINT("iod_dispatch: wrong UID\n"); 22310023SGordon.Ross@Sun.COM rc = EACCES; 22410023SGordon.Ross@Sun.COM goto out; 22510023SGordon.Ross@Sun.COM } 22610023SGordon.Ross@Sun.COM 22710023SGordon.Ross@Sun.COM /* 22810023SGordon.Ross@Sun.COM * The library uses a NULL arg call to check if 22910023SGordon.Ross@Sun.COM * the deamon is running. Just return zero. 23010023SGordon.Ross@Sun.COM */ 23110023SGordon.Ross@Sun.COM if (argp == NULL) { 23210023SGordon.Ross@Sun.COM rc = 0; 23310023SGordon.Ross@Sun.COM goto out; 23410023SGordon.Ross@Sun.COM } 23510023SGordon.Ross@Sun.COM 23610023SGordon.Ross@Sun.COM /* 23710023SGordon.Ross@Sun.COM * Otherwise, the arg must be the (fixed size) 23810023SGordon.Ross@Sun.COM * smb_iod_ssn_t 23910023SGordon.Ross@Sun.COM */ 24010023SGordon.Ross@Sun.COM if (argsz != sizeof (*ssn)) { 24110023SGordon.Ross@Sun.COM rc = EINVAL; 24210023SGordon.Ross@Sun.COM goto out; 24310023SGordon.Ross@Sun.COM } 24410023SGordon.Ross@Sun.COM 24510023SGordon.Ross@Sun.COM mutex_lock(&iod_mutex); 24610023SGordon.Ross@Sun.COM if (iod_terminating) { 24710023SGordon.Ross@Sun.COM mutex_unlock(&iod_mutex); 24810023SGordon.Ross@Sun.COM DPRINT("iod_dispatch: terminating\n"); 24910023SGordon.Ross@Sun.COM rc = EINTR; 25010023SGordon.Ross@Sun.COM goto out; 25110023SGordon.Ross@Sun.COM } 25210023SGordon.Ross@Sun.COM if (iod_thr_count++ == 0) { 25310023SGordon.Ross@Sun.COM alarm(0); 25410023SGordon.Ross@Sun.COM DPRINT("iod_dispatch: cancelled alarm\n"); 25510023SGordon.Ross@Sun.COM } 25610023SGordon.Ross@Sun.COM mutex_unlock(&iod_mutex); 25710023SGordon.Ross@Sun.COM 25810023SGordon.Ross@Sun.COM ssn = (void *) argp; 25910023SGordon.Ross@Sun.COM rc = iod_newvc(ssn); 26010023SGordon.Ross@Sun.COM 26110023SGordon.Ross@Sun.COM mutex_lock(&iod_mutex); 26210023SGordon.Ross@Sun.COM if (--iod_thr_count == 0) { 26310023SGordon.Ross@Sun.COM DPRINT("iod_dispatch: schedule alarm\n"); 264*12140SGordon.Ross@Sun.COM alarm(iod_alarm_time); 26510023SGordon.Ross@Sun.COM } 26610023SGordon.Ross@Sun.COM mutex_unlock(&iod_mutex); 26710023SGordon.Ross@Sun.COM 26810023SGordon.Ross@Sun.COM out: 26910023SGordon.Ross@Sun.COM door_return((void *)&rc, sizeof (rc), NULL, 0); 27010023SGordon.Ross@Sun.COM } 27110023SGordon.Ross@Sun.COM 27210023SGordon.Ross@Sun.COM /* 27310023SGordon.Ross@Sun.COM * Try making a connection with the server described by 27410023SGordon.Ross@Sun.COM * the info in the smb_iod_ssn_t arg. If successful, 27510023SGordon.Ross@Sun.COM * start an IOD thread to service it, then return to 27610023SGordon.Ross@Sun.COM * the client side of the door. 27710023SGordon.Ross@Sun.COM */ 27810023SGordon.Ross@Sun.COM int 27910023SGordon.Ross@Sun.COM iod_newvc(smb_iod_ssn_t *clnt_ssn) 28010023SGordon.Ross@Sun.COM { 28110023SGordon.Ross@Sun.COM smb_ctx_t *ctx; 28210023SGordon.Ross@Sun.COM thread_t tid; 28310023SGordon.Ross@Sun.COM int err; 28410023SGordon.Ross@Sun.COM 28510023SGordon.Ross@Sun.COM 28610023SGordon.Ross@Sun.COM /* 28710023SGordon.Ross@Sun.COM * This needs to essentially "clone" the smb_ctx_t 28810023SGordon.Ross@Sun.COM * from the client side of the door, or at least 28910023SGordon.Ross@Sun.COM * as much of it as we need while creating a VC. 29010023SGordon.Ross@Sun.COM */ 29110023SGordon.Ross@Sun.COM err = smb_ctx_alloc(&ctx); 29210023SGordon.Ross@Sun.COM if (err) 29310023SGordon.Ross@Sun.COM return (err); 29410023SGordon.Ross@Sun.COM bcopy(clnt_ssn, &ctx->ct_iod_ssn, sizeof (ctx->ct_iod_ssn)); 29510023SGordon.Ross@Sun.COM 29610023SGordon.Ross@Sun.COM /* 29710023SGordon.Ross@Sun.COM * Do the initial connection setup here, so we can 29810023SGordon.Ross@Sun.COM * report the outcome to the door client. 29910023SGordon.Ross@Sun.COM */ 30010023SGordon.Ross@Sun.COM err = smb_iod_connect(ctx); 30110023SGordon.Ross@Sun.COM if (err != 0) 30210023SGordon.Ross@Sun.COM goto out; 30310023SGordon.Ross@Sun.COM 30410023SGordon.Ross@Sun.COM /* 30510023SGordon.Ross@Sun.COM * Create the driver session now, so we don't 30610023SGordon.Ross@Sun.COM * race with the door client findvc call. 30710023SGordon.Ross@Sun.COM */ 30810023SGordon.Ross@Sun.COM if ((err = smb_ctx_gethandle(ctx)) != 0) 30910023SGordon.Ross@Sun.COM goto out; 31010023SGordon.Ross@Sun.COM if (ioctl(ctx->ct_dev_fd, SMBIOC_SSN_CREATE, &ctx->ct_ssn) < 0) { 31110023SGordon.Ross@Sun.COM err = errno; 31210023SGordon.Ross@Sun.COM goto out; 31310023SGordon.Ross@Sun.COM } 31410023SGordon.Ross@Sun.COM 31510023SGordon.Ross@Sun.COM /* The rest happens in the iod_work thread. */ 31610023SGordon.Ross@Sun.COM err = thr_create(NULL, 0, iod_work, ctx, THR_DETACHED, &tid); 31710023SGordon.Ross@Sun.COM if (err == 0) { 31810023SGordon.Ross@Sun.COM /* 31910023SGordon.Ross@Sun.COM * Given to the new thread. 32010023SGordon.Ross@Sun.COM * free at end of iod_work 32110023SGordon.Ross@Sun.COM */ 32210023SGordon.Ross@Sun.COM ctx = NULL; 32310023SGordon.Ross@Sun.COM } 32410023SGordon.Ross@Sun.COM 32510023SGordon.Ross@Sun.COM out: 32610023SGordon.Ross@Sun.COM if (ctx) 32710023SGordon.Ross@Sun.COM smb_ctx_free(ctx); 32810023SGordon.Ross@Sun.COM 32910023SGordon.Ross@Sun.COM return (err); 33010023SGordon.Ross@Sun.COM } 33110023SGordon.Ross@Sun.COM 33210023SGordon.Ross@Sun.COM /* 33310023SGordon.Ross@Sun.COM * Be the reader thread for some VC. 33410023SGordon.Ross@Sun.COM * 33510023SGordon.Ross@Sun.COM * This is started by a door call thread, which means 33610023SGordon.Ross@Sun.COM * this is always at least the 2nd thread, therefore 33710023SGordon.Ross@Sun.COM * it should never see thr_count==0 or terminating. 33810023SGordon.Ross@Sun.COM */ 33910023SGordon.Ross@Sun.COM void * 34010023SGordon.Ross@Sun.COM iod_work(void *arg) 34110023SGordon.Ross@Sun.COM { 34210023SGordon.Ross@Sun.COM smb_ctx_t *ctx = arg; 34310023SGordon.Ross@Sun.COM 34410023SGordon.Ross@Sun.COM mutex_lock(&iod_mutex); 34510023SGordon.Ross@Sun.COM if (iod_thr_count++ == 0) { 34610023SGordon.Ross@Sun.COM alarm(0); 34710023SGordon.Ross@Sun.COM DPRINT("iod_work: cancelled alarm\n"); 34810023SGordon.Ross@Sun.COM } 34910023SGordon.Ross@Sun.COM mutex_unlock(&iod_mutex); 35010023SGordon.Ross@Sun.COM 35110023SGordon.Ross@Sun.COM (void) smb_iod_work(ctx); 35210023SGordon.Ross@Sun.COM 35310023SGordon.Ross@Sun.COM mutex_lock(&iod_mutex); 35410023SGordon.Ross@Sun.COM if (--iod_thr_count == 0) { 35510023SGordon.Ross@Sun.COM DPRINT("iod_work: schedule alarm\n"); 356*12140SGordon.Ross@Sun.COM alarm(iod_alarm_time); 35710023SGordon.Ross@Sun.COM } 35810023SGordon.Ross@Sun.COM mutex_unlock(&iod_mutex); 35910023SGordon.Ross@Sun.COM 36010023SGordon.Ross@Sun.COM smb_ctx_free(ctx); 36110023SGordon.Ross@Sun.COM return (NULL); 36210023SGordon.Ross@Sun.COM } 363