mirror of
git://sourceware.org/git/lvm2.git
synced 2024-12-21 13:34:40 +03:00
lvmdbusd: Use pseudo tty to get "lvm>" prompt again
When lvm is compiled with editline, if the file descriptors don't look like a tty, then no "lvm> " prompt is done. Having lvm output the shell prompt when consuming JSON on a report file descriptor is very useful in determining if lvm command is complete.
This commit is contained in:
parent
664a06650d
commit
b3d13c50d7
108
daemons/lvmdbusd/lvm_shell_proxy.py.in
Normal file → Executable file
108
daemons/lvmdbusd/lvm_shell_proxy.py.in
Normal file → Executable file
@ -14,11 +14,11 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
import shlex
|
import shlex
|
||||||
import os
|
import os
|
||||||
|
import pty
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import select
|
import select
|
||||||
from .utils import extract_stack_trace
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
@ -28,7 +28,9 @@ except ImportError:
|
|||||||
|
|
||||||
from lvmdbusd.cfg import LVM_CMD, run
|
from lvmdbusd.cfg import LVM_CMD, run
|
||||||
from lvmdbusd.utils import log_debug, log_error, add_no_notify, make_non_block,\
|
from lvmdbusd.utils import log_debug, log_error, add_no_notify, make_non_block,\
|
||||||
read_decoded
|
read_decoded, extract_stack_trace, LvmBug
|
||||||
|
|
||||||
|
SHELL_PROMPT = "lvm> "
|
||||||
|
|
||||||
|
|
||||||
def _quote_arg(arg):
|
def _quote_arg(arg):
|
||||||
@ -44,7 +46,7 @@ class LVMShellProxy(object):
|
|||||||
# up trying to get one.
|
# up trying to get one.
|
||||||
#
|
#
|
||||||
# Returns stdout, report (JSON), stderr
|
# Returns stdout, report (JSON), stderr
|
||||||
def _read_response(self):
|
def _read_response(self, no_output=False):
|
||||||
stdout = ""
|
stdout = ""
|
||||||
report = ""
|
report = ""
|
||||||
stderr = ""
|
stderr = ""
|
||||||
@ -60,50 +62,49 @@ class LVMShellProxy(object):
|
|||||||
while keep_reading and run.value != 0:
|
while keep_reading and run.value != 0:
|
||||||
try:
|
try:
|
||||||
rd_fd = [
|
rd_fd = [
|
||||||
self.lvm_shell.stdout.fileno(),
|
self.parent_stdout_fd,
|
||||||
self.report_stream.fileno(),
|
self.report_stream.fileno(),
|
||||||
self.lvm_shell.stderr.fileno()]
|
self.parent_stderr_fd]
|
||||||
ready = select.select(rd_fd, [], [], 2)
|
ready = select.select(rd_fd, [], [], 2)
|
||||||
|
|
||||||
for r in ready[0]:
|
for r in ready[0]:
|
||||||
if r == self.lvm_shell.stdout.fileno():
|
if r == self.parent_stdout_fd:
|
||||||
stdout += read_decoded(self.lvm_shell.stdout)
|
stdout += self.parent_stdout.readline()
|
||||||
elif r == self.report_stream.fileno():
|
elif r == self.report_stream.fileno():
|
||||||
report += read_decoded(self.report_stream)
|
report += read_decoded(self.report_stream)
|
||||||
elif r == self.lvm_shell.stderr.fileno():
|
elif r == self.parent_stderr_fd:
|
||||||
stderr += read_decoded(self.lvm_shell.stderr)
|
stderr += self.parent_stderr.readline()
|
||||||
|
|
||||||
# Check to see if the lvm process died on us
|
# Check to see if the lvm process died on us
|
||||||
if self.lvm_shell.poll() is not None:
|
if self.lvm_shell.poll() is not None:
|
||||||
raise Exception(self.lvm_shell.returncode, "%s" % stderr)
|
raise Exception(self.lvm_shell.returncode, "%s" % stderr)
|
||||||
|
|
||||||
cur_report_len = len(report)
|
if stdout.endswith(SHELL_PROMPT):
|
||||||
if cur_report_len != 0:
|
if no_output:
|
||||||
# Only bother to parse if we have more data and the last 2 characters match expected
|
keep_reading = False
|
||||||
# complete JSON, prevents excessive JSON parsing attempts
|
else:
|
||||||
if prev_report_len != cur_report_len and report[-2:] == "}\n":
|
cur_report_len = len(report)
|
||||||
prev_report_len = cur_report_len
|
if cur_report_len != 0:
|
||||||
|
# Only bother to parse if we have more data
|
||||||
|
if prev_report_len != cur_report_len:
|
||||||
|
prev_report_len = cur_report_len
|
||||||
|
# Parse the JSON if it's good we are done,
|
||||||
|
# if not we will try to read some more.
|
||||||
|
try:
|
||||||
|
report_json = json.loads(report)
|
||||||
|
keep_reading = False
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
# Parse the JSON if it's good we are done,
|
if keep_reading:
|
||||||
# if not we will try to read some more.
|
extra_passes -= 1
|
||||||
try:
|
if extra_passes <= 0:
|
||||||
report_json = json.loads(report)
|
if len(report):
|
||||||
keep_reading = False
|
raise LvmBug("Invalid json: %s" %
|
||||||
except ValueError:
|
report)
|
||||||
pass
|
else:
|
||||||
|
raise LvmBug(
|
||||||
# As long as lvm is spewing something on one of the FDs we will
|
"lvm returned no JSON output!")
|
||||||
# keep trying. If we get a few timeouts with no activity, and
|
|
||||||
# we don't have valid JSON, we will raise an error.
|
|
||||||
if len(ready) == 0 and keep_reading:
|
|
||||||
extra_passes -= 1
|
|
||||||
if extra_passes <= 0:
|
|
||||||
if len(report):
|
|
||||||
raise ValueError("Invalid json: %s" %
|
|
||||||
report)
|
|
||||||
else:
|
|
||||||
raise ValueError(
|
|
||||||
"lvm returned no JSON output!")
|
|
||||||
|
|
||||||
except IOError as ioe:
|
except IOError as ioe:
|
||||||
log_debug(str(ioe))
|
log_debug(str(ioe))
|
||||||
@ -119,10 +120,8 @@ class LVMShellProxy(object):
|
|||||||
return stdout, report_json, stderr
|
return stdout, report_json, stderr
|
||||||
|
|
||||||
def _write_cmd(self, cmd):
|
def _write_cmd(self, cmd):
|
||||||
cmd_bytes = bytes(cmd, "utf-8")
|
self.parent_stdin.write(cmd)
|
||||||
num_written = self.lvm_shell.stdin.write(cmd_bytes)
|
self.parent_stdin.flush()
|
||||||
assert (num_written == len(cmd_bytes))
|
|
||||||
self.lvm_shell.stdin.flush()
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# Create a temp directory
|
# Create a temp directory
|
||||||
@ -147,22 +146,37 @@ class LVMShellProxy(object):
|
|||||||
if "LVM" in k:
|
if "LVM" in k:
|
||||||
local_env[k] = v
|
local_env[k] = v
|
||||||
|
|
||||||
|
self.parent_stdin_fd, child_stdin_fd = pty.openpty()
|
||||||
|
self.parent_stdout_fd, child_stdout_fd = pty.openpty()
|
||||||
|
self.parent_stderr_fd, child_stderr_fd = pty.openpty()
|
||||||
|
self.parent_stdin = os.fdopen(self.parent_stdin_fd, "w")
|
||||||
|
self.parent_stdout = os.fdopen(self.parent_stdout_fd, "r")
|
||||||
|
self.parent_stderr = os.fdopen(self.parent_stderr_fd, "r")
|
||||||
|
|
||||||
# run the lvm shell
|
# run the lvm shell
|
||||||
self.lvm_shell = subprocess.Popen(
|
self.lvm_shell = subprocess.Popen(
|
||||||
[LVM_CMD],
|
[LVM_CMD],
|
||||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, env=local_env,
|
stdin=child_stdin_fd,
|
||||||
stderr=subprocess.PIPE, close_fds=True, pass_fds=(lvm_fd,), shell=False)
|
stdout=child_stdout_fd, env=local_env,
|
||||||
|
stderr=child_stderr_fd, close_fds=True,
|
||||||
|
pass_fds=(lvm_fd,), shell=False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
make_non_block(self.lvm_shell.stdout)
|
make_non_block(self.parent_stdout_fd)
|
||||||
make_non_block(self.lvm_shell.stderr)
|
make_non_block(self.parent_stderr_fd)
|
||||||
|
|
||||||
# Close our copy of the lvm_fd, child process is open in its process space
|
# Close our copies of the child FDs there were created with the fork, we don't need them open.
|
||||||
os.close(lvm_fd)
|
os.close(lvm_fd)
|
||||||
|
os.close(child_stdin_fd)
|
||||||
|
os.close(child_stdout_fd)
|
||||||
|
os.close(child_stderr_fd)
|
||||||
|
|
||||||
# Assume we are ready as we may not get the lvm prompt message depending on
|
# wait for the first prompt
|
||||||
# if we are using readline or editline.
|
log_debug("waiting for first prompt...")
|
||||||
|
errors = self._read_response(no_output=True)[2]
|
||||||
|
if errors and len(errors):
|
||||||
|
raise LvmBug(errors)
|
||||||
|
log_debug("lvm prompt read!!!")
|
||||||
except:
|
except:
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
|
Loading…
Reference in New Issue
Block a user