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/>.
import xml . etree . ElementTree as Et
import sys
import inspect
2022-08-18 01:24:08 +03:00
import collections
2016-02-18 02:53:35 +03:00
import ctypes
2022-08-09 04:48:10 +03:00
import errno
import fcntl
2016-02-18 02:53:35 +03:00
import os
2022-08-09 04:48:10 +03:00
import stat
2016-02-18 03:27:57 +03:00
import string
2016-08-12 23:20:49 +03:00
import datetime
2022-08-31 23:04:59 +03:00
import tempfile
2016-02-18 02:53:35 +03:00
import dbus
2016-08-29 22:51:26 +03:00
from lvmdbusd import cfg
2016-11-16 19:46:09 +03:00
# noinspection PyUnresolvedReferences
2016-11-02 01:48:39 +03:00
from gi . repository import GLib
import threading
2017-09-20 23:46:16 +03:00
import traceback
2017-09-25 23:20:03 +03:00
import signal
2016-02-18 02:53:35 +03:00
STDOUT_TTY = os . isatty ( sys . stdout . fileno ( ) )
2019-10-01 23:17:30 +03:00
def _handle_execute ( rc , out , err , interface ) :
if rc == 0 :
cfg . load ( )
else :
# Need to work on error handling, need consistent
raise dbus . exceptions . DBusException (
interface , ' Exit code %s , stderr = %s ' % ( str ( rc ) , err ) )
2016-02-18 02:53:35 +03:00
def rtype ( dbus_type ) :
"""
Decorator making sure that the decorated function returns a value of
specified type .
: param dbus_type : The specific dbus type to return value as
"""
def decorator ( fn ) :
def decorated ( * args , * * kwargs ) :
return dbus_type ( fn ( * args , * * kwargs ) )
return decorated
return decorator
# Field is expected to be a number, handle the corner cases when parsing
@rtype ( dbus . UInt64 )
def n ( v ) :
if not v :
return 0
return int ( float ( v ) )
@rtype ( dbus . UInt32 )
def n32 ( v ) :
if not v :
return 0
return int ( float ( v ) )
2019-10-09 15:49:58 +03:00
@rtype ( dbus . Double )
def d ( v ) :
if not v :
return 0.0
return float ( v )
def _snake_to_pascal ( s ) :
return ' ' . join ( x . title ( ) for x in s . split ( ' _ ' ) )
2016-02-18 02:53:35 +03:00
# noinspection PyProtectedMember
2019-10-09 15:49:58 +03:00
def init_class_from_arguments (
obj_instance , begin_suffix = None , snake_to_pascal = False ) :
2016-02-18 02:53:35 +03:00
for k , v in list ( sys . _getframe ( 1 ) . f_locals . items ( ) ) :
if k != ' self ' :
nt = k
# If the current attribute has a value, but the incoming does
# not, don't overwrite it. Otherwise the default values on the
# property decorator don't work as expected.
cur = getattr ( obj_instance , nt , v )
# print 'Init class %s = %s' % (nt, str(v))
2019-10-09 15:49:58 +03:00
if not ( cur and len ( str ( cur ) ) and ( v is None or len ( str ( v ) ) ) == 0 ) \
and ( begin_suffix is None or nt . startswith ( begin_suffix ) ) :
if begin_suffix and nt . startswith ( begin_suffix ) :
name = nt [ len ( begin_suffix ) : ]
if snake_to_pascal :
name = _snake_to_pascal ( name )
setattr ( obj_instance , name , v )
else :
setattr ( obj_instance , nt , v )
2016-02-18 02:53:35 +03:00
def get_properties ( f ) :
"""
Walks through an object instance or it ' s parent class(es) and determines
which attributes are properties and if they were created to be used for
dbus .
: param f : Object to inspect
: return : A dictionary of tuples with each tuple being :
0 = An array of dicts with the keys being : p_t , p_name ,
p_access ( type , name , access )
1 = Hash of property names and current value
"""
interfaces = dict ( )
for c in inspect . getmro ( f . __class__ ) :
h = vars ( c )
for p , value in h . items ( ) :
if isinstance ( value , property ) :
# We found a property, see if it has a metadata type
key = attribute_type_name ( p )
if key in h :
interface = h [ key ] [ 1 ]
if interface not in interfaces :
interfaces [ interface ] = ( [ ] , { } )
access = ' '
if getattr ( f . __class__ , p ) . fget :
access + = ' read '
if getattr ( f . __class__ , p ) . fset :
access + = ' write '
interfaces [ interface ] [ 0 ] . append (
dict (
p_t = getattr ( f , key ) [ 0 ] ,
p_name = p ,
p_access = access ) )
interfaces [ interface ] [ 1 ] [ p ] = getattr ( f , p )
return interfaces
def get_object_property_diff ( o_prop , n_prop ) :
"""
Walk through each object properties and report what has changed and with
the new values
: param o_prop : Old keys / values
: param n_prop : New keys / values
: return : hash of properties that have changed and their new value
"""
rc = { }
for intf_k , intf_v in o_prop . items ( ) :
for k , v in list ( intf_v [ 1 ] . items ( ) ) :
# print('Comparing %s:%s to %s:%s' %
# (k, o_prop[intf_k][1][k], k, str(n_prop[intf_k][1][k])))
if o_prop [ intf_k ] [ 1 ] [ k ] != n_prop [ intf_k ] [ 1 ] [ k ] :
new_value = n_prop [ intf_k ] [ 1 ] [ k ]
if intf_k not in rc :
rc [ intf_k ] = dict ( )
rc [ intf_k ] [ k ] = new_value
return rc
def add_properties ( xml , interface , props ) :
"""
Given xml that describes the interface , add property values to the XML
for the specified interface .
: param xml : XML to edit
: param interface : Interface to add the properties too
: param props : Output from get_properties
: return : updated XML string
"""
if props :
2016-09-19 17:54:24 +03:00
root = Et . fromstring ( xml )
interface_element = None
2016-02-18 02:53:35 +03:00
2016-09-19 17:54:24 +03:00
# Check to see if interface is present
2016-02-18 02:53:35 +03:00
for c in root :
if c . attrib [ ' name ' ] == interface :
2016-09-19 17:54:24 +03:00
interface_element = c
break
# Interface is not present, lets create it so we have something to
# attach the properties too
if interface_element is None :
interface_element = Et . Element ( " interface " , name = interface )
root . append ( interface_element )
# Add the properties
for p in props :
temp = ' <property type= " %s " name= " %s " access= " %s " /> \n ' % \
( p [ ' p_t ' ] , p [ ' p_name ' ] , p [ ' p_access ' ] )
interface_element . append ( Et . fromstring ( temp ) )
2016-02-18 02:53:35 +03:00
return Et . tostring ( root , encoding = ' utf8 ' )
return xml
def attribute_type_name ( name ) :
"""
Given the property name , return string of the attribute type
: param name :
: return :
"""
return " _ %s _meta " % name
_type_map = dict (
s = dbus . String ,
o = dbus . ObjectPath ,
t = dbus . UInt64 ,
x = dbus . Int64 ,
u = dbus . UInt32 ,
i = dbus . Int32 ,
n = dbus . Int16 ,
q = dbus . UInt16 ,
d = dbus . Double ,
y = dbus . Byte ,
b = dbus . Boolean )
def _pass_through ( v ) :
"""
If we have something which is not a simple type we return the original
value un - wrapped .
: param v :
: return :
"""
return v
def _dbus_type ( t , value ) :
return _type_map . get ( t , _pass_through ) ( value )
def dbus_property ( interface_name , name , dbus_type , doc = None ) :
"""
Creates the get / set properties for the given name . It assumes that the
actual attribute is ' _ ' + name and the attribute metadata is stuffed in
_name_type .
There is probably a better way todo this .
: param interface_name : Dbus interface this property is associated with
: param name : Name of property
: param dbus_type : dbus string type eg . s , t , i , x
: param doc : Python __doc__ for the property
: return :
"""
attribute_name = ' _ ' + name
def getter ( self ) :
t = getattr ( self , attribute_name + ' _meta ' ) [ 0 ]
return _dbus_type ( t , getattr ( self . state , attribute_name [ 1 : ] ) )
prop = property ( getter , None , None , doc )
def decorator ( cls ) :
setattr ( cls , attribute_name + ' _meta ' , ( dbus_type , interface_name ) )
setattr ( cls , name , prop )
return cls
return decorator
def parse_tags ( tags ) :
if len ( tags ) :
if ' , ' in tags :
return tags . split ( ' , ' )
2016-08-25 02:31:15 +03:00
return dbus . Array ( sorted ( [ tags ] ) , signature = ' s ' )
2016-02-18 02:53:35 +03:00
return dbus . Array ( [ ] , signature = ' s ' )
2022-08-18 01:24:08 +03:00
class DebugMessages ( object ) :
def __init__ ( self , size = 5000 ) :
self . queue = collections . deque ( maxlen = size )
self . lock = threading . RLock ( )
def add ( self , message ) :
with self . lock :
self . queue . append ( message )
def dump ( self ) :
if cfg . args and not cfg . args . debug :
with self . lock :
if len ( self . queue ) :
log_error ( " LVM dbus debug messages START last ( %d max) messages " % self . queue . maxlen )
for m in self . queue :
print ( m )
log_error ( " LVM dbus debug messages END " )
self . queue . clear ( )
def _format_log_entry ( msg ) :
2016-02-18 02:53:35 +03:00
tid = ctypes . CDLL ( ' libc.so.6 ' ) . syscall ( 186 )
2022-09-01 05:25:36 +03:00
if not cfg . systemd and STDOUT_TTY :
2016-08-12 23:20:49 +03:00
msg = " %s : %d : %d - %s " % \
( datetime . datetime . now ( ) . strftime ( " % b %d % H: % M: % S. %f " ) ,
os . getpid ( ) , tid , msg )
else :
2022-09-01 05:25:36 +03:00
if cfg . systemd :
# Systemd already puts the daemon pid in the log, we'll just add the tid
msg = " [ %d ]: %s " % ( tid , msg )
else :
msg = " [ %d : %d ]: %s " % ( os . getpid ( ) , tid , msg )
2022-08-18 01:24:08 +03:00
return msg
def _common_log ( msg , * attributes ) :
cfg . stdout_lock . acquire ( )
msg = _format_log_entry ( msg )
2016-02-18 02:53:35 +03:00
if STDOUT_TTY and attributes :
print ( color ( msg , * attributes ) )
else :
print ( msg )
cfg . stdout_lock . release ( )
sys . stdout . flush ( )
# Serializes access to stdout to prevent interleaved output
# @param msg Message to output to stdout
# @return None
def log_debug ( msg , * attributes ) :
2016-08-29 22:52:41 +03:00
if cfg . args and cfg . args . debug :
2016-02-18 02:53:35 +03:00
_common_log ( msg , * attributes )
2022-08-18 01:24:08 +03:00
else :
if cfg . debug :
cfg . debug . add ( _format_log_entry ( msg ) )
2016-02-18 02:53:35 +03:00
def log_error ( msg , * attributes ) :
_common_log ( msg , * attributes )
2022-08-09 04:44:32 +03:00
def log_msg ( msg , * attributes ) :
_common_log ( msg , * attributes )
2017-09-25 23:20:03 +03:00
def dump_threads_stackframe ( ) :
ident_to_name = { }
for thread_object in threading . enumerate ( ) :
ident_to_name [ thread_object . ident ] = thread_object
stacks = [ ]
for thread_ident , frame in sys . _current_frames ( ) . items ( ) :
stack = traceback . format_list ( traceback . extract_stack ( frame ) )
# There is a possibility that a thread gets created after we have
# enumerated all threads, so this lookup table may be incomplete, so
# account for this
if thread_ident in ident_to_name :
thread_name = ident_to_name [ thread_ident ] . name
else :
thread_name = " unknown "
stacks . append ( " Thread: %s " % ( thread_name ) )
stacks . append ( " " . join ( stack ) )
log_error ( " Dumping thread stack frames! \n " + " \n " . join ( stacks ) )
2016-02-18 02:53:35 +03:00
# noinspection PyUnusedLocal
2017-09-25 23:20:03 +03:00
def handler ( signum ) :
try :
2022-08-10 01:44:10 +03:00
# signal 10
2017-09-25 23:20:03 +03:00
if signum == signal . SIGUSR1 :
2022-08-18 01:24:08 +03:00
cfg . debug . dump ( )
2017-09-25 23:20:03 +03:00
dump_threads_stackframe ( )
2022-08-10 01:44:10 +03:00
# signal 12
elif signum == signal . SIGUSR2 :
2022-08-18 01:24:08 +03:00
cfg . debug . dump ( )
2022-08-10 01:55:27 +03:00
cfg . flightrecorder . dump ( )
2017-09-25 23:20:03 +03:00
else :
2022-09-21 18:05:36 +03:00
# If we are getting a SIGTERM, and we sent one to the lvm shell we
# will ignore this and keep running.
if signum == signal . SIGTERM and cfg . ignore_sigterm :
# Clear the flag, so we will exit on SIGTERM if we didn't
# send it.
cfg . ignore_sigterm = False
return True
# If lvm shell is in use, tell it to exit
if cfg . SHELL_IN_USE is not None :
cfg . SHELL_IN_USE . exit_shell ( )
2017-09-25 23:20:03 +03:00
cfg . run . value = 0
2022-08-18 01:24:08 +03:00
log_error ( ' Exiting daemon with signal %d ' % signum )
2017-09-25 23:20:03 +03:00
if cfg . loop is not None :
cfg . loop . quit ( )
2022-08-31 19:18:55 +03:00
except BaseException as be :
st = extract_stack_trace ( be )
2017-09-25 23:20:03 +03:00
log_error ( " signal handler: exception (logged, not reported!) \n %s " % st )
# It's important we report that we handled the exception for the exception
# handler to continue to work, especially for signal 10 (SIGUSR1)
return True
2016-02-18 02:53:35 +03:00
def pv_obj_path_generate ( ) :
return cfg . PV_OBJ_PATH + " / %d " % next ( cfg . pv_id )
def vg_obj_path_generate ( ) :
return cfg . VG_OBJ_PATH + " / %d " % next ( cfg . vg_id )
def lv_object_path_method ( name , meta ) :
if name [ 0 ] == ' [ ' :
return _hidden_lv_obj_path_generate
elif meta [ 0 ] [ 0 ] == ' t ' :
return _thin_pool_obj_path_generate
2019-10-09 15:49:58 +03:00
elif meta [ 0 ] [ 0 ] == ' d ' :
return _vdo_pool_object_path_generate
2016-02-18 02:53:35 +03:00
elif meta [ 0 ] [ 0 ] == ' C ' and ' pool ' in meta [ 1 ] :
return _cache_pool_obj_path_generate
return _lv_obj_path_generate
# Note: None of the individual LV path generate functions should be called
# directly, they should only be dispatched through lv_object_path_method
def _lv_obj_path_generate ( ) :
return cfg . LV_OBJ_PATH + " / %d " % next ( cfg . lv_id )
def _thin_pool_obj_path_generate ( ) :
return cfg . THIN_POOL_PATH + " / %d " % next ( cfg . thin_id )
2019-10-09 15:49:58 +03:00
def _vdo_pool_object_path_generate ( ) :
return cfg . VDO_POOL_PATH + " / %d " % next ( cfg . vdo_id )
2016-02-18 02:53:35 +03:00
def _cache_pool_obj_path_generate ( ) :
return cfg . CACHE_POOL_PATH + " / %d " % next ( cfg . cache_pool_id )
def _hidden_lv_obj_path_generate ( ) :
return cfg . HIDDEN_LV_PATH + " / %d " % next ( cfg . hidden_lv )
def job_obj_path_generate ( ) :
return cfg . JOB_OBJ_PATH + " / %d " % next ( cfg . job_id )
def color ( text , * user_styles ) :
styles = {
# styles
' reset ' : ' \033 [0m ' ,
' bold ' : ' \033 [01m ' ,
' disabled ' : ' \033 [02m ' ,
' underline ' : ' \033 [04m ' ,
' reverse ' : ' \033 [07m ' ,
' strike_through ' : ' \033 [09m ' ,
' invisible ' : ' \033 [08m ' ,
# text colors
' fg_black ' : ' \033 [30m ' ,
' fg_red ' : ' \033 [31m ' ,
' fg_green ' : ' \033 [32m ' ,
' fg_orange ' : ' \033 [33m ' ,
' fg_blue ' : ' \033 [34m ' ,
' fg_purple ' : ' \033 [35m ' ,
' fg_cyan ' : ' \033 [36m ' ,
' fg_light_grey ' : ' \033 [37m ' ,
' fg_dark_grey ' : ' \033 [90m ' ,
' fg_light_red ' : ' \033 [91m ' ,
' fg_light_green ' : ' \033 [92m ' ,
' fg_yellow ' : ' \033 [93m ' ,
' fg_light_blue ' : ' \033 [94m ' ,
' fg_pink ' : ' \033 [95m ' ,
' fg_light_cyan ' : ' \033 [96m ' ,
# background colors
' bg_black ' : ' \033 [40m ' ,
' bg_red ' : ' \033 [41m ' ,
' bg_green ' : ' \033 [42m ' ,
' bg_orange ' : ' \033 [43m ' ,
' bg_blue ' : ' \033 [44m ' ,
' bg_purple ' : ' \033 [45m ' ,
' bg_cyan ' : ' \033 [46m ' ,
' bg_light_grey ' : ' \033 [47m '
}
color_text = ' '
for style in user_styles :
try :
color_text + = styles [ style ]
except KeyError :
return ' def color: parameter {} does not exist ' . format ( style )
color_text + = text
return ' \033 [0m {0} \033 [0m ' . format ( color_text )
def pv_range_append ( cmd , device , start , end ) :
if ( start , end ) == ( 0 , 0 ) :
cmd . append ( device )
else :
if start != 0 and end == 0 :
cmd . append ( " %s : %d - " % ( device , start ) )
else :
cmd . append (
" %s : %d - %d " %
( device , start , end ) )
def pv_dest_ranges ( cmd , pv_dest_range_list ) :
if len ( pv_dest_range_list ) :
for i in pv_dest_range_list :
pv_range_append ( cmd , * i )
def round_size ( size_bytes ) :
bs = 512
remainder = size_bytes % bs
if not remainder :
return size_bytes
return size_bytes + bs - remainder
2016-02-18 03:27:57 +03:00
2016-03-24 20:26:28 +03:00
_ALLOWABLE_CH = string . ascii_letters + string . digits + ' #+-.:=@_ \ / % '
2016-02-18 03:27:57 +03:00
_ALLOWABLE_CH_SET = set ( _ALLOWABLE_CH )
_ALLOWABLE_VG_LV_CH = string . ascii_letters + string . digits + ' .-_+ '
_ALLOWABLE_VG_LV_CH_SET = set ( _ALLOWABLE_VG_LV_CH )
_LV_NAME_RESERVED = ( " _cdata " , " _cmeta " , " _corig " , " _mimage " , " _mlog " ,
2019-10-03 20:19:07 +03:00
" _pmspare " , " _rimage " , " _rmeta " , " _tdata " , " _tmeta " , " _vorigin " , " _vdata " )
2016-02-18 03:27:57 +03:00
# Tags can have the characters, based on the code
# a-zA-Z0-9._-+/=!:&#
_ALLOWABLE_TAG_CH = string . ascii_letters + string . digits + " ._-+/=!:&# "
_ALLOWABLE_TAG_CH_SET = set ( _ALLOWABLE_TAG_CH )
def _allowable_tag ( tag_name ) :
# LVM should impose a length restriction
return set ( tag_name ) < = _ALLOWABLE_TAG_CH_SET
def _allowable_vg_name ( vg_name ) :
if vg_name is None :
raise ValueError ( " VG name is None or empty " )
vg_len = len ( vg_name )
if vg_len == 0 or vg_len > 127 :
raise ValueError ( " VG name ( %s ) length ( %d ) not in the domain 1..127 " %
( vg_name , vg_len ) )
if not set ( vg_name ) < = _ALLOWABLE_VG_LV_CH_SET :
raise ValueError ( " VG name ( %s ) contains invalid character, "
" allowable set( %s ) " % ( vg_name , _ALLOWABLE_VG_LV_CH ) )
if vg_name == " . " or vg_name == " .. " :
raise ValueError ( ' VG name ( %s ) cannot be " . " or " .. " ' % ( vg_name ) )
def _allowable_lv_name ( vg_name , lv_name ) :
if lv_name is None :
raise ValueError ( " LV name is None or empty " )
lv_len = len ( lv_name )
# This length is derived from empirical testing
if lv_len == 0 or ( len ( vg_name ) + lv_len ) > 125 :
raise ValueError ( " LV name ( %s ) length ( %d ) + VG name length "
" not in the domain 1..125 " % ( lv_name , lv_len ) )
if not set ( lv_name ) < = _ALLOWABLE_VG_LV_CH_SET :
raise ValueError ( " LV name ( %s ) contains invalid character, "
" allowable ( %s ) " % ( lv_name , _ALLOWABLE_VG_LV_CH ) )
if any ( x in lv_name for x in _LV_NAME_RESERVED ) :
raise ValueError ( " LV name ( %s ) contains a reserved word, "
" reserved set( %s ) " % ( lv_name , str ( _LV_NAME_RESERVED ) ) )
if lv_name . startswith ( " snapshot " ) or lv_name . startswith ( " pvmove " ) :
raise ValueError ( " LV name ( %s ) starts with a reserved word, "
" reserved set( %s ) " % ( lv_name , str ( [ " snapshot " , " pvmove " ] ) ) )
if lv_name [ 0 ] == ' - ' :
raise ValueError ( " LV name ( %s ) cannot start with a ' - ' "
" character " % lv_name )
def validate_device_path ( interface , device ) :
if not set ( device ) < = _ALLOWABLE_CH_SET :
raise dbus . exceptions . DBusException (
interface , ' Device path ( %s ) has invalid characters, '
' allowable ( %s ) ' % ( device , _ALLOWABLE_CH ) )
def validate_vg_name ( interface , vg_name ) :
try :
_allowable_vg_name ( vg_name )
except ValueError as ve :
raise dbus . exceptions . DBusException (
interface , str ( ve ) )
def validate_lv_name ( interface , vg_name , lv_name ) :
try :
_allowable_lv_name ( vg_name , lv_name )
except ValueError as ve :
raise dbus . exceptions . DBusException (
interface , str ( ve ) )
def validate_tag ( interface , tag ) :
if not _allowable_tag ( tag ) :
raise dbus . exceptions . DBusException (
interface , ' tag ( %s ) contains invalid character, allowable set( %s ) '
% ( tag , _ALLOWABLE_TAG_CH ) )
2016-11-02 01:48:39 +03:00
2022-08-31 23:04:59 +03:00
def add_config_option ( cmdline , key , value ) :
if ' help ' in cmdline :
return cmdline
if key in cmdline :
for i , arg in enumerate ( cmdline ) :
if arg == key :
if len ( cmdline ) < = i + 1 :
raise dbus . exceptions . DBusException ( " Missing value for --config option. " )
cmdline [ i + 1 ] + = " %s " % value
break
else :
cmdline . extend ( [ key , value ] )
return cmdline
2017-03-09 00:35:53 +03:00
def add_no_notify ( cmdline ) :
"""
Given a command line to execute we will see if ` - - config ` is present , if it
is we will add the global / notify_dbus = 0 to it , otherwise we will append it
2017-03-10 01:04:24 +03:00
to the end of the list .
: param : cmdline : The command line to inspect
: type : cmdline : list
2017-03-09 00:35:53 +03:00
: return : cmdline with notify_dbus config option present
2017-03-10 01:04:24 +03:00
: rtype : list
2017-03-09 00:35:53 +03:00
"""
2022-08-17 20:06:17 +03:00
# Only after we have seen an external event will we disable lvm from sending
2017-03-15 18:57:01 +03:00
# us one when we call lvm
2022-08-31 23:04:59 +03:00
rv = cmdline
2017-03-20 17:43:34 +03:00
if cfg . got_external_event :
2022-08-31 23:04:59 +03:00
rv = add_config_option ( rv , " --config " , " global/notify_dbus=0 " )
return rv
2017-03-09 00:35:53 +03:00
2016-11-02 01:48:39 +03:00
# The methods below which start with mt_* are used to execute the desired code
2022-08-17 20:06:17 +03:00
# on the main thread of execution to alleviate any issues the dbus-python
# library with regard to multithreaded access. Essentially, we are trying to
2016-11-02 01:48:39 +03:00
# ensure all dbus library interaction is done from the same thread!
2017-09-22 17:59:50 +03:00
def _async_handler ( call_back , parameters ) :
2017-09-25 23:20:03 +03:00
params_str = " , " . join ( str ( x ) for x in parameters )
2017-09-22 17:59:50 +03:00
log_debug ( ' Main thread execution, callback = %s , parameters = ( %s ) ' %
( str ( call_back ) , params_str ) )
2016-11-02 01:48:39 +03:00
2017-09-22 17:59:50 +03:00
try :
if parameters :
call_back ( * parameters )
else :
call_back ( )
2022-08-31 19:18:55 +03:00
except BaseException as be :
log_error ( " mt_async_call: exception (logged, not reported!) \n %s " % extract_stack_trace ( be ) )
2016-11-16 19:46:09 +03:00
2016-11-02 01:48:39 +03:00
2017-09-22 17:59:50 +03:00
# Execute the function on the main thread with the provided parameters, do
# not return *any* value or wait for the execution to complete!
def mt_async_call ( function_call_back , * parameters ) :
GLib . idle_add ( _async_handler , function_call_back , parameters )
2016-11-02 01:48:39 +03:00
2016-11-30 22:39:48 +03:00
2016-11-02 01:48:39 +03:00
# Run the supplied function and arguments on the main thread and wait for them
# to complete while allowing the ability to get the return value too.
#
# Example:
# result = MThreadRunner(foo, arg1, arg2).done()
#
class MThreadRunner ( object ) :
@staticmethod
def runner ( obj ) :
2016-11-30 21:59:46 +03:00
# noinspection PyProtectedMember
2016-11-02 01:48:39 +03:00
obj . _run ( )
with obj . cond :
obj . function_complete = True
obj . cond . notify_all ( )
def __init__ ( self , function , * args ) :
self . f = function
self . rc = None
2017-09-20 23:46:16 +03:00
self . exception = None
2016-11-02 01:48:39 +03:00
self . args = args
self . function_complete = False
self . cond = threading . Condition ( threading . Lock ( ) )
def done ( self ) :
GLib . idle_add ( MThreadRunner . runner , self )
with self . cond :
if not self . function_complete :
self . cond . wait ( )
2017-09-20 23:46:16 +03:00
if self . exception :
raise self . exception
2016-11-02 01:48:39 +03:00
return self . rc
def _run ( self ) :
2017-09-20 23:46:16 +03:00
try :
2017-09-21 17:22:07 +03:00
if self . args :
2017-09-20 23:46:16 +03:00
self . rc = self . f ( * self . args )
else :
self . rc = self . f ( )
except BaseException as be :
self . exception = be
2016-11-02 01:48:39 +03:00
def _remove_objects ( dbus_objects_rm ) :
for o in dbus_objects_rm :
cfg . om . remove_object ( o , emit_signal = True )
# Remove dbus objects from main thread
def mt_remove_dbus_objects ( objs ) :
MThreadRunner ( _remove_objects , objs ) . done ( )
2021-06-11 18:35:31 +03:00
# Make stream non-blocking
def make_non_block ( stream ) :
2022-08-09 04:48:10 +03:00
flags = fcntl . fcntl ( stream , fcntl . F_GETFL )
fcntl . fcntl ( stream , fcntl . F_SETFL , flags | os . O_NONBLOCK )
2021-06-11 18:35:31 +03:00
def read_decoded ( stream ) :
tmp = stream . read ( )
if tmp :
return tmp . decode ( " utf-8 " )
return ' '
2022-08-09 04:48:10 +03:00
class LockFile ( object ) :
"""
Simple lock file class
Based on Pg .1144 " The Linux Programming Interface " by Michael Kerrisk
"""
def __init__ ( self , lock_file ) :
self . fd = 0
self . lock_file = lock_file
def __enter__ ( self ) :
try :
self . fd = os . open ( self . lock_file , os . O_CREAT | os . O_RDWR , stat . S_IRUSR | stat . S_IWUSR )
# Get and set the close on exec and lock the file
flags = fcntl . fcntl ( self . fd , fcntl . F_GETFD )
flags | = fcntl . FD_CLOEXEC
fcntl . fcntl ( self . fd , fcntl . F_SETFL , flags )
fcntl . lockf ( self . fd , fcntl . LOCK_EX | fcntl . LOCK_NB )
except OSError as e :
if e . errno == errno . EAGAIN :
log_error ( " Daemon already running, exiting! " )
else :
log_error ( " Error during creation of lock file( %s ): errno( %d ), exiting! " % ( self . lock_file , e . errno ) )
sys . exit ( 114 )
def __exit__ ( self , _type , _value , _traceback ) :
os . close ( self . fd )
2022-08-31 19:18:55 +03:00
def extract_stack_trace ( exception ) :
return ' ' . join ( traceback . format_exception ( None , exception , exception . __traceback__ ) )
2022-09-08 23:38:31 +03:00
def lvm_column_key ( key ) :
# Check LV
if key . startswith ( " lv_ " ) or key . startswith ( " vg_ " ) or key . startswith ( " pool_ " ) or \
key . endswith ( " _percent " ) or key . startswith ( " move_ " ) or key . startswith ( " vdo_ " ) or \
key in [ " origin_uuid " , " segtype " , " origin " , " data_lv " , " metadata_lv " ] :
return True
# Check VG
if key . startswith ( " vg_ " ) or key . startswith ( " lv_ " ) or key . startswith ( " pv_ " ) or \
key in [ " max_lv " , " max_pv " , " snap_count " ] :
return True
# Check PV
if key . startswith ( " pv " ) or key . startswith ( " vg " ) or ( key in [ ' dev_size ' , ' pe_start ' ] ) :
return True
return False
2022-08-31 19:20:49 +03:00
class LvmBug ( RuntimeError ) :
"""
Things that are clearly a bug with lvm itself .
"""
def __init__ ( self , msg ) :
super ( ) . __init__ ( msg )
def __str__ ( self ) :
return " lvm bug encountered: %s " % ' ' . join ( self . args )
2022-08-31 23:04:59 +03:00
class LvmDebugData :
def __init__ ( self ) :
self . fd = - 1
self . fn = None
def _remove_file ( self ) :
if self . fn is not None :
os . unlink ( self . fn )
self . fn = None
def _close_fd ( self ) :
if self . fd != - 1 :
os . close ( self . fd )
self . fd = - 1
def setup ( self ) :
# Create a secure filename
self . fd , self . fn = tempfile . mkstemp ( suffix = " .log " , prefix = " lvmdbusd.lvm.debug. " )
return self . fn
def lvm_complete ( self ) :
# Remove the file ASAP, so we decrease our odds of leaving it
# around if the daemon gets killed by a signal -9
self . _remove_file ( )
def dump ( self ) :
# Read the file and log it to log_err
if self . fd != - 1 :
# How big could the verbose debug get?
debug = os . read ( self . fd , 1024 * 1024 * 5 )
debug_txt = debug . decode ( " utf-8 " )
for line in debug_txt . split ( " \n " ) :
log_error ( " lvm debug >>> %s " % line )
self . _close_fd ( )
# In case lvm_complete doesn't get called.
self . _remove_file ( )
def complete ( self ) :
self . _close_fd ( )
# In case lvm_complete doesn't get called.
self . _remove_file ( )
2022-11-29 19:00:39 +03:00
def get_error_msg ( report_json ) :
# Get the error message from the returned JSON
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 ' ]
return error_msg
return None