wexpect-venv/wexpect/host.py
Benedek Racz a1774de30e ok both?
2020-02-04 12:00:46 +01:00

292 lines
13 KiB
Python

"""Wexpect is a Windows variant of pexpect https://pexpect.readthedocs.io.
Wexpect is a Python module for spawning child applications and controlling
them automatically. Wexpect can be used for automating interactive applications
such as ssh, ftp, passwd, telnet, etc. It can be used to a automate setup
scripts for duplicating software package installations on different servers. It
can be used for automated software testing. Wexpect is in the spirit of Don
Libes' Expect, but Wexpect is pure Python. Other Expect-like modules for Python
require TCL and Expect or require C extensions to be compiled. Wexpect does not
use C, Expect, or TCL extensions.
There are two main interfaces to Wexpect -- the function, run() and the class,
spawn. You can call the run() function to execute a command and return the
output. This is a handy replacement for os.system().
For example::
wexpect.run('ls -la')
The more powerful interface is the spawn class. You can use this to spawn an
external child command and then interact with the child by sending lines and
expecting responses.
For example::
child = wexpect.spawn('scp foo myname@host.example.com:.')
child.expect('Password:')
child.sendline(mypassword)
This works even for commands that ask for passwords or other input outside of
the normal stdio streams.
Spawn file is the main (aka. host) class of the wexpect. The user call Spawn, which
start the console_reader as a subprocess, which starts the read child.
Credits: Noah Spurrier, Richard Holden, Marco Molteni, Kimberley Burchett,
Robert Stone, Hartmut Goebel, Chad Schroeder, Erick Tryzelaar, Dave Kirby, Ids
vander Molen, George Todd, Noel Taylor, Nicolas D. Cesar, Alexander Gattin,
Geoffrey Marshall, Francisco Lourenco, Glen Mabey, Karthik Gurusamy, Fernando
Perez, Corey Minyard, Jon Cohen, Guillaume Chazarain, Andrew Ryan, Nick
Craig-Wood, Andrew Stone, Jorgen Grahn, Benedek Racz
Free, open source, and all that good stuff.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Wexpect Copyright (c) 2019 Benedek Racz
"""
import time
import sys
import os
import shutil
import re
import traceback
import types
import psutil
import signal
import socket
import logging
import windll
import pywintypes
import win32process
import win32con
import win32file
import winerror
import win32pipe
from .wexpect_util import ExceptionPexpect
from .wexpect_util import EOF
from .wexpect_util import TIMEOUT
from .wexpect_util import split_command_line
from .wexpect_util import init_logger
from .wexpect_util import EOF_CHAR
from .wexpect_util import SIGNAL_CHARS
logger = logging.getLogger('wexpect')
init_logger(logger)
class SpawnBase:
def __init__(self, command, args=[], timeout=30, maxread=60000, searchwindowsize=None,
logfile=None, cwd=None, env=None, codepage=None, echo=True, safe_exit=True, interact=False, **kwargs):
"""This starts the given command in a child process. This does all the
fork/exec type of stuff for a pty. This is called by __init__. If args
is empty then command will be parsed (split on spaces) and args will be
set to parsed arguments.
The pid and child_fd of this object get set by this method.
Note that it is difficult for this method to fail.
You cannot detect if the child process cannot start.
So the only way you can tell if the child process started
or not is to try to read from the file descriptor. If you get
EOF immediately then it means that the child is already dead.
That may not necessarily be bad because you may haved spawned a child
that performs some task; creates no stdout output; and then dies.
"""
self.host_pid = os.getpid() # That's me
self.console_process = None
self.console_pid = None
self.child_process = None
self.child_pid = None
self.safe_exit = safe_exit
self.searcher = None
self.ignorecase = False
self.before = None
self.after = None
self.match = None
self.match_index = None
self.terminated = True
self.exitstatus = None
self.status = None # status returned by os.waitpid
self.flag_eof = False
self.flag_child_finished = False
self.child_fd = -1 # initially closed
self.timeout = timeout
self.delimiter = EOF
self.cwd = cwd
self.codepage = codepage
self.env = env
self.echo = echo
self.maxread = maxread # max bytes to read at one time into buffer
self.delaybeforesend = 0.1 # Sets sleep time used just before sending data to child. Time in seconds.
self.delayafterterminate = 0.1 # Sets delay in terminate() method to allow kernel time to update process status. Time in seconds.
self.buffer = '' # This is the read buffer. See maxread.
self.searchwindowsize = searchwindowsize # Anything before searchwindowsize point is preserved, but not searched.
self.interact_state = interact
# If command is an int type then it may represent a file descriptor.
if type(command) == type(0):
logger.warning("ExceptionPexpect('Command is an int type. If this is a file descriptor then maybe you want to use fdpexpect.fdspawn which takes an existing file descriptor instead of a command string.')")
raise ExceptionPexpect('Command is an int type. If this is a file descriptor then maybe you want to use fdpexpect.fdspawn which takes an existing file descriptor instead of a command string.')
if type (args) != type([]):
logger.warning("TypeError ('The argument, args, must be a list.')")
raise TypeError ('The argument, args, must be a list.')
if args == []:
self.args = split_command_line(command)
self.command = self.args[0]
else:
self.args = args[:] # work with a copy
self.args.insert (0, command)
self.command = command
command_with_path = shutil.which(self.command)
if command_with_path is None:
logger.warning('The command was not found or was not executable: %s.' % self.command)
raise ExceptionPexpect ('The command was not found or was not executable: %s.' % self.command)
self.command = command_with_path
self.args[0] = self.command
self.name = '<' + ' '.join (self.args) + '>'
self.terminated = False
self.closed = False
self.child_fd = self.startChild(self.args, self.env)
self.get_child_process()
logger.info(f'Child pid: {self.child_pid} Console pid: {self.console_pid}')
# self.connect_to_child()
def __del__(self):
"""This makes sure that no system resources are left open. Python only
garbage collects Python objects, not the child console."""
try:
logger.info('Deleting...')
# if self.child_process is not None:
# self.terminate()
# self.disconnect_from_child()
# if self.safe_exit:
# self.wait()
except:
traceback.print_exc()
logger.warning(traceback.format_exc())
def startChild(self, args, env):
si = win32process.GetStartupInfo()
si.dwFlags = win32process.STARTF_USESHOWWINDOW
si.wShowWindow = win32con.SW_HIDE
dirname = os.path.dirname(sys.executable
if getattr(sys, 'frozen', False) else
os.path.abspath(__file__))
spath = [os.path.dirname(dirname)]
pyargs = ['-c']
if getattr(sys, 'frozen', False):
# If we are running 'frozen', add library.zip and lib\library.zip to sys.path
# py2exe: Needs appropriate 'zipfile' option in setup script and 'bundle_files' 3
spath.append(os.path.join(dirname, 'library.zip'))
spath.append(os.path.join(dirname, 'library.zip',
os.path.basename(os.path.splitext(sys.executable)[0])))
if os.path.isdir(os.path.join(dirname, 'lib')):
dirname = os.path.join(dirname, 'lib')
spath.append(os.path.join(dirname, 'library.zip'))
spath.append(os.path.join(dirname, 'library.zip',
os.path.basename(os.path.splitext(sys.executable)[0])))
pyargs.insert(0, '-S') # skip 'import site'
if getattr(sys, 'frozen', False):
python_executable = os.path.join(dirname, 'python.exe')
else:
python_executable = os.path.join(os.path.dirname(sys.executable), 'python.exe')
cp = self.codepage or windll.kernel32.GetACP()
self.console_class_parameters.update({
'host_pid': self.host_pid,
'local_echo': self.echo,
'interact': self.interact_state,
'cp': cp,
'just_init': True
})
console_class_parameters_kv_pairs = [f'{k}={v}' for k,v in self.console_class_parameters.items() ]
console_class_parameters_str = ', '.join(console_class_parameters_kv_pairs)
child_class_initializator = f"cons = wexpect.{self.console_class_name}(wexpect.join_args({args}), {console_class_parameters_str});"
commandLine = '"%s" %s "%s"' % (python_executable,
' '.join(pyargs),
"import sys;"
f"sys.path = {spath} + sys.path;"
"import wexpect;"
"import time;"
"wexpect.console_reader.logger.info('loggerStart.');"
f"{child_class_initializator}"
"wexpect.console_reader.logger.info(f'Console finished2. {cons.child_exitstatus}');"
"sys.exit(cons.child_exitstatus)"
)
logger.info(f'Console starter command:{commandLine}')
_, _, self.console_pid, __otid = win32process.CreateProcess(None, commandLine, None, None, False,
win32process.CREATE_NEW_CONSOLE, None, self.cwd, si)
def get_console_process(self, force=False):
if force or self.console_process is None:
self.console_process = psutil.Process(self.console_pid)
return self.console_process
def get_child_process(self, force=False):
if force or self.console_process is None:
self.child_process = self.get_console_process()
self.child_pid = self.child_process.pid
return self.child_process
class SpawnPipe(SpawnBase):
def __init__(self, command, args=[], timeout=30, maxread=60000, searchwindowsize=None,
logfile=None, cwd=None, env=None, codepage=None, echo=True, interact=False, **kwargs):
self.pipe = None
self.console_class_name = 'ConsoleReaderPipe'
self.console_class_parameters = {}
super().__init__(command=command, args=args, timeout=timeout, maxread=maxread,
searchwindowsize=searchwindowsize, cwd=cwd, env=env, codepage=codepage, echo=echo, interact=interact)
self.delayafterterminate = 1 # Sets delay in terminate() method to allow kernel time to update process status. Time in seconds.
class SpawnSocket(SpawnBase):
pass
class searcher_re (object):
pass
class searcher_string (object):
pass