]>
Commit | Line | Data |
---|---|---|
1 | package PVE::PTY; | |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use Fcntl; | |
7 | use POSIX qw(O_RDWR O_NOCTTY); | |
8 | ||
9 | # Constants | |
10 | ||
11 | use 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 | ||
53 | sub 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 | ||
75 | my $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 | ||
84 | sub 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 | ||
94 | sub 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 | ||
112 | my $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 | ||
126 | sub 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 | ||
139 | sub 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 | ||
146 | sub 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) | |
153 | sub 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 | ||
161 | sub 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 | ||
168 | sub read_password($;$$) { | |
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); | |
197 | last if $ord == 4; # ^D / EOF | |
198 | if ($ord == 0xA || $ord == 0xD) { | |
199 | # newline, we're done | |
200 | syswrite($outfd, "\r\n", 2); | |
201 | last; | |
202 | } elsif ($ord == 3) { # ^C | |
203 | die "password input aborted\n"; | |
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 | ||
231 | # Class functions | |
232 | ||
233 | sub new { | |
234 | my ($class) = @_; | |
235 | ||
236 | my ($master, $ttyname) = createpty(); | |
237 | ||
238 | my $self = { | |
239 | master => $master, | |
240 | ttyname => $ttyname, | |
241 | }; | |
242 | ||
243 | return bless $self, $class; | |
244 | } | |
245 | ||
246 | # Properties | |
247 | ||
248 | sub master { return $_[0]->{master} } | |
249 | sub ttyname { return $_[0]->{ttyname} } | |
250 | ||
251 | # Methods | |
252 | ||
253 | sub close { | |
254 | my ($self) = @_; | |
255 | close($self->{master}); | |
256 | } | |
257 | ||
258 | sub open_slave { | |
259 | my ($self) = @_; | |
260 | return $openslave->($self->{ttyname}); | |
261 | } | |
262 | ||
263 | sub set_size { | |
264 | my ($self, $columns, $rows) = @_; | |
265 | tcsetsize($self->{master}, $columns, $rows); | |
266 | } | |
267 | ||
268 | # get_size -> (columns, rows) | |
269 | sub get_size { | |
270 | my ($self) = @_; | |
271 | return tcgetsize($self->{master}); | |
272 | } | |
273 | ||
274 | sub kill { | |
275 | my ($self, $signal) = @_; | |
276 | if (!ioctl($self->{master}, TIOCSIG, $signal)) { | |
277 | # kill fallback if the ioctl does not work | |
278 | kill $signal, $self->get_foreground_pid() | |
279 | or die "failed to send signal: $!\n"; | |
280 | } | |
281 | } | |
282 | ||
283 | sub get_foreground_pid { | |
284 | my ($self) = @_; | |
285 | my $pid = pack('L', 0); | |
286 | ioctl($self->{master}, TIOCGPGRP, $pid) | |
287 | or die "failed to get foreground pid: $!\n"; | |
288 | return unpack('L', $pid); | |
289 | } | |
290 | ||
291 | sub has_process { | |
292 | my ($self) = @_; | |
293 | return 0 != $self->get_foreground_pid(); | |
294 | } | |
295 | ||
296 | sub make_controlling_terminal { | |
297 | my ($self) = @_; | |
298 | ||
299 | #lose_controlling_terminal(); | |
300 | POSIX::setsid(); | |
301 | my $slave = $self->open_slave(); | |
302 | ioctl($slave, TIOCSCTTY, 0) | |
303 | or die "failed to change controlling tty: $!\n"; | |
304 | POSIX::dup2(fileno($slave), 0) or die "failed to dup stdin\n"; | |
305 | POSIX::dup2(fileno($slave), 1) or die "failed to dup stdout\n"; | |
306 | POSIX::dup2(fileno($slave), 2) or die "failed to dup stderr\n"; | |
307 | CORE::close($slave) if fileno($slave) > 2; | |
308 | CORE::close($self->{master}); | |
309 | } | |
310 | ||
311 | sub getattr { | |
312 | my ($self) = @_; | |
313 | return tcgetattr($self->{master}); | |
314 | } | |
315 | ||
316 | sub setattr { | |
317 | my ($self, $termios) = @_; | |
318 | return tcsetattr($self->{master}, $termios); | |
319 | } | |
320 | ||
321 | sub send_cc { | |
322 | my ($self, $ccidx) = @_; | |
323 | my $attrs = $self->getattr(); | |
324 | my $data = pack('C', $attrs->{cc}->[$ccidx]); | |
325 | syswrite($self->{master}, $data) | |
326 | == 1 || die "write failed: $!\n"; | |
327 | } | |
328 | ||
329 | sub send_eof { | |
330 | my ($self) = @_; | |
331 | $self->send_cc(VEOF); | |
332 | } | |
333 | ||
334 | sub send_interrupt { | |
335 | my ($self) = @_; | |
336 | $self->send_cc(VINTR); | |
337 | } | |
338 | ||
339 | 1; |