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