1/*	$NetBSD: tty_ptm.c,v 1.26 2009/01/22 14:38:35 yamt Exp $	*/
2
3/*-
4 * Copyright (c) 2004 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: tty_ptm.c,v 1.26 2009/01/22 14:38:35 yamt Exp $");
31
32#include "opt_ptm.h"
33
34/* pty multiplexor driver /dev/ptm{,x} */
35
36#include <sys/param.h>
37#include <sys/systm.h>
38#include <sys/ioctl.h>
39#include <sys/proc.h>
40#include <sys/tty.h>
41#include <sys/stat.h>
42#include <sys/file.h>
43#include <sys/uio.h>
44#include <sys/kernel.h>
45#include <sys/vnode.h>
46#include <sys/namei.h>
47#include <sys/signalvar.h>
48#include <sys/filedesc.h>
49#include <sys/conf.h>
50#include <sys/poll.h>
51#include <sys/pty.h>
52#include <sys/kauth.h>
53
54#include <miscfs/specfs/specdev.h>
55
56#ifdef DEBUG_PTM
57#define DPRINTF(a)	printf a
58#else
59#define DPRINTF(a)
60#endif
61
62#ifdef NO_DEV_PTM
63const struct cdevsw ptm_cdevsw = {
64	noopen, noclose, noread, nowrite, noioctl,
65	nostop, notty, nopoll, nommap, nokqfilter, D_TTY
66};
67#else
68
69static struct ptm_pty *ptm;
70int pts_major, ptc_major;
71
72static dev_t pty_getfree(void);
73static int pty_alloc_master(struct lwp *, int *, dev_t *);
74static int pty_alloc_slave(struct lwp *, int *, dev_t);
75
76void ptmattach(int);
77
78dev_t
79pty_makedev(char ms, int minor)
80{
81	return makedev(ms == 't' ? pts_major : ptc_major, minor);
82}
83
84
85static dev_t
86pty_getfree(void)
87{
88	extern kmutex_t pt_softc_mutex;
89	int i;
90
91	mutex_enter(&pt_softc_mutex);
92	for (i = 0; i < npty; i++) {
93		if (pty_isfree(i, 0))
94			break;
95	}
96	mutex_exit(&pt_softc_mutex);
97	return pty_makedev('t', i);
98}
99
100/*
101 * Hacked up version of vn_open. We _only_ handle ptys and only open
102 * them with FREAD|FWRITE and never deal with creat or stuff like that.
103 *
104 * We need it because we have to fake up root credentials to open the pty.
105 */
106int
107pty_vn_open(struct vnode *vp, struct lwp *l)
108{
109	int error;
110
111	if (vp->v_type != VCHR) {
112		vput(vp);
113		return EINVAL;
114	}
115
116	error = VOP_OPEN(vp, FREAD|FWRITE, lwp0.l_cred);
117
118	if (error) {
119		vput(vp);
120		return error;
121	}
122
123	vp->v_writecount++;
124
125	return 0;
126}
127
128static int
129pty_alloc_master(struct lwp *l, int *fd, dev_t *dev)
130{
131	int error;
132	struct file *fp;
133	struct vnode *vp;
134	int md;
135
136	if ((error = fd_allocfile(&fp, fd)) != 0) {
137		DPRINTF(("fd_allocfile %d\n", error));
138		return error;
139	}
140retry:
141	/* Find and open a free master pty. */
142	*dev = pty_getfree();
143	md = minor(*dev);
144	if ((error = pty_check(md)) != 0) {
145		DPRINTF(("pty_check %d\n", error));
146		goto bad;
147	}
148	if (ptm == NULL) {
149		DPRINTF(("no ptm\n"));
150		error = EOPNOTSUPP;
151		goto bad;
152	}
153	if ((error = (*ptm->allocvp)(ptm, l, &vp, *dev, 'p')) != 0) {
154		DPRINTF(("pty_allocvp %d\n", error));
155		goto bad;
156	}
157
158	if ((error = pty_vn_open(vp, l)) != 0) {
159		DPRINTF(("pty_vn_open %d\n", error));
160		/*
161		 * Check if the master open failed because we lost
162		 * the race to grab it.
163		 */
164		if (error != EIO)
165			goto bad;
166		error = !pty_isfree(md, 1);
167		DPRINTF(("pty_isfree %d\n", error));
168		if (error)
169			goto retry;
170		else
171			goto bad;
172	}
173	fp->f_flag = FREAD|FWRITE;
174	fp->f_type = DTYPE_VNODE;
175	fp->f_ops = &vnops;
176	fp->f_data = vp;
177	VOP_UNLOCK(vp);
178	fd_affix(curproc, fp, *fd);
179	return 0;
180bad:
181	fd_abort(curproc, fp, *fd);
182	return error;
183}
184
185int
186pty_grant_slave(struct lwp *l, dev_t dev)
187{
188	int error;
189	struct vnode *vp;
190
191	/*
192	 * Open the slave.
193	 * namei -> setattr -> unlock -> revoke -> vrele ->
194	 * namei -> open -> unlock
195	 * Three stage rocket:
196	 * 1. Change the owner and permissions on the slave.
197	 * 2. Revoke all the users of the slave.
198	 * 3. open the slave.
199	 */
200	if (ptm == NULL)
201		return EOPNOTSUPP;
202	if ((error = (*ptm->allocvp)(ptm, l, &vp, dev, 't')) != 0)
203		return error;
204
205	if ((vp->v_mount->mnt_flag & MNT_RDONLY) == 0) {
206		struct vattr vattr;
207		(*ptm->getvattr)(ptm, l, &vattr);
208		/* Do the VOP_SETATTR() as root. */
209		error = VOP_SETATTR(vp, &vattr, lwp0.l_cred);
210		if (error) {
211			DPRINTF(("setattr %d\n", error));
212			VOP_UNLOCK(vp);
213			vrele(vp);
214			return error;
215		}
216	}
217	VOP_UNLOCK(vp);
218	VOP_REVOKE(vp, REVOKEALL);
219
220	/*
221	 * The vnode is useless after the revoke, we need to get it again.
222	 */
223	vrele(vp);
224	return 0;
225}
226
227static int
228pty_alloc_slave(struct lwp *l, int *fd, dev_t dev)
229{
230	int error;
231	struct file *fp;
232	struct vnode *vp;
233
234	/* Grab a filedescriptor for the slave */
235	if ((error = fd_allocfile(&fp, fd)) != 0) {
236		DPRINTF(("fd_allocfile %d\n", error));
237		return error;
238	}
239
240	if (ptm == NULL) {
241		error = EOPNOTSUPP;
242		goto bad;
243	}
244
245	if ((error = (*ptm->allocvp)(ptm, l, &vp, dev, 't')) != 0)
246		goto bad;
247	if ((error = pty_vn_open(vp, l)) != 0)
248		goto bad;
249
250	fp->f_flag = FREAD|FWRITE;
251	fp->f_type = DTYPE_VNODE;
252	fp->f_ops = &vnops;
253	fp->f_data = vp;
254	VOP_UNLOCK(vp);
255	fd_affix(curproc, fp, *fd);
256	return 0;
257bad:
258	fd_abort(curproc, fp, *fd);
259	return error;
260}
261
262struct ptm_pty *
263pty_sethandler(struct ptm_pty *nptm)
264{
265	struct ptm_pty *optm = ptm;
266	ptm = nptm;
267	return optm;
268}
269
270int
271pty_fill_ptmget(struct lwp *l, dev_t dev, int cfd, int sfd, void *data)
272{
273	struct ptmget *ptmg = data;
274	int error;
275
276	if (ptm == NULL)
277		return EOPNOTSUPP;
278
279	ptmg->cfd = cfd == -1 ? minor(dev) : cfd;
280	ptmg->sfd = sfd == -1 ? minor(dev) : sfd;
281
282	error = (*ptm->makename)(ptm, l, ptmg->cn, sizeof(ptmg->cn), dev, 'p');
283	if (error)
284		return error;
285
286	return (*ptm->makename)(ptm, l, ptmg->sn, sizeof(ptmg->sn), dev, 't');
287}
288
289void
290/*ARGSUSED*/
291ptmattach(int n)
292{
293	extern const struct cdevsw pts_cdevsw, ptc_cdevsw;
294	/* find the major and minor of the pty devices */
295	if ((pts_major = cdevsw_lookup_major(&pts_cdevsw)) == -1)
296		panic("ptmattach: Can't find pty slave in cdevsw");
297	if ((ptc_major = cdevsw_lookup_major(&ptc_cdevsw)) == -1)
298		panic("ptmattach: Can't find pty master in cdevsw");
299#ifdef COMPAT_BSDPTY
300	ptm = &ptm_bsdpty;
301#endif
302}
303
304static int
305/*ARGSUSED*/
306ptmopen(dev_t dev, int flag, int mode, struct lwp *l)
307{
308	int error;
309	int fd;
310	dev_t ttydev;
311
312	switch(minor(dev)) {
313	case 0:		/* /dev/ptmx */
314	case 2:		/* /emul/linux/dev/ptmx */
315		if ((error = pty_alloc_master(l, &fd, &ttydev)) != 0)
316			return error;
317		if (minor(dev) == 2) {
318			/*
319			 * Linux ptyfs grants the pty right here.
320			 * Handle this case here, instead of writing
321			 * a new linux module.
322			 */
323			if ((error = pty_grant_slave(l, ttydev)) != 0) {
324				file_t *fp = fd_getfile(fd);
325				if (fp != NULL) {
326					fd_close(fd);
327				}
328				return error;
329			}
330		}
331		curlwp->l_dupfd = fd;
332		return EMOVEFD;
333	case 1:		/* /dev/ptm */
334		return 0;
335	default:
336		return ENODEV;
337	}
338}
339
340static int
341/*ARGSUSED*/
342ptmclose(dev_t dev, int flag, int mode, struct lwp *l)
343{
344
345	return (0);
346}
347
348static int
349/*ARGSUSED*/
350ptmioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
351{
352	int error;
353	dev_t newdev;
354	int cfd, sfd;
355	file_t *fp;
356
357	error = 0;
358	switch (cmd) {
359	case TIOCPTMGET:
360		if ((error = pty_alloc_master(l, &cfd, &newdev)) != 0)
361			return error;
362
363		if ((error = pty_grant_slave(l, newdev)) != 0)
364			goto bad;
365
366		if ((error = pty_alloc_slave(l, &sfd, newdev)) != 0)
367			goto bad;
368
369		/* now, put the indices and names into struct ptmget */
370		return pty_fill_ptmget(l, newdev, cfd, sfd, data);
371	default:
372		DPRINTF(("ptmioctl EINVAL\n"));
373		return EINVAL;
374	}
375 bad:
376	fp = fd_getfile(cfd);
377	if (fp != NULL) {
378		fd_close(cfd);
379	}
380	return error;
381}
382
383const struct cdevsw ptm_cdevsw = {
384	ptmopen, ptmclose, noread, nowrite, ptmioctl,
385	nullstop, notty, nopoll, nommap, nokqfilter, D_TTY
386};
387#endif
388