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