github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/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/utf8" 20 21 "github.com/MerlinKodo/gvisor/pkg/abi/linux" 22 "github.com/MerlinKodo/gvisor/pkg/context" 23 "github.com/MerlinKodo/gvisor/pkg/errors/linuxerr" 24 "github.com/MerlinKodo/gvisor/pkg/sentry/arch" 25 "github.com/MerlinKodo/gvisor/pkg/sentry/kernel" 26 "github.com/MerlinKodo/gvisor/pkg/sync" 27 "github.com/MerlinKodo/gvisor/pkg/usermem" 28 "github.com/MerlinKodo/gvisor/pkg/waiter" 29 ) 30 31 const ( 32 // canonMaxBytes is the number of bytes that fit into a single line of 33 // terminal input in canonical mode. This corresponds to N_TTY_BUF_SIZE 34 // in include/linux/tty.h. 35 canonMaxBytes = 4096 36 37 // nonCanonMaxBytes is the maximum number of bytes that can be read at 38 // a time in noncanonical mode. 39 nonCanonMaxBytes = canonMaxBytes - 1 40 41 spacesPerTab = 8 42 ) 43 44 // lineDiscipline dictates how input and output are handled between the 45 // pseudoterminal (pty) master and replica. It can be configured to alter I/O, 46 // modify control characters (e.g. Ctrl-C for SIGINT), etc. The following man 47 // pages are good resources for how to affect the line discipline: 48 // 49 // - termios(3) 50 // - tty_ioctl(4) 51 // 52 // This file corresponds most closely to drivers/tty/n_tty.c. 53 // 54 // lineDiscipline has a simple structure but supports a multitude of options 55 // (see the above man pages). It consists of two queues of bytes: one from the 56 // terminal master to replica (the input queue) and one from replica to master 57 // (the output queue). When bytes are written to one end of the pty, the line 58 // discipline reads the bytes, modifies them or takes special action if 59 // required, and enqueues them to be read by the other end of the pty: 60 // 61 // input from terminal +-------------+ input to process (e.g. bash) 62 // +------------------------>| input queue |---------------------------+ 63 // | (inputQueueWrite) +-------------+ (inputQueueRead) | 64 // | | 65 // | v 66 // 67 // masterFD replicaFD 68 // 69 // ^ | 70 // | | 71 // | output to terminal +--------------+ output from process | 72 // +------------------------| output queue |<--------------------------+ 73 // (outputQueueRead) +--------------+ (outputQueueWrite) 74 // 75 // There is special handling for the ECHO option, where bytes written to the 76 // input queue are also output back to the terminal by being written to 77 // l.outQueue by the input queue transformer. 78 // 79 // Lock order: 80 // 81 // termiosMu 82 // inQueue.mu 83 // outQueue.mu 84 // 85 // +stateify savable 86 type lineDiscipline struct { 87 // sizeMu protects size. 88 sizeMu sync.Mutex `state:"nosave"` 89 90 // size is the terminal size (width and height). 91 size linux.WindowSize 92 93 // inQueue is the input queue of the terminal. 94 inQueue queue 95 96 // outQueue is the output queue of the terminal. 97 outQueue queue 98 99 // termiosMu protects termios. 100 termiosMu sync.RWMutex `state:"nosave"` 101 102 // termios is the terminal configuration used by the lineDiscipline. 103 termios linux.KernelTermios 104 105 // column is the location in a row of the cursor. This is important for 106 // handling certain special characters like backspace. 107 column int 108 109 // numReplicas is the number of replica file descriptors. 110 numReplicas int 111 112 // masterWaiter is used to wait on the master end of the TTY. 113 masterWaiter waiter.Queue 114 115 // replicaWaiter is used to wait on the replica end of the TTY. 116 replicaWaiter waiter.Queue 117 118 // terminal is the terminal linked to this lineDiscipline. 119 terminal *Terminal 120 } 121 122 func newLineDiscipline(termios linux.KernelTermios, terminal *Terminal) *lineDiscipline { 123 ld := lineDiscipline{ 124 termios: termios, 125 terminal: terminal, 126 } 127 ld.inQueue.transformer = &inputQueueTransformer{} 128 ld.outQueue.transformer = &outputQueueTransformer{} 129 return &ld 130 } 131 132 // getTermios gets the linux.Termios for the tty. 133 func (l *lineDiscipline) getTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) { 134 l.termiosMu.RLock() 135 defer l.termiosMu.RUnlock() 136 // We must copy a Termios struct, not KernelTermios. 137 t := l.termios.ToTermios() 138 _, err := t.CopyOut(task, args[2].Pointer()) 139 return 0, err 140 } 141 142 // setTermios sets a linux.Termios for the tty. 143 func (l *lineDiscipline) setTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) { 144 l.termiosMu.Lock() 145 oldCanonEnabled := l.termios.LEnabled(linux.ICANON) 146 // We must copy a Termios struct, not KernelTermios. 147 var t linux.Termios 148 _, err := t.CopyIn(task, args[2].Pointer()) 149 l.termios.FromTermios(t) 150 151 // If canonical mode is turned off, move bytes from inQueue's wait 152 // buffer to its read buffer. Anything already in the read buffer is 153 // now readable. 154 if oldCanonEnabled && !l.termios.LEnabled(linux.ICANON) { 155 l.inQueue.mu.Lock() 156 l.inQueue.pushWaitBufLocked(l) 157 l.inQueue.readable = true 158 l.inQueue.mu.Unlock() 159 l.termiosMu.Unlock() 160 l.replicaWaiter.Notify(waiter.ReadableEvents) 161 } else { 162 l.termiosMu.Unlock() 163 } 164 165 return 0, err 166 } 167 168 func (l *lineDiscipline) windowSize(t *kernel.Task, args arch.SyscallArguments) error { 169 l.sizeMu.Lock() 170 defer l.sizeMu.Unlock() 171 _, err := l.size.CopyOut(t, args[2].Pointer()) 172 return err 173 } 174 175 func (l *lineDiscipline) setWindowSize(t *kernel.Task, args arch.SyscallArguments) error { 176 l.sizeMu.Lock() 177 defer l.sizeMu.Unlock() 178 _, err := l.size.CopyIn(t, args[2].Pointer()) 179 return err 180 } 181 182 func (l *lineDiscipline) masterReadiness() waiter.EventMask { 183 // We don't have to lock a termios because the default master termios 184 // is immutable. 185 return l.inQueue.writeReadiness(&linux.MasterTermios) | l.outQueue.readReadiness(&linux.MasterTermios) 186 } 187 188 func (l *lineDiscipline) replicaReadiness() waiter.EventMask { 189 l.termiosMu.RLock() 190 defer l.termiosMu.RUnlock() 191 return l.outQueue.writeReadiness(&l.termios) | l.inQueue.readReadiness(&l.termios) 192 } 193 194 func (l *lineDiscipline) inputQueueReadSize(t *kernel.Task, io usermem.IO, args arch.SyscallArguments) error { 195 return l.inQueue.readableSize(t, io, args) 196 } 197 198 func (l *lineDiscipline) inputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) { 199 l.termiosMu.RLock() 200 n, pushed, notifyEcho, err := l.inQueue.read(ctx, dst, l) 201 isCanon := l.termios.LEnabled(linux.ICANON) 202 l.termiosMu.RUnlock() 203 if err != nil { 204 return 0, err 205 } 206 if n > 0 { 207 if notifyEcho { 208 l.masterWaiter.Notify(waiter.ReadableEvents | waiter.WritableEvents) 209 } else { 210 l.masterWaiter.Notify(waiter.WritableEvents) 211 } 212 if pushed { 213 l.replicaWaiter.Notify(waiter.ReadableEvents) 214 } 215 return n, nil 216 } 217 if notifyEcho { 218 l.masterWaiter.Notify(waiter.ReadableEvents) 219 } 220 if !pushed && isCanon { 221 return 0, nil // EOF 222 } 223 224 return 0, linuxerr.ErrWouldBlock 225 } 226 227 func (l *lineDiscipline) inputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) { 228 l.termiosMu.RLock() 229 n, notifyEcho, err := l.inQueue.write(ctx, src, l) 230 l.termiosMu.RUnlock() 231 if err != nil { 232 return 0, err 233 } 234 if notifyEcho { 235 l.masterWaiter.Notify(waiter.ReadableEvents) 236 } 237 if n > 0 { 238 l.replicaWaiter.Notify(waiter.ReadableEvents) 239 return n, nil 240 } 241 return 0, linuxerr.ErrWouldBlock 242 } 243 244 func (l *lineDiscipline) outputQueueReadSize(t *kernel.Task, io usermem.IO, args arch.SyscallArguments) error { 245 return l.outQueue.readableSize(t, io, args) 246 } 247 248 func (l *lineDiscipline) outputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) { 249 l.termiosMu.RLock() 250 // Ignore notifyEcho, as it cannot happen when reading from the output queue. 251 n, pushed, _, err := l.outQueue.read(ctx, dst, l) 252 l.termiosMu.RUnlock() 253 if err != nil { 254 return 0, err 255 } 256 if n > 0 { 257 l.replicaWaiter.Notify(waiter.WritableEvents) 258 if pushed { 259 l.masterWaiter.Notify(waiter.ReadableEvents) 260 } 261 return n, nil 262 } 263 return 0, linuxerr.ErrWouldBlock 264 } 265 266 func (l *lineDiscipline) outputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) { 267 l.termiosMu.RLock() 268 // Ignore notifyEcho, as it cannot happen when writing to the output queue. 269 n, _, err := l.outQueue.write(ctx, src, l) 270 l.termiosMu.RUnlock() 271 if err != nil { 272 return 0, err 273 } 274 if n > 0 { 275 l.masterWaiter.Notify(waiter.ReadableEvents) 276 return n, nil 277 } 278 return 0, linuxerr.ErrWouldBlock 279 } 280 281 // replicaOpen is called when a replica file descriptor is opened. 282 func (l *lineDiscipline) replicaOpen() { 283 l.termiosMu.Lock() 284 defer l.termiosMu.Unlock() 285 l.numReplicas++ 286 } 287 288 // replicaClose is called when a replica file descriptor is closed. 289 func (l *lineDiscipline) replicaClose() { 290 l.termiosMu.Lock() 291 defer l.termiosMu.Unlock() 292 l.numReplicas-- 293 } 294 295 // transformer is a helper interface to make it easier to stateify queue. 296 type transformer interface { 297 // transform functions require queue's mutex to be held. 298 // The boolean indicates whether there was any echoed bytes. 299 transform(*lineDiscipline, *queue, []byte) (int, bool) 300 } 301 302 // outputQueueTransformer implements transformer. It performs line discipline 303 // transformations on the output queue. 304 // 305 // +stateify savable 306 type outputQueueTransformer struct{} 307 308 // transform does output processing for one end of the pty. See 309 // drivers/tty/n_tty.c:do_output_char for an analogous kernel function. 310 // 311 // Preconditions: 312 // - l.termiosMu must be held for reading. 313 // - q.mu must be held. 314 func (*outputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) (int, bool) { 315 // transformOutput is effectively always in noncanonical mode, as the 316 // master termios never has ICANON set. 317 318 if !l.termios.OEnabled(linux.OPOST) { 319 q.readBuf = append(q.readBuf, buf...) 320 if len(q.readBuf) > 0 { 321 q.readable = true 322 } 323 return len(buf), false 324 } 325 326 var ret int 327 for len(buf) > 0 { 328 size := l.peek(buf) 329 cBytes := append([]byte{}, buf[:size]...) 330 ret += size 331 buf = buf[size:] 332 // We're guaranteed that cBytes has at least one element. 333 switch cBytes[0] { 334 case '\n': 335 if l.termios.OEnabled(linux.ONLRET) { 336 l.column = 0 337 } 338 if l.termios.OEnabled(linux.ONLCR) { 339 q.readBuf = append(q.readBuf, '\r', '\n') 340 continue 341 } 342 case '\r': 343 if l.termios.OEnabled(linux.ONOCR) && l.column == 0 { 344 continue 345 } 346 if l.termios.OEnabled(linux.OCRNL) { 347 cBytes[0] = '\n' 348 if l.termios.OEnabled(linux.ONLRET) { 349 l.column = 0 350 } 351 break 352 } 353 l.column = 0 354 case '\t': 355 spaces := spacesPerTab - l.column%spacesPerTab 356 if l.termios.OutputFlags&linux.TABDLY == linux.XTABS { 357 l.column += spaces 358 q.readBuf = append(q.readBuf, bytes.Repeat([]byte{' '}, spacesPerTab)...) 359 continue 360 } 361 l.column += spaces 362 case '\b': 363 if l.column > 0 { 364 l.column-- 365 } 366 default: 367 l.column++ 368 } 369 q.readBuf = append(q.readBuf, cBytes...) 370 } 371 if len(q.readBuf) > 0 { 372 q.readable = true 373 } 374 return ret, false 375 } 376 377 // inputQueueTransformer implements transformer. It performs line discipline 378 // transformations on the input queue. 379 // 380 // +stateify savable 381 type inputQueueTransformer struct{} 382 383 // transform does input processing for one end of the pty. Characters read are 384 // transformed according to flags set in the termios struct. See 385 // drivers/tty/n_tty.c:n_tty_receive_char_special for an analogous kernel 386 // function. 387 // It returns an extra boolean indicating whether any characters need to be 388 // echoed, in which case we need to notify readers. 389 // 390 // Preconditions: 391 // - l.termiosMu must be held for reading. 392 // - q.mu must be held. 393 func (*inputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) (int, bool) { 394 // If there's a line waiting to be read in canonical mode, don't write 395 // anything else to the read buffer. 396 if l.termios.LEnabled(linux.ICANON) && q.readable { 397 return 0, false 398 } 399 400 maxBytes := nonCanonMaxBytes 401 if l.termios.LEnabled(linux.ICANON) { 402 maxBytes = canonMaxBytes 403 } 404 405 var ret int 406 var notifyEcho bool 407 for len(buf) > 0 && len(q.readBuf) < canonMaxBytes { 408 size := l.peek(buf) 409 cBytes := append([]byte{}, buf[:size]...) 410 // We're guaranteed that cBytes has at least one element. 411 switch cBytes[0] { 412 case '\r': 413 if l.termios.IEnabled(linux.IGNCR) { 414 buf = buf[size:] 415 ret += size 416 continue 417 } 418 if l.termios.IEnabled(linux.ICRNL) { 419 cBytes[0] = '\n' 420 } 421 case '\n': 422 if l.termios.IEnabled(linux.INLCR) { 423 cBytes[0] = '\r' 424 } 425 case l.termios.ControlCharacters[linux.VINTR]: // ctrl-c 426 // The input queue is reading from the master TTY and 427 // writing to the replica TTY which is connected to the 428 // interactive program (like bash). We want to send the 429 // signal the process connected to the replica TTY. 430 l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGINT)) 431 case l.termios.ControlCharacters[linux.VSUSP]: // ctrl-z 432 l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGTSTP)) 433 case l.termios.ControlCharacters[linux.VQUIT]: // ctrl-\ 434 l.terminal.replicaKTTY.SignalForegroundProcessGroup(kernel.SignalInfoPriv(linux.SIGQUIT)) 435 } 436 437 // In canonical mode, we discard non-terminating characters 438 // after the first 4095. 439 if l.shouldDiscard(q, cBytes) { 440 buf = buf[size:] 441 ret += size 442 continue 443 } 444 445 // Stop if the buffer would be overfilled. 446 if len(q.readBuf)+size > maxBytes { 447 break 448 } 449 buf = buf[size:] 450 ret += size 451 452 // If we get EOF, make the buffer available for reading. 453 if l.termios.LEnabled(linux.ICANON) && l.termios.IsEOF(cBytes[0]) { 454 q.readable = true 455 break 456 } 457 458 q.readBuf = append(q.readBuf, cBytes...) 459 460 // Anything written to the readBuf will have to be echoed. 461 if l.termios.LEnabled(linux.ECHO) { 462 l.outQueue.writeBytes(cBytes, l) 463 notifyEcho = true 464 } 465 466 // If we finish a line, make it available for reading. 467 if l.termios.LEnabled(linux.ICANON) && l.termios.IsTerminating(cBytes) { 468 q.readable = true 469 break 470 } 471 } 472 473 // In noncanonical mode, everything is readable. 474 if !l.termios.LEnabled(linux.ICANON) && len(q.readBuf) > 0 { 475 q.readable = true 476 } 477 478 return ret, notifyEcho 479 } 480 481 // shouldDiscard returns whether c should be discarded. In canonical mode, if 482 // too many bytes are enqueued, we keep reading input and discarding it until 483 // we find a terminating character. Signal/echo processing still occurs. 484 // 485 // Precondition: 486 // - l.termiosMu must be held for reading. 487 // - q.mu must be held. 488 func (l *lineDiscipline) shouldDiscard(q *queue, cBytes []byte) bool { 489 return l.termios.LEnabled(linux.ICANON) && len(q.readBuf)+len(cBytes) >= canonMaxBytes && !l.termios.IsTerminating(cBytes) 490 } 491 492 // peek returns the size in bytes of the next character to process. As long as 493 // b isn't empty, peek returns a value of at least 1. 494 func (l *lineDiscipline) peek(b []byte) int { 495 size := 1 496 // If UTF-8 support is enabled, runes might be multiple bytes. 497 if l.termios.IEnabled(linux.IUTF8) { 498 _, size = utf8.DecodeRune(b) 499 } 500 return size 501 }