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