1181905Sed/*- 2181905Sed * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org> 3154833Scognet * All rights reserved. 4154833Scognet * 5181905Sed * Portions of this software were developed under sponsorship from Snow 6181905Sed * B.V., the Netherlands. 7154833Scognet * 8154833Scognet * Redistribution and use in source and binary forms, with or without 9154833Scognet * modification, are permitted provided that the following conditions 10154833Scognet * are met: 11154833Scognet * 1. Redistributions of source code must retain the above copyright 12154833Scognet * notice, this list of conditions and the following disclaimer. 13154833Scognet * 2. Redistributions in binary form must reproduce the above copyright 14154833Scognet * notice, this list of conditions and the following disclaimer in the 15154833Scognet * documentation and/or other materials provided with the distribution. 16154833Scognet * 17181905Sed * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18154833Scognet * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19154833Scognet * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20181905Sed * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21154833Scognet * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22154833Scognet * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23154833Scognet * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24154833Scognet * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25154833Scognet * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26154833Scognet * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27154833Scognet * SUCH DAMAGE. 28154833Scognet */ 29154833Scognet 30154833Scognet#include <sys/cdefs.h> 31154833Scognet__FBSDID("$FreeBSD$"); 32154833Scognet 33181905Sed/* Add compatibility bits for FreeBSD. */ 34181905Sed#define PTS_COMPAT 35196886Sed/* Add pty(4) compat bits. */ 36181905Sed#define PTS_EXTERNAL 37181905Sed/* Add bits to make Linux binaries work. */ 38181905Sed#define PTS_LINUX 39181905Sed 40154833Scognet#include <sys/param.h> 41154833Scognet#include <sys/lock.h> 42181905Sed#include <sys/condvar.h> 43181905Sed#include <sys/conf.h> 44154833Scognet#include <sys/fcntl.h> 45181905Sed#include <sys/file.h> 46181905Sed#include <sys/filedesc.h> 47181905Sed#include <sys/filio.h> 48154833Scognet#include <sys/kernel.h> 49191484Sed#include <sys/limits.h> 50154833Scognet#include <sys/malloc.h> 51181905Sed#include <sys/poll.h> 52181905Sed#include <sys/proc.h> 53220279Strasz#include <sys/racct.h> 54181905Sed#include <sys/resourcevar.h> 55181905Sed#include <sys/serial.h> 56181905Sed#include <sys/stat.h> 57181905Sed#include <sys/syscall.h> 58181905Sed#include <sys/syscallsubr.h> 59191484Sed#include <sys/sysctl.h> 60181905Sed#include <sys/sysent.h> 61181905Sed#include <sys/sysproto.h> 62181905Sed#include <sys/systm.h> 63181905Sed#include <sys/tty.h> 64181905Sed#include <sys/ttycom.h> 65154833Scognet 66181905Sed#include <machine/stdarg.h> 67154833Scognet 68191484Sed/* 69191484Sed * Our utmp(5) format is limited to 8-byte TTY line names. This means 70191484Sed * we can at most allocate 1000 pseudo-terminals ("pts/999"). Allow 71191484Sed * users to increase this number, assuming they have manually increased 72191484Sed * UT_LINESIZE. 73191484Sed */ 74181905Sedstatic struct unrhdr *pts_pool; 75154833Scognet 76181905Sedstatic MALLOC_DEFINE(M_PTS, "pts", "pseudo tty device"); 77154833Scognet 78181905Sed/* 79181905Sed * Per-PTS structure. 80181905Sed * 81181905Sed * List of locks 82181905Sed * (t) locked by tty_lock() 83181905Sed * (c) const until freeing 84181905Sed */ 85181905Sedstruct pts_softc { 86181905Sed int pts_unit; /* (c) Device unit number. */ 87181905Sed unsigned int pts_flags; /* (t) Device flags. */ 88183308Sed#define PTS_PKT 0x1 /* Packet mode. */ 89183308Sed#define PTS_FINISHED 0x2 /* Return errors on read()/write(). */ 90182764Sed char pts_pkt; /* (t) Unread packet mode data. */ 91154833Scognet 92181905Sed struct cv pts_inwait; /* (t) Blocking write() on master. */ 93181905Sed struct selinfo pts_inpoll; /* (t) Select queue for write(). */ 94181905Sed struct cv pts_outwait; /* (t) Blocking read() on master. */ 95181905Sed struct selinfo pts_outpoll; /* (t) Select queue for read(). */ 96154833Scognet 97181905Sed#ifdef PTS_EXTERNAL 98181905Sed struct cdev *pts_cdev; /* (c) Master device node. */ 99181905Sed#endif /* PTS_EXTERNAL */ 100154833Scognet 101219238Strasz struct ucred *pts_cred; /* (c) Resource limit. */ 102181905Sed}; 103154833Scognet 104181905Sed/* 105181905Sed * Controller-side file operations. 106154833Scognet */ 107154833Scognet 108181905Sedstatic int 109181905Sedptsdev_read(struct file *fp, struct uio *uio, struct ucred *active_cred, 110181905Sed int flags, struct thread *td) 111181905Sed{ 112181905Sed struct tty *tp = fp->f_data; 113181905Sed struct pts_softc *psc = tty_softc(tp); 114182764Sed int error = 0; 115182764Sed char pkt; 116154833Scognet 117181905Sed if (uio->uio_resid == 0) 118181905Sed return (0); 119154833Scognet 120182764Sed tty_lock(tp); 121182764Sed 122182764Sed for (;;) { 123182764Sed /* 124182764Sed * Implement packet mode. When packet mode is turned on, 125182764Sed * the first byte contains a bitmask of events that 126182764Sed * occured (start, stop, flush, window size, etc). 127182764Sed */ 128182764Sed if (psc->pts_flags & PTS_PKT && psc->pts_pkt) { 129182764Sed pkt = psc->pts_pkt; 130182764Sed psc->pts_pkt = 0; 131182764Sed tty_unlock(tp); 132182764Sed 133182764Sed error = ureadc(pkt, uio); 134181905Sed return (error); 135182764Sed } 136154833Scognet 137182764Sed /* 138182764Sed * Transmit regular data. 139182764Sed * 140182764Sed * XXX: We shouldn't use ttydisc_getc_poll()! Even 141182764Sed * though in this implementation, there is likely going 142182764Sed * to be data, we should just call ttydisc_getc_uio() 143182764Sed * and use its return value to sleep. 144182764Sed */ 145182764Sed if (ttydisc_getc_poll(tp)) { 146182764Sed if (psc->pts_flags & PTS_PKT) { 147182764Sed /* 148182764Sed * XXX: Small race. Fortunately PTY 149182764Sed * consumers aren't multithreaded. 150182764Sed */ 151154833Scognet 152182764Sed tty_unlock(tp); 153182764Sed error = ureadc(TIOCPKT_DATA, uio); 154182764Sed if (error) 155182764Sed return (error); 156182764Sed tty_lock(tp); 157182764Sed } 158182764Sed 159182764Sed error = ttydisc_getc_uio(tp, uio); 160181905Sed break; 161182764Sed } 162154833Scognet 163181905Sed /* Maybe the device isn't used anyway. */ 164183308Sed if (psc->pts_flags & PTS_FINISHED) 165181905Sed break; 166154833Scognet 167181905Sed /* Wait for more data. */ 168181905Sed if (fp->f_flag & O_NONBLOCK) { 169181905Sed error = EWOULDBLOCK; 170181905Sed break; 171181905Sed } 172181905Sed error = cv_wait_sig(&psc->pts_outwait, tp->t_mtx); 173181905Sed if (error != 0) 174181905Sed break; 175181905Sed } 176182764Sed 177181905Sed tty_unlock(tp); 178154833Scognet 179181905Sed return (error); 180181905Sed} 181154833Scognet 182181905Sedstatic int 183181905Sedptsdev_write(struct file *fp, struct uio *uio, struct ucred *active_cred, 184181905Sed int flags, struct thread *td) 185181905Sed{ 186181905Sed struct tty *tp = fp->f_data; 187181905Sed struct pts_softc *psc = tty_softc(tp); 188181905Sed char ib[256], *ibstart; 189181905Sed size_t iblen, rintlen; 190181905Sed int error = 0; 191154833Scognet 192182764Sed if (uio->uio_resid == 0) 193182764Sed return (0); 194154833Scognet 195182764Sed for (;;) { 196181905Sed ibstart = ib; 197181905Sed iblen = MIN(uio->uio_resid, sizeof ib); 198181905Sed error = uiomove(ib, iblen, uio); 199182764Sed 200181905Sed tty_lock(tp); 201196036Sed if (error != 0) { 202196036Sed iblen = 0; 203181905Sed goto done; 204196036Sed } 205154833Scognet 206181905Sed /* 207181905Sed * When possible, avoid the slow path. rint_bypass() 208181905Sed * copies all input to the input queue at once. 209181905Sed */ 210182764Sed MPASS(iblen > 0); 211182764Sed do { 212196452Sed rintlen = ttydisc_rint_simple(tp, ibstart, iblen); 213196452Sed ibstart += rintlen; 214196452Sed iblen -= rintlen; 215196452Sed if (iblen == 0) { 216196452Sed /* All data written. */ 217196452Sed break; 218181905Sed } 219181905Sed 220181905Sed /* Maybe the device isn't used anyway. */ 221183308Sed if (psc->pts_flags & PTS_FINISHED) { 222182001Sed error = EIO; 223181905Sed goto done; 224181905Sed } 225181905Sed 226181905Sed /* Wait for more data. */ 227181905Sed if (fp->f_flag & O_NONBLOCK) { 228181905Sed error = EWOULDBLOCK; 229181905Sed goto done; 230181905Sed } 231181905Sed 232181905Sed /* Wake up users on the slave side. */ 233181905Sed ttydisc_rint_done(tp); 234181905Sed error = cv_wait_sig(&psc->pts_inwait, tp->t_mtx); 235181905Sed if (error != 0) 236181905Sed goto done; 237182764Sed } while (iblen > 0); 238182764Sed 239182764Sed if (uio->uio_resid == 0) 240182764Sed break; 241182764Sed tty_unlock(tp); 242154833Scognet } 243181905Sed 244181905Seddone: ttydisc_rint_done(tp); 245181905Sed tty_unlock(tp); 246196036Sed 247196036Sed /* 248196036Sed * Don't account for the part of the buffer that we couldn't 249196036Sed * pass to the TTY. 250196036Sed */ 251196036Sed uio->uio_resid += iblen; 252181905Sed return (error); 253154833Scognet} 254154833Scognet 255181905Sedstatic int 256185942Sedptsdev_truncate(struct file *fp, off_t length, struct ucred *active_cred, 257185942Sed struct thread *td) 258185942Sed{ 259185942Sed 260185942Sed return (EINVAL); 261185942Sed} 262185942Sed 263185942Sedstatic int 264181905Sedptsdev_ioctl(struct file *fp, u_long cmd, void *data, 265181905Sed struct ucred *active_cred, struct thread *td) 266154833Scognet{ 267181905Sed struct tty *tp = fp->f_data; 268181905Sed struct pts_softc *psc = tty_softc(tp); 269181905Sed int error = 0, sig; 270154833Scognet 271181905Sed switch (cmd) { 272181905Sed case FIONBIO: 273181905Sed /* This device supports non-blocking operation. */ 274181905Sed return (0); 275186030Sed case FIONREAD: 276186030Sed tty_lock(tp); 277186030Sed if (psc->pts_flags & PTS_FINISHED) { 278186030Sed /* Force read() to be called. */ 279186030Sed *(int *)data = 1; 280186030Sed } else { 281186030Sed *(int *)data = ttydisc_getc_poll(tp); 282186030Sed } 283186030Sed tty_unlock(tp); 284186030Sed return (0); 285181905Sed case FIODGNAME: { 286181905Sed struct fiodgname_arg *fgn; 287181905Sed const char *p; 288181905Sed int i; 289154833Scognet 290181905Sed /* Reverse device name lookups, for ptsname() and ttyname(). */ 291181905Sed fgn = data; 292188822Sed p = tty_devname(tp); 293181905Sed i = strlen(p) + 1; 294181905Sed if (i > fgn->len) 295181905Sed return (EINVAL); 296181905Sed return copyout(p, fgn->buf, i); 297181905Sed } 298223575Sed 299181905Sed /* 300181905Sed * We need to implement TIOCGPGRP and TIOCGSID here again. When 301181905Sed * called on the pseudo-terminal master, it should not check if 302181905Sed * the terminal is the foreground terminal of the calling 303181905Sed * process. 304181905Sed * 305181905Sed * TIOCGETA is also implemented here. Various Linux PTY routines 306181905Sed * often call isatty(), which is implemented by tcgetattr(). 307181905Sed */ 308181905Sed#ifdef PTS_LINUX 309181905Sed case TIOCGETA: 310181905Sed /* Obtain terminal flags through tcgetattr(). */ 311181905Sed tty_lock(tp); 312189222Sed *(struct termios*)data = tp->t_termios; 313181905Sed tty_unlock(tp); 314181905Sed return (0); 315181905Sed#endif /* PTS_LINUX */ 316181905Sed case TIOCSETAF: 317181905Sed case TIOCSETAW: 318181905Sed /* 319181905Sed * We must make sure we turn tcsetattr() calls of TCSAFLUSH and 320181905Sed * TCSADRAIN into something different. If an application would 321181905Sed * call TCSAFLUSH or TCSADRAIN on the master descriptor, it may 322181905Sed * deadlock waiting for all data to be read. 323181905Sed */ 324181905Sed cmd = TIOCSETA; 325181905Sed break; 326181905Sed#if defined(PTS_COMPAT) || defined(PTS_LINUX) 327181905Sed case TIOCGPTN: 328181905Sed /* 329181905Sed * Get the device unit number. 330181905Sed */ 331181905Sed if (psc->pts_unit < 0) 332181905Sed return (ENOTTY); 333181905Sed *(unsigned int *)data = psc->pts_unit; 334181905Sed return (0); 335181905Sed#endif /* PTS_COMPAT || PTS_LINUX */ 336181905Sed case TIOCGPGRP: 337181905Sed /* Get the foreground process group ID. */ 338181905Sed tty_lock(tp); 339181905Sed if (tp->t_pgrp != NULL) 340181905Sed *(int *)data = tp->t_pgrp->pg_id; 341181905Sed else 342181905Sed *(int *)data = NO_PID; 343181905Sed tty_unlock(tp); 344181905Sed return (0); 345181905Sed case TIOCGSID: 346181905Sed /* Get the session leader process ID. */ 347181905Sed tty_lock(tp); 348181905Sed if (tp->t_session == NULL) 349181905Sed error = ENOTTY; 350181905Sed else 351181905Sed *(int *)data = tp->t_session->s_sid; 352181905Sed tty_unlock(tp); 353181905Sed return (error); 354181905Sed case TIOCPTMASTER: 355181905Sed /* Yes, we are a pseudo-terminal master. */ 356181905Sed return (0); 357181905Sed case TIOCSIG: 358181905Sed /* Signal the foreground process group. */ 359181905Sed sig = *(int *)data; 360181905Sed if (sig < 1 || sig >= NSIG) 361181905Sed return (EINVAL); 362154833Scognet 363181905Sed tty_lock(tp); 364181905Sed tty_signal_pgrp(tp, sig); 365181905Sed tty_unlock(tp); 366181905Sed return (0); 367181905Sed case TIOCPKT: 368181905Sed /* Enable/disable packet mode. */ 369181905Sed tty_lock(tp); 370181905Sed if (*(int *)data) 371181905Sed psc->pts_flags |= PTS_PKT; 372181905Sed else 373181905Sed psc->pts_flags &= ~PTS_PKT; 374181905Sed tty_unlock(tp); 375181905Sed return (0); 376181905Sed } 377154833Scognet 378181905Sed /* Just redirect this ioctl to the slave device. */ 379181905Sed tty_lock(tp); 380201532Sed error = tty_ioctl(tp, cmd, data, fp->f_flag, td); 381181905Sed tty_unlock(tp); 382188822Sed if (error == ENOIOCTL) 383188822Sed error = ENOTTY; 384162778Smbr 385181905Sed return (error); 386154833Scognet} 387154833Scognet 388154833Scognetstatic int 389181905Sedptsdev_poll(struct file *fp, int events, struct ucred *active_cred, 390181905Sed struct thread *td) 391154833Scognet{ 392181905Sed struct tty *tp = fp->f_data; 393181905Sed struct pts_softc *psc = tty_softc(tp); 394181905Sed int revents = 0; 395154833Scognet 396181905Sed tty_lock(tp); 397181905Sed 398183308Sed if (psc->pts_flags & PTS_FINISHED) { 399181905Sed /* Slave device is not opened. */ 400181905Sed tty_unlock(tp); 401195444Sed return ((events & (POLLIN|POLLRDNORM)) | POLLHUP); 402154833Scognet } 403181905Sed 404181905Sed if (events & (POLLIN|POLLRDNORM)) { 405181905Sed /* See if we can getc something. */ 406182764Sed if (ttydisc_getc_poll(tp) || 407182764Sed (psc->pts_flags & PTS_PKT && psc->pts_pkt)) 408181905Sed revents |= events & (POLLIN|POLLRDNORM); 409154833Scognet } 410181905Sed if (events & (POLLOUT|POLLWRNORM)) { 411181905Sed /* See if we can rint something. */ 412181905Sed if (ttydisc_rint_poll(tp)) 413181905Sed revents |= events & (POLLOUT|POLLWRNORM); 414154833Scognet } 415181905Sed 416181905Sed /* 417181905Sed * No need to check for POLLHUP here. This device cannot be used 418181905Sed * as a callout device, which means we always have a carrier, 419181905Sed * because the master is. 420181905Sed */ 421181905Sed 422181905Sed if (revents == 0) { 423181905Sed /* 424181905Sed * This code might look misleading, but the naming of 425181905Sed * poll events on this side is the opposite of the slave 426181905Sed * device. 427181905Sed */ 428181905Sed if (events & (POLLIN|POLLRDNORM)) 429181905Sed selrecord(td, &psc->pts_outpoll); 430181905Sed if (events & (POLLOUT|POLLWRNORM)) 431181905Sed selrecord(td, &psc->pts_inpoll); 432181905Sed } 433181905Sed 434181905Sed tty_unlock(tp); 435181905Sed 436181905Sed return (revents); 437154833Scognet} 438154833Scognet 439185942Sed/* 440185942Sed * kqueue support. 441185942Sed */ 442185942Sed 443185942Sedstatic void 444185942Sedpts_kqops_read_detach(struct knote *kn) 445185942Sed{ 446185942Sed struct file *fp = kn->kn_fp; 447185942Sed struct tty *tp = fp->f_data; 448185942Sed struct pts_softc *psc = tty_softc(tp); 449185942Sed 450185942Sed knlist_remove(&psc->pts_outpoll.si_note, kn, 0); 451185942Sed} 452185942Sed 453154833Scognetstatic int 454185942Sedpts_kqops_read_event(struct knote *kn, long hint) 455185942Sed{ 456185942Sed struct file *fp = kn->kn_fp; 457185942Sed struct tty *tp = fp->f_data; 458185942Sed struct pts_softc *psc = tty_softc(tp); 459185942Sed 460185942Sed if (psc->pts_flags & PTS_FINISHED) { 461185942Sed kn->kn_flags |= EV_EOF; 462185942Sed return (1); 463185942Sed } else { 464185942Sed kn->kn_data = ttydisc_getc_poll(tp); 465185942Sed return (kn->kn_data > 0); 466185942Sed } 467185942Sed} 468185942Sed 469185942Sedstatic void 470185942Sedpts_kqops_write_detach(struct knote *kn) 471185942Sed{ 472185942Sed struct file *fp = kn->kn_fp; 473185942Sed struct tty *tp = fp->f_data; 474185942Sed struct pts_softc *psc = tty_softc(tp); 475185942Sed 476185942Sed knlist_remove(&psc->pts_inpoll.si_note, kn, 0); 477185942Sed} 478185942Sed 479185942Sedstatic int 480185942Sedpts_kqops_write_event(struct knote *kn, long hint) 481185942Sed{ 482185942Sed struct file *fp = kn->kn_fp; 483185942Sed struct tty *tp = fp->f_data; 484185942Sed struct pts_softc *psc = tty_softc(tp); 485185942Sed 486185942Sed if (psc->pts_flags & PTS_FINISHED) { 487185942Sed kn->kn_flags |= EV_EOF; 488185942Sed return (1); 489185942Sed } else { 490185942Sed kn->kn_data = ttydisc_rint_poll(tp); 491185942Sed return (kn->kn_data > 0); 492185942Sed } 493185942Sed} 494185942Sed 495197134Srwatsonstatic struct filterops pts_kqops_read = { 496197134Srwatson .f_isfd = 1, 497197134Srwatson .f_detach = pts_kqops_read_detach, 498197134Srwatson .f_event = pts_kqops_read_event, 499197134Srwatson}; 500197134Srwatsonstatic struct filterops pts_kqops_write = { 501197134Srwatson .f_isfd = 1, 502197134Srwatson .f_detach = pts_kqops_write_detach, 503197134Srwatson .f_event = pts_kqops_write_event, 504197134Srwatson}; 505185942Sed 506185942Sedstatic int 507185942Sedptsdev_kqfilter(struct file *fp, struct knote *kn) 508185942Sed{ 509185942Sed struct tty *tp = fp->f_data; 510185942Sed struct pts_softc *psc = tty_softc(tp); 511185942Sed int error = 0; 512185942Sed 513185942Sed tty_lock(tp); 514185942Sed 515185942Sed switch (kn->kn_filter) { 516185942Sed case EVFILT_READ: 517185942Sed kn->kn_fop = &pts_kqops_read; 518185942Sed knlist_add(&psc->pts_outpoll.si_note, kn, 1); 519185942Sed break; 520185942Sed case EVFILT_WRITE: 521185942Sed kn->kn_fop = &pts_kqops_write; 522185942Sed knlist_add(&psc->pts_inpoll.si_note, kn, 1); 523185942Sed break; 524185942Sed default: 525185942Sed error = EINVAL; 526185942Sed break; 527185942Sed } 528185942Sed 529185942Sed tty_unlock(tp); 530185942Sed return (error); 531185942Sed} 532185942Sed 533185942Sedstatic int 534181905Sedptsdev_stat(struct file *fp, struct stat *sb, struct ucred *active_cred, 535181905Sed struct thread *td) 536154833Scognet{ 537181905Sed struct tty *tp = fp->f_data; 538181905Sed#ifdef PTS_EXTERNAL 539181905Sed struct pts_softc *psc = tty_softc(tp); 540181905Sed#endif /* PTS_EXTERNAL */ 541183239Sed struct cdev *dev = tp->t_dev; 542154833Scognet 543181905Sed /* 544181905Sed * According to POSIX, we must implement an fstat(). This also 545181905Sed * makes this implementation compatible with Linux binaries, 546181905Sed * because Linux calls fstat() on the pseudo-terminal master to 547181905Sed * obtain st_rdev. 548181905Sed * 549183239Sed * XXX: POSIX also mentions we must fill in st_dev, but how? 550181905Sed */ 551181905Sed 552181905Sed bzero(sb, sizeof *sb); 553181905Sed#ifdef PTS_EXTERNAL 554181905Sed if (psc->pts_cdev != NULL) 555181905Sed sb->st_ino = sb->st_rdev = dev2udev(psc->pts_cdev); 556181905Sed else 557181905Sed#endif /* PTS_EXTERNAL */ 558181905Sed sb->st_ino = sb->st_rdev = tty_udev(tp); 559183239Sed 560205792Sed sb->st_atim = dev->si_atime; 561205792Sed sb->st_ctim = dev->si_ctime; 562205792Sed sb->st_mtim = dev->si_mtime; 563183239Sed sb->st_uid = dev->si_uid; 564183239Sed sb->st_gid = dev->si_gid; 565183239Sed sb->st_mode = dev->si_mode | S_IFCHR; 566223575Sed 567181905Sed return (0); 568154833Scognet} 569154833Scognet 570154833Scognetstatic int 571181905Sedptsdev_close(struct file *fp, struct thread *td) 572154833Scognet{ 573181905Sed struct tty *tp = fp->f_data; 574154833Scognet 575181905Sed /* Deallocate TTY device. */ 576181905Sed tty_lock(tp); 577181905Sed tty_rel_gone(tp); 578181905Sed 579206395Skib /* 580206395Skib * Open of /dev/ptmx or /dev/ptyXX changes the type of file 581206395Skib * from DTYPE_VNODE to DTYPE_PTS. vn_open() increases vnode 582206395Skib * use count, we need to decrement it, and possibly do other 583206395Skib * required cleanup. 584206395Skib */ 585206395Skib if (fp->f_vnode != NULL) 586206395Skib return (vnops.fo_close(fp, td)); 587206395Skib 588181905Sed return (0); 589154833Scognet} 590154833Scognet 591181905Sedstatic struct fileops ptsdev_ops = { 592181905Sed .fo_read = ptsdev_read, 593181905Sed .fo_write = ptsdev_write, 594185942Sed .fo_truncate = ptsdev_truncate, 595181905Sed .fo_ioctl = ptsdev_ioctl, 596181905Sed .fo_poll = ptsdev_poll, 597185942Sed .fo_kqfilter = ptsdev_kqfilter, 598181905Sed .fo_stat = ptsdev_stat, 599181905Sed .fo_close = ptsdev_close, 600224914Skib .fo_chmod = invfo_chmod, 601224914Skib .fo_chown = invfo_chown, 602181905Sed .fo_flags = DFLAG_PASSABLE, 603181905Sed}; 604181905Sed 605154833Scognet/* 606181905Sed * Driver-side hooks. 607154833Scognet */ 608181905Sed 609181905Sedstatic void 610181905Sedptsdrv_outwakeup(struct tty *tp) 611154833Scognet{ 612181905Sed struct pts_softc *psc = tty_softc(tp); 613154833Scognet 614181905Sed cv_broadcast(&psc->pts_outwait); 615181905Sed selwakeup(&psc->pts_outpoll); 616185942Sed KNOTE_LOCKED(&psc->pts_outpoll.si_note, 0); 617154833Scognet} 618154833Scognet 619154833Scognetstatic void 620181905Sedptsdrv_inwakeup(struct tty *tp) 621154833Scognet{ 622181905Sed struct pts_softc *psc = tty_softc(tp); 623154833Scognet 624181905Sed cv_broadcast(&psc->pts_inwait); 625181905Sed selwakeup(&psc->pts_inpoll); 626185942Sed KNOTE_LOCKED(&psc->pts_inpoll.si_note, 0); 627154833Scognet} 628154833Scognet 629183308Sedstatic int 630183308Sedptsdrv_open(struct tty *tp) 631183308Sed{ 632183308Sed struct pts_softc *psc = tty_softc(tp); 633183308Sed 634183308Sed psc->pts_flags &= ~PTS_FINISHED; 635183308Sed 636183308Sed return (0); 637183308Sed} 638183308Sed 639154833Scognetstatic void 640181905Sedptsdrv_close(struct tty *tp) 641154833Scognet{ 642183308Sed struct pts_softc *psc = tty_softc(tp); 643154833Scognet 644181905Sed /* Wake up any blocked readers/writers. */ 645186382Sed psc->pts_flags |= PTS_FINISHED; 646181905Sed ptsdrv_outwakeup(tp); 647181905Sed ptsdrv_inwakeup(tp); 648154833Scognet} 649154833Scognet 650181905Sedstatic void 651182764Sedptsdrv_pktnotify(struct tty *tp, char event) 652182764Sed{ 653182764Sed struct pts_softc *psc = tty_softc(tp); 654182764Sed 655182764Sed /* 656182764Sed * Clear conflicting flags. 657182764Sed */ 658182764Sed 659182764Sed switch (event) { 660182764Sed case TIOCPKT_STOP: 661182764Sed psc->pts_pkt &= ~TIOCPKT_START; 662182764Sed break; 663182764Sed case TIOCPKT_START: 664182764Sed psc->pts_pkt &= ~TIOCPKT_STOP; 665182764Sed break; 666182764Sed case TIOCPKT_NOSTOP: 667182764Sed psc->pts_pkt &= ~TIOCPKT_DOSTOP; 668182764Sed break; 669182764Sed case TIOCPKT_DOSTOP: 670182764Sed psc->pts_pkt &= ~TIOCPKT_NOSTOP; 671182764Sed break; 672182764Sed } 673182764Sed 674182764Sed psc->pts_pkt |= event; 675182764Sed ptsdrv_outwakeup(tp); 676182764Sed} 677182764Sed 678182764Sedstatic void 679181905Sedptsdrv_free(void *softc) 680154833Scognet{ 681181905Sed struct pts_softc *psc = softc; 682154833Scognet 683181905Sed /* Make device number available again. */ 684181905Sed if (psc->pts_unit >= 0) 685181905Sed free_unr(pts_pool, psc->pts_unit); 686154833Scognet 687219238Strasz chgptscnt(psc->pts_cred->cr_ruidinfo, -1, 0); 688220279Strasz racct_sub_cred(psc->pts_cred, RACCT_NPTS, 1); 689219238Strasz crfree(psc->pts_cred); 690154833Scognet 691225177Sattilio seldrain(&psc->pts_inpoll); 692225177Sattilio seldrain(&psc->pts_outpoll); 693185942Sed knlist_destroy(&psc->pts_inpoll.si_note); 694185942Sed knlist_destroy(&psc->pts_outpoll.si_note); 695185942Sed 696181905Sed#ifdef PTS_EXTERNAL 697181905Sed /* Destroy master device as well. */ 698181905Sed if (psc->pts_cdev != NULL) 699181905Sed destroy_dev_sched(psc->pts_cdev); 700181905Sed#endif /* PTS_EXTERNAL */ 701181905Sed 702181905Sed free(psc, M_PTS); 703154833Scognet} 704154833Scognet 705181905Sedstatic struct ttydevsw pts_class = { 706181905Sed .tsw_flags = TF_NOPREFIX, 707181905Sed .tsw_outwakeup = ptsdrv_outwakeup, 708181905Sed .tsw_inwakeup = ptsdrv_inwakeup, 709183308Sed .tsw_open = ptsdrv_open, 710181905Sed .tsw_close = ptsdrv_close, 711182764Sed .tsw_pktnotify = ptsdrv_pktnotify, 712181905Sed .tsw_free = ptsdrv_free, 713181905Sed}; 714181905Sed 715196886Sed#ifndef PTS_EXTERNAL 716196886Sedstatic 717196886Sed#endif /* !PTS_EXTERNAL */ 718196886Sedint 719181905Sedpts_alloc(int fflags, struct thread *td, struct file *fp) 720154833Scognet{ 721220279Strasz int unit, ok, error; 722154833Scognet struct tty *tp; 723181905Sed struct pts_softc *psc; 724181905Sed struct proc *p = td->td_proc; 725219238Strasz struct ucred *cred = td->td_ucred; 726154833Scognet 727181905Sed /* Resource limiting. */ 728181905Sed PROC_LOCK(p); 729220279Strasz error = racct_add(p, RACCT_NPTS, 1); 730220279Strasz if (error != 0) { 731220279Strasz PROC_UNLOCK(p); 732220279Strasz return (EAGAIN); 733220279Strasz } 734219238Strasz ok = chgptscnt(cred->cr_ruidinfo, 1, lim_cur(p, RLIMIT_NPTS)); 735220279Strasz if (!ok) { 736220279Strasz racct_sub(p, RACCT_NPTS, 1); 737220279Strasz PROC_UNLOCK(p); 738220279Strasz return (EAGAIN); 739220279Strasz } 740181905Sed PROC_UNLOCK(p); 741154833Scognet 742181905Sed /* Try to allocate a new pts unit number. */ 743181905Sed unit = alloc_unr(pts_pool); 744181905Sed if (unit < 0) { 745220279Strasz racct_sub(p, RACCT_NPTS, 1); 746219238Strasz chgptscnt(cred->cr_ruidinfo, -1, 0); 747181905Sed return (EAGAIN); 748154833Scognet } 749154833Scognet 750181905Sed /* Allocate TTY and softc. */ 751181905Sed psc = malloc(sizeof(struct pts_softc), M_PTS, M_WAITOK|M_ZERO); 752200685Sed cv_init(&psc->pts_inwait, "ptsin"); 753200685Sed cv_init(&psc->pts_outwait, "ptsout"); 754154833Scognet 755181905Sed psc->pts_unit = unit; 756219238Strasz psc->pts_cred = crhold(cred); 757154833Scognet 758193018Sed tp = tty_alloc(&pts_class, psc); 759193951Skib knlist_init_mtx(&psc->pts_inpoll.si_note, tp->t_mtx); 760193951Skib knlist_init_mtx(&psc->pts_outpoll.si_note, tp->t_mtx); 761154833Scognet 762181905Sed /* Expose the slave device as well. */ 763181905Sed tty_makedev(tp, td->td_ucred, "pts/%u", psc->pts_unit); 764154833Scognet 765181905Sed finit(fp, fflags, DTYPE_PTS, tp, &ptsdev_ops); 766181905Sed 767181905Sed return (0); 768154833Scognet} 769154833Scognet 770181905Sed#ifdef PTS_EXTERNAL 771181905Sedint 772181905Sedpts_alloc_external(int fflags, struct thread *td, struct file *fp, 773181905Sed struct cdev *dev, const char *name) 774154833Scognet{ 775220279Strasz int ok, error; 776181905Sed struct tty *tp; 777181905Sed struct pts_softc *psc; 778181905Sed struct proc *p = td->td_proc; 779219238Strasz struct ucred *cred = td->td_ucred; 780154833Scognet 781181905Sed /* Resource limiting. */ 782181905Sed PROC_LOCK(p); 783220279Strasz error = racct_add(p, RACCT_NPTS, 1); 784220279Strasz if (error != 0) { 785220279Strasz PROC_UNLOCK(p); 786220279Strasz return (EAGAIN); 787220279Strasz } 788219238Strasz ok = chgptscnt(cred->cr_ruidinfo, 1, lim_cur(p, RLIMIT_NPTS)); 789220279Strasz if (!ok) { 790220279Strasz racct_sub(p, RACCT_NPTS, 1); 791220279Strasz PROC_UNLOCK(p); 792220279Strasz return (EAGAIN); 793220279Strasz } 794181905Sed PROC_UNLOCK(p); 795154833Scognet 796181905Sed /* Allocate TTY and softc. */ 797181905Sed psc = malloc(sizeof(struct pts_softc), M_PTS, M_WAITOK|M_ZERO); 798200685Sed cv_init(&psc->pts_inwait, "ptsin"); 799200685Sed cv_init(&psc->pts_outwait, "ptsout"); 800154833Scognet 801181905Sed psc->pts_unit = -1; 802181905Sed psc->pts_cdev = dev; 803219238Strasz psc->pts_cred = crhold(cred); 804154833Scognet 805193018Sed tp = tty_alloc(&pts_class, psc); 806193951Skib knlist_init_mtx(&psc->pts_inpoll.si_note, tp->t_mtx); 807193951Skib knlist_init_mtx(&psc->pts_outpoll.si_note, tp->t_mtx); 808154833Scognet 809181905Sed /* Expose the slave device as well. */ 810181905Sed tty_makedev(tp, td->td_ucred, "%s", name); 811154833Scognet 812181905Sed finit(fp, fflags, DTYPE_PTS, tp, &ptsdev_ops); 813154833Scognet 814181905Sed return (0); 815154833Scognet} 816181905Sed#endif /* PTS_EXTERNAL */ 817154833Scognet 818181905Sedint 819225617Skmacysys_posix_openpt(struct thread *td, struct posix_openpt_args *uap) 820154833Scognet{ 821181905Sed int error, fd; 822181905Sed struct file *fp; 823154833Scognet 824154833Scognet /* 825181905Sed * POSIX states it's unspecified when other flags are passed. We 826181905Sed * don't allow this. 827154833Scognet */ 828250973Sjilles if (uap->flags & ~(O_RDWR|O_NOCTTY|O_CLOEXEC)) 829181905Sed return (EINVAL); 830223575Sed 831250973Sjilles error = falloc(td, &fp, &fd, uap->flags); 832181905Sed if (error) 833154833Scognet return (error); 834181905Sed 835181905Sed /* Allocate the actual pseudo-TTY. */ 836181905Sed error = pts_alloc(FFLAGS(uap->flags & O_ACCMODE), td, fp); 837181905Sed if (error != 0) { 838181905Sed fdclose(td->td_proc->p_fd, fp, fd, td); 839243584Smjg fdrop(fp, td); 840181905Sed return (error); 841154833Scognet } 842154833Scognet 843181905Sed /* Pass it back to userspace. */ 844181905Sed td->td_retval[0] = fd; 845181905Sed fdrop(fp, td); 846154833Scognet 847181905Sed return (0); 848154833Scognet} 849181905Sed 850154833Scognetstatic void 851181905Sedpts_init(void *unused) 852154833Scognet{ 853154833Scognet 854191484Sed pts_pool = new_unrhdr(0, INT_MAX, NULL); 855154833Scognet} 856154833Scognet 857181905SedSYSINIT(pts, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, pts_init, NULL); 858