[pve-devel] applied 1..3 [PATCH common 1/4] Add PVE::PTY helper class

Wolfgang Bumiller w.bumiller at proxmox.com
Mon Nov 27 14:23:37 CET 2017


Applied patches 1..3, waiting for feedback on 4.

On Fri, Nov 24, 2017 at 11:24:22AM +0100, Wolfgang Bumiller wrote:
> Signed-off-by: Wolfgang Bumiller <w.bumiller at proxmox.com>
> ---
> This will be used primarily by the xtermjs console client.
> I'm adding this to pve-common since it also presents the
> opportunity to add a simple read_password implementation
> replacing the pve-common patch in the previous read_pasword
> patch series.
> 
>  src/PVE/PTY.pm | 279 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 279 insertions(+)
>  create mode 100644 src/PVE/PTY.pm
> 
> diff --git a/src/PVE/PTY.pm b/src/PVE/PTY.pm
> new file mode 100644
> index 0000000..5f6c7a7
> --- /dev/null
> +++ b/src/PVE/PTY.pm
> @@ -0,0 +1,279 @@
> +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,
> +
> +    # from fcntl.h
> +    O_CLOEXEC => 02000000,
> +};
> +
> +# Utility functions
> +
> +sub createpty() {
> +    # Open the master file descriptor:
> +    sysopen(my $master, '/dev/ptmx', O_RDWR | O_NOCTTY | O_CLOEXEC)
> +	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";
> +}
> +
> +# 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;
> -- 
> 2.11.0




More information about the pve-devel mailing list