]> git.proxmox.com Git - pve-common.git/blame - src/PVE/PTY.pm
bump version to 8.1.2
[pve-common.git] / src / PVE / PTY.pm
CommitLineData
a0f008cd
WB
1package PVE::PTY;
2
3use strict;
4use warnings;
5
6use Fcntl;
7use POSIX qw(O_RDWR O_NOCTTY);
8
9# Constants
10
11use constant {
12 TCGETS => 0x5401, # fixed, from asm-generic/ioctls.h
13 TCSETS => 0x5402, # fixed, from asm-generic/ioctls.h
14 TIOCGWINSZ => 0x5413, # fixed, from asm-generic/ioctls.h
15 TIOCSWINSZ => 0x5414, # fixed, from asm-generic/ioctls.h
16 TIOCSCTTY => 0x540E, # fixed, from asm-generic/ioctls.h
17 TIOCNOTTY => 0x5422, # fixed, from asm-generic/ioctls.h
18 TIOCGPGRP => 0x540F, # fixed, from asm-generic/ioctls.h
19 TIOCSPGRP => 0x5410, # fixed, from asm-generic/ioctls.h
20
21 # IOC: dir:2 size:14 type:8 nr:8
22 # Get pty number: dir=2 size=4 type='T' nr=0x30
23 TIOCGPTN => 0x80045430,
24
25 # Set pty lock: dir=1 size=4 type='T' nr=0x31
26 TIOCSPTLCK => 0x40045431,
27
28 # Send signal: dir=1 size=4 type='T' nr=0x36
29 TIOCSIG => 0x40045436,
30
31 # c_cc indices:
32 VINTR => 0,
33 VQUIT => 1,
34 VERASE => 2,
35 VKILL => 3,
36 VEOF => 4,
37 VTIME => 5,
38 VMIN => 6,
39 VSWTC => 7,
40 VSTART => 8,
41 VSTOP => 9,
42 VSUSP => 10,
43 VEOL => 11,
44 VREPRINT => 12,
45 VDISCARD => 13,
46 VWERASE => 14,
47 VLNEXT => 15,
48 VEOL2 => 16,
49};
50
51# Utility functions
52
53sub createpty() {
54 # Open the master file descriptor:
55 sysopen(my $master, '/dev/ptmx', O_RDWR | O_NOCTTY)
56 or die "failed to create pty: $!\n";
57
58 # Find the tty number
59 my $ttynum = pack('L', 0);
60 ioctl($master, TIOCGPTN, $ttynum)
61 or die "failed to query pty number: $!\n";
62 $ttynum = unpack('L', $ttynum);
63
64 # Get the slave name/path
65 my $ttyname = "/dev/pts/$ttynum";
66
67 # Unlock
68 my $false = pack('L', 0);
69 ioctl($master, TIOCSPTLCK, $false)
70 or die "failed to unlock pty: $!\n";
71
72 return ($master, $ttyname);
73}
74
75my $openslave = sub {
76 my ($ttyname) = @_;
77
78 # Create a slave file descriptor:
79 sysopen(my $slave, $ttyname, O_RDWR | O_NOCTTY)
80 or die "failed to open slave pty handle: $!\n";
81 return $slave;
82};
83
84sub lose_controlling_terminal() {
85 # Can we open our current terminal?
86 if (sysopen(my $ttyfd, '/dev/tty', O_RDWR)) {
87 # Disconnect:
88 ioctl($ttyfd, TIOCNOTTY, 0)
89 or die "failed to disconnect controlling tty: $!\n";
90 close($ttyfd);
91 }
92}
93
94sub termios(%) {
95 my (%termios) = @_;
96 my $cc = $termios{cc} // [];
97 if (@$cc < 19) {
98 push @$cc, (0) x (19-@$cc);
99 } elsif (@$cc > 19) {
100 @$cc = $$cc[0..18];
101 }
102
103 return pack('LLLLCC[19]',
104 $termios{iflag} || 0,
105 $termios{oflag} || 0,
106 $termios{cflag} || 0,
107 $termios{lflag} || 0,
108 $termios{line} || 0,
109 @$cc);
110}
111
112my $parse_termios = sub {
113 my ($blob) = @_;
114 my ($iflag, $oflag, $cflag, $lflag, $line, @cc) =
115 unpack('LLLLCC[19]', $blob);
116 return {
117 iflag => $iflag,
118 oflag => $oflag,
119 cflag => $cflag,
120 lflag => $lflag,
121 line => $line,
122 cc => \@cc
123 };
124};
125
126sub cfmakeraw($) {
127 my ($termios) = @_;
128 $termios->{iflag} &=
129 ~(POSIX::IGNBRK | POSIX::BRKINT | POSIX::PARMRK | POSIX::ISTRIP |
130 POSIX::INLCR | POSIX::IGNCR | POSIX::ICRNL | POSIX::IXON);
131 $termios->{oflag} &= ~POSIX::OPOST;
132 $termios->{lflag} &=
133 ~(POSIX::ECHO | POSIX::ECHONL | POSIX::ICANON | POSIX::ISIG |
134 POSIX::IEXTEN);
135 $termios->{cflag} &= ~(POSIX::CSIZE | POSIX::PARENB);
136 $termios->{cflag} |= POSIX::CS8;
137}
138
139sub tcgetattr($) {
140 my ($fd) = @_;
141 my $blob = termios();
142 ioctl($fd, TCGETS, $blob) or die "failed to get terminal attributes\n";
143 return $parse_termios->($blob);
144}
145
146sub tcsetattr($$) {
147 my ($fd, $termios) = @_;
148 my $blob = termios(%$termios);
149 ioctl($fd, TCSETS, $blob) or die "failed to set terminal attributes\n";
150}
151
152# tcgetsize -> (columns, rows)
153sub tcgetsize($) {
154 my ($fd) = @_;
155 my $struct_winsz = pack('SSSS', 0, 0, 0, 0);
156 ioctl($fd, TIOCGWINSZ, $struct_winsz)
157 or die "failed to get window size: $!\n";
158 return reverse unpack('SS', $struct_winsz);
159}
160
161sub tcsetsize($$$) {
162 my ($fd, $columns, $rows) = @_;
163 my $struct_winsz = pack('SSSS', $rows, $columns, 0, 0);
164 ioctl($fd, TIOCSWINSZ, $struct_winsz)
165 or die "failed to set window size: $!\n";
166}
167
c93778bc 168sub read_password($;$$) {
14064251
WB
169 my ($query, $infd, $outfd) = @_;
170
171 my $password = '';
172
173 $infd //= \*STDIN;
174
175 if (!-t $infd) { # Not a terminal? Then just get a line...
176 local $/ = "\n";
177 $password = <$infd>;
178 die "EOF while reading password\n" if !defined $password;
179 chomp $password; # Chop off the newline
180 return $password;
181 }
182
183 $outfd //= \*STDOUT;
184
185 # Raw read loop:
186 my $old_termios;
187 $old_termios = tcgetattr($infd);
188 my $raw_termios = {%$old_termios};
189 cfmakeraw($raw_termios);
190 tcsetattr($infd, $raw_termios);
191 eval {
192 my $echo = undef;
193 my ($ch, $got);
194 syswrite($outfd, $query, length($query));
195 while (($got = sysread($infd, $ch, 1))) {
196 my ($ord) = unpack('C', $ch);
ee29490f
WB
197 last if $ord == 4; # ^D / EOF
198 if ($ord == 0xA || $ord == 0xD) {
14064251
WB
199 # newline, we're done
200 syswrite($outfd, "\r\n", 2);
201 last;
ee29490f
WB
202 } elsif ($ord == 3) { # ^C
203 die "password input aborted\n";
14064251
WB
204 } elsif ($ord == 0x7f) {
205 # backspace - if it's the first key disable
206 # asterisks
207 $echo //= 0;
208 if (length($password)) {
209 chop $password;
210 syswrite($outfd, "\b \b", 3);
211 }
212 } elsif ($ord == 0x09) {
213 # TAB disables the asterisk-echo
214 $echo = 0;
215 } else {
216 # other character, append to password, if it's
217 # the first character enable asterisks echo
218 $echo //= 1;
219 $password .= $ch;
220 syswrite($outfd, '*', 1) if $echo;
221 }
222 }
223 die "read error: $!\n" if !defined($got);
224 };
225 my $err = $@;
226 tcsetattr($infd, $old_termios);
227 die $err if $err;
228 return $password;
229}
230
ac927fbc
DC
231sub get_confirmed_password {
232 my $pw1 = read_password('Enter new password: ');
233 my $pw2 = read_password('Retype new password: ');
234 die "passwords do not match\n" if $pw1 ne $pw2;
235 return $pw1;
236}
237
a0f008cd
WB
238# Class functions
239
240sub new {
241 my ($class) = @_;
242
243 my ($master, $ttyname) = createpty();
244
245 my $self = {
246 master => $master,
247 ttyname => $ttyname,
248 };
249
250 return bless $self, $class;
251}
252
253# Properties
254
255sub master { return $_[0]->{master} }
256sub ttyname { return $_[0]->{ttyname} }
257
258# Methods
259
260sub close {
261 my ($self) = @_;
262 close($self->{master});
263}
264
265sub open_slave {
266 my ($self) = @_;
267 return $openslave->($self->{ttyname});
268}
269
270sub set_size {
271 my ($self, $columns, $rows) = @_;
272 tcsetsize($self->{master}, $columns, $rows);
273}
274
275# get_size -> (columns, rows)
276sub get_size {
277 my ($self) = @_;
278 return tcgetsize($self->{master});
279}
280
281sub kill {
282 my ($self, $signal) = @_;
283 if (!ioctl($self->{master}, TIOCSIG, $signal)) {
284 # kill fallback if the ioctl does not work
285 kill $signal, $self->get_foreground_pid()
286 or die "failed to send signal: $!\n";
287 }
288}
289
290sub get_foreground_pid {
291 my ($self) = @_;
292 my $pid = pack('L', 0);
293 ioctl($self->{master}, TIOCGPGRP, $pid)
294 or die "failed to get foreground pid: $!\n";
295 return unpack('L', $pid);
296}
297
298sub has_process {
299 my ($self) = @_;
300 return 0 != $self->get_foreground_pid();
301}
302
303sub make_controlling_terminal {
304 my ($self) = @_;
305
306 #lose_controlling_terminal();
307 POSIX::setsid();
308 my $slave = $self->open_slave();
309 ioctl($slave, TIOCSCTTY, 0)
310 or die "failed to change controlling tty: $!\n";
311 POSIX::dup2(fileno($slave), 0) or die "failed to dup stdin\n";
312 POSIX::dup2(fileno($slave), 1) or die "failed to dup stdout\n";
313 POSIX::dup2(fileno($slave), 2) or die "failed to dup stderr\n";
314 CORE::close($slave) if fileno($slave) > 2;
315 CORE::close($self->{master});
316}
317
318sub getattr {
319 my ($self) = @_;
320 return tcgetattr($self->{master});
321}
322
323sub setattr {
324 my ($self, $termios) = @_;
325 return tcsetattr($self->{master}, $termios);
326}
327
328sub send_cc {
329 my ($self, $ccidx) = @_;
330 my $attrs = $self->getattr();
331 my $data = pack('C', $attrs->{cc}->[$ccidx]);
332 syswrite($self->{master}, $data)
333 == 1 || die "write failed: $!\n";
334}
335
336sub send_eof {
337 my ($self) = @_;
338 $self->send_cc(VEOF);
339}
340
341sub send_interrupt {
342 my ($self) = @_;
343 $self->send_cc(VINTR);
344}
345
3461;