| /*- |
| * Copyright (c) 2008 Ed Schouten <ed@FreeBSD.org> |
| * All rights reserved. |
| * Copyright 2015-2024 Rivoreo |
| * |
| * Portions of this software were developed under sponsorship from Snow |
| * B.V., the Netherlands. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include <sys/cdefs.h> |
| #include <sys/param.h> |
| #include <sys/types.h> |
| #include <sys/kernel.h> |
| #include <sys/module.h> |
| #include <sys/conf.h> |
| #include <sys/malloc.h> |
| #include <sys/fcntl.h> |
| #include <sys/sysctl.h> |
| #include <sys/systm.h> |
| #include <sys/tty.h> |
| #include <fs/devfs/devfs_int.h> |
| |
| /* |
| * This driver implements a BSD-style compatibility naming scheme for |
| * the pts(4) driver. We just call into pts(4) to create the actual PTY. |
| * To make sure we don't use the same PTY multiple times, we abuse |
| * si_drv1 inside the cdev to mark whether the PTY is in use. |
| * |
| * This version preallocates pty master devices in devfs. |
| */ |
| |
| struct pts_softc { |
| int pts_unit; /* (c) Device unit number. */ |
| unsigned int pts_flags; /* (t) Device flags. */ |
| char pts_pkt; /* (t) Unread packet mode data. */ |
| struct cv pts_inwait; /* (t) Blocking write() on master. */ |
| struct selinfo pts_inpoll; /* (t) Select queue for write(). */ |
| struct cv pts_outwait; /* (t) Blocking read() on master. */ |
| struct selinfo pts_outpoll; /* (t) Select queue for read(). */ |
| struct cdev *pts_cdev; /* (c) Master device node. */ |
| struct ucred *pts_cred; /* (c) Resource limit. */ |
| }; |
| |
| static unsigned int pty_warningcnt = 1; |
| SYSCTL_UINT(_kern, OID_AUTO, pty_warningcnt, CTLFLAG_RW, |
| &pty_warningcnt, 0, |
| "Warnings that will be triggered upon legacy PTY allocation"); |
| |
| static unsigned int pty_preallocate_count = 256; |
| |
| static void remove_master_dev_pointer_from_pts(const char *slave_name) { |
| struct cdev_priv *cdp; |
| TAILQ_FOREACH(cdp, &cdevp_list, cdp_list) { |
| if(strcmp(cdp->cdp_c.si_name, slave_name) == 0) { |
| struct tty *tty = cdp->cdp_c.si_drv1; |
| if(!tty) break; |
| struct pts_softc *ptsc = tty->t_devswsoftc; |
| if(!ptsc) break; |
| ptsc->pts_cdev = NULL; |
| break; |
| } |
| } |
| } |
| |
| static int |
| ptydev_fdopen(struct cdev *dev, int fflags, struct thread *td, struct file *fp) |
| { |
| int error; |
| char name[6]; /* "ttyXX" */ |
| |
| if (!atomic_cmpset_ptr((uintptr_t *)&dev->si_drv1, 0, 1)) |
| return (EBUSY); |
| |
| /* Generate device name and create PTY. */ |
| strlcpy(name, devtoname(dev), sizeof(name)); |
| name[0] = 't'; |
| |
| error = pts_alloc_external(fflags & (FREAD|FWRITE), td, fp, dev, name); |
| if(error) return error; |
| |
| remove_master_dev_pointer_from_pts(name); |
| |
| /* Raise a warning when a legacy PTY has been allocated. */ |
| counted_warning(&pty_warningcnt, "is using legacy pty devices"); |
| |
| return (0); |
| } |
| |
| static int ptydev_close(struct cdev *dev, int __unused f_flags, int __unused dev_type, struct thread *__unused td) { |
| char slave_name[6]; |
| slave_name[0] = 't'; |
| strlcpy(slave_name + 1, devtoname(dev) + 1, sizeof slave_name - 1); |
| remove_master_dev_pointer_from_pts(slave_name); |
| dev->si_drv1 = 0; |
| return 0; |
| } |
| |
| static struct cdevsw ptydev_cdevsw = { |
| .d_version = D_VERSION, |
| .d_fdopen = ptydev_fdopen, |
| .d_close = ptydev_close, |
| .d_name = "ptydev", |
| }; |
| |
| MALLOC_DEFINE(M_PTYDEV, "ptydev_array", "Pool for holding pointers to pty cdevs"); |
| |
| static unsigned int preallocated_pty_count = 0; |
| static struct cdev **preallocated_pty; |
| |
| #define PTY_NAME_1 "pqrslmnoPQRSLMNO" |
| #define PTY_NAME_2 "0123456789abcdefghijklmnopqrstuv" |
| |
| static int reallocate_pty() { |
| int e = 0; |
| //printf("reallocate_pty: pty_preallocate_count = %u, preallocated_pty_count = %u\n", pty_preallocate_count, preallocated_pty_count); |
| if(pty_preallocate_count < preallocated_pty_count) { |
| do { |
| struct cdev *dev = preallocated_pty[--preallocated_pty_count]; |
| if(!atomic_cmpset_ptr((uintptr_t *)&dev->si_drv1, 0, 1)) { |
| preallocated_pty_count++; |
| e = EBUSY; |
| break; |
| } |
| destroy_dev(dev); |
| } while(pty_preallocate_count < preallocated_pty_count); |
| preallocated_pty = realloc(preallocated_pty, preallocated_pty_count * sizeof(struct cdev *), M_PTYDEV, M_WAITOK); |
| } else if(pty_preallocate_count > preallocated_pty_count) { |
| const char *u1 = PTY_NAME_1; |
| const char *u2 = PTY_NAME_2; |
| unsigned int count = 0; |
| preallocated_pty = realloc(preallocated_pty, pty_preallocate_count * sizeof(struct cdev *), M_PTYDEV, M_WAITOK); |
| for(u1 = PTY_NAME_1; *u1; u1++) { |
| for(u2 = PTY_NAME_2; *u2; u2++) { |
| struct cdev *dev; |
| if(count < preallocated_pty_count) { |
| count++; |
| continue; |
| } |
| e = make_dev_p(MAKEDEV_ETERNAL_KLD, &dev, &ptydev_cdevsw, NULL, UID_ROOT, GID_WHEEL, 0666, "pty%c%c", *u1, *u2); |
| if(e) goto done; |
| preallocated_pty[count++] = dev; |
| if(count >= pty_preallocate_count) goto done; |
| } |
| } |
| done: |
| preallocated_pty_count = count; |
| } |
| pty_preallocate_count = preallocated_pty_count; |
| return e; |
| } |
| |
| static int pty_preallocate_sysctl_handler(SYSCTL_HANDLER_ARGS) { |
| unsigned int value = pty_preallocate_count; |
| int e = sysctl_handle_int(oidp, &value, 0, req); |
| if(e) return e; |
| if(!req->newptr) return 0; |
| if(value > (sizeof PTY_NAME_1 - 1) * (sizeof PTY_NAME_2 - 1)) { |
| //printf("pty_prealloc: requested preallocation number too large\n"); |
| return EINVAL; |
| } |
| pty_preallocate_count = value; |
| if(!M_PTYDEV->ks_handle) return 0; // Due to CTLFLAG_TUN |
| e = reallocate_pty(); |
| if(e) printf("pty_prealloc: kern.pty_preallocate: reallocate_pty error %d\n", e); |
| return 0; |
| } |
| |
| SYSCTL_OID(_kern, OID_AUTO, pty_preallocate, CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_TUN, SYSCTL_NULL_INT_PTR, 0, pty_preallocate_sysctl_handler, "I", "Number of pty master devices to preallocate"); |
| |
| static int |
| pty_prealloc_modevent(module_t __unused mod, int type, void *__unused data) |
| { |
| int e; |
| switch(type) { |
| case MOD_LOAD: |
| if(module_lookupbyname("pty")) { |
| printf("pty_prealloc: conflicting module pty already loaded\n"); |
| return EBUSY; |
| } |
| e = reallocate_pty(); |
| if(e) return e; |
| break; |
| case MOD_SHUTDOWN: |
| break; |
| case MOD_UNLOAD: |
| // TODO: need locking |
| pty_preallocate_count = 0; |
| e = reallocate_pty(); |
| if(e) return e; |
| free(preallocated_pty, M_PTYDEV); |
| break; |
| default: |
| return (EOPNOTSUPP); |
| } |
| |
| return (0); |
| } |
| |
| DEV_MODULE(pty_prealloc, pty_prealloc_modevent, NULL); |