github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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/SagerNet/gvisor/pkg/abi/linux" 22 "github.com/SagerNet/gvisor/pkg/context" 23 "github.com/SagerNet/gvisor/pkg/sentry/arch" 24 "github.com/SagerNet/gvisor/pkg/sentry/kernel" 25 "github.com/SagerNet/gvisor/pkg/sync" 26 "github.com/SagerNet/gvisor/pkg/syserror" 27 "github.com/SagerNet/gvisor/pkg/usermem" 28 "github.com/SagerNet/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 // masterFD replicaFD 67 // ^ | 68 // | | 69 // | output to terminal +--------------+ output from process | 70 // +------------------------| output queue |<--------------------------+ 71 // (outputQueueRead) +--------------+ (outputQueueWrite) 72 // 73 // Lock order: 74 // termiosMu 75 // inQueue.mu 76 // outQueue.mu 77 // 78 // +stateify savable 79 type lineDiscipline struct { 80 // sizeMu protects size. 81 sizeMu sync.Mutex `state:"nosave"` 82 83 // size is the terminal size (width and height). 84 size linux.WindowSize 85 86 // inQueue is the input queue of the terminal. 87 inQueue queue 88 89 // outQueue is the output queue of the terminal. 90 outQueue queue 91 92 // termiosMu protects termios. 93 termiosMu sync.RWMutex `state:"nosave"` 94 95 // termios is the terminal configuration used by the lineDiscipline. 96 termios linux.KernelTermios 97 98 // column is the location in a row of the cursor. This is important for 99 // handling certain special characters like backspace. 100 column int 101 102 // masterWaiter is used to wait on the master end of the TTY. 103 masterWaiter waiter.Queue 104 105 // replicaWaiter is used to wait on the replica end of the TTY. 106 replicaWaiter waiter.Queue 107 } 108 109 func newLineDiscipline(termios linux.KernelTermios) *lineDiscipline { 110 ld := lineDiscipline{termios: termios} 111 ld.inQueue.transformer = &inputQueueTransformer{} 112 ld.outQueue.transformer = &outputQueueTransformer{} 113 return &ld 114 } 115 116 // getTermios gets the linux.Termios for the tty. 117 func (l *lineDiscipline) getTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) { 118 l.termiosMu.RLock() 119 defer l.termiosMu.RUnlock() 120 // We must copy a Termios struct, not KernelTermios. 121 t := l.termios.ToTermios() 122 _, err := t.CopyOut(task, args[2].Pointer()) 123 return 0, err 124 } 125 126 // setTermios sets a linux.Termios for the tty. 127 func (l *lineDiscipline) setTermios(task *kernel.Task, args arch.SyscallArguments) (uintptr, error) { 128 l.termiosMu.Lock() 129 defer l.termiosMu.Unlock() 130 oldCanonEnabled := l.termios.LEnabled(linux.ICANON) 131 // We must copy a Termios struct, not KernelTermios. 132 var t linux.Termios 133 _, err := t.CopyIn(task, args[2].Pointer()) 134 l.termios.FromTermios(t) 135 136 // If canonical mode is turned off, move bytes from inQueue's wait 137 // buffer to its read buffer. Anything already in the read buffer is 138 // now readable. 139 if oldCanonEnabled && !l.termios.LEnabled(linux.ICANON) { 140 l.inQueue.mu.Lock() 141 l.inQueue.pushWaitBufLocked(l) 142 l.inQueue.readable = true 143 l.inQueue.mu.Unlock() 144 l.replicaWaiter.Notify(waiter.ReadableEvents) 145 } 146 147 return 0, err 148 } 149 150 func (l *lineDiscipline) windowSize(t *kernel.Task, args arch.SyscallArguments) error { 151 l.sizeMu.Lock() 152 defer l.sizeMu.Unlock() 153 _, err := l.size.CopyOut(t, args[2].Pointer()) 154 return err 155 } 156 157 func (l *lineDiscipline) setWindowSize(t *kernel.Task, args arch.SyscallArguments) error { 158 l.sizeMu.Lock() 159 defer l.sizeMu.Unlock() 160 _, err := l.size.CopyIn(t, args[2].Pointer()) 161 return err 162 } 163 164 func (l *lineDiscipline) masterReadiness() waiter.EventMask { 165 // We don't have to lock a termios because the default master termios 166 // is immutable. 167 return l.inQueue.writeReadiness(&linux.MasterTermios) | l.outQueue.readReadiness(&linux.MasterTermios) 168 } 169 170 func (l *lineDiscipline) replicaReadiness() waiter.EventMask { 171 l.termiosMu.RLock() 172 defer l.termiosMu.RUnlock() 173 return l.outQueue.writeReadiness(&l.termios) | l.inQueue.readReadiness(&l.termios) 174 } 175 176 func (l *lineDiscipline) inputQueueReadSize(t *kernel.Task, io usermem.IO, args arch.SyscallArguments) error { 177 return l.inQueue.readableSize(t, io, args) 178 } 179 180 func (l *lineDiscipline) inputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) { 181 l.termiosMu.RLock() 182 defer l.termiosMu.RUnlock() 183 n, pushed, err := l.inQueue.read(ctx, dst, l) 184 if err != nil { 185 return 0, err 186 } 187 if n > 0 { 188 l.masterWaiter.Notify(waiter.WritableEvents) 189 if pushed { 190 l.replicaWaiter.Notify(waiter.ReadableEvents) 191 } 192 return n, nil 193 } 194 return 0, syserror.ErrWouldBlock 195 } 196 197 func (l *lineDiscipline) inputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) { 198 l.termiosMu.RLock() 199 defer l.termiosMu.RUnlock() 200 n, err := l.inQueue.write(ctx, src, l) 201 if err != nil { 202 return 0, err 203 } 204 if n > 0 { 205 l.replicaWaiter.Notify(waiter.ReadableEvents) 206 return n, nil 207 } 208 return 0, syserror.ErrWouldBlock 209 } 210 211 func (l *lineDiscipline) outputQueueReadSize(t *kernel.Task, io usermem.IO, args arch.SyscallArguments) error { 212 return l.outQueue.readableSize(t, io, args) 213 } 214 215 func (l *lineDiscipline) outputQueueRead(ctx context.Context, dst usermem.IOSequence) (int64, error) { 216 l.termiosMu.RLock() 217 defer l.termiosMu.RUnlock() 218 n, pushed, err := l.outQueue.read(ctx, dst, l) 219 if err != nil { 220 return 0, err 221 } 222 if n > 0 { 223 l.replicaWaiter.Notify(waiter.WritableEvents) 224 if pushed { 225 l.masterWaiter.Notify(waiter.ReadableEvents) 226 } 227 return n, nil 228 } 229 return 0, syserror.ErrWouldBlock 230 } 231 232 func (l *lineDiscipline) outputQueueWrite(ctx context.Context, src usermem.IOSequence) (int64, error) { 233 l.termiosMu.RLock() 234 defer l.termiosMu.RUnlock() 235 n, err := l.outQueue.write(ctx, src, l) 236 if err != nil { 237 return 0, err 238 } 239 if n > 0 { 240 l.masterWaiter.Notify(waiter.ReadableEvents) 241 return n, nil 242 } 243 return 0, syserror.ErrWouldBlock 244 } 245 246 // transformer is a helper interface to make it easier to stateify queue. 247 type transformer interface { 248 // transform functions require queue's mutex to be held. 249 transform(*lineDiscipline, *queue, []byte) int 250 } 251 252 // outputQueueTransformer implements transformer. It performs line discipline 253 // transformations on the output queue. 254 // 255 // +stateify savable 256 type outputQueueTransformer struct{} 257 258 // transform does output processing for one end of the pty. See 259 // drivers/tty/n_tty.c:do_output_char for an analogous kernel function. 260 // 261 // Preconditions: 262 // * l.termiosMu must be held for reading. 263 // * q.mu must be held. 264 func (*outputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) int { 265 // transformOutput is effectively always in noncanonical mode, as the 266 // master termios never has ICANON set. 267 268 if !l.termios.OEnabled(linux.OPOST) { 269 q.readBuf = append(q.readBuf, buf...) 270 if len(q.readBuf) > 0 { 271 q.readable = true 272 } 273 return len(buf) 274 } 275 276 var ret int 277 for len(buf) > 0 { 278 size := l.peek(buf) 279 cBytes := append([]byte{}, buf[:size]...) 280 ret += size 281 buf = buf[size:] 282 // We're guaranteed that cBytes has at least one element. 283 switch cBytes[0] { 284 case '\n': 285 if l.termios.OEnabled(linux.ONLRET) { 286 l.column = 0 287 } 288 if l.termios.OEnabled(linux.ONLCR) { 289 q.readBuf = append(q.readBuf, '\r', '\n') 290 continue 291 } 292 case '\r': 293 if l.termios.OEnabled(linux.ONOCR) && l.column == 0 { 294 continue 295 } 296 if l.termios.OEnabled(linux.OCRNL) { 297 cBytes[0] = '\n' 298 if l.termios.OEnabled(linux.ONLRET) { 299 l.column = 0 300 } 301 break 302 } 303 l.column = 0 304 case '\t': 305 spaces := spacesPerTab - l.column%spacesPerTab 306 if l.termios.OutputFlags&linux.TABDLY == linux.XTABS { 307 l.column += spaces 308 q.readBuf = append(q.readBuf, bytes.Repeat([]byte{' '}, spacesPerTab)...) 309 continue 310 } 311 l.column += spaces 312 case '\b': 313 if l.column > 0 { 314 l.column-- 315 } 316 default: 317 l.column++ 318 } 319 q.readBuf = append(q.readBuf, cBytes...) 320 } 321 if len(q.readBuf) > 0 { 322 q.readable = true 323 } 324 return ret 325 } 326 327 // inputQueueTransformer implements transformer. It performs line discipline 328 // transformations on the input queue. 329 // 330 // +stateify savable 331 type inputQueueTransformer struct{} 332 333 // transform does input processing for one end of the pty. Characters read are 334 // transformed according to flags set in the termios struct. See 335 // drivers/tty/n_tty.c:n_tty_receive_char_special for an analogous kernel 336 // function. 337 // 338 // Preconditions: 339 // * l.termiosMu must be held for reading. 340 // * q.mu must be held. 341 func (*inputQueueTransformer) transform(l *lineDiscipline, q *queue, buf []byte) int { 342 // If there's a line waiting to be read in canonical mode, don't write 343 // anything else to the read buffer. 344 if l.termios.LEnabled(linux.ICANON) && q.readable { 345 return 0 346 } 347 348 maxBytes := nonCanonMaxBytes 349 if l.termios.LEnabled(linux.ICANON) { 350 maxBytes = canonMaxBytes 351 } 352 353 var ret int 354 for len(buf) > 0 && len(q.readBuf) < canonMaxBytes { 355 size := l.peek(buf) 356 cBytes := append([]byte{}, buf[:size]...) 357 // We're guaranteed that cBytes has at least one element. 358 switch cBytes[0] { 359 case '\r': 360 if l.termios.IEnabled(linux.IGNCR) { 361 buf = buf[size:] 362 ret += size 363 continue 364 } 365 if l.termios.IEnabled(linux.ICRNL) { 366 cBytes[0] = '\n' 367 } 368 case '\n': 369 if l.termios.IEnabled(linux.INLCR) { 370 cBytes[0] = '\r' 371 } 372 } 373 374 // In canonical mode, we discard non-terminating characters 375 // after the first 4095. 376 if l.shouldDiscard(q, cBytes) { 377 buf = buf[size:] 378 ret += size 379 continue 380 } 381 382 // Stop if the buffer would be overfilled. 383 if len(q.readBuf)+size > maxBytes { 384 break 385 } 386 buf = buf[size:] 387 ret += size 388 389 // If we get EOF, make the buffer available for reading. 390 if l.termios.LEnabled(linux.ICANON) && l.termios.IsEOF(cBytes[0]) { 391 q.readable = true 392 break 393 } 394 395 q.readBuf = append(q.readBuf, cBytes...) 396 397 // Anything written to the readBuf will have to be echoed. 398 if l.termios.LEnabled(linux.ECHO) { 399 l.outQueue.writeBytes(cBytes, l) 400 l.masterWaiter.Notify(waiter.ReadableEvents) 401 } 402 403 // If we finish a line, make it available for reading. 404 if l.termios.LEnabled(linux.ICANON) && l.termios.IsTerminating(cBytes) { 405 q.readable = true 406 break 407 } 408 } 409 410 // In noncanonical mode, everything is readable. 411 if !l.termios.LEnabled(linux.ICANON) && len(q.readBuf) > 0 { 412 q.readable = true 413 } 414 415 return ret 416 } 417 418 // shouldDiscard returns whether c should be discarded. In canonical mode, if 419 // too many bytes are enqueued, we keep reading input and discarding it until 420 // we find a terminating character. Signal/echo processing still occurs. 421 // 422 // Precondition: 423 // * l.termiosMu must be held for reading. 424 // * q.mu must be held. 425 func (l *lineDiscipline) shouldDiscard(q *queue, cBytes []byte) bool { 426 return l.termios.LEnabled(linux.ICANON) && len(q.readBuf)+len(cBytes) >= canonMaxBytes && !l.termios.IsTerminating(cBytes) 427 } 428 429 // peek returns the size in bytes of the next character to process. As long as 430 // b isn't empty, peek returns a value of at least 1. 431 func (l *lineDiscipline) peek(b []byte) int { 432 size := 1 433 // If UTF-8 support is enabled, runes might be multiple bytes. 434 if l.termios.IEnabled(linux.IUTF8) { 435 _, size = utf8.DecodeRune(b) 436 } 437 return size 438 }