term.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. // Copyright 2019 Yunion
  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. package term
  15. import (
  16. "io"
  17. "os"
  18. "github.com/moby/term"
  19. "yunion.io/x/onecloud/pkg/util/interrupt"
  20. )
  21. // SafeFunc is a function to be invoked by TTY.
  22. type SafeFunc func() error
  23. // TTY helps invoke a function and preserve the state of the terminal, even if the process is
  24. // terminated during execution. It also provides support for terminal resizing for remote command
  25. // execution/attachment.
  26. type TTY struct {
  27. // In is a reader representing stdin. It is a required field.
  28. In io.Reader
  29. // Out is a writer representing stdout. It must be set to support terminal resizing. It is an
  30. // optional field.
  31. Out io.Writer
  32. // Raw is true if the terminal should be set raw.
  33. Raw bool
  34. // TryDev indicates the TTY should try to open /dev/tty if the provided input
  35. // is not a file descriptor.
  36. TryDev bool
  37. // Parent is an optional interrupt handler provided to this function - if provided
  38. // it will be invoked after the terminal state is restored. If it is not provided,
  39. // a signal received during the TTY will result in os.Exit(0) being invoked.
  40. Parent *interrupt.Handler
  41. // sizeQueue is set after a call to MonitorSize() and is used to monitor SIGWINCH signals when the
  42. // user's terminal resizes.
  43. sizeQueue *sizeQueue
  44. }
  45. // IsTerminalIn returns true if t.In is a terminal. Does not check /dev/tty
  46. // even if TryDev is set.
  47. func (t TTY) IsTerminalIn() bool {
  48. return IsTerminal(t.In)
  49. }
  50. // IsTerminalOut returns true if t.Out is a terminal. Does not check /dev/tty
  51. // even if TryDev is set.
  52. func (t TTY) IsTerminalOut() bool {
  53. return IsTerminal(t.Out)
  54. }
  55. // IsTerminal returns whether the passed object is a terminal or not
  56. func IsTerminal(i interface{}) bool {
  57. _, terminal := term.GetFdInfo(i)
  58. return terminal
  59. }
  60. // Safe invokes the provided function and will attempt to ensure that when the
  61. // function returns (or a termination signal is sent) that the terminal state
  62. // is reset to the condition it was in prior to the function being invoked. If
  63. // t.Raw is true the terminal will be put into raw mode prior to calling the function.
  64. // If the input file descriptor is not a TTY and TryDev is true, the /dev/tty file
  65. // will be opened (if available).
  66. func (t TTY) Safe(fn SafeFunc) error {
  67. inFd, isTerminal := term.GetFdInfo(t.In)
  68. if !isTerminal && t.TryDev {
  69. if f, err := os.Open("/dev/tty"); err == nil {
  70. defer f.Close()
  71. inFd = f.Fd()
  72. isTerminal = term.IsTerminal(inFd)
  73. }
  74. }
  75. if !isTerminal {
  76. return fn()
  77. }
  78. var state *term.State
  79. var err error
  80. if t.Raw {
  81. state, err = term.MakeRaw(inFd)
  82. } else {
  83. state, err = term.SaveState(inFd)
  84. }
  85. if err != nil {
  86. return err
  87. }
  88. return interrupt.Chain(t.Parent, func() {
  89. if t.sizeQueue != nil {
  90. t.sizeQueue.stop()
  91. }
  92. term.RestoreTerminal(inFd, state)
  93. }).Run(fn)
  94. }