diff --git a/client/lib/engine/bin/FileSystemLib.py b/client/lib/engine/bin/FileSystemLib.py index d8f5d1a..384c969 100755 --- a/client/lib/engine/bin/FileSystemLib.py +++ b/client/lib/engine/bin/FileSystemLib.py @@ -425,7 +425,7 @@ def ogGetMountPoint(disk, par): #@return Código de salida: True - formateado, False - sin formato o error. #*/ ## def ogIsFormated(disk, part): - PART = ogDiskToDev(disk, part) + PART = DiskLib.ogDiskToDev (disk, part) if not PART: return diff --git a/client/lib/engine/bin/ImageLib.py b/client/lib/engine/bin/ImageLib.py index 95be66e..aa27350 100644 --- a/client/lib/engine/bin/ImageLib.py +++ b/client/lib/engine/bin/ImageLib.py @@ -100,7 +100,7 @@ def ogCreateImageSyntax (dev, imgfile, tool='partclone', level='gzip'): 'bzip': ' | bzip -c > ', }.get (level, ' > ') - print (f'param1 ({param1}) param2 ({param2}) param3 ({param3}) imgfile ({imgfile})') + #print (f'param1 ({param1}) param2 ({param2}) param3 ({param3}) imgfile ({imgfile})') if param1: return f'{param1} {param2} {param3} {imgfile}' @@ -148,14 +148,14 @@ def ogRestoreImageSyntax (imgfile, part, tool=None, level=None): 3: ' bzip -dc ', 'bzip': ' bzip -dc ', }.get (level, '') - print (f'tool ({tool}) level ({level}) compressor ({compressor})') + #print (f'tool ({tool}) level ({level}) compressor ({compressor})') if compressor == '': SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f'Compressor no valid {level}') ## original bash code is broken: 'return' is never called #return mbuffer = '| mbuffer -q -m 40M ' if shutil.which ('mbuffer') else ' ' - print (f'mbuffer ({mbuffer})') + #print (f'mbuffer ({mbuffer})') if 'ntfsclone' == tool: tool = f'| ntfsclone --restore-image --overwrite {part} -' elif 'partimage' == tool: @@ -169,7 +169,7 @@ def ogRestoreImageSyntax (imgfile, part, tool=None, level=None): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f'Tools imaging no valid {tool}') ## original bash code is broken: 'return' is never called #return - print (f'tool ({tool})') + #print (f'tool ({tool})') return f'{compressor} {imgfile} {mbuffer} {tool}'.strip() @@ -202,7 +202,7 @@ def ogGetImageInfo (imgfile): ## original bash idiom is: $($COMPRESSOR -dc $IMGFILE 2>/dev/null | head -n 40 > $FILEHEAD) || ogRaiseError ## the purpose of which I can't fully comprehend - print (f'shelling out "{compressor} -dc {imgfile} |head -n 40 > {filehead}"') + #print (f'shelling out "{compressor} -dc {imgfile} |head -n 40 > {filehead}"') if subprocess.run (f'{compressor} -dc {imgfile} |head -n 40 > {filehead}', shell=True).returncode: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_IMAGE, f'Image format is not valid {imgfile}') return @@ -256,7 +256,7 @@ def ogGetImageInfo (imgfile): if False == imgdetect and not os.path.exists ('/dev/loop2'): filehead_contents = Path (filehead).read_bytes() if b'ntfsclone-image' in filehead_contents: - print (f'shelling out "cat {filenead} | ntfsclone --restore --overwrite /dev/loop2 - 2>&1"') + #print (f'shelling out "cat {filenead} | ntfsclone --restore --overwrite /dev/loop2 - 2>&1"') ntfscloneinfo = subprocess.run (f'cat {filenead} | ntfsclone --restore --overwrite /dev/loop2 - 2>&1', shell=True, capture_output=True, text=True).stdout #ntfscloneinfo = subprocess.run (['cat', '/tmp/foo-ntfsclone'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True).stdout ## sacado de claude 3 haiku diff --git a/client/lib/engine/bin/NetLib.py b/client/lib/engine/bin/NetLib.py index 831a3cb..197eaaf 100755 --- a/client/lib/engine/bin/NetLib.py +++ b/client/lib/engine/bin/NetLib.py @@ -247,8 +247,8 @@ def ogGetRepoIp(): return None if 'filesystems' not in j: return None - source = j['filesystems']['source'] - fstype = j['filesystems']['fstype'] + source = j['filesystems'][0]['source'] + fstype = j['filesystems'][0]['fstype'] if 'nfs' == fstype: return source.split(":")[0] elif 'cifs' == fstype: return source.split("/")[2] diff --git a/client/lib/engine/bin/ProtocolLib.py b/client/lib/engine/bin/ProtocolLib.py index f4f21c9..4ddc703 100644 --- a/client/lib/engine/bin/ProtocolLib.py +++ b/client/lib/engine/bin/ProtocolLib.py @@ -1,8 +1,15 @@ #!/usr/bin/python3 +import subprocess +import re +import json + import ogGlobals import SystemLib import ImageLib +import FileSystemLib +import StringLib +import NetLib #/** #@file ProtocolLib.py @@ -42,11 +49,11 @@ def ogUcastSyntax (op, sess, file=None, device=None, tool=None, level=None): raise TypeError ('missing required argument: "device"') if tool is None: raise TypeError ('missing required argument: "tool"') - if tool not in ['partclone', 'PARTCLONE', 'partimage', 'PARTIMAGE', 'ntfsclone', 'NTFSCLONE']: + if tool.lower() not in ['partclone', 'partimage', 'ntfsclone']: raise TypeError (f'argument "tool" has unsupported value "{tool}"') if level is None: raise TypeError ('missing required argument: "level"') - if level not in ['lzop', 'gzip', 'LZOP', 'GZIP', '0', '1']: + if level.lower() not in ['lzop', 'gzip', '0', '1']: raise TypeError (f'argument "level" has unsupported value "{level}"') elif 'SENDFILE' == op or 'RECEIVERFILE' == op: if file is None: @@ -70,7 +77,7 @@ def ogUcastSyntax (op, sess, file=None, device=None, tool=None, level=None): address += f'-O {session[i]}:{portbase}' else: address = f'{session[1]}:{portbase}' - print (f'nati mode ({mode}) address ({address})') + #print (f'nati mode ({mode}) address ({address})') if 'SENDPARTITION' == op: syn = ImageLib.ogCreateImageSyntax (device, ' ', tool, level) @@ -78,7 +85,7 @@ def ogUcastSyntax (op, sess, file=None, device=None, tool=None, level=None): ## otherwise, param2 in ImageLib.ogCreateImageSyntax() is not '| mbuffer' but empty ## and then parts[2] is out of range parts = syn.split ('|') - print (f'syn ({syn}) parts ({parts})') + #print (f'syn ({syn}) parts ({parts})') prog1 = f'{parts[0]}|{parts[2]}'.strip() prog1 = prog1.replace ('>', '').strip() return f'{prog1} | mbuffer {address}' @@ -140,6 +147,28 @@ def ogUcastSyntax (op, sess, file=None, device=None, tool=None, level=None): #*/ ## # +def _clientip(): + ipas = subprocess.run (['ip', '-json', 'address', 'show', 'up'], capture_output=True, text=True).stdout + ipasj = json.loads (ipas) + addresses = [] + for e in ipasj: + if 'lo' == e['ifname']: continue + if 'vboxnet' in e['ifname']: continue + if 'br-' in e['ifname']: continue + if 'tun' in e['ifname']: continue + if 'addr_info' not in e: continue + addrs = e['addr_info'] + for a in addrs: + if 'inet' != a['family']: continue + addresses.append ({ 'local': a['local'], 'prefixlen': a['prefixlen'] }) + return addresses + +def _binary_ip (ip): + for l in subprocess.run (['ipcalc', '--nocolor', ip ], capture_output=True, text=True).stdout.splitlines(): + if 'Address' not in l: continue + match = re.search (r'^(Address:)\s+(\S+)\s+(.*$)', l).group(3).replace (' ', '').replace ('.', '') + break + return match #/** # ogMcastSyntax @@ -158,6 +187,168 @@ def ogUcastSyntax (op, sess, file=None, device=None, tool=None, level=None): #*/ ## # +#ogMcastSyntax SENDPARTITION 9000:full-duplex|half-duplex|broadcast:239.194.17.36:80M:50:60 device tools level +#ogMcastSyntax RECEIVERPARTITION 9000 device tools level +#ogMcastSyntax RECEIVERPARTITION 9000:172.17.88.161:40:120 device tools level + +#ogMcastSyntax SENDFILE 9000:full-duplex|half-duplex|broadcast:239.194.17.36:80M:50:60 file +#ogMcastSyntax RECEIVERFILE 9000 file +#ogMcastSyntax RECEIVERFILE 9000:172.17.88.161:40:120 file +def ogMcastSyntax (op, sess, file=None, device=None, tool=None, level=None): + if 'SENDPARTITION' == op or 'RECEIVERPARTITION' == op: + if device is None: + raise TypeError ('missing required argument: "device"') + if tool is None: + raise TypeError ('missing required argument: "tool"') + if tool.lower() not in ['partclone', 'partimage', 'ntfsclone']: + raise TypeError (f'argument "tool" has unsupported value "{tool}"') + if level is None: + raise TypeError ('missing required argument: "level"') + if level.lower() not in ['lzop', 'gzip', '0', '1']: + raise TypeError (f'argument "level" has unsupported value "{level}"') + elif 'SENDFILE' == op or 'RECEIVERFILE' == op: + if file is None: + raise TypeError ('missing required argument: "file"') + else: + raise TypeError ('first parameter should match (SEND|RECEIVER)(PARTITION|FILE), eg. "SENDFILE"') + + if 'SEND' in op: mode = 'server' + else: mode = 'client' + + try: + isudpcast = subprocess.run (['udp-receiver', '--help'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True).stdout + except subprocess.CalledProcessError: + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTEXEC, 'upd-cast no existe') + return + + session = sess.split (':') + + PERROR = 0 + if 'server' == mode: + if 6 != len (session): + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, 'parametros session de servidor multicast no completa') + PERROR = 2 + elif 'client' == mode: + if 4 < len (session): + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, 'parametros session de cliente multicast no completa') + PERROR = 2 + + mbuffer = " --pipe 'mbuffer -q -m 20M' " + portbase = int (session[0]) + if portbase not in range (9000, 9100, 2): + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'McastSession portbase {portbase}') + PERROR = 3 + + if 'server' == mode: + method, address, bitrate, nclients, maxtime = session[1:] + + if method.lower() not in ['full-duplex', 'half-duplex', 'broadcast']: + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'McastSession method {method}') + PERROR = 4 + + if not StringLib.ogCheckIpAddress (address): + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'McastSession address {address}') + PERROR = 5 + + ## the original regex has a backslash: ^[0-9]{1,3}\M$ + ## not sure why + if not re.search (r'^[0-9]{1,3}M$', bitrate): + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'McastSession bitrate {bitrate}') + PERROR = 6 + + if not re.search (r'^[0-9]{1,10}$', nclients): + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'McastSession nclients {nclients}') + PERROR = 7 + + if not re.search (r'^[0-9]{1,10}$', maxtime): + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'McastSession maxtime {maxtime}') + PERROR = 8 + + cerror = '8x8/128' + syntaxserver = f'udp-sender {mbuffer} --nokbd --portbase {portbase} --{method} --mcast-data-address {address} --fec {cerror} --max-bitrate {bitrate} --ttl 16 --min-clients {nclients} --max-wait {maxtime} --autostart {maxtime} --log /tmp/mcast.log' + + if PERROR: + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_MCASTSYNTAXT, f' {PERROR}') + return + + + if 'client' == mode: + other = session[1:] + #print (f'session ({session}) other ({other})') + serveraddress = other[0] if len (other) > 0 else '' + starttimeout = other[1] if len (other) > 1 else '' + receivertimeout = other[2] if len (other) > 2 else '' + + ## serveraddres + if StringLib.ogCheckIpAddress (serveraddress): + serveraddress = f' --mcast-rdv-address {serveraddress}' + else: + repoip = NetLib.ogGetRepoIp() + clientip = _clientip() + #print (f'repoip ({repoip}) clientip ({clientip})') + if 1 != len (clientip): + raise Exception ('more than one local IP address found') + c = clientip[0] + #print (f'c ({c})') + clientip = c['local'] + mascara = c['prefixlen'] + #print (f'clientip ({clientip}) mascara ({mascara})') + + ripbt = _binary_ip (repoip) + ipbt = _binary_ip (clientip) + reposubred = ripbt[0:mascara] + clientsubred = ipbt[0:mascara] + #print (f'ripbt ({ripbt})') + #print (f'ipbt ({ipbt})') + #print (f'reposubred ({reposubred})') + #print (f'clientsubred ({clientsubred})') + if reposubred == clientsubred: serveraddress = ' ' + else: serveraddress = f' --mcast-rdv-address {repoip}' + + ## starttimeout + if re.search (r'^[0-9]{1,10}$', starttimeout): + if 0 == starttimeout: starttimeout = ' ' + else: starttimeout = f' --start-timeout {starttimeout}' + else: + starttimeout = f' --start-timeout {ogGlobals.MCASTERRORSESSION}' + if 'start-timeout' not in isudpcast: starttimeout = ' ' + + ## receivertimeout + if re.search (r'^[0-9]{1,10}$', receivertimeout): + if 0 == receivertimeout: receivertimeout = ' ' + else: receivertimeout = f' --receive-timeout {receivertimeout}' + else: + receivertimeout = f' --receive-timeout {ogGlobals.MCASTWAIT}' + if 'receive-timeout' not in isudpcast: receivertimeout = ' ' + + syntaxclient = f'udp-receiver {mbuffer} --portbase {portbase} {serveraddress} {starttimeout} {receivertimeout} --log /tmp/mcast.log' + + if 'SENDPARTITION' == op: + syn = ImageLib.ogCreateImageSyntax (device, ' ', tool, level) + ## REQUIRES package mbuffer to be installed!! + ## otherwise, param2 in ImageLib.ogCreateImageSyntax() is not '| mbuffer' but empty + ## and then parts[2] is out of range + parts = syn.split ('|') + #print (f'syn ({syn}) parts ({parts})') + prog1 = f'{parts[0]}|{parts[2]}'.strip() + prog1 = prog1.replace ('>', '').strip() + return f'{prog1} | {syntaxserver}' + + elif 'RECEIVERPARTITION' == op: + syn = ImageLib.ogRestoreImageSyntax (' ', device, tool, level) + parts = syn.split ('|') + compressor = parts[0].strip() + tools = parts[-1].strip() + return f'{syntaxclient} | {compressor} | {tools} ' + + elif 'SENDFILE' == op: + return f'{syntaxserver} --file {file}' + + elif 'RECEIVERFILE' == op: + return f'{syntaxclient} --file {file}' + + else: + raise Exception (f'unknown op ({op})--this should not happen') diff --git a/client/lib/engine/bin/StringLib.py b/client/lib/engine/bin/StringLib.py index 2c9e150..49505d2 100755 --- a/client/lib/engine/bin/StringLib.py +++ b/client/lib/engine/bin/StringLib.py @@ -26,6 +26,18 @@ def ogCheckStringInReg(element, regex): return re.match(regex, element) is not None + + +#/** +# ogCheckIpAddress +#@brief Función para determinar si una cadena es una dirección ipv4 válida +#@param 1 string de la ip a comprobar +#@return 0 si es una dirección válida +#@return 1 si NO es una dirección válida +#@exception OG_ERR_FORMAT formato incorrecto. +#@note +#@todo +#*/ ## def ogCheckIpAddress(ip): """ Función para determinar si una cadena es una dirección ipv4 válida. diff --git a/client/lib/engine/bin/ogGlobals.py b/client/lib/engine/bin/ogGlobals.py index bf9c1b9..63713c9 100755 --- a/client/lib/engine/bin/ogGlobals.py +++ b/client/lib/engine/bin/ogGlobals.py @@ -30,7 +30,23 @@ TZ='Europe/Madrid' ## engine.cfg OGLOGSESSION='/tmp/session.log' OGLOGCOMMAND='/tmp/command.log' -NODEBUGFUNCTIONS='ogCreateImageSyntax ogGetHivePath ogGetOsType ogRestoreImageSyntax ogUnmountAll ogUnmountCache' +#OGWINCHKDISK=True #Hacer chkdisk tras la clonacion +#ACTIONCACHEFULL=None #Que hacer cuando la cache no tenga espacio libre. [ NONE | FORMAT ] ] +#RESTOREPROTOCOLNOTCACHE=None #Que protocolo de restauracion usar en el caso de que no exista cache o no exista espacio sufiente. [NONE | UNICAST | MULTICAST].NONE retorna error +#IMGPROG='partclone' +#IMGCOMP='lzop' +#IMGEXT='img' +#IMGREDUCE=True +#OGWINREDUCE=True #Al enviar particion reducir el sistema de archivos previamente. +MCASTERRORSESSION=120 #timeout (segundos) para abortar la sesion de multicast si no contacta con el servidor de multicast. Valor asignado a 0, utiliza los valores por defecto de udp-cast +MCASTWAIT=30 # timeout (segundos) para abortar la la transferencia si se interrumpe. Valor asignado a 0, utiliza los valores por defecto de udp-cast +#CREATESPEED=100000*4 # Factor para calcular el time-out al crear la imagen. 100000k -> 4s +#FACTORSYNC=120 # Factor de compresion para las imagenes (windos en ext4). +#BACKUP=False # Realizar copia de seguridad antes de crear la imagen. +#IMGFS='EXT4' # Sistema de archivo de la imagenes sincronizadas. EXT4 o BTRFS +#OGSLEEP=20 # Tiempo de sleep antes de realizar el reboot +NODEBUGFUNCTIONS='ogCreateImageSyntax ogGetHivePath ogGetOsType ogRestoreImageSyntax ogUnmountAll ogUnmountCache' # Funciones que no deben mostrar salida de avisos si son llamadas por otras funciones. +#DEFAULTSPEED='' ## /engine.cfg