1/* $NetBSD: tty_ptm.c,v 1.37 2015/08/24 22:50:32 pooka 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.37 2015/08/24 22:50:32 pooka Exp $");
31
32#ifdef _KERNEL_OPT
33#include "opt_compat_netbsd.h"
34#include "opt_ptm.h"
35#endif
36
37/* pty multiplexor driver /dev/ptm{,x} */
38
39#include <sys/param.h>
40#include <sys/systm.h>
41#include <sys/ioctl.h>
42#include <sys/proc.h>
43#include <sys/tty.h>
44#include <sys/stat.h>
45#include <sys/file.h>
46#include <sys/uio.h>
47#include <sys/kernel.h>
48#include <sys/vnode.h>
49#include <sys/namei.h>
50#include <sys/signalvar.h>
51#include <sys/filedesc.h>
52#include <sys/conf.h>
53#include <sys/poll.h>
54#include <sys/pty.h>
55#include <sys/kauth.h>
56
57#include <miscfs/specfs/specdev.h>
58
59#ifdef COMPAT_60
60#include <compat/sys/ttycom.h>
61#endif /* COMPAT_60 */
62
63#include "ioconf.h"
64
65#ifdef DEBUG_PTM
66#define DPRINTF(a) printf a
67#else
68#define DPRINTF(a)
69#endif
70
71#ifdef NO_DEV_PTM
72const struct cdevsw ptm_cdevsw = {
73 .d_open = noopen,
74 .d_close = noclose,
75 .d_read = noread,
76 .d_write = nowrite,
77 .d_ioctl = noioctl,
78 .d_stop = nostop,
79 .d_tty = notty,
80 .d_poll = nopoll,
81 .d_mmap = nommap,
82 .d_kqfilter = nokqfilter,
83 .d_discard = nodiscard,
84 .d_flag = D_TTY
85};
86#else
87
88static struct ptm_pty *ptm;
89int pts_major, ptc_major;
90
91static dev_t pty_getfree(void);
92static int pty_alloc_master(struct lwp *, int *, dev_t *, struct mount *);
93static int pty_alloc_slave(struct lwp *, int *, dev_t, struct mount *);
94static int pty_vn_open(struct vnode *, struct lwp *);
95
96int
97pty_getmp(struct lwp *l, struct mount **mpp)
98{
99 if (ptm == NULL)
100 return EOPNOTSUPP;
101
102 return (*ptm->getmp)(l, mpp);
103}
104
105dev_t
106pty_makedev(char ms, int minor)
107{
108 return makedev(ms == 't' ? pts_major : ptc_major, minor);
109}
110
111
112static dev_t
113pty_getfree(void)
114{
115 extern kmutex_t pt_softc_mutex;
116 int i;
117
118 mutex_enter(&pt_softc_mutex);
119 for (i = 0; i < npty; i++) {
120 if (pty_isfree(i, 0))
121 break;
122 }
123 mutex_exit(&pt_softc_mutex);
124 return pty_makedev('t', i);
125}
126
127/*
128 * Hacked up version of vn_open. We _only_ handle ptys and only open
129 * them with FREAD|FWRITE and never deal with creat or stuff like that.
130 *
131 * We need it because we have to fake up root credentials to open the pty.
132 */
133int
134pty_vn_open(struct vnode *vp, struct lwp *l)
135{
136 int error;
137
138 if (vp->v_type != VCHR) {
139 vput(vp);
140 return EINVAL;
141 }
142
143 error = VOP_OPEN(vp, FREAD|FWRITE, lwp0.l_cred);
144
145 if (error) {
146 vput(vp);
147 return error;
148 }
149
150 vp->v_writecount++;
151
152 return 0;
153}
154
155static int
156pty_alloc_master(struct lwp *l, int *fd, dev_t *dev, struct mount *mp)
157{
158 int error;
159 struct file *fp;
160 struct vnode *vp;
161 int md;
162
163 if ((error = fd_allocfile(&fp, fd)) != 0) {
164 DPRINTF(("fd_allocfile %d\n", error));
165 return error;
166 }
167retry:
168 /* Find and open a free master pty. */
169 *dev = pty_getfree();
170 md = minor(*dev);
171 if ((error = pty_check(md)) != 0) {
172 DPRINTF(("pty_check %d\n", error));
173 goto bad;
174 }
175 if (ptm == NULL) {
176 DPRINTF(("no ptm\n"));
177 error = EOPNOTSUPP;
178 goto bad;
179 }
180 if ((error = (*ptm->allocvp)(mp, l, &vp, *dev, 'p')) != 0) {
181 DPRINTF(("pty_allocvp %d\n", error));
182 goto bad;
183 }
184
185 if ((error = pty_vn_open(vp, l)) != 0) {
186 DPRINTF(("pty_vn_open %d\n", error));
187 /*
188 * Check if the master open failed because we lost
189 * the race to grab it.
190 */
191 if (error != EIO)
192 goto bad;
193 error = !pty_isfree(md, 1);
194 DPRINTF(("pty_isfree %d\n", error));
195 if (error)
196 goto retry;
197 else
198 goto bad;
199 }
200 fp->f_flag = FREAD|FWRITE;
201 fp->f_type = DTYPE_VNODE;
202 fp->f_ops = &vnops;
203 fp->f_vnode = vp;
204 VOP_UNLOCK(vp);
205 fd_affix(curproc, fp, *fd);
206 return 0;
207bad:
208 fd_abort(curproc, fp, *fd);
209 return error;
210}
211
212int
213pty_grant_slave(struct lwp *l, dev_t dev, struct mount *mp)
214{
215 int error;
216 struct vnode *vp;
217
218 /*
219 * Open the slave.
220 * namei -> setattr -> unlock -> revoke -> vrele ->
221 * namei -> open -> unlock
222 * Three stage rocket:
223 * 1. Change the owner and permissions on the slave.
224 * 2. Revoke all the users of the slave.
225 * 3. open the slave.
226 */
227 if (ptm == NULL)
228 return EOPNOTSUPP;
229 if ((error = (*ptm->allocvp)(mp, l, &vp, dev, 't')) != 0)
230 return error;
231
232 if ((vp->v_mount->mnt_flag & MNT_RDONLY) == 0) {
233 struct vattr vattr;
234 (*ptm->getvattr)(mp, l, &vattr);
235 /* Do the VOP_SETATTR() as root. */
236 error = VOP_SETATTR(vp, &vattr, lwp0.l_cred);
237 if (error) {
238 DPRINTF(("setattr %d\n", error));
239 VOP_UNLOCK(vp);
240 vrele(vp);
241 return error;
242 }
243 }
244 VOP_UNLOCK(vp);
245 VOP_REVOKE(vp, REVOKEALL);
246
247 /*
248 * The vnode is useless after the revoke, we need to get it again.
249 */
250 vrele(vp);
251 return 0;
252}
253
254static int
255pty_alloc_slave(struct lwp *l, int *fd, dev_t dev, struct mount *mp)
256{
257 int error;
258 struct file *fp;
259 struct vnode *vp;
260
261 /* Grab a filedescriptor for the slave */
262 if ((error = fd_allocfile(&fp, fd)) != 0) {
263 DPRINTF(("fd_allocfile %d\n", error));
264 return error;
265 }
266
267 if (ptm == NULL) {
268 error = EOPNOTSUPP;
269 goto bad;
270 }
271
272 if ((error = (*ptm->allocvp)(mp, l, &vp, dev, 't')) != 0)
273 goto bad;
274 if ((error = pty_vn_open(vp, l)) != 0)
275 goto bad;
276
277 fp->f_flag = FREAD|FWRITE;
278 fp->f_type = DTYPE_VNODE;
279 fp->f_ops = &vnops;
280 fp->f_vnode = vp;
281 VOP_UNLOCK(vp);
282 fd_affix(curproc, fp, *fd);
283 return 0;
284bad:
285 fd_abort(curproc, fp, *fd);
286 return error;
287}
288
289struct ptm_pty *
290pty_sethandler(struct ptm_pty *nptm)
291{
292 struct ptm_pty *optm = ptm;
293 ptm = nptm;
294 return optm;
295}
296
297int
298pty_fill_ptmget(struct lwp *l, dev_t dev, int cfd, int sfd, void *data, struct mount *mp)
299{
300 struct ptmget *ptmg = data;
301 int error;
302
303 if (ptm == NULL)
304 return EOPNOTSUPP;
305
306 ptmg->cfd = cfd == -1 ? minor(dev) : cfd;
307 ptmg->sfd = sfd == -1 ? minor(dev) : sfd;
308
309 error = (*ptm->makename)(mp, l, ptmg->cn, sizeof(ptmg->cn), dev, 'p');
310 if (error)
311 return error;
312
313 return (*ptm->makename)(mp, l, ptmg->sn, sizeof(ptmg->sn), dev, 't');
314}
315
316void
317/*ARGSUSED*/
318ptmattach(int n)
319{
320 extern const struct cdevsw pts_cdevsw, ptc_cdevsw;
321 /* find the major and minor of the pty devices */
322 if ((pts_major = cdevsw_lookup_major(&pts_cdevsw)) == -1)
323 panic("ptmattach: Can't find pty slave in cdevsw");
324 if ((ptc_major = cdevsw_lookup_major(&ptc_cdevsw)) == -1)
325 panic("ptmattach: Can't find pty master in cdevsw");
326#ifdef COMPAT_BSDPTY
327 ptm = &ptm_bsdpty;
328#endif
329}
330
331static int
332/*ARGSUSED*/
333ptmopen(dev_t dev, int flag, int mode, struct lwp *l)
334{
335 int error;
336 int fd;
337 dev_t ttydev;
338 struct mount *mp;
339
340 switch(minor(dev)) {
341 case 0: /* /dev/ptmx */
342 case 2: /* /emul/linux/dev/ptmx */
343 if ((error = pty_getmp(l, &mp)) != 0)
344 return error;
345 if ((error = pty_alloc_master(l, &fd, &ttydev, mp)) != 0)
346 return error;
347 if (minor(dev) == 2) {
348 /*
349 * Linux ptyfs grants the pty right here.
350 * Handle this case here, instead of writing
351 * a new linux module.
352 */
353 if ((error = pty_grant_slave(l, ttydev, mp)) != 0) {
354 file_t *fp = fd_getfile(fd);
355 if (fp != NULL) {
356 fd_close(fd);
357 }
358 return error;
359 }
360 }
361 curlwp->l_dupfd = fd;
362 return EMOVEFD;
363 case 1: /* /dev/ptm */
364 return 0;
365 default:
366 return ENODEV;
367 }
368}
369
370static int
371/*ARGSUSED*/
372ptmclose(dev_t dev, int flag, int mode, struct lwp *l)
373{
374
375 return (0);
376}
377
378static int
379/*ARGSUSED*/
380ptmioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
381{
382 int error;
383 dev_t newdev;
384 int cfd, sfd;
385 file_t *fp;
386 struct mount *mp;
387
388 error = 0;
389 switch (cmd) {
390 case TIOCPTMGET:
391 if ((error = pty_getmp(l, &mp)) != 0)
392 return error;
393
394 if ((error = pty_alloc_master(l, &cfd, &newdev, mp)) != 0)
395 return error;
396
397 if ((error = pty_grant_slave(l, newdev, mp)) != 0)
398 goto bad;
399
400 if ((error = pty_alloc_slave(l, &sfd, newdev, mp)) != 0)
401 goto bad;
402
403 /* now, put the indices and names into struct ptmget */
404 if ((error = pty_fill_ptmget(l, newdev, cfd, sfd, data, mp)) != 0)
405 goto bad2;
406 return 0;
407 default:
408#ifdef COMPAT_60
409 error = compat_60_ptmioctl(dev, cmd, data, flag, l);
410 if (error != EPASSTHROUGH)
411 return error;
412#endif /* COMPAT_60 */
413 DPRINTF(("ptmioctl EINVAL\n"));
414 return EINVAL;
415 }
416bad2:
417 fp = fd_getfile(sfd);
418 if (fp != NULL) {
419 fd_close(sfd);
420 }
421 bad:
422 fp = fd_getfile(cfd);
423 if (fp != NULL) {
424 fd_close(cfd);
425 }
426 return error;
427}
428
429const struct cdevsw ptm_cdevsw = {
430 .d_open = ptmopen,
431 .d_close = ptmclose,
432 .d_read = noread,
433 .d_write = nowrite,
434 .d_ioctl = ptmioctl,
435 .d_stop = nullstop,
436 .d_tty = notty,
437 .d_poll = nopoll,
438 .d_mmap = nommap,
439 .d_kqfilter = nokqfilter,
440 .d_discard = nodiscard,
441 .d_flag = D_TTY
442};
443#endif
444