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