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