4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
27 * vnode ops for the /dev/pts directory
28 * The lookup is based on the internal pty table. We also
29 * override readdir in order to delete pts nodes no longer
33 #include <sys/types.h>
34 #include <sys/param.h>
35 #include <sys/sysmacros.h>
36 #include <sys/sunndi.h>
37 #include <fs/fs_subr.h>
38 #include <sys/fs/dv_node.h>
39 #include <sys/fs/sdev_impl.h>
40 #include <sys/policy.h>
43 #include <sys/vfs_opreg.h>
45 #define DEVPTS_UID_DEFAULT 0
46 #define DEVPTS_GID_DEFAULT 3
47 #define DEVPTS_DEVMODE_DEFAULT (0620)
49 #define isdigit(ch) ((ch) >= '0' && (ch) <= '9')
51 static vattr_t devpts_vattr
= {
52 AT_TYPE
|AT_MODE
|AT_UID
|AT_GID
, /* va_mask */
54 S_IFCHR
| DEVPTS_DEVMODE_DEFAULT
, /* va_mode */
55 DEVPTS_UID_DEFAULT
, /* va_uid */
56 DEVPTS_GID_DEFAULT
, /* va_gid */
60 struct vnodeops
*devpts_vnodeops
;
63 devpts_getvnodeops(void)
65 return (devpts_vnodeops
);
69 * Convert string to minor number. Some care must be taken
70 * as we are processing user input. Catch cases like
71 * /dev/pts/4foo and /dev/pts/-1
74 devpts_strtol(const char *nm
, minor_t
*mp
)
79 if (nm
== NULL
|| !isdigit(*nm
))
83 if (ddi_strtol(nm
, &endptr
, 10, &uminor
) != 0 ||
84 *endptr
!= '\0' || uminor
< 0) {
88 *mp
= (minor_t
)uminor
;
93 * Check if a pts sdev_node is still valid - i.e. it represents a current pty.
94 * This serves two purposes
95 * - only valid pts nodes are returned during lookup() and readdir().
96 * - since pts sdev_nodes are not actively destroyed when a pty goes
97 * away, we use the validator to do deferred cleanup i.e. when such
98 * nodes are encountered during subsequent lookup() and readdir().
102 devpts_validate(struct sdev_node
*dv
)
108 char *nm
= dv
->sdev_name
;
110 ASSERT(dv
->sdev_state
== SDEV_READY
);
112 /* validate only READY nodes */
113 if (dv
->sdev_state
!= SDEV_READY
) {
114 sdcmn_err(("dev fs: skipping: node not ready %s(%p)",
116 return (SDEV_VTOR_SKIP
);
119 if (devpts_strtol(nm
, &min
) != 0) {
120 sdcmn_err7(("devpts_validate: not a valid minor: %s\n", nm
));
121 return (SDEV_VTOR_INVALID
);
125 * Check if pts driver is attached
127 if (ptms_slave_attached() == (major_t
)-1) {
128 sdcmn_err7(("devpts_validate: slave not attached\n"));
129 return (SDEV_VTOR_INVALID
);
132 if (ptms_minor_valid(min
, &uid
, &gid
) == 0) {
133 if (ptms_minor_exists(min
)) {
134 sdcmn_err7(("devpts_validate: valid in different zone "
136 return (SDEV_VTOR_SKIP
);
138 sdcmn_err7(("devpts_validate: %s not valid pty\n",
140 return (SDEV_VTOR_INVALID
);
144 ASSERT(dv
->sdev_attr
);
145 if (dv
->sdev_attr
->va_uid
!= uid
|| dv
->sdev_attr
->va_gid
!= gid
) {
146 dv
->sdev_attr
->va_uid
= uid
;
147 dv
->sdev_attr
->va_gid
= gid
;
149 dv
->sdev_attr
->va_atime
= now
;
150 dv
->sdev_attr
->va_mtime
= now
;
151 dv
->sdev_attr
->va_ctime
= now
;
152 sdcmn_err7(("devpts_validate: update uid/gid/times%s\n", nm
));
155 return (SDEV_VTOR_VALID
);
159 * This callback is invoked from devname_lookup_func() to create
160 * a pts entry when the node is not found in the cache.
164 devpts_create_rvp(struct sdev_node
*ddv
, char *nm
,
165 void **arg
, cred_t
*cred
, void *whatever
, char *whichever
)
172 struct vattr
*vap
= (struct vattr
*)arg
;
174 if (devpts_strtol(nm
, &min
) != 0) {
175 sdcmn_err7(("devpts_create_rvp: not a valid minor: %s\n", nm
));
180 * Check if pts driver is attached and if it is
181 * get the major number.
183 maj
= ptms_slave_attached();
184 if (maj
== (major_t
)-1) {
185 sdcmn_err7(("devpts_create_rvp: slave not attached\n"));
190 * Only allow creation of ptys allocated to our zone
192 if (!ptms_minor_valid(min
, &uid
, &gid
)) {
193 sdcmn_err7(("devpts_create_rvp: %s not valid pty"
194 "or not valid in this zone\n", nm
));
200 * This is a valid pty (at least at this point in time).
201 * Create the node by setting the attribute. The rest
202 * is taken care of by devname_lookup_func().
205 vap
->va_rdev
= makedevice(maj
, min
);
217 * Clean pts sdev_nodes that are no longer valid.
220 devpts_prunedir(struct sdev_node
*ddv
)
223 struct sdev_node
*dv
, *next
= NULL
;
224 int (*vtor
)(struct sdev_node
*) = NULL
;
226 ASSERT(ddv
->sdev_flags
& SDEV_VTOR
);
228 vtor
= (int (*)(struct sdev_node
*))sdev_get_vtor(ddv
);
231 if (rw_tryupgrade(&ddv
->sdev_contents
) == NULL
) {
232 rw_exit(&ddv
->sdev_contents
);
233 rw_enter(&ddv
->sdev_contents
, RW_WRITER
);
236 for (dv
= SDEV_FIRST_ENTRY(ddv
); dv
; dv
= next
) {
237 next
= SDEV_NEXT_ENTRY(ddv
, dv
);
239 /* validate and prune only ready nodes */
240 if (dv
->sdev_state
!= SDEV_READY
)
244 case SDEV_VTOR_VALID
:
247 case SDEV_VTOR_INVALID
:
248 case SDEV_VTOR_STALE
:
249 sdcmn_err7(("prunedir: destroy invalid "
250 "node: %s(%p)\n", dv
->sdev_name
, (void *)dv
));
257 /* remove the cache node */
258 (void) sdev_cache_update(ddv
, &dv
, dv
->sdev_name
,
262 rw_downgrade(&ddv
->sdev_contents
);
266 * Lookup for /dev/pts directory
267 * If the entry does not exist, the devpts_create_rvp() callback
268 * is invoked to create it. Nodes do not persist across reboot.
270 * There is a potential denial of service here via
271 * fattach on top of a /dev/pts node - any permission changes
272 * applied to the node, apply to the fattached file and not
273 * to the underlying pts node. As a result when the previous
274 * user fdetaches, the pts node is still owned by the previous
275 * owner. To prevent this we don't allow fattach() on top of a pts
276 * node. This is done by a modification in the namefs filesystem
277 * where we check if the underlying node has the /dev/pts vnodeops.
278 * We do this via VOP_REALVP() on the underlying specfs node.
279 * sdev_nodes currently don't have a realvp. If a realvp is ever
280 * created for sdev_nodes, then VOP_REALVP() will return the
281 * actual realvp (possibly a ufs vnode). This will defeat the check
282 * in namefs code which checks if VOP_REALVP() returns a devpts
283 * node. We add an ASSERT here in /dev/pts lookup() to check for
284 * this condition. If sdev_nodes ever get a VOP_REALVP() entry point,
285 * change the code in the namefs filesystem code (in nm_mount()) to
286 * access the realvp of the specfs node directly instead of using
291 devpts_lookup(struct vnode
*dvp
, char *nm
, struct vnode
**vpp
,
292 struct pathname
*pnp
, int flags
, struct vnode
*rdir
, struct cred
*cred
,
293 caller_context_t
*ct
, int *direntflags
, pathname_t
*realpnp
)
295 struct sdev_node
*sdvp
= VTOSDEV(dvp
);
296 struct sdev_node
*dv
;
297 struct vnode
*rvp
= NULL
;
300 error
= devname_lookup_func(sdvp
, nm
, vpp
, cred
, devpts_create_rvp
,
304 switch ((*vpp
)->v_type
) {
306 dv
= VTOSDEV(VTOS(*vpp
)->s_realvp
);
307 ASSERT(VOP_REALVP(SDEVTOV(dv
), &rvp
, NULL
) == ENOSYS
);
313 cmn_err(CE_PANIC
, "devpts_lookup: Unsupported node "
314 "type: %p: %d", (void *)(*vpp
), (*vpp
)->v_type
);
317 ASSERT(SDEV_HELD(dv
));
324 * We allow create to find existing nodes
325 * - if the node doesn't exist - EROFS
326 * - creating an existing dir read-only succeeds, otherwise EISDIR
327 * - exclusive creates fail - EEXIST
331 devpts_create(struct vnode
*dvp
, char *nm
, struct vattr
*vap
, vcexcl_t excl
,
332 int mode
, struct vnode
**vpp
, struct cred
*cred
, int flag
,
333 caller_context_t
*ct
, vsecattr_t
*vsecp
)
340 error
= devpts_lookup(dvp
, nm
, &vp
, NULL
, 0, NULL
, cred
, ct
, NULL
,
345 else if (vp
->v_type
== VDIR
&& (mode
& VWRITE
))
348 error
= VOP_ACCESS(vp
, mode
, 0, cred
, ct
);
354 } else if (error
== ENOENT
) {
362 * Display all instantiated pts (slave) device nodes.
363 * A /dev/pts entry will be created only after the first lookup of the slave
368 devpts_readdir(struct vnode
*dvp
, struct uio
*uiop
, struct cred
*cred
,
369 int *eofp
, caller_context_t
*ct
, int flags
)
371 struct sdev_node
*sdvp
= VTOSDEV(dvp
);
372 if (uiop
->uio_offset
== 0) {
373 devpts_prunedir(sdvp
);
376 return (devname_readdir_func(dvp
, uiop
, cred
, eofp
, 0));
381 devpts_set_id(struct sdev_node
*dv
, struct vattr
*vap
, int protocol
)
383 ASSERT((protocol
& AT_UID
) || (protocol
& AT_GID
));
384 ptms_set_owner(getminor(SDEVTOV(dv
)->v_rdev
),
385 vap
->va_uid
, vap
->va_gid
);
392 devpts_setattr(struct vnode
*vp
, struct vattr
*vap
, int flags
,
393 struct cred
*cred
, caller_context_t
*ctp
)
395 ASSERT((vp
->v_type
== VCHR
) || (vp
->v_type
== VDIR
));
396 return (devname_setattr_func(vp
, vap
, flags
, cred
,
397 devpts_set_id
, AT_UID
|AT_GID
));
402 * We override lookup and readdir to build entries based on the
403 * in kernel pty table. Also override setattr/setsecattr to
404 * avoid persisting permissions.
406 const fs_operation_def_t devpts_vnodeops_tbl
[] = {
407 VOPNAME_READDIR
, { .vop_readdir
= devpts_readdir
},
408 VOPNAME_LOOKUP
, { .vop_lookup
= devpts_lookup
},
409 VOPNAME_CREATE
, { .vop_create
= devpts_create
},
410 VOPNAME_SETATTR
, { .vop_setattr
= devpts_setattr
},
411 VOPNAME_REMOVE
, { .error
= fs_nosys
},
412 VOPNAME_MKDIR
, { .error
= fs_nosys
},
413 VOPNAME_RMDIR
, { .error
= fs_nosys
},
414 VOPNAME_SYMLINK
, { .error
= fs_nosys
},
415 VOPNAME_SETSECATTR
, { .error
= fs_nosys
},