#!/usr/bin/env python3 # Copyright (C) 2015-2016 Red Hat, Inc. All rights reserved. # # This copyrighted material is made available to anyone wishing to use, # modify, copy, or redistribute it subject to the terms and conditions # of the GNU General Public License v.2. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Copyright 2015-2016, Vratislav Podzimek import subprocess import shlex from fcntl import fcntl, F_GETFL, F_SETFL from os import O_NONBLOCK import traceback import sys import re try: from .cfg import LVM_CMD from .utils import log_debug, log_error except: from cfg import LVM_CMD from utils import log_debug, log_error SHELL_PROMPT = "lvm> " def _quote_arg(arg): if len(shlex.split(arg)) > 1: return '"%s"' % arg else: return arg class LVMShellProxy(object): def _read_until_prompt(self): prev_ec = None stdout = "" while not stdout.endswith(SHELL_PROMPT): try: tmp = self.lvm_shell.stdout.read() if tmp: stdout += tmp.decode("utf-8") except IOError: # nothing written yet pass # strip the prompt from the STDOUT before returning and grab the exit # code if it's available m = self.re.match(stdout) if m: prev_ec = int(m.group(2)) strip_idx = -1 * len(m.group(1)) else: strip_idx = -1 * len(SHELL_PROMPT) return stdout[:strip_idx], prev_ec def _read_line(self): while True: try: tmp = self.lvm_shell.stdout.readline() if tmp: return tmp.decode("utf-8") except IOError: pass def _discard_echo(self, expected): line = "" while line != expected: # GNU readline inserts some interesting characters at times... line += self._read_line().replace(' \r', '') def _write_cmd(self, cmd): cmd_bytes = bytes(cmd, "utf-8") num_written = self.lvm_shell.stdin.write(cmd_bytes) assert (num_written == len(cmd_bytes)) self.lvm_shell.stdin.flush() def _lvm_echos(self): echo = False cmd = "version\n" self._write_cmd(cmd) line = self._read_line() if line == cmd: echo = True self._read_until_prompt() return echo def __init__(self): self.re = re.compile(".*(\[(-?[0-9]+)\] lvm> $)", re.DOTALL) # run the lvm shell self.lvm_shell = subprocess.Popen( [LVM_CMD], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) flags = fcntl(self.lvm_shell.stdout, F_GETFL) fcntl(self.lvm_shell.stdout, F_SETFL, flags | O_NONBLOCK) flags = fcntl(self.lvm_shell.stderr, F_GETFL) fcntl(self.lvm_shell.stderr, F_SETFL, flags | O_NONBLOCK) # wait for the first prompt self._read_until_prompt() # Check to see if the version of LVM we are using is running with # gnu readline which will echo our writes from stdin to stdout self.echo = self._lvm_echos() def call_lvm(self, argv, debug=False): # create the command string cmd = " ".join(_quote_arg(arg) for arg in argv) cmd += "\n" # run the command by writing it to the shell's STDIN self._write_cmd(cmd) # If lvm is utilizing gnu readline, it echos stdin to stdout if self.echo: self._discard_echo(cmd) # read everything from the STDOUT to the next prompt stdout, exit_code = self._read_until_prompt() # read everything from STDERR if there's something (we waited for the # prompt on STDOUT so there should be all or nothing at this point on # STDERR) stderr = None try: t_error = self.lvm_shell.stderr.read() if t_error: stderr = t_error.decode("utf-8") except IOError: # nothing on STDERR pass if exit_code is not None: rc = exit_code else: # LVM does write to stderr even when it did complete successfully, # so without having the exit code in the prompt we can never be # sure. if stderr: rc = 1 else: rc = 0 if debug or rc != 0: log_error(('CMD: %s' % cmd)) log_error(("EC = %d" % rc)) log_error(("STDOUT=\n %s\n" % stdout)) log_error(("STDERR=\n %s\n" % stderr)) return (rc, stdout, stderr) def __del__(self): self.lvm_shell.terminate() if __name__ == "__main__": shell = LVMShellProxy() in_line = "start" try: while in_line: in_line = input("lvm> ") if in_line: ret, out, err, = shell.call_lvm(in_line.split()) print(("RET: %d" % ret)) print(("OUT:\n%s" % out)) print(("ERR:\n%s" % err)) except KeyboardInterrupt: pass except EOFError: pass except Exception: traceback.print_exc(file=sys.stdout) finally: print()