xref: /titanic_44/usr/src/uts/common/io/sysmsg.c (revision 49df45665521d358380d3324f56fbc1ef143e72f)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * System message redirection driver for Sun.
31  *
32  * Redirects system message output to the device designated as the underlying
33  * "hardware" console, as given by the value of sysmvp.  The implementation
34  * assumes that sysmvp denotes a STREAMS device; the assumption is justified
35  * since consoles must be capable of effecting tty semantics.
36  */
37 
38 #include <sys/types.h>
39 #include <sys/kmem.h>
40 #include <sys/open.h>
41 #include <sys/param.h>
42 #include <sys/systm.h>
43 #include <sys/signal.h>
44 #include <sys/cred.h>
45 #include <sys/user.h>
46 #include <sys/proc.h>
47 #include <sys/vnode.h>
48 #include <sys/uio.h>
49 #include <sys/stat.h>
50 #include <sys/file.h>
51 #include <sys/session.h>
52 #include <sys/stream.h>
53 #include <sys/strsubr.h>
54 #include <sys/poll.h>
55 #include <sys/debug.h>
56 #include <sys/sysmsg_impl.h>
57 #include <sys/conf.h>
58 #include <sys/termios.h>
59 #include <sys/errno.h>
60 #include <sys/modctl.h>
61 #include <sys/pathname.h>
62 #include <sys/ddi.h>
63 #include <sys/sunddi.h>
64 #include <sys/consdev.h>
65 #include <sys/policy.h>
66 
67 /*
68  * internal functions
69  */
70 static int sysmopen(dev_t *, int, int, cred_t *);
71 static int sysmclose(dev_t, int, int, cred_t *);
72 static int sysmread(dev_t, struct uio *, cred_t *);
73 static int sysmwrite(dev_t, struct uio *, cred_t *);
74 static int sysmioctl(dev_t, int, intptr_t, int, cred_t *, int *);
75 static int sysmpoll(dev_t, short, int, short *, struct pollhead **);
76 static int sysm_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
77 static int sysm_attach(dev_info_t *, ddi_attach_cmd_t);
78 static int sysm_detach(dev_info_t *, ddi_detach_cmd_t);
79 static void bind_consadm_conf(char *);
80 static int checkarg(dev_t);
81 
82 static dev_info_t *sysm_dip;		/* private copy of devinfo pointer */
83 
84 static struct cb_ops sysm_cb_ops = {
85 
86 	sysmopen,		/* open */
87 	sysmclose,		/* close */
88 	nodev,			/* strategy */
89 	nodev,			/* print */
90 	nodev,			/* dump */
91 	sysmread,		/* read */
92 	sysmwrite,		/* write */
93 	sysmioctl,		/* ioctl */
94 	nodev,			/* devmap */
95 	nodev,			/* mmap */
96 	nodev, 			/* segmap */
97 	sysmpoll,		/* poll */
98 	ddi_prop_op,		/* cb_prop_op */
99 	NULL,			/* streamtab  */
100 	D_NEW | D_MP,		/* Driver compatibility flag */
101 	CB_REV,			/* cb_rev */
102 	nodev,			/* aread */
103 	nodev			/* awrite */
104 };
105 
106 static struct dev_ops sysm_ops = {
107 
108 	DEVO_REV,		/* devo_rev, */
109 	0,			/* refcnt  */
110 	sysm_info,		/* info */
111 	nulldev,		/* identify */
112 	nulldev,		/* probe */
113 	sysm_attach,		/* attach */
114 	sysm_detach,		/* detach */
115 	nodev,			/* reset */
116 	&sysm_cb_ops,		/* driver operations */
117 	(struct bus_ops *)0,	/* bus operations */
118 	nulldev			/* power */
119 
120 };
121 
122 /*
123  * Global variables associated with the console device:
124  */
125 
126 #define	SYS_SYSMIN	0	/* sysmsg minor number */
127 #define	SYS_MSGMIN	1	/* msglog minor number */
128 #define	SYSPATHLEN	255	/* length of device path */
129 
130 /*
131  * Private driver state:
132  */
133 
134 #define	MAXDEVS 5
135 
136 typedef struct {
137 	dev_t	dca_devt;
138 	int	dca_flags;
139 	vnode_t	*dca_vp;
140 	krwlock_t	dca_lock;
141 	char	dca_name[SYSPATHLEN];
142 } devicecache_t;
143 
144 /* list of dyn. + persist. config'ed dev's */
145 static devicecache_t sysmcache[MAXDEVS];
146 static kmutex_t	dcvp_mutex;
147 static vnode_t	*dcvp = NULL;
148 
149 /* flags for device cache */
150 #define	SYSM_DISABLED	0x0
151 #define	SYSM_ENABLED	0x1
152 
153 /*
154  * Module linkage information for the kernel.
155  */
156 
157 static struct modldrv modldrv = {
158 	&mod_driverops, /* Type of module.  This one is a pseudo driver */
159 	"System message redirection (fanout) driver %I%",
160 	&sysm_ops,	/* driver ops */
161 };
162 
163 static struct modlinkage modlinkage = {
164 	MODREV_1,
165 	&modldrv,
166 	NULL
167 };
168 
169 int
170 _init(void)
171 {
172 	return (mod_install(&modlinkage));
173 }
174 
175 int
176 _fini(void)
177 {
178 	return (mod_remove(&modlinkage));
179 }
180 
181 int
182 _info(struct modinfo *modinfop)
183 {
184 	return (mod_info(&modlinkage, modinfop));
185 }
186 
187 /*
188  * DDI glue routines
189  */
190 static int
191 sysm_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
192 {
193 	int i;
194 
195 	switch (cmd) {
196 	case DDI_ATTACH:
197 		ASSERT(sysm_dip == NULL);
198 
199 		if (ddi_create_minor_node(devi, "sysmsg", S_IFCHR,
200 		    SYS_SYSMIN, DDI_PSEUDO, NULL) == DDI_FAILURE ||
201 		    ddi_create_minor_node(devi, "msglog", S_IFCHR,
202 		    SYS_MSGMIN, DDI_PSEUDO, NULL) == DDI_FAILURE) {
203 			ddi_remove_minor_node(devi, NULL);
204 			return (DDI_FAILURE);
205 		}
206 
207 		for (i = 0; i < MAXDEVS; i++) {
208 			rw_init(&sysmcache[i].dca_lock, NULL, RW_DRIVER, NULL);
209 		}
210 
211 		/* set everything up .. */
212 		bind_consadm_conf("/etc/consadm.conf");
213 		sysm_dip = devi;
214 		return (DDI_SUCCESS);
215 	case DDI_SUSPEND:
216 	case DDI_PM_SUSPEND:
217 		return (DDI_SUCCESS);
218 	default:
219 		return (DDI_FAILURE);
220 	}
221 }
222 
223 static int
224 sysm_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
225 {
226 	int i;
227 
228 	switch (cmd) {
229 	case DDI_DETACH:
230 		ASSERT(sysm_dip == devi);
231 
232 		if (dcvp) {
233 			(void) VOP_CLOSE(dcvp, FWRITE, 1, (offset_t)0, kcred);
234 			VN_RELE(dcvp);
235 			dcvp = NULL;
236 		}
237 		for (i = 0; i < MAXDEVS; i++) {
238 			if (sysmcache[i].dca_vp != NULL) {
239 				(void) VOP_CLOSE(sysmcache[i].dca_vp, 0,
240 				    1, (offset_t)0, 0);
241 				VN_RELE(sysmcache[i].dca_vp);
242 			}
243 			sysmcache[i].dca_vp = NULL;
244 			rw_destroy(&sysmcache[i].dca_lock);
245 		}
246 
247 		ddi_remove_minor_node(devi, NULL);
248 		sysm_dip = NULL;
249 		return (DDI_SUCCESS);
250 
251 	case DDI_SUSPEND:
252 	case DDI_PM_SUSPEND:
253 		return (DDI_SUCCESS);
254 	default:
255 		return (DDI_FAILURE);
256 	}
257 
258 }
259 
260 /* ARGSUSED */
261 static int
262 sysm_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
263 {
264 	int rval = DDI_FAILURE;
265 	minor_t instance;
266 
267 	instance = getminor((dev_t)arg);
268 
269 	switch (infocmd) {
270 	case DDI_INFO_DEVT2DEVINFO:
271 		if (sysm_dip != NULL &&
272 		    (instance == SYS_SYSMIN || instance == SYS_MSGMIN)) {
273 			*result = sysm_dip;
274 			rval = DDI_SUCCESS;
275 		}
276 		break;
277 
278 	case DDI_INFO_DEVT2INSTANCE:
279 		if (instance == SYS_SYSMIN || instance == SYS_MSGMIN) {
280 			*result = NULL;
281 			rval = DDI_SUCCESS;
282 		}
283 		break;
284 
285 	default:
286 		break;
287 	}
288 
289 	return (rval);
290 }
291 
292 /*
293  * Parse the contents of the buffer, and bind the named
294  * devices as auxiliary consoles using our own ioctl routine.
295  *
296  * Comments begin with '#' and are terminated only by a newline
297  * Device names begin with a '/', and are terminated by a newline,
298  * space, '#' or tab.
299  */
300 static void
301 parse_buffer(char *buf, ssize_t fsize)
302 {
303 	char *ebuf = buf + fsize;
304 	char *devname = NULL;
305 	int eatcomments = 0;
306 
307 	while (buf < ebuf) {
308 		if (eatcomments) {
309 			if (*buf++ == '\n')
310 				eatcomments = 0;
311 			continue;
312 		}
313 		switch (*buf) {
314 		case '/':
315 			if (devname == NULL)
316 				devname = buf;
317 			break;
318 		case '#':
319 			eatcomments = 1;
320 			/*FALLTHROUGH*/
321 		case ' ':
322 		case '\t':
323 		case '\n':
324 			*buf = '\0';
325 			if (devname == NULL)
326 				break;
327 			(void) sysmioctl(NODEV, CIOCSETCONSOLE,
328 			    (intptr_t)devname, FNATIVE|FKIOCTL|FREAD|FWRITE,
329 			    kcred, NULL);
330 			devname = NULL;
331 			break;
332 		default:
333 			break;
334 		}
335 		buf++;
336 	}
337 }
338 
339 #define	CNSADM_BYTES_MAX	2000	/* XXX  nasty fixed size */
340 
341 static void
342 bind_consadm_conf(char *path)
343 {
344 	struct vattr vattr;
345 	vnode_t *vp;
346 	void *buf;
347 	size_t size;
348 	ssize_t resid;
349 	int err = 0;
350 
351 	if (vn_open(path, UIO_SYSSPACE, FREAD, 0, &vp, 0, 0) != 0)
352 		return;
353 	vattr.va_mask = AT_SIZE;
354 	if ((err = VOP_GETATTR(vp, &vattr, 0, kcred)) != 0) {
355 		cmn_err(CE_WARN, "sysmsg: getattr: '%s': error %d",
356 		    path, err);
357 		goto closevp;
358 	}
359 
360 	size = vattr.va_size > CNSADM_BYTES_MAX ?
361 	    CNSADM_BYTES_MAX : (ssize_t)vattr.va_size;
362 	buf = kmem_alloc(size, KM_SLEEP);
363 
364 	if ((err = vn_rdwr(UIO_READ, vp, buf, size, (offset_t)0,
365 	    UIO_SYSSPACE, 0, (rlim64_t)0, kcred, &resid)) != 0)
366 		cmn_err(CE_WARN, "sysmsg: vn_rdwr: '%s': error %d",
367 		    path, err);
368 	else
369 		parse_buffer(buf, size - resid);
370 
371 	kmem_free(buf, size);
372 closevp:
373 	(void) VOP_CLOSE(vp, FREAD, 1, (offset_t)0, kcred);
374 	VN_RELE(vp);
375 }
376 
377 /* ARGSUSED */
378 static int
379 sysmopen(dev_t *dev, int flag, int state, cred_t *cred)
380 {
381 	int	i;
382 	vnode_t	*vp;
383 	minor_t instance;
384 
385 	instance = getminor(*dev);
386 
387 	if (state != OTYP_CHR || (instance != 0 && instance != 1))
388 		return (ENXIO);
389 
390 	mutex_enter(&dcvp_mutex);
391 	if ((dcvp == NULL) && (vn_open("/dev/console",
392 	    UIO_SYSSPACE, FWRITE, 0, &dcvp, 0, 0) != 0)) {
393 		mutex_exit(&dcvp_mutex);
394 		return (ENXIO);
395 	}
396 	mutex_exit(&dcvp_mutex);
397 
398 	for (i = 0; i < MAXDEVS; i++) {
399 		rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
400 		if ((sysmcache[i].dca_flags & SYSM_ENABLED) &&
401 		    sysmcache[i].dca_vp == NULL) {
402 			/*
403 			 * 4196476 - FTRUNC was causing E10K to return EINVAL
404 			 * on open
405 			 */
406 			flag = flag & ~FTRUNC;
407 			/*
408 			 * Open failures on the auxiliary consoles are
409 			 * not returned because we don't care if some
410 			 * subset get an error. We know the default console
411 			 * is okay, and preserve the semantics of the
412 			 * open for the default console.
413 			 * Set NONBLOCK|NDELAY in case there's no carrier.
414 			 */
415 			if (vn_open(sysmcache[i].dca_name, UIO_SYSSPACE,
416 			    flag | FNONBLOCK | FNDELAY, 0, &vp, 0, 0) == 0)
417 				sysmcache[i].dca_vp = vp;
418 		}
419 		rw_exit(&sysmcache[i].dca_lock);
420 	}
421 
422 	return (0);
423 }
424 
425 /* ARGSUSED */
426 static int
427 sysmclose(dev_t dev, int flag, int state, cred_t *cred)
428 {
429 	int	i;
430 
431 	if (state != OTYP_CHR)
432 		return (ENXIO);
433 
434 	/*
435 	 * Close the auxiliary consoles, we're not concerned with
436 	 * passing up the errors.
437 	 */
438 	for (i = 0; i < MAXDEVS; i++) {
439 		rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
440 		if (sysmcache[i].dca_vp != NULL) {
441 			(void) VOP_CLOSE(sysmcache[i].dca_vp, flag,
442 			    1, (offset_t)0, cred);
443 			VN_RELE(sysmcache[i].dca_vp);
444 			sysmcache[i].dca_vp = NULL;
445 		}
446 		rw_exit(&sysmcache[i].dca_lock);
447 	}
448 
449 	return (0);
450 }
451 
452 /* Reads occur only on the default console */
453 
454 /* ARGSUSED */
455 static int
456 sysmread(dev_t dev, struct uio *uio, cred_t *cred)
457 {
458 	ASSERT(dcvp != NULL);
459 	return (VOP_READ(dcvp, uio, 0, cred, NULL));
460 }
461 
462 /* ARGSUSED */
463 static int
464 sysmwrite(dev_t dev, struct uio *uio, cred_t *cred)
465 {
466 	int	i = 0;
467 	iovec_t	uio_iov;
468 	struct uio	tuio;
469 
470 	ASSERT(dcvp != NULL);
471 	ASSERT(uio != NULL);
472 
473 	for (i = 0; i < MAXDEVS; i++) {
474 		rw_enter(&sysmcache[i].dca_lock, RW_READER);
475 		if (sysmcache[i].dca_vp != NULL &&
476 		    (sysmcache[i].dca_flags & SYSM_ENABLED)) {
477 			tuio = *uio;
478 			uio_iov = *(uio->uio_iov);
479 			tuio.uio_iov = &uio_iov;
480 			(void) VOP_WRITE(sysmcache[i].dca_vp, &tuio, 0, cred,
481 				NULL);
482 		}
483 		rw_exit(&sysmcache[i].dca_lock);
484 	}
485 	return (VOP_WRITE(dcvp, uio, 0, cred, NULL));
486 }
487 
488 /* ARGSUSED */
489 static int
490 sysmioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cred, int *rvalp)
491 {
492 	int	rval = 0;
493 	int	error = 0;
494 	size_t	size = 0;
495 	int	i;
496 	char	*infop;
497 	char	found = 0;
498 	dev_t	newdevt = (dev_t)NODEV;	/* because 0 == /dev/console */
499 	vnode_t	*vp;
500 
501 	switch (cmd) {
502 	case CIOCGETCONSOLE:
503 		/* Sum over the number of enabled devices */
504 		for (i = 0; i < MAXDEVS; i++) {
505 			if (sysmcache[i].dca_flags & SYSM_ENABLED)
506 				/* list is space separated, followed by NULL */
507 				size += strlen(sysmcache[i].dca_name) + 1;
508 		}
509 		if (size == 0)
510 			return (0);
511 		break;
512 	case CIOCSETCONSOLE:
513 	case CIOCRMCONSOLE:
514 		size = sizeof (sysmcache[0].dca_name);
515 		break;
516 	case CIOCTTYCONSOLE:
517 	{
518 		dev_t	d;
519 		dev32_t	d32;
520 		extern dev_t rwsconsdev, rconsdev, uconsdev;
521 		proc_t	*p;
522 
523 		if (drv_getparm(UPROCP, &p) != 0)
524 			return (ENODEV);
525 		else
526 			d = cttydev(p);
527 		/*
528 		 * If the controlling terminal is the real
529 		 * or workstation console device, map to what the
530 		 * user thinks is the console device.
531 		 */
532 		if (d == rwsconsdev || d == rconsdev)
533 			d = uconsdev;
534 		if ((flag & FMODELS) != FNATIVE) {
535 			if (!cmpldev(&d32, d))
536 				return (EOVERFLOW);
537 			if (ddi_copyout(&d32, (caddr_t)arg, sizeof (d32),
538 			    flag))
539 				return (EFAULT);
540 		} else {
541 			if (ddi_copyout(&d, (caddr_t)arg, sizeof (d), flag))
542 				return (EFAULT);
543 		}
544 		return (0);
545 	}
546 	default:
547 		/* everything else is sent to the console device */
548 		return (VOP_IOCTL(dcvp, cmd, arg, flag, cred, rvalp));
549 	}
550 
551 	if ((rval = secpolicy_console(cred)) != 0)
552 		return (EPERM);
553 
554 	infop = kmem_alloc(size, KM_SLEEP);
555 	if (flag & FKIOCTL)
556 		error = copystr((caddr_t)arg, infop, size, NULL);
557 	else
558 		error = copyinstr((caddr_t)arg, infop, size, NULL);
559 
560 	if (error) {
561 		switch (cmd) {
562 		case CIOCGETCONSOLE:
563 			/*
564 			 * If the buffer is null, then return a byte count
565 			 * to user land.
566 			 */
567 			*rvalp = size;
568 			goto err_exit;
569 		default:
570 			rval = EFAULT;
571 			goto err_exit;
572 		}
573 	}
574 
575 	if (infop[0] != NULL) {
576 		if ((rval = lookupname(infop, UIO_SYSSPACE, FOLLOW,
577 		    NULLVPP, &vp)) == 0) {
578 			if (vp->v_type != VCHR) {
579 				VN_RELE(vp);
580 				rval = EINVAL;
581 				goto err_exit;
582 			}
583 			newdevt = vp->v_rdev;
584 			VN_RELE(vp);
585 		} else
586 			goto err_exit;
587 	}
588 
589 	switch (cmd) {
590 	case CIOCGETCONSOLE:
591 		/*
592 		 * Return the list of device names that are enabled.
593 		 */
594 		for (i = 0; i < MAXDEVS; i++) {
595 			rw_enter(&sysmcache[i].dca_lock, RW_READER);
596 			if (sysmcache[i].dca_flags & SYSM_ENABLED) {
597 				if (infop[0] != NULL)
598 					(void) strcat(infop, " ");
599 				(void) strcat(infop, sysmcache[i].dca_name);
600 			}
601 			rw_exit(&sysmcache[i].dca_lock);
602 		}
603 		if (rval == 0 && copyoutstr(infop, (void *)arg, size, NULL))
604 			rval = EFAULT;
605 		break;
606 
607 	case CIOCSETCONSOLE:
608 		if ((rval = checkarg(newdevt)) != 0)
609 			break;
610 		/*
611 		 * The device does not have to be open or disabled to
612 		 * perform the set console.
613 		 */
614 		for (i = 0; i < MAXDEVS; i++) {
615 			rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
616 			if (sysmcache[i].dca_devt == newdevt &&
617 			    (sysmcache[i].dca_flags & SYSM_ENABLED)) {
618 				(void) strcpy(sysmcache[i].dca_name, infop);
619 				rval = EEXIST;
620 				rw_exit(&sysmcache[i].dca_lock);
621 				break;
622 			} else if (sysmcache[i].dca_devt == newdevt &&
623 			    sysmcache[i].dca_flags == SYSM_DISABLED) {
624 				sysmcache[i].dca_flags |= SYSM_ENABLED;
625 				(void) strcpy(sysmcache[i].dca_name, infop);
626 				rw_exit(&sysmcache[i].dca_lock);
627 				found = 1;
628 				break;
629 			} else if (sysmcache[i].dca_devt == 0) {
630 				ASSERT(sysmcache[i].dca_vp == NULL &&
631 				    sysmcache[i].dca_flags == SYSM_DISABLED);
632 				(void) strcpy(sysmcache[i].dca_name, infop);
633 				sysmcache[i].dca_flags = SYSM_ENABLED;
634 				sysmcache[i].dca_devt = newdevt;
635 				rw_exit(&sysmcache[i].dca_lock);
636 				found = 1;
637 				break;
638 			}
639 			rw_exit(&sysmcache[i].dca_lock);
640 		}
641 		if (found == 0 && rval == 0)
642 			rval = ENOENT;
643 		break;
644 
645 	case CIOCRMCONSOLE:
646 		for (i = 0; i < MAXDEVS; i++) {
647 			rw_enter(&sysmcache[i].dca_lock, RW_WRITER);
648 			if (sysmcache[i].dca_devt == newdevt) {
649 				sysmcache[i].dca_flags = SYSM_DISABLED;
650 				sysmcache[i].dca_name[0] = '\0';
651 				rw_exit(&sysmcache[i].dca_lock);
652 				found = 1;
653 				break;
654 			}
655 			rw_exit(&sysmcache[i].dca_lock);
656 		}
657 		if (found == 0)
658 			rval = ENOENT;
659 		break;
660 
661 	default:
662 		break;
663 	}
664 
665 err_exit:
666 	kmem_free(infop, size);
667 	return (rval);
668 }
669 
670 /* As with the read, we poll only the default console */
671 
672 /* ARGSUSED */
673 static int
674 sysmpoll(dev_t dev, short events, int anyyet, short *reventsp,
675 	struct pollhead **phpp)
676 {
677 	return (VOP_POLL(dcvp, events, anyyet, reventsp, phpp));
678 }
679 
680 /* Sanity check that the device is good */
681 static int
682 checkarg(dev_t devt)
683 {
684 	int rval = 0;
685 	vnode_t	*vp;
686 	extern dev_t rwsconsdev, rconsdev, uconsdev;
687 
688 	if (devt == rconsdev || devt == rwsconsdev || devt == uconsdev) {
689 		rval = EBUSY;
690 		goto err_exit;
691 	}
692 	if ((rval = lookupname("/dev/sysmsg", UIO_SYSSPACE, FOLLOW,
693 	    NULLVPP, &vp)) == 0) {
694 		if (devt == vp->v_rdev) {
695 			VN_RELE(vp);
696 			rval = EINVAL;
697 			goto err_exit;
698 		}
699 		VN_RELE(vp);
700 	}
701 	if ((rval = lookupname("/dev/msglog", UIO_SYSSPACE, FOLLOW,
702 	    NULLVPP, &vp)) == 0) {
703 		if (devt == vp->v_rdev) {
704 			VN_RELE(vp);
705 			rval = EINVAL;
706 			goto err_exit;
707 		}
708 		VN_RELE(vp);
709 	}
710 
711 err_exit:
712 	return (rval);
713 }
714