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 |
72 | const 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 | |
88 | static struct ptm_pty *ptm; |
89 | int pts_major, ptc_major; |
90 | |
91 | static dev_t pty_getfree(void); |
92 | static int pty_alloc_master(struct lwp *, int *, dev_t *, struct mount *); |
93 | static int pty_alloc_slave(struct lwp *, int *, dev_t, struct mount *); |
94 | static int pty_vn_open(struct vnode *, struct lwp *); |
95 | |
96 | int |
97 | pty_getmp(struct lwp *l, struct mount **mpp) |
98 | { |
99 | if (ptm == NULL) |
100 | return EOPNOTSUPP; |
101 | |
102 | return (*ptm->getmp)(l, mpp); |
103 | } |
104 | |
105 | dev_t |
106 | pty_makedev(char ms, int minor) |
107 | { |
108 | return makedev(ms == 't' ? pts_major : ptc_major, minor); |
109 | } |
110 | |
111 | |
112 | static dev_t |
113 | pty_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 | */ |
133 | int |
134 | pty_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 | |
155 | static int |
156 | pty_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 | } |
167 | retry: |
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; |
207 | bad: |
208 | fd_abort(curproc, fp, *fd); |
209 | return error; |
210 | } |
211 | |
212 | int |
213 | pty_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 | |
254 | static int |
255 | pty_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; |
284 | bad: |
285 | fd_abort(curproc, fp, *fd); |
286 | return error; |
287 | } |
288 | |
289 | struct ptm_pty * |
290 | pty_sethandler(struct ptm_pty *nptm) |
291 | { |
292 | struct ptm_pty *optm = ptm; |
293 | ptm = nptm; |
294 | return optm; |
295 | } |
296 | |
297 | int |
298 | pty_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 | |
316 | void |
317 | /*ARGSUSED*/ |
318 | ptmattach(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 | |
331 | static int |
332 | /*ARGSUSED*/ |
333 | ptmopen(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 | |
370 | static int |
371 | /*ARGSUSED*/ |
372 | ptmclose(dev_t dev, int flag, int mode, struct lwp *l) |
373 | { |
374 | |
375 | return (0); |
376 | } |
377 | |
378 | static int |
379 | /*ARGSUSED*/ |
380 | ptmioctl(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 | } |
416 | bad2: |
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 | |
429 | const 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 | |