gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/fsimpl/devpts/line_discipline.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package devpts 16 17 import ( 18 "bytes" 19 "unicode" 20 "unicode/utf8" 21 22 "gvisor.dev/gvisor/pkg/abi/linux" 23 "gvisor.dev/gvisor/pkg/context" 24 "gvisor.dev/gvisor/pkg/errors/linuxerr" 25 "gvisor.dev/gvisor/pkg/sentry/arch" 26 "gvisor.dev/gvisor/pkg/sentry/kernel" 27 "gvisor.dev/gvisor/pkg/sync" 28 "gvisor.dev/gvisor/pkg/usermem" 29 "gvisor.dev/gvisor/pkg/waiter" 30 ) 31 32 const ( 33 // canonMaxBytes is the number of bytes that fit into a single line of 34 // terminal input in canonical mode. This corresponds to N_TTY_BUF_SIZE 35 // in include/linux/tty.h. 36 canonMaxBytes = 4096 37 38 // nonCanonMaxBytes is the maximum number of bytes that can be read at 39 // a time in noncanonical mode. 40 nonCanonMaxBytes = canonMaxBytes - 1 41 42 spacesPerTab = 8 43 ) 44 45 // lineDiscipline dictates how input and output are handled between the 46 // pseudoterminal (pty) master and replica. It can be configured to alter I/O, 47 // modify control characters (e.g. Ctrl-C for SIGINT), etc. The following man 48 // pages are good resources for how to affect the line discipline: 49 // 50 // - termios(3) 51 // - tty_ioctl(4) 52 // 53 // This file corresponds most closely to drivers/tty/n_tty.c. 54 // 55 // lineDiscipline has a simple structure but supports a multitude of options 56 // (see the above man pages). It consists of two queues of bytes: one from the 57 // terminal master to replica (the input queue) and one from replica to master 58 // (the output queue). When bytes are written to one end of the pty, the line 59 // discipline reads the bytes, modifies them or takes special action if 60 // required, and enqueues them to be read by the other end of the pty: 61 // 62 // input from terminal +-------------+ input to process (e.g. bash) 63 // +------------------------>| input queue |---------------------------+ 64 // | (inputQueueWrite) +-------------+ (inputQueueRead) | 65 // | | 66 // | v 67 // 68 // masterFD replicaFD 69 // 70 // ^ | 71 // | | 72 // | output to terminal +--------------+ output from process | 73 // +------------------------| output queue |<--------------------------+ 74 // (outputQueueRead) +--------------+ (outputQueueWrite) 75 // 76 // There is special handling for the ECHO option, where bytes written to the 77 // input queue are also output back to the terminal by being written to 78 // l.outQueue by the input queue transformer. 79 // 80 // Lock order: 81 // 82 // termiosMu 83 // inQueue.mu 84 // outQueue.mu 85 // 86 // +stateify savable 87 type lineDiscipline struct { 88 // sizeMu protects size. 89 sizeMu sync.Mutex `state:"nosave"` 90 91 // size is the terminal size (width and height). 92 size linux.WindowSize 93 94 // inQueue is the input queue of the terminal. 95 inQueue queue 96 97 // outQueue is the output queue of the terminal. 98 outQueue queue 99 100 // termiosMu protects termios. 101 termiosMu sync.RWMutex `state:"nosave"` 102 103 // termios is the terminal configuration used by the lineDiscipline. 104 termios linux.KernelTermios 105 106 // column is the location in a row of the cursor. This is important for 107 // handling certain special characters like backspace. 108 column int 109 110 // numReplicas is the number of replica file descriptors. 111 numReplicas int 112 113 // masterWaiter is used to wait on the master end of the TTY. 114 masterWaiter waiter.Queue 115 116 // replicaWaiter is used to wait on the replica end of the TTY. 117 replicaWaiter waiter.Queue 118 119 // terminal is the terminal linked to this lineDiscipline. 120 terminal *Terminal 121 } 122 123 func newLineDiscipline(termios linux.KernelTermios, terminal *Terminal) *lineDiscipline { 124 ld := lineDiscipline{ 125 termios: termios, 126 terminal: terminal, 127 } 128 ld.inQueue.transformer = &inputQueueTransformer{} 129 ld.outQueue.transformer = &outputQueueTransformer{} 130 return &ld 131 } 132 133 // getTermios gets the linux.Termios for the tty. 134 func (l *lineDiscipline) getTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) { 135 l.termiosMu.RLock() 136 defer l.termiosMu.RUnlock() 137 // We must copy a Termios struct, not KernelTermios. 138 t := l.termios.ToTermios() 139 _, err := t.CopyOut(task, args[2].Pointer()) 140 return 0, err 141 } 142 143 // setTermios sets a linux.Termios for the tty. 144 func (l *lineDiscipline) setTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) { 145 l.termiosMu.Lock() 146 oldCanonEnabled := l.termios.LEnabled(linux.ICANON) 147 // We must copy a Termios struct, not KernelTermios. 148 var t linux.Termios 149 _, err := t.CopyIn(task, args[2].Pointer()) 150 l.termios.FromTermios(t) 151 152 // If canonical mode is turned off, move bytes from inQueue's wait 153 // buffer to its read buffer. Anything already in the read buffer is 154 // now readable. 155 if oldCanonEnabled && !l.termios.LEnabled(linux.ICANON) { 156 l.inQueue.mu.Lock() 157 l.inQueue.pushWaitBufLocked(l) 158 l.inQueue.readable = len(l.inQueue.readBuf) > 0 159 l.inQueue.mu.Unlock() 160 l.termiosMu.Unlock() 161 l.replicaWaiter.Notify(waiter.ReadableEvents) 162 } else { 163 l.termiosMu.Unlock() 164 } 165 166 return 0, err 167 } 168 169 func (l *lineDiscipline) windowSize(t *kernel.Task, args arch.SyscallArguments) error { 170 l.sizeMu.Lock() 171 defer l.sizeMu.Unlock() 172 _, err := l.size.CopyOut(t, args[2].Pointer()) 173 return err 174 } 175 176 func (l *lineDiscipline) setWindowSize(t *kernel.Task, args arch.SyscallArguments) error { 177 l.sizeMu.Lock() 178 defer l.sizeMu.Unlock() 179 _, err := l.size.CopyIn(t, args[2].Pointer()) 180 return err 181 } 182 183 func (l *lineDiscipline) masterReadiness() waiter.EventMask { 184 // The master termios is immutable so termiosMu is not needed. 185 res := l.inQueue.writeReadiness(&linux.MasterTermios) | l.outQueue.readReadiness(&linux.MasterTermios) 186 l.termiosMu.RLock() 187 if l.numReplicas == 0 { 188 res |= waiter.EventHUp 189 } 190 l.termiosMu.RUnlock() 191 return res 192 } 193 194 func (l *lineDiscipline) replicaReadiness() waiter.EventMask { 195 l.termiosMu.RLock() 196 defer l.termiosMu.RUnlock() 197 return l.outQueue.writeReadiness(&l.termios) | l.inQueue.readReadiness(&l.termios) 198 } 199 200 func (l *lineDiscipline) inputQueueReadSize(t *kernel.Task, io usermem.IO, args arch.SyscallArguments) error { 201 return l.inQueue.readableSize(t, io, args) 202 } 203 204 func (l *lineDiscipline) inputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) { 205 l.termiosMu.RLock() 206 n, pushed, notifyEcho, err := l.inQueue.read(ctx, dst, l) 207 isCanon := l.termios.LEnabled(linux.ICANON) 208 l.termiosMu.RUnlock() 209 if err != nil { 210 return 0, err 211 } 212 if n > 0 { 213 if notifyEcho { 214 l.masterWaiter.Notify(waiter.ReadableEvents | waiter.WritableEvents) 215 } else { 216 l.masterWaiter.Notify(waiter.WritableEvents) 217 } 218 if pushed { 219 l.replicaWaiter.Notify(waiter.ReadableEvents) 220 } 221 return n, nil 222 } 223 if notifyEcho { 224 l.masterWaiter.Notify(waiter.ReadableEvents) 225 } 226 if !pushed && isCanon { 227 return 0, nil // EOF 228 } 229 230 return 0, linuxerr.ErrWouldBlock 231 } 232 233 func (l *lineDiscipline) inputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) { 234 l.termiosMu.RLock() 235 n, notifyEcho, err := l.inQueue.write(ctx, src, l) 236 l.termiosMu.RUnlock() 237 if err != nil { 238 return 0, err 239 } 240 if notifyEcho { 241 l.masterWaiter.Notify(waiter.ReadableEvents) 242 } 243 if n > 0 { 244 l.replicaWaiter.Notify(waiter.ReadableEvents) 245 return n, nil 246 } 247 return 0, linuxerr.ErrWouldBlock 248 } 249 250 func (l *lineDiscipline) outputQueueReadSize(t *kernel.Task, io usermem.IO, args arch.SyscallArguments) error { 251 return l.outQueue.readableSize(t, io, args) 252 } 253 254 func (l *lineDiscipline) outputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) { 255 l.termiosMu.RLock() 256 // Ignore notifyEcho, as it cannot happen when reading from the output queue. 257 n, pushed, _, err := l.outQueue.read(ctx, dst, l) 258 l.termiosMu.RUnlock() 259 if err != nil { 260 return 0, err 261 } 262 if n > 0 { 263 l.replicaWaiter.Notify(waiter.WritableEvents) 264 if pushed { 265 l.masterWaiter.Notify(waiter.ReadableEvents) 266 } 267 return n, nil 268 } 269 return 0, linuxerr.ErrWouldBlock 270 } 271 272 func (l *lineDiscipline) outputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) { 273 l.termiosMu.RLock() 274 // Ignore notifyEcho, as it cannot happen when writing to the output queue. 275 n, _, err := l.outQueue.write(ctx, src, l) 276 l.termiosMu.RUnlock() 277 if err != nil { 278 return 0, err 279 } 280 l.masterWaiter.Notify(waiter.ReadableEvents) 281 return n, nil 282 } 283 284 // replicaOpen is called when a replica file descriptor is opened. 285 func (l *lineDiscipline) replicaOpen() { 286 l.termiosMu.Lock() 287 defer l.termiosMu.Unlock() 288 l.numReplicas++ 289 } 290 291 // replicaClose is called when a replica file descriptor is closed. 292 func (l *lineDiscipline) replicaClose() { 293 l.termiosMu.Lock() 294 l.numReplicas-- 295 notify := l.numReplicas == 0 296 l.termiosMu.Unlock() 297 if notify { 298 l.masterWaiter.Notify(waiter.EventHUp) 299 } 300 } 301 302 // transformer is a helper interface to make it easier to stateify queue. 303 type transformer interface { 304 // transform functions require queue's mutex to be held. 305 // The boolean indicates whether there was any echoed bytes. 306 transform(*lineDiscipline, *queue, []byte) (int, bool) 307 } 308 309 // outputQueueTransformer implements transformer. It performs line discipline 310 // transformations on the output queue. 311 // 312 // +stateify savable 313 type outputQueueTransformer struct{} 314 315 // transform does output processing for one end of the pty. See 316 // drivers/tty/n_tty.c:do_output_char for an analogous kernel function. 317 // 318 // Preconditions: 319 // - l.termiosMu must be held for reading. 320 // - q.mu must be held. 321 func (*outputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) (int, bool) { 322 // transformOutput is effectively always in noncanonical mode, as the 323 // master termios never has ICANON set. 324 sizeBudget := nonCanonMaxBytes - len(q.readBuf) 325 if sizeBudget <= 0 { 326 return 0, false 327 } 328 329 if !l.termios.OEnabled(linux.OPOST) { 330 copySize := min(len(buf), sizeBudget) 331 q.readBuf = append(q.readBuf, buf[:copySize]...) 332 if len(q.readBuf) > 0 { 333 q.readable = true 334 } 335 return copySize, false 336 } 337 338 var ret int 339 340 Outer: 341 for ; len(buf) > 0 && sizeBudget > 0; sizeBudget = nonCanonMaxBytes - len(q.readBuf) { 342 size := l.peek(buf) 343 if size > sizeBudget { 344 break Outer 345 } 346 cBytes := append([]byte{}, buf[:size]...) 347 buf = buf[size:] 348 // We're guaranteed that cBytes has at least one element. 349 cByteSwitch: 350 switch cBytes[0] { 351 case '\n': 352 if l.termios.OEnabled(linux.ONLRET) { 353 l.column = 0 354 } 355 if l.termios.OEnabled(linux.ONLCR) { 356 if sizeBudget < 2 { 357 break Outer 358 } 359 ret += size 360 q.readBuf = append(q.readBuf, '\r', '\n') 361 continue Outer 362 } 363 case '\r': 364 if l.termios.OEnabled(linux.ONOCR) && l.column == 0 { 365 // Treat the carriage return as processed, since it's a no-op. 366 ret += size 367 continue Outer 368 } 369 if l.termios.OEnabled(linux.OCRNL) { 370 cBytes[0] = '\n' 371 if l.termios.OEnabled(linux.ONLRET) { 372 l.column = 0 373 } 374 break cByteSwitch 375 } 376 l.column = 0 377 case '\t': 378 spaces := spacesPerTab - l.column%spacesPerTab 379 if l.termios.OutputFlags&linux.TABDLY == linux.XTABS { 380 if sizeBudget < spacesPerTab { 381 break Outer 382 } 383 ret += size 384 l.column += spaces 385 q.readBuf = append(q.readBuf, bytes.Repeat([]byte{' '}, spacesPerTab)...) 386 continue Outer 387 } 388 l.column += spaces 389 case '\b': 390 if l.column > 0 { 391 l.column-- 392 } 393 default: 394 l.column++ 395 } 396 ret += size 397 q.readBuf = append(q.readBuf, cBytes...) 398 } 399 if len(q.readBuf) > 0 { 400 q.readable = true 401 } 402 return ret, false 403 } 404 405 // inputQueueTransformer implements transformer. It performs line discipline 406 // transformations on the input queue. 407 // 408 // +stateify savable 409 type inputQueueTransformer struct{} 410 411 // transform does input processing for one end of the pty. Characters read are 412 // transformed according to flags set in the termios struct. See 413 // drivers/tty/n_tty.c:n_tty_receive_char_special for an analogous kernel 414 // function. 415 // It returns an extra boolean indicating whether any characters need to be 416 // echoed, in which case we need to notify readers. 417 // 418 // Preconditions: 419 // - l.termiosMu must be held for reading. 420 // - q.mu must be held. 421 func (*inputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) (int, bool) { 422 // If there's a line waiting to be read in canonical mode, don't write 423 // anything else to the read buffer. 424 if l.termios.LEnabled(linux.ICANON) && q.readable { 425 return 0, false 426 } 427 428 maxBytes := nonCanonMaxBytes 429 if l.termios.LEnabled(linux.ICANON) { 430 maxBytes = canonMaxBytes 431 } 432 433 var ret int 434 var notifyEcho bool 435 for len(buf) > 0 && len(q.readBuf) < canonMaxBytes { 436 size := l.peek(buf) 437 cBytes := append([]byte{}, buf[:size]...) 438 // We're guaranteed that cBytes has at least one element. 439 switch cBytes[0] { 440 case '\r': 441 if l.termios.IEnabled(linux.IGNCR) { 442 buf = buf[size:] 443 ret += size 444 continue 445 } 446 if l.termios.IEnabled(linux.ICRNL) { 447 cBytes[0] = '\n' 448 } 449 case '\n': 450 if l.termios.IEnabled(linux.INLCR) { 451 cBytes[0] = '\r' 452 } 453 case l.termios.ControlCharacters[linux.VINTR]: // ctrl-c 454 // The input queue is reading from the master TTY and 455 // writing to the replica TTY which is connected to the 456 // interactive program (like bash). We want to send the 457 // signal the process connected to the replica TTY. 458 l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGINT)) 459 case l.termios.ControlCharacters[linux.VSUSP]: // ctrl-z 460 l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGTSTP)) 461 case l.termios.ControlCharacters[linux.VQUIT]: // ctrl-\ 462 l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGQUIT)) 463 464 // In canonical mode, some characters need to be handled specially; for example, backspace. 465 // This roughly aligns with n_tty.c:n_tty_receive_char_canon and n_tty.c:eraser 466 // cBytes[0] == ControlCharacters[linux.VKILL] is also handled by n_tty.c:eraser, but this isn't implemented 467 case l.termios.ControlCharacters[linux.VWERASE]: 468 if !l.termios.LEnabled(linux.IEXTEN) { 469 break 470 } 471 fallthrough 472 case l.termios.ControlCharacters[linux.VERASE]: 473 if !l.termios.LEnabled(linux.ICANON) { 474 break 475 } 476 477 c := cBytes[0] 478 killType := linux.VERASE 479 if c == l.termios.ControlCharacters[linux.VWERASE] { 480 killType = linux.VWERASE 481 } 482 seenAlphanumeric := false 483 for len(q.readBuf) > 0 { 484 // Erase a character. If IUTF8 is enabled, erase an entire multibyte unicode character. 485 var toErase byte 486 cnt := 0 487 isContinuationByte := true 488 for ; cnt < len(q.readBuf) && isContinuationByte; cnt++ { 489 toErase = q.readBuf[len(q.readBuf)-cnt-1] 490 isContinuationByte = l.termios.IEnabled(linux.IUTF8) && (toErase&0xc0) == 0x80 491 } 492 if isContinuationByte { 493 // Do not partially erase a multibyte unicode character. 494 break 495 } 496 497 // VWERASE will continue erasing characters until we encounter the first non-alphanumeric character 498 // that follows some alphanumeric character. We consider "_" to be alphanumeric. 499 if killType == linux.VWERASE { 500 if unicode.IsLetter(rune(toErase)) || unicode.IsDigit(rune(toErase)) || toErase == '_' { 501 seenAlphanumeric = true 502 } else if seenAlphanumeric { 503 break 504 } 505 } 506 507 q.readBuf = q.readBuf[:len(q.readBuf)-cnt] 508 if l.termios.LEnabled(linux.ECHO) { 509 if l.termios.LEnabled(linux.ECHOPRT) { 510 // Not implemented 511 } else if killType == linux.VERASE && !l.termios.LEnabled(linux.ECHOE) { 512 // Not implemented 513 } else if toErase == '\t' { 514 // Not implemented 515 } else { 516 const unicodeDelete byte = 0x7f 517 isCtrl := toErase < 0x20 || toErase == unicodeDelete 518 echoctl := l.termios.LEnabled(linux.ECHOCTL) 519 520 charsToDelete := 1 521 if isCtrl { 522 // echoctl controls how we echo control characters, which also determines how we delete them. 523 if echoctl { 524 // echoctl echoes control characters as ^X, so we need to erase two characters. 525 charsToDelete = 2 526 } else { 527 // if echoctl is disabled, we don't echo control characters so we don't have to erase anything. 528 charsToDelete = 0 529 } 530 } 531 for i := 0; i < charsToDelete; i++ { 532 // Linux's kernel does character deletion with this sequence 533 // of bytes, presumably because some older terminals don't erase 534 // characters with \b, so we need to "erase" the old character 535 // by writing a space over it. 536 l.outQueue.writeBytes([]byte{'\b', ' ', '\b'}, l) 537 } 538 } 539 } 540 541 // VERASE only erases a single character 542 if killType == linux.VERASE { 543 break 544 } 545 } 546 547 buf = buf[1:] 548 ret += 1 549 notifyEcho = true 550 continue 551 } 552 553 // In canonical mode, we discard non-terminating characters 554 // after the first 4095. 555 if l.shouldDiscard(q, cBytes) { 556 buf = buf[size:] 557 ret += size 558 continue 559 } 560 561 // Stop if the buffer would be overfilled. 562 if len(q.readBuf)+size > maxBytes { 563 break 564 } 565 buf = buf[size:] 566 ret += size 567 568 // If we get EOF, make the buffer available for reading. 569 if l.termios.LEnabled(linux.ICANON) && l.termios.IsEOF(cBytes[0]) { 570 q.readable = true 571 break 572 } 573 574 q.readBuf = append(q.readBuf, cBytes...) 575 576 // Anything written to the readBuf will have to be echoed. 577 if l.termios.LEnabled(linux.ECHO) { 578 l.outQueue.writeBytes(cBytes, l) 579 notifyEcho = true 580 } 581 582 // If we finish a line, make it available for reading. 583 if l.termios.LEnabled(linux.ICANON) && l.termios.IsTerminating(cBytes) { 584 q.readable = true 585 break 586 } 587 } 588 589 // In noncanonical mode, everything is readable. 590 if !l.termios.LEnabled(linux.ICANON) && len(q.readBuf) > 0 { 591 q.readable = true 592 } 593 594 return ret, notifyEcho 595 } 596 597 // shouldDiscard returns whether c should be discarded. In canonical mode, if 598 // too many bytes are enqueued, we keep reading input and discarding it until 599 // we find a terminating character. Signal/echo processing still occurs. 600 // 601 // Precondition: 602 // - l.termiosMu must be held for reading. 603 // - q.mu must be held. 604 func (l *lineDiscipline) shouldDiscard(q *queue, cBytes []byte) bool { 605 return l.termios.LEnabled(linux.ICANON) && len(q.readBuf)+len(cBytes) >= canonMaxBytes && !l.termios.IsTerminating(cBytes) 606 } 607 608 // peek returns the size in bytes of the next character to process. As long as 609 // b isn't empty, peek returns a value of at least 1. 610 func (l *lineDiscipline) peek(b []byte) int { 611 size := 1 612 // If UTF-8 support is enabled, runes might be multiple bytes. 613 if l.termios.IEnabled(linux.IUTF8) { 614 _, size = utf8.DecodeRune(b) 615 } 616 return size 617 }