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