xref: /titanic_51/usr/src/uts/common/fs/dev/sdev_ptsops.c (revision 9e5aa9d8a21f8efa8ba9c9e4a0aa6edc66d07eb2)
1facf4a8dSllai1 /*
2facf4a8dSllai1  * CDDL HEADER START
3facf4a8dSllai1  *
4facf4a8dSllai1  * The contents of this file are subject to the terms of the
5facf4a8dSllai1  * Common Development and Distribution License (the "License").
6facf4a8dSllai1  * You may not use this file except in compliance with the License.
7facf4a8dSllai1  *
8facf4a8dSllai1  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9facf4a8dSllai1  * or http://www.opensolaris.org/os/licensing.
10facf4a8dSllai1  * See the License for the specific language governing permissions
11facf4a8dSllai1  * and limitations under the License.
12facf4a8dSllai1  *
13facf4a8dSllai1  * When distributing Covered Code, include this CDDL HEADER in each
14facf4a8dSllai1  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15facf4a8dSllai1  * If applicable, add the following below this CDDL HEADER, with the
16facf4a8dSllai1  * fields enclosed by brackets "[]" replaced with your own identifying
17facf4a8dSllai1  * information: Portions Copyright [yyyy] [name of copyright owner]
18facf4a8dSllai1  *
19facf4a8dSllai1  * CDDL HEADER END
20facf4a8dSllai1  */
21facf4a8dSllai1 /*
22aac43a5fSjg  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23facf4a8dSllai1  * Use is subject to license terms.
24facf4a8dSllai1  */
25facf4a8dSllai1 
26facf4a8dSllai1 /*
27facf4a8dSllai1  * vnode ops for the /dev/pts directory
28facf4a8dSllai1  *	The lookup is based on the internal pty table. We also
29facf4a8dSllai1  *	override readdir in order to delete pts nodes no longer
30facf4a8dSllai1  *	in use.
31facf4a8dSllai1  */
32facf4a8dSllai1 
33facf4a8dSllai1 #include <sys/types.h>
34facf4a8dSllai1 #include <sys/param.h>
35facf4a8dSllai1 #include <sys/sysmacros.h>
36facf4a8dSllai1 #include <sys/sunndi.h>
37facf4a8dSllai1 #include <fs/fs_subr.h>
38facf4a8dSllai1 #include <sys/fs/dv_node.h>
39facf4a8dSllai1 #include <sys/fs/sdev_impl.h>
40facf4a8dSllai1 #include <sys/policy.h>
41facf4a8dSllai1 #include <sys/ptms.h>
42facf4a8dSllai1 #include <sys/stat.h>
43aa59c4cbSrsb #include <sys/vfs_opreg.h>
44facf4a8dSllai1 
45facf4a8dSllai1 #define	DEVPTS_UID_DEFAULT	0
46facf4a8dSllai1 #define	DEVPTS_GID_DEFAULT	3
47facf4a8dSllai1 #define	DEVPTS_DEVMODE_DEFAULT	(0620)
48facf4a8dSllai1 
49facf4a8dSllai1 #define	isdigit(ch)	((ch) >= '0' && (ch) <= '9')
50facf4a8dSllai1 
51facf4a8dSllai1 static vattr_t devpts_vattr = {
52facf4a8dSllai1 	AT_TYPE|AT_MODE|AT_UID|AT_GID,		/* va_mask */
53facf4a8dSllai1 	VCHR,					/* va_type */
54facf4a8dSllai1 	S_IFCHR | DEVPTS_DEVMODE_DEFAULT,	/* va_mode */
55facf4a8dSllai1 	DEVPTS_UID_DEFAULT,			/* va_uid */
56facf4a8dSllai1 	DEVPTS_GID_DEFAULT,			/* va_gid */
57facf4a8dSllai1 	0					/* 0 hereafter */
58facf4a8dSllai1 };
59facf4a8dSllai1 
60facf4a8dSllai1 struct vnodeops		*devpts_vnodeops;
61facf4a8dSllai1 
62facf4a8dSllai1 struct vnodeops *
63facf4a8dSllai1 devpts_getvnodeops(void)
64facf4a8dSllai1 {
65facf4a8dSllai1 	return (devpts_vnodeops);
66facf4a8dSllai1 }
67facf4a8dSllai1 
68facf4a8dSllai1 /*
69facf4a8dSllai1  * Convert string to minor number. Some care must be taken
70facf4a8dSllai1  * as we are processing user input. Catch cases like
71facf4a8dSllai1  * /dev/pts/4foo and /dev/pts/-1
72facf4a8dSllai1  */
73facf4a8dSllai1 static int
74facf4a8dSllai1 devpts_strtol(const char *nm, minor_t *mp)
75facf4a8dSllai1 {
76facf4a8dSllai1 	long uminor = 0;
77facf4a8dSllai1 	char *endptr = NULL;
78facf4a8dSllai1 
79facf4a8dSllai1 	if (nm == NULL || !isdigit(*nm))
80facf4a8dSllai1 		return (EINVAL);
81facf4a8dSllai1 
82facf4a8dSllai1 	*mp = 0;
83facf4a8dSllai1 	if (ddi_strtol(nm, &endptr, 10, &uminor) != 0 ||
84facf4a8dSllai1 	    *endptr != '\0' || uminor < 0) {
85facf4a8dSllai1 		return (EINVAL);
86facf4a8dSllai1 	}
87facf4a8dSllai1 
88bc1009abSjg 	*mp = (minor_t)uminor;
89facf4a8dSllai1 	return (0);
90facf4a8dSllai1 }
91facf4a8dSllai1 
92facf4a8dSllai1 /*
93facf4a8dSllai1  * Check if a pts sdev_node is still valid - i.e. it represents a current pty.
94facf4a8dSllai1  * This serves two purposes
95facf4a8dSllai1  *	- only valid pts nodes are returned during lookup() and readdir().
96facf4a8dSllai1  *	- since pts sdev_nodes are not actively destroyed when a pty goes
97facf4a8dSllai1  *	  away, we use the validator to do deferred cleanup i.e. when such
98facf4a8dSllai1  *	  nodes are encountered during subsequent lookup() and readdir().
99facf4a8dSllai1  */
100facf4a8dSllai1 /*ARGSUSED*/
101facf4a8dSllai1 int
102facf4a8dSllai1 devpts_validate(struct sdev_node *dv)
103facf4a8dSllai1 {
104facf4a8dSllai1 	minor_t min;
105facf4a8dSllai1 	uid_t uid;
106facf4a8dSllai1 	gid_t gid;
107facf4a8dSllai1 	timestruc_t now;
108facf4a8dSllai1 	char *nm = dv->sdev_name;
109facf4a8dSllai1 
110facf4a8dSllai1 	ASSERT(dv->sdev_state == SDEV_READY);
111facf4a8dSllai1 
112facf4a8dSllai1 	/* validate only READY nodes */
113facf4a8dSllai1 	if (dv->sdev_state != SDEV_READY) {
114facf4a8dSllai1 		sdcmn_err(("dev fs: skipping: node not ready %s(%p)",
115facf4a8dSllai1 		    nm, (void *)dv));
116facf4a8dSllai1 		return (SDEV_VTOR_SKIP);
117facf4a8dSllai1 	}
118facf4a8dSllai1 
119facf4a8dSllai1 	if (devpts_strtol(nm, &min) != 0) {
120facf4a8dSllai1 		sdcmn_err7(("devpts_validate: not a valid minor: %s\n", nm));
121facf4a8dSllai1 		return (SDEV_VTOR_INVALID);
122facf4a8dSllai1 	}
123facf4a8dSllai1 
124facf4a8dSllai1 	/*
125facf4a8dSllai1 	 * Check if pts driver is attached
126facf4a8dSllai1 	 */
127facf4a8dSllai1 	if (ptms_slave_attached() == (major_t)-1) {
128facf4a8dSllai1 		sdcmn_err7(("devpts_validate: slave not attached\n"));
129facf4a8dSllai1 		return (SDEV_VTOR_INVALID);
130facf4a8dSllai1 	}
131facf4a8dSllai1 
132facf4a8dSllai1 	if (ptms_minor_valid(min, &uid, &gid) == 0) {
133facf4a8dSllai1 		if (ptms_minor_exists(min)) {
134facf4a8dSllai1 			sdcmn_err7(("devpts_validate: valid in different zone "
135facf4a8dSllai1 			    "%s\n", nm));
136facf4a8dSllai1 			return (SDEV_VTOR_SKIP);
137facf4a8dSllai1 		} else {
138facf4a8dSllai1 			sdcmn_err7(("devpts_validate: %s not valid pty\n",
139facf4a8dSllai1 			    nm));
140facf4a8dSllai1 			return (SDEV_VTOR_INVALID);
141facf4a8dSllai1 		}
142facf4a8dSllai1 	}
143facf4a8dSllai1 
144facf4a8dSllai1 	ASSERT(dv->sdev_attr);
145facf4a8dSllai1 	if (dv->sdev_attr->va_uid != uid || dv->sdev_attr->va_gid != gid) {
146facf4a8dSllai1 		dv->sdev_attr->va_uid = uid;
147facf4a8dSllai1 		dv->sdev_attr->va_gid = gid;
148facf4a8dSllai1 		gethrestime(&now);
149facf4a8dSllai1 		dv->sdev_attr->va_atime = now;
150facf4a8dSllai1 		dv->sdev_attr->va_mtime = now;
151facf4a8dSllai1 		dv->sdev_attr->va_ctime = now;
152facf4a8dSllai1 		sdcmn_err7(("devpts_validate: update uid/gid/times%s\n", nm));
153facf4a8dSllai1 	}
154facf4a8dSllai1 
155facf4a8dSllai1 	return (SDEV_VTOR_VALID);
156facf4a8dSllai1 }
157facf4a8dSllai1 
158facf4a8dSllai1 /*
159facf4a8dSllai1  * This callback is invoked from devname_lookup_func() to create
160facf4a8dSllai1  * a pts entry when the node is not found in the cache.
161facf4a8dSllai1  */
162facf4a8dSllai1 /*ARGSUSED*/
163facf4a8dSllai1 static int
164facf4a8dSllai1 devpts_create_rvp(struct sdev_node *ddv, char *nm,
165facf4a8dSllai1     void **arg, cred_t *cred, void *whatever, char *whichever)
166facf4a8dSllai1 {
167facf4a8dSllai1 	minor_t min;
168facf4a8dSllai1 	major_t maj;
169facf4a8dSllai1 	uid_t uid;
170facf4a8dSllai1 	gid_t gid;
171facf4a8dSllai1 	timestruc_t now;
172facf4a8dSllai1 	struct vattr *vap = (struct vattr *)arg;
173facf4a8dSllai1 
174facf4a8dSllai1 	if (devpts_strtol(nm, &min) != 0) {
175facf4a8dSllai1 		sdcmn_err7(("devpts_create_rvp: not a valid minor: %s\n", nm));
176facf4a8dSllai1 		return (-1);
177facf4a8dSllai1 	}
178facf4a8dSllai1 
179facf4a8dSllai1 	/*
180facf4a8dSllai1 	 * Check if pts driver is attached and if it is
181facf4a8dSllai1 	 * get the major number.
182facf4a8dSllai1 	 */
183facf4a8dSllai1 	maj = ptms_slave_attached();
184facf4a8dSllai1 	if (maj == (major_t)-1) {
185facf4a8dSllai1 		sdcmn_err7(("devpts_create_rvp: slave not attached\n"));
186facf4a8dSllai1 		return (-1);
187facf4a8dSllai1 	}
188facf4a8dSllai1 
189facf4a8dSllai1 	/*
190facf4a8dSllai1 	 * Only allow creation of ptys allocated to our zone
191facf4a8dSllai1 	 */
192facf4a8dSllai1 	if (!ptms_minor_valid(min, &uid, &gid)) {
193facf4a8dSllai1 		sdcmn_err7(("devpts_create_rvp: %s not valid pty"
194facf4a8dSllai1 		    "or not valid in this zone\n", nm));
195facf4a8dSllai1 		return (-1);
196facf4a8dSllai1 	}
197facf4a8dSllai1 
198facf4a8dSllai1 
199facf4a8dSllai1 	/*
200facf4a8dSllai1 	 * This is a valid pty (at least at this point in time).
201facf4a8dSllai1 	 * Create the node by setting the attribute. The rest
202facf4a8dSllai1 	 * is taken care of by devname_lookup_func().
203facf4a8dSllai1 	 */
204facf4a8dSllai1 	*vap = devpts_vattr;
205facf4a8dSllai1 	vap->va_rdev = makedevice(maj, min);
206facf4a8dSllai1 	vap->va_uid = uid;
207facf4a8dSllai1 	vap->va_gid = gid;
208facf4a8dSllai1 	gethrestime(&now);
209facf4a8dSllai1 	vap->va_atime = now;
210facf4a8dSllai1 	vap->va_mtime = now;
211facf4a8dSllai1 	vap->va_ctime = now;
212facf4a8dSllai1 
213facf4a8dSllai1 	return (0);
214facf4a8dSllai1 }
215facf4a8dSllai1 
216facf4a8dSllai1 /*
217facf4a8dSllai1  * Clean pts sdev_nodes that are no longer valid.
218facf4a8dSllai1  */
219facf4a8dSllai1 static void
220facf4a8dSllai1 devpts_prunedir(struct sdev_node *ddv)
221facf4a8dSllai1 {
222facf4a8dSllai1 	struct vnode *vp;
223facf4a8dSllai1 	struct sdev_node *dv, *next = NULL;
224facf4a8dSllai1 	int (*vtor)(struct sdev_node *) = NULL;
225facf4a8dSllai1 
226facf4a8dSllai1 	ASSERT(ddv->sdev_flags & SDEV_VTOR);
227facf4a8dSllai1 
228facf4a8dSllai1 	vtor = (int (*)(struct sdev_node *))sdev_get_vtor(ddv);
229facf4a8dSllai1 	ASSERT(vtor);
230facf4a8dSllai1 
231facf4a8dSllai1 	if (rw_tryupgrade(&ddv->sdev_contents) == NULL) {
232facf4a8dSllai1 		rw_exit(&ddv->sdev_contents);
233facf4a8dSllai1 		rw_enter(&ddv->sdev_contents, RW_WRITER);
234facf4a8dSllai1 	}
235facf4a8dSllai1 
236aac43a5fSjg 	for (dv = SDEV_FIRST_ENTRY(ddv); dv; dv = next) {
237aac43a5fSjg 		next = SDEV_NEXT_ENTRY(ddv, dv);
238facf4a8dSllai1 
239facf4a8dSllai1 		/* validate and prune only ready nodes */
240facf4a8dSllai1 		if (dv->sdev_state != SDEV_READY)
241facf4a8dSllai1 			continue;
242facf4a8dSllai1 
243facf4a8dSllai1 		switch (vtor(dv)) {
244facf4a8dSllai1 		case SDEV_VTOR_VALID:
245facf4a8dSllai1 		case SDEV_VTOR_SKIP:
246facf4a8dSllai1 			continue;
247facf4a8dSllai1 		case SDEV_VTOR_INVALID:
248b127ac41SPhilip Kirk 		case SDEV_VTOR_STALE:
249facf4a8dSllai1 			sdcmn_err7(("prunedir: destroy invalid "
250facf4a8dSllai1 			    "node: %s(%p)\n", dv->sdev_name, (void *)dv));
251facf4a8dSllai1 			break;
252facf4a8dSllai1 		}
253facf4a8dSllai1 		vp = SDEVTOV(dv);
254facf4a8dSllai1 		if (vp->v_count > 0)
255facf4a8dSllai1 			continue;
256facf4a8dSllai1 		SDEV_HOLD(dv);
257facf4a8dSllai1 		/* remove the cache node */
258facf4a8dSllai1 		(void) sdev_cache_update(ddv, &dv, dv->sdev_name,
259facf4a8dSllai1 		    SDEV_CACHE_DELETE);
260*9e5aa9d8SRobert Mustacchi 		SDEV_RELE(dv);
261facf4a8dSllai1 	}
262facf4a8dSllai1 	rw_downgrade(&ddv->sdev_contents);
263facf4a8dSllai1 }
264facf4a8dSllai1 
265facf4a8dSllai1 /*
266facf4a8dSllai1  * Lookup for /dev/pts directory
267facf4a8dSllai1  *	If the entry does not exist, the devpts_create_rvp() callback
268facf4a8dSllai1  *	is invoked to create it. Nodes do not persist across reboot.
26949e92448Svikram  *
27049e92448Svikram  * There is a potential denial of service here via
27149e92448Svikram  * fattach on top of a /dev/pts node - any permission changes
27249e92448Svikram  * applied to the node, apply to the fattached file and not
27349e92448Svikram  * to the underlying pts node. As a result when the previous
27449e92448Svikram  * user fdetaches, the pts node is still owned by the previous
27549e92448Svikram  * owner. To prevent this we don't allow fattach() on top of a pts
27649e92448Svikram  * node. This is done by a modification in the namefs filesystem
27749e92448Svikram  * where we check if the underlying node has the /dev/pts vnodeops.
27849e92448Svikram  * We do this via VOP_REALVP() on the underlying specfs node.
27949e92448Svikram  * sdev_nodes currently don't have a realvp. If a realvp is ever
28049e92448Svikram  * created for sdev_nodes, then VOP_REALVP() will return the
28149e92448Svikram  * actual realvp (possibly a ufs vnode). This will defeat the check
28249e92448Svikram  * in namefs code which checks if VOP_REALVP() returns a devpts
28349e92448Svikram  * node. We add an ASSERT here in /dev/pts lookup() to check for
28449e92448Svikram  * this condition. If sdev_nodes ever get a VOP_REALVP() entry point,
28549e92448Svikram  * change the code in the namefs filesystem code (in nm_mount()) to
28649e92448Svikram  * access the realvp of the specfs node directly instead of using
28749e92448Svikram  * VOP_REALVP().
288facf4a8dSllai1  */
289facf4a8dSllai1 /*ARGSUSED3*/
290facf4a8dSllai1 static int
291facf4a8dSllai1 devpts_lookup(struct vnode *dvp, char *nm, struct vnode **vpp,
292da6c28aaSamw     struct pathname *pnp, int flags, struct vnode *rdir, struct cred *cred,
293da6c28aaSamw     caller_context_t *ct, int *direntflags, pathname_t *realpnp)
294facf4a8dSllai1 {
295facf4a8dSllai1 	struct sdev_node *sdvp = VTOSDEV(dvp);
296facf4a8dSllai1 	struct sdev_node *dv;
29749e92448Svikram 	struct vnode *rvp = NULL;
298facf4a8dSllai1 	int error;
299facf4a8dSllai1 
300facf4a8dSllai1 	error = devname_lookup_func(sdvp, nm, vpp, cred, devpts_create_rvp,
301facf4a8dSllai1 	    SDEV_VATTR);
302facf4a8dSllai1 
303facf4a8dSllai1 	if (error == 0) {
304facf4a8dSllai1 		switch ((*vpp)->v_type) {
305facf4a8dSllai1 		case VCHR:
306facf4a8dSllai1 			dv = VTOSDEV(VTOS(*vpp)->s_realvp);
307da6c28aaSamw 			ASSERT(VOP_REALVP(SDEVTOV(dv), &rvp, NULL) == ENOSYS);
308facf4a8dSllai1 			break;
309facf4a8dSllai1 		case VDIR:
310facf4a8dSllai1 			dv = VTOSDEV(*vpp);
311facf4a8dSllai1 			break;
312facf4a8dSllai1 		default:
313facf4a8dSllai1 			cmn_err(CE_PANIC, "devpts_lookup: Unsupported node "
314facf4a8dSllai1 			    "type: %p: %d", (void *)(*vpp), (*vpp)->v_type);
315facf4a8dSllai1 			break;
316facf4a8dSllai1 		}
317facf4a8dSllai1 		ASSERT(SDEV_HELD(dv));
318facf4a8dSllai1 	}
319facf4a8dSllai1 
320facf4a8dSllai1 	return (error);
321facf4a8dSllai1 }
322facf4a8dSllai1 
323facf4a8dSllai1 /*
324facf4a8dSllai1  * We allow create to find existing nodes
325facf4a8dSllai1  *	- if the node doesn't exist - EROFS
326facf4a8dSllai1  *	- creating an existing dir read-only succeeds, otherwise EISDIR
327facf4a8dSllai1  *	- exclusive creates fail - EEXIST
328facf4a8dSllai1  */
329facf4a8dSllai1 /*ARGSUSED2*/
330facf4a8dSllai1 static int
331facf4a8dSllai1 devpts_create(struct vnode *dvp, char *nm, struct vattr *vap, vcexcl_t excl,
332da6c28aaSamw     int mode, struct vnode **vpp, struct cred *cred, int flag,
333da6c28aaSamw     caller_context_t *ct, vsecattr_t *vsecp)
334facf4a8dSllai1 {
335facf4a8dSllai1 	int error;
336facf4a8dSllai1 	struct vnode *vp;
337facf4a8dSllai1 
338facf4a8dSllai1 	*vpp = NULL;
339facf4a8dSllai1 
340da6c28aaSamw 	error = devpts_lookup(dvp, nm, &vp, NULL, 0, NULL, cred, ct, NULL,
341da6c28aaSamw 	    NULL);
342facf4a8dSllai1 	if (error == 0) {
343facf4a8dSllai1 		if (excl == EXCL)
344facf4a8dSllai1 			error = EEXIST;
345facf4a8dSllai1 		else if (vp->v_type == VDIR && (mode & VWRITE))
346facf4a8dSllai1 			error = EISDIR;
347facf4a8dSllai1 		else
348da6c28aaSamw 			error = VOP_ACCESS(vp, mode, 0, cred, ct);
349facf4a8dSllai1 
350facf4a8dSllai1 		if (error) {
351facf4a8dSllai1 			VN_RELE(vp);
352facf4a8dSllai1 		} else
353facf4a8dSllai1 			*vpp = vp;
354facf4a8dSllai1 	} else if (error == ENOENT) {
355facf4a8dSllai1 		error = EROFS;
356facf4a8dSllai1 	}
357facf4a8dSllai1 
358facf4a8dSllai1 	return (error);
359facf4a8dSllai1 }
360facf4a8dSllai1 
361facf4a8dSllai1 /*
362facf4a8dSllai1  * Display all instantiated pts (slave) device nodes.
363facf4a8dSllai1  * A /dev/pts entry will be created only after the first lookup of the slave
364facf4a8dSllai1  * device succeeds.
365facf4a8dSllai1  */
366da6c28aaSamw /*ARGSUSED4*/
367facf4a8dSllai1 static int
368facf4a8dSllai1 devpts_readdir(struct vnode *dvp, struct uio *uiop, struct cred *cred,
369da6c28aaSamw     int *eofp, caller_context_t *ct, int flags)
370facf4a8dSllai1 {
371facf4a8dSllai1 	struct sdev_node *sdvp = VTOSDEV(dvp);
372facf4a8dSllai1 	if (uiop->uio_offset == 0) {
373facf4a8dSllai1 		devpts_prunedir(sdvp);
374facf4a8dSllai1 	}
375facf4a8dSllai1 
376facf4a8dSllai1 	return (devname_readdir_func(dvp, uiop, cred, eofp, 0));
377facf4a8dSllai1 }
378facf4a8dSllai1 
379facf4a8dSllai1 
380facf4a8dSllai1 static int
381facf4a8dSllai1 devpts_set_id(struct sdev_node *dv, struct vattr *vap, int protocol)
382facf4a8dSllai1 {
383facf4a8dSllai1 	ASSERT((protocol & AT_UID) || (protocol & AT_GID));
384facf4a8dSllai1 	ptms_set_owner(getminor(SDEVTOV(dv)->v_rdev),
385facf4a8dSllai1 	    vap->va_uid, vap->va_gid);
386facf4a8dSllai1 	return (0);
387facf4a8dSllai1 
388facf4a8dSllai1 }
389facf4a8dSllai1 
390cbcfaf83Sjg /*ARGSUSED4*/
391facf4a8dSllai1 static int
392facf4a8dSllai1 devpts_setattr(struct vnode *vp, struct vattr *vap, int flags,
393cbcfaf83Sjg     struct cred *cred, caller_context_t *ctp)
394facf4a8dSllai1 {
395facf4a8dSllai1 	ASSERT((vp->v_type == VCHR) || (vp->v_type == VDIR));
396facf4a8dSllai1 	return (devname_setattr_func(vp, vap, flags, cred,
397facf4a8dSllai1 	    devpts_set_id, AT_UID|AT_GID));
398facf4a8dSllai1 }
399facf4a8dSllai1 
40049e92448Svikram 
401facf4a8dSllai1 /*
402facf4a8dSllai1  * We override lookup and readdir to build entries based on the
403facf4a8dSllai1  * in kernel pty table. Also override setattr/setsecattr to
404facf4a8dSllai1  * avoid persisting permissions.
405facf4a8dSllai1  */
406facf4a8dSllai1 const fs_operation_def_t devpts_vnodeops_tbl[] = {
407aa59c4cbSrsb 	VOPNAME_READDIR,	{ .vop_readdir = devpts_readdir },
408aa59c4cbSrsb 	VOPNAME_LOOKUP,		{ .vop_lookup = devpts_lookup },
409aa59c4cbSrsb 	VOPNAME_CREATE,		{ .vop_create = devpts_create },
410aa59c4cbSrsb 	VOPNAME_SETATTR,	{ .vop_setattr = devpts_setattr },
411aa59c4cbSrsb 	VOPNAME_REMOVE,		{ .error = fs_nosys },
412aa59c4cbSrsb 	VOPNAME_MKDIR,		{ .error = fs_nosys },
413aa59c4cbSrsb 	VOPNAME_RMDIR,		{ .error = fs_nosys },
414aa59c4cbSrsb 	VOPNAME_SYMLINK,	{ .error = fs_nosys },
415aa59c4cbSrsb 	VOPNAME_SETSECATTR,	{ .error = fs_nosys },
416facf4a8dSllai1 	NULL,			NULL
417facf4a8dSllai1 };
418