From a3c2dcc3726261d6463ea35102d86863d698021b Mon Sep 17 00:00:00 2001 From: Tony Asleson Date: Mon, 6 Jun 2022 09:56:32 -0500 Subject: [PATCH 9/9] lvmdbusd: Don't require "lvm> " prompt for shell Depending on how lvm is compiled, it may not present the "lvm> " prompt when using the lvm shell. Don't require it to be present. Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=2090391 (cherry picked from commit 691494268502ddb20da2a14568984c0fa4f29f50) --- daemons/lvmdbusd/lvm_shell_proxy.py.in | 83 +++++++++++++------------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/daemons/lvmdbusd/lvm_shell_proxy.py.in b/daemons/lvmdbusd/lvm_shell_proxy.py.in index 1a5051a92..e106ca36f 100644 --- a/daemons/lvmdbusd/lvm_shell_proxy.py.in +++ b/daemons/lvmdbusd/lvm_shell_proxy.py.in @@ -19,7 +19,6 @@ import sys import tempfile import time import select -import copy try: import simplejson as json @@ -31,8 +30,6 @@ from lvmdbusd.cfg import LVM_CMD from lvmdbusd.utils import log_debug, log_error, add_no_notify, make_non_block,\ read_decoded -SHELL_PROMPT = "lvm> " - def _quote_arg(arg): if len(shlex.split(arg)) > 1: @@ -43,10 +40,11 @@ def _quote_arg(arg): class LVMShellProxy(object): - # Read until we get prompt back and a result - # @param: no_output Caller expects no output to report FD - # Returns stdout, report, stderr (report is JSON!) - def _read_until_prompt(self, no_output=False): + # Read REPORT FD until we have a complete and valid JSON record or give + # up trying to get one. + # + # Returns stdout, report (JSON), stderr + def _read_response(self): stdout = "" report = "" stderr = "" @@ -58,6 +56,7 @@ class LVMShellProxy(object): # Try reading from all FDs to prevent one from filling up and causing # a hang. Keep reading until we get the prompt back and the report # FD does not contain valid JSON + while keep_reading: try: rd_fd = [ @@ -78,32 +77,33 @@ class LVMShellProxy(object): if self.lvm_shell.poll() is not None: raise Exception(self.lvm_shell.returncode, "%s" % stderr) - if stdout.endswith(SHELL_PROMPT): - if no_output: - keep_reading = False - else: - cur_report_len = len(report) - 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 - - if 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!") + cur_report_len = len(report) + if cur_report_len != 0: + # Only bother to parse if we have more data and the last 2 characters match expected + # complete JSON, prevents excessive JSON parsing attempts + if prev_report_len != cur_report_len and report[-2:] == "}\n": + 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 + + # As long as lvm is spewing something on one of the FDs we will + # 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: log_debug(str(ioe)) @@ -118,7 +118,6 @@ class LVMShellProxy(object): self.lvm_shell.stdin.flush() def __init__(self): - # Create a temp directory tmp_dir = tempfile.mkdtemp(prefix="lvmdbus_") tmp_file = "%s/lvmdbus_report" % (tmp_dir) @@ -139,6 +138,11 @@ class LVMShellProxy(object): local_env = {"LC_ALL": "C", "LVM_REPORT_FD": "%s" % lvm_fd, "LVM_COMMAND_PROFILE": "lvmdbusd", "LVM_LOG_FILE_MAX_LINES": "0"} + # If any env variables contain LVM we will propagate them too + for k, v in os.environ.items(): + if "LVM" in k: + local_env[k] = v + # run the lvm shell self.lvm_shell = subprocess.Popen( [LVM_CMD], @@ -152,10 +156,9 @@ class LVMShellProxy(object): # Close our copy of the lvm_fd, child process is open in its process space os.close(lvm_fd) - # wait for the first prompt - errors = self._read_until_prompt(no_output=True)[2] - if errors and len(errors): - raise RuntimeError(errors) + # Assume we are ready as we may not get the lvm prompt message depending on + # if we are using readline or editline. + except: raise finally: @@ -169,7 +172,7 @@ class LVMShellProxy(object): self._write_cmd('lastlog\n') # read everything from the STDOUT to the next prompt - stdout, report_json, stderr = self._read_until_prompt() + stdout, report_json, stderr = self._read_response() if 'log' in report_json: error_msg = "" # Walk the entire log array and build an error string @@ -203,7 +206,7 @@ class LVMShellProxy(object): self._write_cmd(cmd) # read everything from the STDOUT to the next prompt - stdout, report_json, stderr = self._read_until_prompt() + stdout, report_json, stderr = self._read_response() # Parse the report to see what happened if 'log' in report_json: -- 2.37.1