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:
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");
+};