_process_win32.py
205 lines
| 6.6 KiB
| text/x-python
|
PythonLexer
Fernando Perez
|
r2908 | """Windows-specific implementation of process utilities. | ||
This file is only meant to be imported by process.py, not by end-users. | ||||
""" | ||||
#----------------------------------------------------------------------------- | ||||
Matthias BUSSONNIER
|
r5390 | # Copyright (C) 2010-2011 The IPython Development Team | ||
Fernando Perez
|
r2908 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
# stdlib | ||||
import os | ||||
import sys | ||||
Jörgen Stenarson
|
r5517 | import ctypes | ||
Itamar Turner-Trauring
|
r25504 | import time | ||
Fernando Perez
|
r2908 | |||
Jörgen Stenarson
|
r5517 | from ctypes import c_int, POINTER | ||
from ctypes.wintypes import LPCWSTR, HLOCAL | ||||
Itamar Turner-Trauring
|
r25503 | from subprocess import STDOUT, TimeoutExpired | ||
Itamar Turner-Trauring
|
r25513 | from threading import Thread | ||
Fernando Perez
|
r2908 | |||
# our own imports | ||||
MinRK
|
r5713 | from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split | ||
Jörgen Stenarson
|
r5517 | from . import py3compat | ||
Brandon Parsons
|
r6716 | from .encoding import DEFAULT_ENCODING | ||
Fernando Perez
|
r2908 | |||
#----------------------------------------------------------------------------- | ||||
# Function definitions | ||||
#----------------------------------------------------------------------------- | ||||
class AvoidUNCPath(object): | ||||
"""A context manager to protect command execution from UNC paths. | ||||
In the Win32 API, commands can't be invoked with the cwd being a UNC path. | ||||
This context manager temporarily changes directory to the 'C:' drive on | ||||
entering, and restores the original working directory on exit. | ||||
The context manager returns the starting working directory *if* it made a | ||||
change and None otherwise, so that users can apply the necessary adjustment | ||||
to their system calls in the event of a change. | ||||
Thomas Kluyver
|
r12553 | Examples | ||
-------- | ||||
Fernando Perez
|
r2908 | :: | ||
cmd = 'dir' | ||||
with AvoidUNCPath() as path: | ||||
if path is not None: | ||||
cmd = '"pushd %s &&"%s' % (path, cmd) | ||||
os.system(cmd) | ||||
""" | ||||
def __enter__(self): | ||||
Srinivas Reddy Thatiparthy
|
r23045 | self.path = os.getcwd() | ||
Fernando Perez
|
r2908 | self.is_unc_path = self.path.startswith(r"\\") | ||
if self.is_unc_path: | ||||
# change to c drive (as cmd.exe cannot handle UNC addresses) | ||||
os.chdir("C:") | ||||
Fernando Perez
|
r2909 | return self.path | ||
else: | ||||
# We return None to signal that there was no change in the working | ||||
# directory | ||||
return None | ||||
Fernando Perez
|
r2908 | |||
def __exit__(self, exc_type, exc_value, traceback): | ||||
if self.is_unc_path: | ||||
os.chdir(self.path) | ||||
def _find_cmd(cmd): | ||||
"""Find the full path to a .bat or .exe using the win32api module.""" | ||||
try: | ||||
from win32api import SearchPath | ||||
Ram Rachum
|
r25833 | except ImportError as e: | ||
raise ImportError('you need to have pywin32 installed for this to work') from e | ||||
Fernando Perez
|
r2908 | else: | ||
PATH = os.environ['PATH'] | ||||
extensions = ['.exe', '.com', '.bat', '.py'] | ||||
path = None | ||||
for ext in extensions: | ||||
try: | ||||
Thomas Kluyver
|
r10678 | path = SearchPath(PATH, cmd, ext)[0] | ||
Fernando Perez
|
r2908 | except: | ||
pass | ||||
if path is None: | ||||
raise OSError("command %r not found" % cmd) | ||||
else: | ||||
return path | ||||
def _system_body(p): | ||||
"""Callback for _system.""" | ||||
Brandon Parsons
|
r6716 | enc = DEFAULT_ENCODING | ||
Itamar Turner-Trauring
|
r25513 | |||
def stdout_read(): | ||||
for line in read_no_interrupt(p.stdout).splitlines(): | ||||
line = line.decode(enc, 'replace') | ||||
print(line, file=sys.stdout) | ||||
def stderr_read(): | ||||
for line in read_no_interrupt(p.stderr).splitlines(): | ||||
line = line.decode(enc, 'replace') | ||||
print(line, file=sys.stderr) | ||||
Thread(target=stdout_read).start() | ||||
Thread(target=stderr_read).start() | ||||
Fernando Perez
|
r2908 | |||
Itamar Turner-Trauring
|
r25503 | # Wait to finish for returncode. Unfortunately, Python has a bug where | ||
# wait() isn't interruptible (https://bugs.python.org/issue28168) so poll in | ||||
# a loop instead of just doing `return p.wait()`. | ||||
while True: | ||||
Itamar Turner-Trauring
|
r25504 | result = p.poll() | ||
if result is None: | ||||
time.sleep(0.01) | ||||
else: | ||||
return result | ||||
MinRK
|
r3910 | |||
Fernando Perez
|
r2908 | |||
def system(cmd): | ||||
"""Win32 version of os.system() that works with network shares. | ||||
Note that this implementation returns None, as meant for use in IPython. | ||||
Parameters | ||||
---------- | ||||
Thomas Kluyver
|
r13738 | cmd : str or list | ||
Matthias Bussonnier
|
r26419 | A command to be executed in the system shell. | ||
Fernando Perez
|
r2908 | |||
Returns | ||||
------- | ||||
Itamar Turner-Trauring
|
r25510 | int : child process' exit code. | ||
Fernando Perez
|
r2908 | """ | ||
Mark Wiebe
|
r6130 | # The controller provides interactivity with both | ||
# stdin and stdout | ||||
Jonathan March
|
r7424 | #import _process_win32_controller | ||
#_process_win32_controller.system(cmd) | ||||
Fernando Perez
|
r2908 | |||
Jonathan March
|
r7424 | with AvoidUNCPath() as path: | ||
if path is not None: | ||||
cmd = '"pushd %s &&"%s' % (path, cmd) | ||||
return process_handler(cmd, _system_body) | ||||
Fernando Perez
|
r2908 | |||
def getoutput(cmd): | ||||
"""Return standard output of executing cmd in a shell. | ||||
Accepts the same arguments as os.system(). | ||||
Parameters | ||||
---------- | ||||
Thomas Kluyver
|
r13738 | cmd : str or list | ||
Matthias Bussonnier
|
r26419 | A command to be executed in the system shell. | ||
Fernando Perez
|
r2908 | |||
Returns | ||||
------- | ||||
stdout : str | ||||
""" | ||||
with AvoidUNCPath() as path: | ||||
if path is not None: | ||||
cmd = '"pushd %s &&"%s' % (path, cmd) | ||||
out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT) | ||||
if out is None: | ||||
Jörgen Stenarson
|
r7518 | out = b'' | ||
Hugo
|
r24010 | return py3compat.decode(out) | ||
Jörgen Stenarson
|
r5517 | |||
Jörgen Stenarson
|
r5533 | try: | ||
CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW | ||||
CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)] | ||||
Jörgen Stenarson
|
r6728 | CommandLineToArgvW.restype = POINTER(LPCWSTR) | ||
Jörgen Stenarson
|
r5533 | LocalFree = ctypes.windll.kernel32.LocalFree | ||
LocalFree.res_type = HLOCAL | ||||
LocalFree.arg_types = [HLOCAL] | ||||
MinRK
|
r5713 | def arg_split(commandline, posix=False, strict=True): | ||
Jörgen Stenarson
|
r5533 | """Split a command line's arguments in a shell-like manner. | ||
This is a special version for windows that use a ctypes call to CommandLineToArgvW | ||||
luzpaz
|
r24084 | to do the argv splitting. The posix parameter is ignored. | ||
Matthias Bussonnier
|
r26419 | |||
MinRK
|
r5713 | If strict=False, process_common.arg_split(...strict=False) is used instead. | ||
Jörgen Stenarson
|
r5533 | """ | ||
#CommandLineToArgvW returns path to executable if called with empty string. | ||||
if commandline.strip() == "": | ||||
return [] | ||||
MinRK
|
r5713 | if not strict: | ||
# not really a cl-arg, fallback on _process_common | ||||
return py_arg_split(commandline, posix=posix, strict=strict) | ||||
Jörgen Stenarson
|
r5533 | argvn = c_int() | ||
result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn)) | ||||
result_array_type = LPCWSTR * argvn.value | ||||
Jörgen Stenarson
|
r6728 | result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))] | ||
Jörgen Stenarson
|
r5533 | retval = LocalFree(result_pointer) | ||
return result | ||||
except AttributeError: | ||||
MinRK
|
r5713 | arg_split = py_arg_split | ||
Thomas Kluyver
|
r17181 | |||
def check_pid(pid): | ||||
# OpenProcess returns 0 if no such process (of ours) exists | ||||
# positive int otherwise | ||||
return bool(ctypes.windll.kernel32.OpenProcess(1,0,pid)) | ||||