hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit 24ec13dd2059062f78b7eebb1ff4d35a43ee911a
parent b3c209bf53834c37f2408d20e1a9ce1dfd224ad9
Author: the lemons <citrons@mondecitronne.com>
Date:   Thu,  9 Jun 2022 11:38:33 -0500

unix::tty: implement pty handling

implement functions for creating/handling pseudoterminals

* openpty opens a pseudoterminal and returns a master/slave pair
* ptsname returns the filename of the slave
* set_winsize changes the dimensions of the pseudoterminal

Signed-off-by: Re Elbertson <citrons@mondecitronne.com>

Diffstat:
Mrt/+freebsd/syscalls.ha | 4++++
Mrt/+freebsd/types.ha | 3+++
Mrt/+linux/types.ha | 4++++
Aunix/tty/+freebsd/pty.ha | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aunix/tty/+linux/pty.ha | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aunix/tty/pty.ha | 45+++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 196 insertions(+), 0 deletions(-)

diff --git a/rt/+freebsd/syscalls.ha b/rt/+freebsd/syscalls.ha @@ -494,3 +494,7 @@ export fn sysctlbyname(name: str, oldp: nullable *void, oldlenp: nullable *size, export fn dup2(oldfd: int, newfd: int) (int | errno) = { return wrap_return(syscall2(SYS_dup2, oldfd: u64, newfd: u64))?: int; }; + +export fn posix_openpt(flags: int) (int | errno) = { + return wrap_return(syscall1(SYS_posix_openpt, flags: u64))?: int; +}; diff --git a/rt/+freebsd/types.ha b/rt/+freebsd/types.ha @@ -235,8 +235,11 @@ export type cc = enum char { }; export def TIOCGWINSZ: u64 = 0x40087468; +export def TIOCSWINSZ: u64 = 0x80087467; export def TIOCGETA: u64 = 0x402c7413; export def TIOCSETA: u64 = 0x802c7414; +export def TIOCPTMASTER: u64 = 0x2000741c; +export def FIODGNAME: u64 = 0x80106678; export type rusage = struct { ru_utime: timeval, diff --git a/rt/+linux/types.ha b/rt/+linux/types.ha @@ -716,8 +716,12 @@ export type tcflag = enum uint { export def TIOCGWINSZ: u64 = 0x5413; +export def TIOCSWINSZ: u64 = 0x5414; export def TIOCSCTTY: u64 = 0x540e; export def TIOCNOTTY: u64 = 0x5422; +export def TIOCGPTN: u64 = 0x80045430; +export def TIOCGPTPEER: u64 = 0x5441; +export def TIOCSPTLCK: u64 = 0x40045431; export def TCGETS: u64 = 0x5401; export def TCSETS: u64 = 0x5402; diff --git a/unix/tty/+freebsd/pty.ha b/unix/tty/+freebsd/pty.ha @@ -0,0 +1,70 @@ +// License: MPL-2.0 +// (c) 2022 Re Elbertson <citrons@mondecitronne.com> +use errors; +use fmt; +use fs; +use io; +use os; +use rt; +use strings; + +// Opens an available pseudoterminal master. +fn open_master() (io::file | fs::error) = { + match (rt::posix_openpt(rt::O_RDWR | rt::O_NOCTTY)) { + case let e: rt::errno => + return errors::errno(e); + case let i: int => + return io::fdopen(i); + }; +}; + +// Returns a file descriptor referring to the pseudoterminal slave for a +// pseudoterminal master. +fn get_slave(master: io::file) (io::file | fs::error) = + os::open(ptsname(master)?, fs::flags::RDWR); + +// Returns the filename of the pseudoterminal slave. +export fn ptsname(master: io::file) (str | error) = { + // Ensure that the file descriptor refers to a master + match (rt::ioctl(master, rt::TIOCPTMASTER, null)) { + case let e: rt::errno => + if (e: int == rt::EBADF) return errors::invalid + else return errors::unsupported; + case => void; + }; + + let name: [rt::PATH_MAX]u8 = [0...]; + let fiodgname_arg = (len(name), &name); + match (rt::ioctl(master, rt::FIODGNAME, &fiodgname_arg)) { + case let e: rt::errno => + switch (e: int) { + case rt::EBADF => + return errors::invalid; + case rt::EINVAL, rt::ENOTTY => + return errors::unsupported; + case => + abort("Unexpected error from ioctl"); + }; + case => + static let path_buf: [rt::PATH_MAX]u8 = [0...]; + return fmt::bsprintf(path_buf, + "/dev/{}", strings::fromc(&name: *const char)); + }; +}; + +// Sets the dimensions of the underlying pseudoterminal for an [[io::file]]. +export fn set_winsize(pty: io::file, sz: ttysize) (void | error) = { + let wsz = rt::winsize { ws_row = sz.rows, ws_col = sz.columns, ... }; + match (rt::ioctl(pty, rt::TIOCSWINSZ, &wsz)) { + case let e: rt::errno => + switch (e: int) { + case rt::EBADF, rt::EINVAL => + return errors::invalid; + case rt::ENOTTY => + return errors::unsupported; + case => + abort("Unexpected error from ioctl"); + }; + case => void; + }; +}; diff --git a/unix/tty/+linux/pty.ha b/unix/tty/+linux/pty.ha @@ -0,0 +1,70 @@ +// License: MPL-2.0 +// (c) 2022 Re Elbertson <citrons@mondecitronne.com> +use errors; +use fmt; +use fs; +use io; +use os; +use rt; + +// Opens an available pseudoterminal master. +fn open_master() (io::file | fs::error) = { + return os::open("/dev/ptmx", fs::flags::RDWR); +}; + +// Returns a file descriptor referring to the pseudoterminal slave for a +// pseudoterminal master. +fn get_slave(master: io::file) (io::file | fs::error) = { + // Unlock the pseudoterminal slave + match (rt::ioctl(master, rt::TIOCSPTLCK, &0)) { + case rt::errno => + return errors::invalid; + case => void; + }; + + let ioctl = rt::ioctl( + master, rt::TIOCGPTPEER, + (rt::O_RDWR | rt::O_NOCTTY): u64); + match (ioctl) { + case let e: rt::errno => + return errors::errno(e); + case let fd: int => + return io::fdopen(fd); + }; +}; + +// Returns the filename of the pseudoterminal slave. +export fn ptsname(master: io::file) (str | error) = { + let pty = 0; + match (rt::ioctl(master, rt::TIOCGPTN, &pty)) { + case let e: rt::errno => + switch (e: int) { + case rt::EBADF => + return errors::invalid; + case rt::ENOTTY => + return errors::unsupported; + case => + abort("Unexpected error from ioctl"); + }; + case => + static let buf: [9 + 20]u8 = [0...]; + return fmt::bsprintf(buf[..len(buf)], "/dev/pts/{}", pty); + }; +}; + +// Sets the dimensions of the underlying pseudoterminal for an [[io::file]]. +export fn set_winsize(pty: io::file, sz: ttysize) (void | error) = { + let wsz = rt::winsize { ws_row = sz.rows, ws_col = sz.columns, ... }; + match (rt::ioctl(pty, rt::TIOCSWINSZ, &wsz)) { + case let e: rt::errno => + switch (e: int) { + case rt::EBADF => + return errors::invalid; + case rt::ENOTTY => + return errors::unsupported; + case => + abort("Unexpected error from ioctl"); + }; + case => void; + }; +}; diff --git a/unix/tty/pty.ha b/unix/tty/pty.ha @@ -0,0 +1,45 @@ +// License: MPL-2.0 +// (c) 2022 Re Elbertson <citrons@mondecitronne.com> +use bufio; +use errors; +use fmt; +use fs; +use io; +use os; +use strings; + +// Opens an available pseudoterminal and returns the file descriptors of the +// master and slave. +export fn openpty() ((io::file, io::file) | fs::error) = { + let master = open_master()?; + let slave = match (get_slave(master)) { + case let e: fs::error => + io::close(master)!; + return e; + case let s: io::file => + yield s; + }; + + return (master, slave); +}; + +@test fn pty() void = { + let pty = openpty()!; + defer io::close(pty.1)!; + defer io::close(pty.0)!; + + assert(fs::exists(os::cwd, ptsname(pty.0)!)); + + for (let i: u16 = 5; i < 100; i += 1) { + let sz1 = ttysize { rows = i, columns = i }; + set_winsize(pty.1, sz1)!; + let sz2 = winsize(pty.1)!; + assert(sz2.rows == sz1.rows); + assert(sz2.columns == sz1.columns); + }; + + fmt::fprintln(pty.0, "hello, world")!; + let s = strings::fromutf8(bufio::scanline(pty.1) as []u8); + defer free(s); + assert(s == "hello, world"); +};