io.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. from __future__ import with_statement
  2. import sys
  3. import time
  4. import re
  5. import socket
  6. from select import select
  7. from fabric.state import env, output, win32
  8. from fabric.auth import get_password, set_password
  9. import fabric.network
  10. from fabric.network import ssh, normalize
  11. from fabric.utils import RingBuffer
  12. from fabric.exceptions import CommandTimeout
  13. if win32:
  14. import msvcrt
  15. def _endswith(char_list, substring):
  16. tail = char_list[-1 * len(substring):]
  17. substring = list(substring)
  18. return tail == substring
  19. def _has_newline(bytelist):
  20. return '\r' in bytelist or '\n' in bytelist
  21. def output_loop(*args, **kwargs):
  22. OutputLooper(*args, **kwargs).loop()
  23. class OutputLooper(object):
  24. def __init__(self, chan, attr, stream, capture, timeout):
  25. self.chan = chan
  26. self.stream = stream
  27. self.capture = capture
  28. self.timeout = timeout
  29. self.read_func = getattr(chan, attr)
  30. self.prefix = "[%s] %s: " % (
  31. env.host_string,
  32. "out" if attr == 'recv' else "err"
  33. )
  34. self.printing = getattr(output, 'stdout' if (attr == 'recv') else 'stderr')
  35. self.linewise = (env.linewise or env.parallel)
  36. self.reprompt = False
  37. self.read_size = 4096
  38. self.write_buffer = RingBuffer([], maxlen=len(self.prefix))
  39. def _flush(self, text):
  40. self.stream.write(text)
  41. # Actually only flush if not in linewise mode.
  42. # When linewise is set (e.g. in parallel mode) flushing makes
  43. # doubling-up of line prefixes, and other mixed output, more likely.
  44. if not env.linewise:
  45. self.stream.flush()
  46. self.write_buffer.extend(text)
  47. def loop(self):
  48. """
  49. Loop, reading from <chan>.<attr>(), writing to <stream> and buffering to <capture>.
  50. Will raise `~fabric.exceptions.CommandTimeout` if network timeouts
  51. continue to be seen past the defined ``self.timeout`` threshold.
  52. (Timeouts before then are considered part of normal short-timeout fast
  53. network reading; see Fabric issue #733 for background.)
  54. """
  55. # Initialize loop variables
  56. initial_prefix_printed = False
  57. seen_cr = False
  58. line = []
  59. # Allow prefix to be turned off.
  60. if not env.output_prefix:
  61. self.prefix = ""
  62. start = time.time()
  63. while True:
  64. # Handle actual read
  65. try:
  66. bytelist = self.read_func(self.read_size)
  67. except socket.timeout:
  68. elapsed = time.time() - start
  69. if self.timeout is not None and elapsed > self.timeout:
  70. raise CommandTimeout(timeout=self.timeout)
  71. continue
  72. # Empty byte == EOS
  73. if bytelist == '':
  74. # If linewise, ensure we flush any leftovers in the buffer.
  75. if self.linewise and line:
  76. self._flush(self.prefix)
  77. self._flush("".join(line))
  78. break
  79. # A None capture variable implies that we're in open_shell()
  80. if self.capture is None:
  81. # Just print directly -- no prefixes, no capturing, nada
  82. # And since we know we're using a pty in this mode, just go
  83. # straight to stdout.
  84. self._flush(bytelist)
  85. # Otherwise, we're in run/sudo and need to handle capturing and
  86. # prompts.
  87. else:
  88. # Print to user
  89. if self.printing:
  90. printable_bytes = bytelist
  91. # Small state machine to eat \n after \r
  92. if printable_bytes[-1] == "\r":
  93. seen_cr = True
  94. if printable_bytes[0] == "\n" and seen_cr:
  95. printable_bytes = printable_bytes[1:]
  96. seen_cr = False
  97. while _has_newline(printable_bytes) and printable_bytes != "":
  98. # at most 1 split !
  99. cr = re.search("(\r\n|\r|\n)", printable_bytes)
  100. if cr is None:
  101. break
  102. end_of_line = printable_bytes[:cr.start(0)]
  103. printable_bytes = printable_bytes[cr.end(0):]
  104. if not initial_prefix_printed:
  105. self._flush(self.prefix)
  106. if _has_newline(end_of_line):
  107. end_of_line = ''
  108. if self.linewise:
  109. self._flush("".join(line) + end_of_line + "\n")
  110. line = []
  111. else:
  112. self._flush(end_of_line + "\n")
  113. initial_prefix_printed = False
  114. if self.linewise:
  115. line += [printable_bytes]
  116. else:
  117. if not initial_prefix_printed:
  118. self._flush(self.prefix)
  119. initial_prefix_printed = True
  120. self._flush(printable_bytes)
  121. # Now we have handled printing, handle interactivity
  122. read_lines = re.split(r"(\r|\n|\r\n)", bytelist)
  123. for fragment in read_lines:
  124. # Store in capture buffer
  125. self.capture += fragment
  126. # Handle prompts
  127. expected, response = self._get_prompt_response()
  128. if expected:
  129. del self.capture[-1 * len(expected):]
  130. self.chan.sendall(str(response) + '\n')
  131. else:
  132. prompt = _endswith(self.capture, env.sudo_prompt)
  133. try_again = (_endswith(self.capture, env.again_prompt + '\n')
  134. or _endswith(self.capture, env.again_prompt + '\r\n'))
  135. if prompt:
  136. self.prompt()
  137. elif try_again:
  138. self.try_again()
  139. # Print trailing new line if the last thing we printed was our line
  140. # prefix.
  141. if self.prefix and "".join(self.write_buffer) == self.prefix:
  142. self._flush('\n')
  143. def prompt(self):
  144. # Obtain cached password, if any
  145. password = get_password(*normalize(env.host_string))
  146. # Remove the prompt itself from the capture buffer. This is
  147. # backwards compatible with Fabric 0.9.x behavior; the user
  148. # will still see the prompt on their screen (no way to avoid
  149. # this) but at least it won't clutter up the captured text.
  150. del self.capture[-1 * len(env.sudo_prompt):]
  151. # If the password we just tried was bad, prompt the user again.
  152. if (not password) or self.reprompt:
  153. # Print the prompt and/or the "try again" notice if
  154. # output is being hidden. In other words, since we need
  155. # the user's input, they need to see why we're
  156. # prompting them.
  157. if not self.printing:
  158. self._flush(self.prefix)
  159. if self.reprompt:
  160. self._flush(env.again_prompt + '\n' + self.prefix)
  161. self._flush(env.sudo_prompt)
  162. # Prompt for, and store, password. Give empty prompt so the
  163. # initial display "hides" just after the actually-displayed
  164. # prompt from the remote end.
  165. self.chan.input_enabled = False
  166. password = fabric.network.prompt_for_password(
  167. prompt=" ", no_colon=True, stream=self.stream
  168. )
  169. self.chan.input_enabled = True
  170. # Update env.password, env.passwords if necessary
  171. user, host, port = normalize(env.host_string)
  172. # TODO: in 2.x, make sure to only update sudo-specific password
  173. # config values, not login ones.
  174. set_password(user, host, port, password)
  175. # Reset reprompt flag
  176. self.reprompt = False
  177. # Send current password down the pipe
  178. self.chan.sendall(password + '\n')
  179. def try_again(self):
  180. # Remove text from capture buffer
  181. self.capture = self.capture[:len(env.again_prompt)]
  182. # Set state so we re-prompt the user at the next prompt.
  183. self.reprompt = True
  184. def _get_prompt_response(self):
  185. """
  186. Iterate through the request prompts dict and return the response and
  187. original request if we find a match
  188. """
  189. for tup in env.prompts.iteritems():
  190. if _endswith(self.capture, tup[0]):
  191. return tup
  192. return None, None
  193. def input_loop(chan, using_pty):
  194. while not chan.exit_status_ready():
  195. if win32:
  196. have_char = msvcrt.kbhit()
  197. else:
  198. r, w, x = select([sys.stdin], [], [], 0.0)
  199. have_char = (r and r[0] == sys.stdin)
  200. if have_char and chan.input_enabled:
  201. # Send all local stdin to remote end's stdin
  202. byte = msvcrt.getch() if win32 else sys.stdin.read(1)
  203. chan.sendall(byte)
  204. # Optionally echo locally, if needed.
  205. if not using_pty and env.echo_stdin:
  206. # Not using fastprint() here -- it prints as 'user'
  207. # output level, don't want it to be accidentally hidden
  208. sys.stdout.write(byte)
  209. sys.stdout.flush()
  210. time.sleep(ssh.io_sleep)