2017-12-12 15:10:51 +03:00
#!@PYTHON3@
2016-02-18 02:53:35 +03:00
# 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 <http://www.gnu.org/licenses/>.
#
# Copyright 2015-2016, Vratislav Podzimek <vpodzime@redhat.com>
import subprocess
import shlex
2016-08-12 23:23:05 +03:00
import os
2016-02-18 02:53:35 +03:00
import traceback
import sys
2016-08-12 23:23:05 +03:00
import tempfile
import time
import select
try :
import simplejson as json
except ImportError :
import json
2016-02-18 02:53:35 +03:00
2022-08-18 01:21:19 +03:00
from lvmdbusd . cfg import LVM_CMD , run
2021-06-11 18:35:31 +03:00
from lvmdbusd . utils import log_debug , log_error , add_no_notify , make_non_block , \
read_decoded
2016-02-18 02:53:35 +03:00
def _quote_arg ( arg ) :
if len ( shlex . split ( arg ) ) > 1 :
return ' " %s " ' % arg
else :
return arg
class LVMShellProxy ( object ) :
2016-11-29 20:07:21 +03:00
2022-06-06 17:56:32 +03:00
# 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 ) :
2016-02-18 02:53:35 +03:00
stdout = " "
2016-08-12 23:23:05 +03:00
report = " "
stderr = " "
2016-11-17 00:57:04 +03:00
keep_reading = True
2016-11-29 20:07:21 +03:00
extra_passes = 3
report_json = { }
prev_report_len = 0
2016-02-18 02:53:35 +03:00
2016-08-12 23:23:05 +03:00
# Try reading from all FDs to prevent one from filling up and causing
2016-11-29 20:07:21 +03:00
# a hang. Keep reading until we get the prompt back and the report
# FD does not contain valid JSON
2022-06-06 17:56:32 +03:00
2022-08-18 01:21:19 +03:00
while keep_reading and run . value != 0 :
2016-02-18 02:53:35 +03:00
try :
2016-08-12 23:23:05 +03:00
rd_fd = [
self . lvm_shell . stdout . fileno ( ) ,
2016-11-29 21:37:59 +03:00
self . report_stream . fileno ( ) ,
2016-08-12 23:23:05 +03:00
self . lvm_shell . stderr . fileno ( ) ]
ready = select . select ( rd_fd , [ ] , [ ] , 2 )
for r in ready [ 0 ] :
if r == self . lvm_shell . stdout . fileno ( ) :
2021-06-11 18:35:31 +03:00
stdout + = read_decoded ( self . lvm_shell . stdout )
2016-11-29 21:37:59 +03:00
elif r == self . report_stream . fileno ( ) :
2021-06-11 18:35:31 +03:00
report + = read_decoded ( self . report_stream )
2016-08-12 23:23:05 +03:00
elif r == self . lvm_shell . stderr . fileno ( ) :
2021-06-11 18:35:31 +03:00
stderr + = read_decoded ( self . lvm_shell . stderr )
2016-08-12 23:23:05 +03:00
2016-10-07 21:45:30 +03:00
# Check to see if the lvm process died on us
2022-05-25 23:51:14 +03:00
if self . lvm_shell . poll ( ) is not None :
2016-10-07 21:45:30 +03:00
raise Exception ( self . lvm_shell . returncode , " %s " % stderr )
2022-06-06 17:56:32 +03:00
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! " )
2016-11-17 00:57:04 +03:00
2016-08-12 23:23:05 +03:00
except IOError as ioe :
log_debug ( str ( ioe ) )
2016-02-18 02:53:35 +03:00
pass
2016-11-29 20:07:21 +03:00
return stdout , report_json , stderr
2016-02-18 02:53:35 +03:00
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 ( )
2016-08-12 23:23:05 +03:00
def __init__ ( self ) :
# Create a temp directory
tmp_dir = tempfile . mkdtemp ( prefix = " lvmdbus_ " )
tmp_file = " %s /lvmdbus_report " % ( tmp_dir )
2016-02-18 02:53:35 +03:00
2022-06-07 16:21:03 +03:00
# Create a fifo for the report output
os . mkfifo ( tmp_file , 0o600 )
2016-02-18 02:53:35 +03:00
2022-05-26 18:44:02 +03:00
# Open the fifo for use to read and for lvm child process to write to.
2016-11-29 21:37:59 +03:00
self . report_fd = os . open ( tmp_file , os . O_NONBLOCK )
self . report_stream = os . fdopen ( self . report_fd , ' rb ' , 0 )
2022-05-26 18:44:02 +03:00
lvm_fd = os . open ( tmp_file , os . O_WRONLY )
2016-08-12 23:23:05 +03:00
2022-05-26 18:44:02 +03:00
# Set up the environment for using our own socket for reporting and disable the abort
# logic if lvm logs too much, which easily happens when utilizing the lvm shell.
local_env = { " LC_ALL " : " C " , " LVM_REPORT_FD " : " %s " % lvm_fd , " LVM_COMMAND_PROFILE " : " lvmdbusd " ,
" LVM_LOG_FILE_MAX_LINES " : " 0 " }
2016-10-11 17:36:47 +03:00
2022-06-06 17:56:32 +03:00
# 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
2016-02-18 02:53:35 +03:00
# run the lvm shell
self . lvm_shell = subprocess . Popen (
2022-05-26 18:44:02 +03:00
[ LVM_CMD ] ,
2016-08-12 23:23:05 +03:00
stdin = subprocess . PIPE , stdout = subprocess . PIPE , env = local_env ,
2022-05-26 18:44:02 +03:00
stderr = subprocess . PIPE , close_fds = True , pass_fds = ( lvm_fd , ) , shell = False )
2016-10-07 21:46:59 +03:00
try :
2021-06-11 18:35:31 +03:00
make_non_block ( self . lvm_shell . stdout )
make_non_block ( self . lvm_shell . stderr )
2016-10-07 21:46:59 +03:00
2022-05-26 18:44:02 +03:00
# Close our copy of the lvm_fd, child process is open in its process space
os . close ( lvm_fd )
2022-06-06 17:56:32 +03:00
# Assume we are ready as we may not get the lvm prompt message depending on
# if we are using readline or editline.
2016-10-07 21:46:59 +03:00
except :
raise
finally :
2022-06-06 17:57:20 +03:00
# These will get deleted when the FD count goes to zero, so we
2016-11-29 21:37:59 +03:00
# can be sure to clean up correctly no matter how we finish
2016-10-07 21:46:59 +03:00
os . unlink ( tmp_file )
os . rmdir ( tmp_dir )
2016-08-12 23:23:05 +03:00
2022-08-17 20:05:06 +03:00
def get_last_log ( self ) :
2016-08-12 23:23:05 +03:00
self . _write_cmd ( ' lastlog \n ' )
2022-08-25 16:33:50 +03:00
report_json = self . _read_response ( ) [ 1 ]
2022-08-17 20:05:06 +03:00
return LVMShellProxy . get_error_msg ( report_json )
2016-08-12 23:23:05 +03:00
2022-08-17 20:05:06 +03:00
@staticmethod
def get_error_msg ( report_json ) :
# Get the error message from the returned JSON
2016-11-29 20:07:21 +03:00
if ' log ' in report_json :
error_msg = " "
# Walk the entire log array and build an error string
for log_entry in report_json [ ' log ' ] :
if log_entry [ ' log_type ' ] == " error " :
if error_msg :
error_msg + = ' , ' + log_entry [ ' log_message ' ]
else :
error_msg = log_entry [ ' log_message ' ]
2016-08-12 23:23:05 +03:00
2016-11-29 20:07:21 +03:00
return error_msg
2016-08-12 23:23:05 +03:00
2022-08-17 20:05:06 +03:00
return None
2016-02-18 02:53:35 +03:00
def call_lvm ( self , argv , debug = False ) :
2016-08-12 23:23:05 +03:00
rc = 1
error_msg = " "
2016-10-07 21:45:30 +03:00
if self . lvm_shell . poll ( ) :
raise Exception (
self . lvm_shell . returncode ,
" Underlying lvm shell process is not present! " )
2017-03-09 00:35:53 +03:00
argv = add_no_notify ( argv )
2016-02-18 02:53:35 +03:00
# 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 )
# read everything from the STDOUT to the next prompt
2022-06-06 17:56:32 +03:00
stdout , report_json , stderr = self . _read_response ( )
2016-02-18 02:53:35 +03:00
2016-08-12 23:23:05 +03:00
# Parse the report to see what happened
2016-11-29 20:07:21 +03:00
if ' log ' in report_json :
2018-12-18 18:51:50 +03:00
ret_code = int ( report_json [ ' log ' ] [ - 1 : ] [ 0 ] [ ' log_ret_code ' ] )
# If we have an exported vg we get a log_ret_code == 5 when
# we do a 'fullreport'
2022-08-17 20:05:06 +03:00
# Note: 0 == error
2018-12-18 18:51:50 +03:00
if ( ret_code == 1 ) or ( ret_code == 5 and argv [ 0 ] == ' fullreport ' ) :
2016-11-29 20:07:21 +03:00
rc = 0
else :
2022-08-17 20:05:06 +03:00
# Depending on where lvm fails the command, it may not have anything
# to report for "lastlog", so we need to check for a message in the
# report json too.
error_msg = self . get_last_log ( )
if error_msg is None :
error_msg = LVMShellProxy . get_error_msg ( report_json )
if error_msg is None :
error_msg = ' No error reason provided! (missing " log " section) '
2016-02-18 02:53:35 +03:00
if debug or rc != 0 :
log_error ( ( ' CMD: %s ' % cmd ) )
log_error ( ( " EC = %d " % rc ) )
2016-08-12 23:23:05 +03:00
log_error ( ( " ERROR_MSG= \n %s \n " % error_msg ) )
2016-02-18 02:53:35 +03:00
2022-08-18 01:21:19 +03:00
if run . value == 0 :
# Try to clean up lvm shelll process
log_debug ( " exiting lvm shell as we are shutting down " )
self . exit_shell ( )
raise Exception ( " Daemon is exiting! " )
2016-11-29 20:07:21 +03:00
return rc , report_json , error_msg
2016-02-18 02:53:35 +03:00
2016-08-25 02:29:35 +03:00
def exit_shell ( self ) :
try :
self . _write_cmd ( ' exit \n ' )
except Exception as e :
log_error ( str ( e ) )
2016-02-18 02:53:35 +03:00
def __del__ ( self ) :
2016-08-12 23:23:05 +03:00
try :
self . lvm_shell . terminate ( )
except :
pass
2016-02-18 02:53:35 +03:00
if __name__ == " __main__ " :
2022-05-25 23:58:15 +03:00
print ( " USING LVM BINARY: %s " % LVM_CMD )
2016-02-18 02:53:35 +03:00
try :
2022-05-25 23:58:15 +03:00
if len ( sys . argv ) > 1 and sys . argv [ 1 ] == " bisect " :
shell = LVMShellProxy ( )
shell . exit_shell ( )
else :
shell = LVMShellProxy ( )
in_line = " start "
try :
while in_line :
in_line = input ( " lvm> " )
if in_line :
start = time . time ( )
ret , out , err = shell . call_lvm ( in_line . split ( ) )
end = time . time ( )
print ( ( " RC: %d " % ret ) )
print ( ( " OUT: \n %s " % out ) )
print ( ( " ERR: \n %s " % err ) )
print ( " Command = %f seconds " % ( end - start ) )
except KeyboardInterrupt :
pass
except EOFError :
pass
2016-02-18 02:53:35 +03:00
except Exception :
traceback . print_exc ( file = sys . stdout )
2022-05-25 23:58:15 +03:00
sys . exit ( 1 )
sys . exit ( 0 )