From: Dietmar Maurer Date: Mon, 4 Jun 2018 06:28:31 +0000 (+0200) Subject: include PVE/PTY.pm (copied from pve-common) X-Git-Url: https://git.proxmox.com/?p=pve-client.git;a=commitdiff_plain;h=d950c2e69c577d09c8d3e917082669b774f3c6e1;hp=cf87ca4254e3851e3e36adf5086fba286c5cd882 include PVE/PTY.pm (copied from pve-common) --- diff --git a/Makefile b/Makefile index 8fb26bf..4fe1845 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,7 @@ install: pve-api-definition.js install -m 0644 PVE/JSONSchema.pm ${LIB_DIR}/PVE install -m 0644 PVE/RESTHandler.pm ${LIB_DIR}/PVE install -m 0644 PVE/CLIHandler.pm ${LIB_DIR}/PVE + install -m 0644 PVE/PTY.pm ${LIB_DIR}/PVE # install pveclient install -D -m 0644 PVE/APIClient/Helpers.pm ${LIB_DIR}/PVE/APIClient/Helpers.pm install -D -m 0644 PVE/APIClient/Config.pm ${LIB_DIR}/PVE/APIClient/Config.pm diff --git a/PVE/PTY.pm b/PVE/PTY.pm new file mode 100644 index 0000000..23d76c0 --- /dev/null +++ b/PVE/PTY.pm @@ -0,0 +1,339 @@ +package PVE::PTY; + +use strict; +use warnings; + +use Fcntl; +use POSIX qw(O_RDWR O_NOCTTY); + +# Constants + +use constant { + TCGETS => 0x5401, # fixed, from asm-generic/ioctls.h + TCSETS => 0x5402, # fixed, from asm-generic/ioctls.h + TIOCGWINSZ => 0x5413, # fixed, from asm-generic/ioctls.h + TIOCSWINSZ => 0x5414, # fixed, from asm-generic/ioctls.h + TIOCSCTTY => 0x540E, # fixed, from asm-generic/ioctls.h + TIOCNOTTY => 0x5422, # fixed, from asm-generic/ioctls.h + TIOCGPGRP => 0x540F, # fixed, from asm-generic/ioctls.h + TIOCSPGRP => 0x5410, # fixed, from asm-generic/ioctls.h + + # IOC: dir:2 size:14 type:8 nr:8 + # Get pty number: dir=2 size=4 type='T' nr=0x30 + TIOCGPTN => 0x80045430, + + # Set pty lock: dir=1 size=4 type='T' nr=0x31 + TIOCSPTLCK => 0x40045431, + + # Send signal: dir=1 size=4 type='T' nr=0x36 + TIOCSIG => 0x40045436, + + # c_cc indices: + VINTR => 0, + VQUIT => 1, + VERASE => 2, + VKILL => 3, + VEOF => 4, + VTIME => 5, + VMIN => 6, + VSWTC => 7, + VSTART => 8, + VSTOP => 9, + VSUSP => 10, + VEOL => 11, + VREPRINT => 12, + VDISCARD => 13, + VWERASE => 14, + VLNEXT => 15, + VEOL2 => 16, +}; + +# Utility functions + +sub createpty() { + # Open the master file descriptor: + sysopen(my $master, '/dev/ptmx', O_RDWR | O_NOCTTY) + or die "failed to create pty: $!\n"; + + # Find the tty number + my $ttynum = pack('L', 0); + ioctl($master, TIOCGPTN, $ttynum) + or die "failed to query pty number: $!\n"; + $ttynum = unpack('L', $ttynum); + + # Get the slave name/path + my $ttyname = "/dev/pts/$ttynum"; + + # Unlock + my $false = pack('L', 0); + ioctl($master, TIOCSPTLCK, $false) + or die "failed to unlock pty: $!\n"; + + return ($master, $ttyname); +} + +my $openslave = sub { + my ($ttyname) = @_; + + # Create a slave file descriptor: + sysopen(my $slave, $ttyname, O_RDWR | O_NOCTTY) + or die "failed to open slave pty handle: $!\n"; + return $slave; +}; + +sub lose_controlling_terminal() { + # Can we open our current terminal? + if (sysopen(my $ttyfd, '/dev/tty', O_RDWR)) { + # Disconnect: + ioctl($ttyfd, TIOCNOTTY, 0) + or die "failed to disconnect controlling tty: $!\n"; + close($ttyfd); + } +} + +sub termios(%) { + my (%termios) = @_; + my $cc = $termios{cc} // []; + if (@$cc < 19) { + push @$cc, (0) x (19-@$cc); + } elsif (@$cc > 19) { + @$cc = $$cc[0..18]; + } + + return pack('LLLLCC[19]', + $termios{iflag} || 0, + $termios{oflag} || 0, + $termios{cflag} || 0, + $termios{lflag} || 0, + $termios{line} || 0, + @$cc); +} + +my $parse_termios = sub { + my ($blob) = @_; + my ($iflag, $oflag, $cflag, $lflag, $line, @cc) = + unpack('LLLLCC[19]', $blob); + return { + iflag => $iflag, + oflag => $oflag, + cflag => $cflag, + lflag => $lflag, + line => $line, + cc => \@cc + }; +}; + +sub cfmakeraw($) { + my ($termios) = @_; + $termios->{iflag} &= + ~(POSIX::IGNBRK | POSIX::BRKINT | POSIX::PARMRK | POSIX::ISTRIP | + POSIX::INLCR | POSIX::IGNCR | POSIX::ICRNL | POSIX::IXON); + $termios->{oflag} &= ~POSIX::OPOST; + $termios->{lflag} &= + ~(POSIX::ECHO | POSIX::ECHONL | POSIX::ICANON | POSIX::ISIG | + POSIX::IEXTEN); + $termios->{cflag} &= ~(POSIX::CSIZE | POSIX::PARENB); + $termios->{cflag} |= POSIX::CS8; +} + +sub tcgetattr($) { + my ($fd) = @_; + my $blob = termios(); + ioctl($fd, TCGETS, $blob) or die "failed to get terminal attributes\n"; + return $parse_termios->($blob); +} + +sub tcsetattr($$) { + my ($fd, $termios) = @_; + my $blob = termios(%$termios); + ioctl($fd, TCSETS, $blob) or die "failed to set terminal attributes\n"; +} + +# tcgetsize -> (columns, rows) +sub tcgetsize($) { + my ($fd) = @_; + my $struct_winsz = pack('SSSS', 0, 0, 0, 0); + ioctl($fd, TIOCGWINSZ, $struct_winsz) + or die "failed to get window size: $!\n"; + return reverse unpack('SS', $struct_winsz); +} + +sub tcsetsize($$$) { + my ($fd, $columns, $rows) = @_; + my $struct_winsz = pack('SSSS', $rows, $columns, 0, 0); + ioctl($fd, TIOCSWINSZ, $struct_winsz) + or die "failed to set window size: $!\n"; +} + +sub read_password($;$$) { + my ($query, $infd, $outfd) = @_; + + my $password = ''; + + $infd //= \*STDIN; + + if (!-t $infd) { # Not a terminal? Then just get a line... + local $/ = "\n"; + $password = <$infd>; + die "EOF while reading password\n" if !defined $password; + chomp $password; # Chop off the newline + return $password; + } + + $outfd //= \*STDOUT; + + # Raw read loop: + my $old_termios; + $old_termios = tcgetattr($infd); + my $raw_termios = {%$old_termios}; + cfmakeraw($raw_termios); + tcsetattr($infd, $raw_termios); + eval { + my $echo = undef; + my ($ch, $got); + syswrite($outfd, $query, length($query)); + while (($got = sysread($infd, $ch, 1))) { + my ($ord) = unpack('C', $ch); + last if $ord == 4; # ^D / EOF + if ($ord == 0xA || $ord == 0xD) { + # newline, we're done + syswrite($outfd, "\r\n", 2); + last; + } elsif ($ord == 3) { # ^C + die "password input aborted\n"; + } elsif ($ord == 0x7f) { + # backspace - if it's the first key disable + # asterisks + $echo //= 0; + if (length($password)) { + chop $password; + syswrite($outfd, "\b \b", 3); + } + } elsif ($ord == 0x09) { + # TAB disables the asterisk-echo + $echo = 0; + } else { + # other character, append to password, if it's + # the first character enable asterisks echo + $echo //= 1; + $password .= $ch; + syswrite($outfd, '*', 1) if $echo; + } + } + die "read error: $!\n" if !defined($got); + }; + my $err = $@; + tcsetattr($infd, $old_termios); + die $err if $err; + return $password; +} + +# Class functions + +sub new { + my ($class) = @_; + + my ($master, $ttyname) = createpty(); + + my $self = { + master => $master, + ttyname => $ttyname, + }; + + return bless $self, $class; +} + +# Properties + +sub master { return $_[0]->{master} } +sub ttyname { return $_[0]->{ttyname} } + +# Methods + +sub close { + my ($self) = @_; + close($self->{master}); +} + +sub open_slave { + my ($self) = @_; + return $openslave->($self->{ttyname}); +} + +sub set_size { + my ($self, $columns, $rows) = @_; + tcsetsize($self->{master}, $columns, $rows); +} + +# get_size -> (columns, rows) +sub get_size { + my ($self) = @_; + return tcgetsize($self->{master}); +} + +sub kill { + my ($self, $signal) = @_; + if (!ioctl($self->{master}, TIOCSIG, $signal)) { + # kill fallback if the ioctl does not work + kill $signal, $self->get_foreground_pid() + or die "failed to send signal: $!\n"; + } +} + +sub get_foreground_pid { + my ($self) = @_; + my $pid = pack('L', 0); + ioctl($self->{master}, TIOCGPGRP, $pid) + or die "failed to get foreground pid: $!\n"; + return unpack('L', $pid); +} + +sub has_process { + my ($self) = @_; + return 0 != $self->get_foreground_pid(); +} + +sub make_controlling_terminal { + my ($self) = @_; + + #lose_controlling_terminal(); + POSIX::setsid(); + my $slave = $self->open_slave(); + ioctl($slave, TIOCSCTTY, 0) + or die "failed to change controlling tty: $!\n"; + POSIX::dup2(fileno($slave), 0) or die "failed to dup stdin\n"; + POSIX::dup2(fileno($slave), 1) or die "failed to dup stdout\n"; + POSIX::dup2(fileno($slave), 2) or die "failed to dup stderr\n"; + CORE::close($slave) if fileno($slave) > 2; + CORE::close($self->{master}); +} + +sub getattr { + my ($self) = @_; + return tcgetattr($self->{master}); +} + +sub setattr { + my ($self, $termios) = @_; + return tcsetattr($self->{master}, $termios); +} + +sub send_cc { + my ($self, $ccidx) = @_; + my $attrs = $self->getattr(); + my $data = pack('C', $attrs->{cc}->[$ccidx]); + syswrite($self->{master}, $data) + == 1 || die "write failed: $!\n"; +} + +sub send_eof { + my ($self) = @_; + $self->send_cc(VEOF); +} + +sub send_interrupt { + my ($self) = @_; + $self->send_cc(VINTR); +} + +1; diff --git a/README b/README index 1e25828..d85e6ed 100644 --- a/README +++ b/README @@ -8,4 +8,5 @@ PVE/Exception.pm PVE/JSONSchema.pm PVE/RESTHandler.pm PVE/SafeSyslog.pm -PVE/Tools.pm: only selected/required helpers \ No newline at end of file +PVE/Tools.pm: only selected/required helpers +PVE/PTY.pm