xref: /illumos-gate/usr/src/uts/common/fs/smbclnt/netsmb/smb_dev.c (revision 25a5294972c1d74b8257cf0ec6d7a93f2da6a8ca)
1 /*
2  * Copyright (c) 2000-2001 Boris Popov
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *    This product includes software developed by Boris Popov.
16  * 4. Neither the name of the author nor the names of any co-contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 /*
34  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
35  * Use is subject to license terms.
36  *
37  * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
38  */
39 
40 #include <sys/types.h>
41 #include <sys/param.h>
42 #include <sys/errno.h>
43 #include <sys/sysmacros.h>
44 #include <sys/uio.h>
45 #include <sys/buf.h>
46 #include <sys/modctl.h>
47 #include <sys/open.h>
48 #include <sys/file.h>
49 #include <sys/kmem.h>
50 #include <sys/conf.h>
51 #include <sys/cmn_err.h>
52 #include <sys/stat.h>
53 #include <sys/ddi.h>
54 #include <sys/sunddi.h>
55 #include <sys/sunldi.h>
56 #include <sys/policy.h>
57 #include <sys/zone.h>
58 #include <sys/pathname.h>
59 #include <sys/mount.h>
60 #include <sys/sdt.h>
61 #include <fs/fs_subr.h>
62 #include <sys/modctl.h>
63 #include <sys/devops.h>
64 #include <sys/thread.h>
65 #include <sys/socket.h>
66 #include <sys/zone.h>
67 
68 #include <netsmb/smb_osdep.h>
69 #include <netsmb/mchain.h>		/* for "htoles()" */
70 
71 #include <netsmb/smb.h>
72 #include <netsmb/smb2.h>
73 #include <netsmb/smb_conn.h>
74 #include <netsmb/smb_subr.h>
75 #include <netsmb/smb_dev.h>
76 #include <netsmb/smb_pass.h>
77 
78 #ifndef	_KERNEL
79 #include <libfknsmb.h>
80 
81 #define	_init(v)	nsmb_drv_init(v)
82 #define	_fini(v)	nsmb_drv_fini(v)
83 
84 #endif	/* _KERNEL */
85 
86 #define	NSMB_MIN_MINOR	1
87 #define	NSMB_MAX_MINOR	L_MAXMIN32
88 
89 /* for version checks */
90 const uint32_t nsmb_version = NSMB_VERSION;
91 
92 /* for smb_nbst_create() */
93 dev_t nsmb_dev_tcp = NODEV;
94 dev_t nsmb_dev_tcp6 = NODEV;
95 
96 static void *statep;
97 static major_t nsmb_major;
98 static minor_t last_minor = NSMB_MIN_MINOR;
99 static kmutex_t  dev_lck;
100 
101 /*
102  * cb_ops device operations.
103  */
104 static int nsmb_open(dev_t *devp, int flag, int otyp, cred_t *credp);
105 static int nsmb_close(dev_t dev, int flag, int otyp, cred_t *credp);
106 static int nsmb_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
107 				cred_t *credp, int *rvalp);
108 static int nsmb_close2(smb_dev_t *sdp, cred_t *cr);
109 
110 #ifdef	_KERNEL
111 
112 static dev_info_t *nsmb_dip;
113 
114 /* Zone support */
115 zone_key_t nsmb_zone_key;
116 extern void nsmb_zone_shutdown(zoneid_t zoneid, void *data);
117 extern void nsmb_zone_destroy(zoneid_t zoneid, void *data);
118 
119 /* smbfs cb_ops */
120 static struct cb_ops nsmb_cbops = {
121 	nsmb_open,	/* open */
122 	nsmb_close,	/* close */
123 	nodev,		/* strategy */
124 	nodev,		/* print */
125 	nodev,		/* dump */
126 	nodev,		/* read */
127 	nodev,		/* write */
128 	nsmb_ioctl,	/* ioctl */
129 	nodev,		/* devmap */
130 	nodev,		/* mmap */
131 	nodev,		/* segmap */
132 	nochpoll,	/* poll */
133 	ddi_prop_op,	/* prop_op */
134 	NULL,		/* stream */
135 	D_MP,		/* cb_flag */
136 	CB_REV,		/* rev */
137 	nodev,		/* int (*cb_aread)() */
138 	nodev		/* int (*cb_awrite)() */
139 };
140 
141 /*
142  * Device options
143  */
144 static int nsmb_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
145 static int nsmb_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
146 static int nsmb_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd,
147 	void *arg, void **result);
148 
149 static struct dev_ops nsmb_ops = {
150 	DEVO_REV,	/* devo_rev, */
151 	0,		/* refcnt  */
152 	nsmb_getinfo,	/* info */
153 	nulldev,	/* identify */
154 	nulldev,	/* probe */
155 	nsmb_attach,	/* attach */
156 	nsmb_detach,	/* detach */
157 	nodev,		/* reset */
158 	&nsmb_cbops,	/* driver ops - devctl interfaces */
159 	NULL,		/* bus operations */
160 	NULL,		/* power */
161 	ddi_quiesce_not_needed,	/* quiesce */
162 };
163 
164 /*
165  * Module linkage information.
166  */
167 
168 static struct modldrv nsmb_modldrv = {
169 	&mod_driverops,				/* Driver module */
170 	"SMBFS network driver",
171 	&nsmb_ops				/* Driver ops */
172 };
173 
174 static struct modlinkage nsmb_modlinkage = {
175 	MODREV_1,
176 	(void *)&nsmb_modldrv,
177 	NULL
178 };
179 
180 #endif	/* _KERNEL */
181 
182 int
_init(void)183 _init(void)
184 {
185 #ifdef	_KERNEL
186 	int error;
187 #endif	/* _KERNEL */
188 
189 	(void) ddi_soft_state_init(&statep, sizeof (smb_dev_t), 1);
190 
191 	/* Can initialize some mutexes also. */
192 	mutex_init(&dev_lck, NULL, MUTEX_DRIVER, NULL);
193 
194 	/* Connection data structures. */
195 	(void) smb_sm_init();
196 
197 	/* Initialize password Key chain DB. */
198 	smb_pkey_init();
199 
200 #ifdef	_KERNEL
201 	zone_key_create(&nsmb_zone_key, NULL, nsmb_zone_shutdown,
202 	    nsmb_zone_destroy);
203 
204 	/*
205 	 * Install the module.  Do this after other init,
206 	 * to prevent entrances before we're ready.
207 	 */
208 	if ((error = mod_install((&nsmb_modlinkage))) != 0) {
209 
210 		/* Same as 2nd half of _fini */
211 		(void) zone_key_delete(nsmb_zone_key);
212 		smb_pkey_fini();
213 		smb_sm_done();
214 		mutex_destroy(&dev_lck);
215 		ddi_soft_state_fini(&statep);
216 
217 		return (error);
218 	}
219 #else	/* _KERNEL */
220 	streams_msg_init();
221 	/* No attach, so need to set major. */
222 	nsmb_major = 1;
223 	/* And these, for smb_nbst_create() */
224 	nsmb_dev_tcp = AF_INET;
225 	nsmb_dev_tcp6 = AF_INET6;
226 #endif	/* _KERNEL */
227 
228 	return (0);
229 }
230 
231 int
_fini(void)232 _fini(void)
233 {
234 	int status;
235 
236 	/*
237 	 * Prevent unload if we have active VCs
238 	 * or stored passwords
239 	 */
240 	if ((status = smb_sm_idle()) != 0)
241 		return (status);
242 	if ((status = smb_pkey_idle()) != 0)
243 		return (status);
244 
245 #ifdef	_KERNEL
246 	/*
247 	 * Remove the module.  Do this before destroying things,
248 	 * to prevent new entrances while we're destorying.
249 	 */
250 	if ((status = mod_remove(&nsmb_modlinkage)) != 0) {
251 		return (status);
252 	}
253 
254 	(void) zone_key_delete(nsmb_zone_key);
255 #endif	/* _KERNEL */
256 
257 	/* Destroy password Key chain DB. */
258 	smb_pkey_fini();
259 
260 	smb_sm_done();
261 
262 	mutex_destroy(&dev_lck);
263 	ddi_soft_state_fini(&statep);
264 
265 	return (status);
266 }
267 
268 #ifdef	_KERNEL
269 
270 int
_info(struct modinfo * modinfop)271 _info(struct modinfo *modinfop)
272 {
273 	return (mod_info(&nsmb_modlinkage, modinfop));
274 }
275 
276 /*ARGSUSED*/
277 static int
nsmb_getinfo(dev_info_t * dip,ddi_info_cmd_t cmd,void * arg,void ** result)278 nsmb_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
279 {
280 	int ret = DDI_SUCCESS;
281 
282 	switch (cmd) {
283 	case DDI_INFO_DEVT2DEVINFO:
284 		*result = nsmb_dip;
285 		break;
286 	case DDI_INFO_DEVT2INSTANCE:
287 		*result = NULL;
288 		break;
289 	default:
290 		ret = DDI_FAILURE;
291 	}
292 	return (ret);
293 }
294 
295 static int
nsmb_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)296 nsmb_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
297 {
298 	major_t tmaj;
299 
300 	if (cmd != DDI_ATTACH)
301 		return (DDI_FAILURE);
302 
303 	/*
304 	 * We only support only one "instance".  Note that
305 	 * "instances" are different from minor units.
306 	 * We get one (unique) minor unit per open.
307 	 */
308 	if (ddi_get_instance(dip) > 0)
309 		return (DDI_FAILURE);
310 
311 	if (ddi_create_minor_node(dip, "nsmb", S_IFCHR, 0, DDI_PSEUDO,
312 	    0) == DDI_FAILURE) {
313 		cmn_err(CE_WARN, "nsmb_attach: create minor");
314 		return (DDI_FAILURE);
315 	}
316 
317 	/*
318 	 * We need the major number a couple places,
319 	 * i.e. in smb_dev2share()
320 	 */
321 	nsmb_major = ddi_name_to_major(NSMB_NAME);
322 
323 	/*
324 	 * We also need major numbers for t_kopen
325 	 */
326 	tmaj = ddi_name_to_major("tcp");
327 	if (tmaj == DDI_MAJOR_T_NONE)
328 		cmn_err(CE_NOTE, "no tcp major?");
329 	else
330 		nsmb_dev_tcp = makedevice(tmaj, 0);
331 	tmaj = ddi_name_to_major("tcp6");
332 	if (tmaj == DDI_MAJOR_T_NONE)
333 		cmn_err(CE_NOTE, "no tcp6 major?");
334 	else
335 		nsmb_dev_tcp6 = makedevice(tmaj, 0);
336 
337 	nsmb_dip = dip;
338 	ddi_report_dev(dip);
339 	return (DDI_SUCCESS);
340 }
341 
342 /*ARGSUSED*/
343 static int
nsmb_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)344 nsmb_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
345 {
346 
347 	if (cmd != DDI_DETACH)
348 		return (DDI_FAILURE);
349 	if (ddi_get_instance(dip) > 0)
350 		return (DDI_FAILURE);
351 
352 	nsmb_dip = NULL;
353 	ddi_remove_minor_node(dip, NULL);
354 
355 	return (DDI_SUCCESS);
356 }
357 
358 #else	/* _KERNEL */
359 
360 /*
361  * Wrappers for libfknsmb: ioctl, open, close, load
362  */
363 
364 /*ARGSUSED*/
365 int
nsmb_drv_ioctl(dev32_t dev32,int cmd,intptr_t arg,int flags)366 nsmb_drv_ioctl(dev32_t dev32, int cmd, intptr_t arg, int flags)
367 {
368 	dev_t dev = expldev(dev32);
369 	cred_t *cr = CRED();
370 	int err;
371 
372 	err = nsmb_ioctl(dev, cmd, arg, flags, cr, NULL);
373 	return (err);
374 }
375 
376 /*ARGSUSED*/
377 int
nsmb_drv_open(dev32_t * dev32p,int flags,int otyp)378 nsmb_drv_open(dev32_t *dev32p, int flags, int otyp)
379 {
380 	dev_t dev = expldev(*dev32p);
381 	int err;
382 
383 	err = nsmb_open(&dev, flags, otyp, CRED());
384 	if (err == 0) {
385 		/*
386 		 * We have NSMB_MAX_MINOR == L_MAXMIN32
387 		 * therefore cmpldev never fails.
388 		 */
389 		VERIFY(cmpldev(dev32p, dev) != 0);
390 	}
391 	return (err);
392 }
393 
394 /*ARGSUSED*/
395 int
nsmb_drv_close(dev32_t dev32,int flags,int otyp)396 nsmb_drv_close(dev32_t dev32, int flags, int otyp)
397 {
398 	dev_t dev = expldev(dev32);
399 	int err;
400 
401 	err = nsmb_close(dev, flags, otyp, CRED());
402 	return (err);
403 }
404 
405 /*
406  * This function intentionally does nothing.  It's used only to
407  * force libfknsmb to load at program start so one can set
408  * breakpoints etc. without debugger "force load" tricks.
409  */
410 void
nsmb_drv_load(void)411 nsmb_drv_load(void)
412 {
413 }
414 
415 #endif	/* _KERNEL */
416 
417 /*ARGSUSED*/
418 static int
nsmb_ioctl(dev_t dev,int cmd,intptr_t arg,int flags,cred_t * cr,int * rvalp)419 nsmb_ioctl(dev_t dev, int cmd, intptr_t arg, int flags,	/* model.h */
420     cred_t *cr, int *rvalp)
421 {
422 	smb_dev_t *sdp;
423 	int err;
424 
425 	sdp = ddi_get_soft_state(statep, getminor(dev));
426 	if (sdp == NULL) {
427 		return (EBADF);
428 	}
429 	if ((sdp->sd_flags & NSMBFL_OPEN) == 0) {
430 		return (EBADF);
431 	}
432 
433 	/*
434 	 * Dont give access if the zone id is not as the same as we
435 	 * set in the nsmb_open or dont belong to the global zone.
436 	 * Check if the user belongs to this zone..
437 	 */
438 	if (sdp->zoneid != getzoneid())
439 		return (EIO);
440 
441 	/*
442 	 * We have a zone_shutdown call back that kills all the VCs
443 	 * in a zone that's shutting down.  That action will cause
444 	 * all of these ioctls to fail on such VCs, so no need to
445 	 * check the zone status here on every ioctl call.
446 	 */
447 
448 	err = smb_usr_ioctl(sdp, cmd, arg, flags, cr);
449 
450 	return (err);
451 }
452 
453 /*
454  * This does "clone" open, meaning it automatically
455  * assigns an available minor unit for each open.
456  */
457 /*ARGSUSED*/
458 static int
nsmb_open(dev_t * dev,int flags,int otyp,cred_t * cr)459 nsmb_open(dev_t *dev, int flags, int otyp, cred_t *cr)
460 {
461 	smb_dev_t *sdp;
462 	minor_t m;
463 
464 	mutex_enter(&dev_lck);
465 
466 	for (m = last_minor + 1; m != last_minor; m++) {
467 		if (m > NSMB_MAX_MINOR)
468 			m = NSMB_MIN_MINOR;
469 
470 		if (ddi_get_soft_state(statep, m) == NULL) {
471 			last_minor = m;
472 			goto found;
473 		}
474 	}
475 
476 	/* No available minor units. */
477 	mutex_exit(&dev_lck);
478 	return (ENXIO);
479 
480 found:
481 	/* NB: dev_lck still held */
482 	if (ddi_soft_state_zalloc(statep, m) == DDI_FAILURE) {
483 		mutex_exit(&dev_lck);
484 		return (ENXIO);
485 	}
486 	if ((sdp = ddi_get_soft_state(statep, m)) == NULL) {
487 		mutex_exit(&dev_lck);
488 		return (ENXIO);
489 	}
490 	*dev = makedevice(nsmb_major, m);
491 	mutex_exit(&dev_lck);
492 
493 	sdp->sd_flags |= NSMBFL_OPEN;
494 	sdp->zoneid = crgetzoneid(cr);
495 	mutex_init(&sdp->sd_lock, NULL, MUTEX_DRIVER, NULL);
496 
497 	return (0);
498 }
499 
500 /*ARGSUSED*/
501 static int
nsmb_close(dev_t dev,int flags,int otyp,cred_t * cr)502 nsmb_close(dev_t dev, int flags, int otyp, cred_t *cr)
503 {
504 	minor_t inst = getminor(dev);
505 	smb_dev_t *sdp;
506 	int err;
507 
508 	/*
509 	 * 1. Check the validity of the minor number.
510 	 * 2. Release any shares/vc associated  with the connection.
511 	 * 3. Can close the minor number.
512 	 * 4. Deallocate any resources allocated in open() call.
513 	 */
514 
515 	sdp = ddi_get_soft_state(statep, inst);
516 	if (sdp != NULL)
517 		err = nsmb_close2(sdp, cr);
518 	else
519 		err = ENXIO;
520 
521 	/*
522 	 * Free the instance
523 	 */
524 	mutex_enter(&dev_lck);
525 	ddi_soft_state_free(statep, inst);
526 	mutex_exit(&dev_lck);
527 	return (err);
528 }
529 
530 /*ARGSUSED*/
531 static int
nsmb_close2(smb_dev_t * sdp,cred_t * cr)532 nsmb_close2(smb_dev_t *sdp, cred_t *cr)
533 {
534 	struct smb_vc *vcp;
535 	struct smb_share *ssp;
536 	struct smb_fh *fhp;
537 
538 	fhp = sdp->sd_fh;
539 	if (fhp != NULL)
540 		smb_fh_rele(fhp);
541 
542 	ssp = sdp->sd_share;
543 	if (ssp != NULL)
544 		smb_share_rele(ssp);
545 
546 	vcp = sdp->sd_vc;
547 	if (vcp != NULL) {
548 		/*
549 		 * If this dev minor was opened by smbiod,
550 		 * mark this VC as "dead" because it now
551 		 * will have no IOD to service it.
552 		 */
553 		if (sdp->sd_flags & NSMBFL_IOD)
554 			smb_iod_disconnect(vcp);
555 		smb_vc_rele(vcp);
556 	}
557 	mutex_destroy(&sdp->sd_lock);
558 
559 	return (0);
560 }
561 
562 /*
563  * Helper for SMBIOC_DUP_DEV
564  * Duplicate state from the FD @arg ("from") onto
565  * the FD for this device instance.
566  */
567 int
smb_usr_dup_dev(smb_dev_t * sdp,intptr_t arg,int flags)568 smb_usr_dup_dev(smb_dev_t *sdp, intptr_t arg, int flags)
569 {
570 #ifdef	_KERNEL
571 	file_t *fp = NULL;
572 	vnode_t *vp;
573 #endif	/* _KERNEL */
574 	smb_dev_t *from_sdp;
575 	dev_t dev;
576 	int32_t ufd;
577 	int err;
578 
579 	/* Should be no VC */
580 	if (sdp->sd_vc != NULL)
581 		return (EISCONN);
582 
583 	/*
584 	 * Get from_sdp (what we will duplicate)
585 	 */
586 	if (ddi_copyin((void *) arg, &ufd, sizeof (ufd), flags))
587 		return (EFAULT);
588 #ifdef	_KERNEL
589 	if ((fp = getf(ufd)) == NULL)
590 		return (EBADF);
591 	/* rele fp below */
592 	vp = fp->f_vnode;
593 	dev = vp->v_rdev;
594 #else	/* _KERNEL */
595 	/*
596 	 * No getf(ufd) -- ufd is really a dev32_t
597 	 */
598 	dev = expldev((dev32_t)ufd);
599 #endif	/* _KERNEL */
600 	if (dev == 0 || dev == NODEV ||
601 	    getmajor(dev) != nsmb_major) {
602 		err = EINVAL;
603 		goto out;
604 	}
605 
606 	from_sdp = ddi_get_soft_state(statep, getminor(dev));
607 	if (from_sdp == NULL) {
608 		err = EINVAL;
609 		goto out;
610 	}
611 
612 	/*
613 	 * Duplicate VC and share references onto this FD.
614 	 */
615 	if ((sdp->sd_vc = from_sdp->sd_vc) != NULL)
616 		smb_vc_hold(sdp->sd_vc);
617 	if ((sdp->sd_share = from_sdp->sd_share) != NULL)
618 		smb_share_hold(sdp->sd_share);
619 	sdp->sd_level = from_sdp->sd_level;
620 	err = 0;
621 
622 out:
623 #ifdef	_KERNEL
624 	if (fp)
625 		releasef(ufd);
626 #endif	/* _KERNEL */
627 	return (err);
628 }
629 
630 
631 /*
632  * Helper used by smbfs_mount
633  */
634 int
smb_dev2share(int fd,struct smb_share ** sspp)635 smb_dev2share(int fd, struct smb_share **sspp)
636 {
637 #ifdef	_KERNEL
638 	file_t *fp = NULL;
639 	vnode_t *vp;
640 #endif	/* _KERNEL */
641 	smb_dev_t *sdp;
642 	smb_share_t *ssp;
643 	dev_t dev;
644 	int err;
645 
646 #ifdef	_KERNEL
647 	if ((fp = getf(fd)) == NULL)
648 		return (EBADF);
649 	/* rele fp below */
650 	vp = fp->f_vnode;
651 	dev = vp->v_rdev;
652 #else	/* _KERNEL */
653 	/*
654 	 * No getf(ufd) -- fd is really a dev32_t
655 	 */
656 	dev = expldev((dev32_t)fd);
657 #endif	/* _KERNEL */
658 	if (dev == 0 || dev == NODEV ||
659 	    getmajor(dev) != nsmb_major) {
660 		err = EINVAL;
661 		goto out;
662 	}
663 
664 	sdp = ddi_get_soft_state(statep, getminor(dev));
665 	if (sdp == NULL) {
666 		err = EINVAL;
667 		goto out;
668 	}
669 
670 	ssp = sdp->sd_share;
671 	if (ssp == NULL) {
672 		err = ENOTCONN;
673 		goto out;
674 	}
675 
676 	/*
677 	 * Our caller gains a ref. to this share.
678 	 */
679 	*sspp = ssp;
680 	smb_share_hold(ssp);
681 	err = 0;
682 
683 out:
684 #ifdef	_KERNEL
685 	if (fp)
686 		releasef(fd);
687 #endif	/* _KERNEL */
688 	return (err);
689 }
690