#!/usr/bin/python3 import shutil import subprocess import os import os.path import re from pathlib import Path import DiskLib import FileSystemLib import SystemLib import ogGlobals import FileLib import CacheLib import NetLib ## ProtocolLib.ogUcastSyntax() calls ogCreateImageSyntax() ## in ogCreateImageSyntax(), param2 may contain a pipe or may be empty ## if param2 is empty, then ogUcastSyntax(): ## - does a .split() ## - accesses the third element of the resulting array (ie. does "parts[2]") ## - promptly gets an IndexError exception ## ## param2 in ogCreateImageSyntax() only contains a pipe if 'mbuffer' is installed ## therefore, a hard dependency on mbuffer is created ## ## raise an Exception at import time if mbuffer is not present if not shutil.which ('mbuffer'): raise FileNotFoundError ('"mbuffer" utility must be present') #/** #@file ImageLib.py #@brief Librería o clase Image #@class Image #@brief Funciones para creación, restauración y clonación de imágenes de sistemas. #@warning License: GNU GPLv3+ #*/ #/** # ogCreateImageSyntax path_device path_filename [str_tool] [str_compressionlevel] #@brief Genera una cadena de texto con la instrucción para crear un fichero imagen #@param path_device dispositivo Linux del sistema de archivos #@param path_fileneme path absoluto del fichero imagen #@param [opcional] str_tool herrmaienta de clonacion [partimage, partclone, ntfsclone] #@param [opcional] str_compressionlevel nivel de compresion. [0 -none-, 1-lzop-, 2-gzip] #@return str_command - cadena con el comando que se debe ejecutar. #@warning Salida nula si se producen errores. #@TODO introducir las herramientas fsarchiver, dd #*/ ## #ogCreateImageSyntax /dev/sda1 /opt/opengnsys/images/prueba.img partclone lzop #ogCreateImageSyntax /dev/sda1 /opt/opengnsys/images/prueba.img def ogCreateImageSyntax (dev, imgfile, tool='partclone', level='gzip'): if 'ntfsclone' == tool: param1 = f'ntfsclone --force --save-image -O - {dev}' elif 'partimage' == tool or 'default' == tool: param1 = f'partimage -M -f3 -o -d -B gui=no -c -z0 --volume=0 save {dev} stdout' elif 'partclone' == tool: disk, part = DiskLib.ogDevToDisk (dev).split() fs = FileSystemLib.ogGetFsType (disk, part) param1 = { 'EXT2': 'partclone.extfs', 'EXT3': 'partclone.extfs', 'EXT4': 'partclone.extfs', 'BTRFS': 'partclone.btrfs', 'REISERFS': 'partclone.reiserfs', 'REISER4': 'partclone.reiser4', 'JFS': 'partclone.jfs', 'XFS': 'partclone.xfs', 'F2FS': 'partclone.f2fs', 'NILFS2': 'partclone.nilfs2', 'NTFS': 'partclone.ntfs', 'EXFAT': 'partclone.exfat', 'FAT16': 'partclone.fat', 'FAT32': 'partclone.fat', 'HFS': 'partclone.hfsp', 'HFSPLUS': 'partclone.hfsp', 'UFS': 'partclone.ufs', 'VMFS': 'partclone.vmfs', }.get (fs, 'partclone.imager') dash_c = '-c' if not shutil.which (param1): param1 = 'partclone.dd' # Algunas versiones de partclone.dd no tienen opción "-c". out = subprocess.run (['partclone.dd', '--help'], capture_output=True, text=True).stdout if ' -c' not in out: dash_c = '' param1=f'{param1} -d0 -F {dash_c} -s {dev}' else: raise Exception (f'unknown tool "{tool}"') param2 = '| mbuffer -q -m 40M ' if shutil.which ('mbuffer') else ' ' param3 = { 0: ' > ', 'none': ' > ', 1: ' | lzop > ', 'lzop': ' | lzop > ', 2: ' | gzip -c > ', 'gzip': ' | gzip -c > ', 3: ' | bzip -c > ', 'bzip': ' | bzip -c > ', }.get (level, ' > ') #print (f'param1 ({param1}) param2 ({param2}) param3 ({param3}) imgfile ({imgfile})') if param1: return f'{param1} {param2} {param3} {imgfile}' #/** # ogRestoreImageSyntax path_filename path_device [str_tools] [str_compressionlevel] #@brief Genera una cadena de texto con la instrucción para crear un fichero imagen #@param path_device dispositivo Linux del sistema de archivos #@param path_fileneme path absoluto del fichero imagen #@param [opcional] str_tools herrmaienta de clonacion [partimage, partclone, ntfsclone] #@param [opcional] str_compressionlevel nivel de compresion. [0 -none-, 1-lzop-, 2-gzip] #@return cadena con el comando que se debe ejecutar. #@exception OG_ERR_FORMAT formato incorrecto. #@warning En pruebas iniciales #@TODO introducir las herramientas fsarchiver, dd #@TODO introducir el nivel de compresion gzip #*/ ## #ogRestoreImageSyntax /opt/opengnsys/images/prueba.img /dev/sda1 [partclone] [lzop] def ogRestoreImageSyntax (imgfile, part, tool=None, level=None): if not os.path.exists (imgfile): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, imgfile) ## original bash code is broken: 'return' is never called #return if tool is None or level is None: infoimg = ogGetImageInfo (imgfile) if not infoimg: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f'no image {imgfile}') ## original bash code is broken: 'return' is never called #return try: tool, level = infoimg.split (':')[0:2] except: tool, level = '', '' return ogRestoreImageSyntax (imgfile, part, tool, level) tool = tool.lower() level = level.lower() compressor = { 0: ' ', 'none': ' ', 1: ' lzop -dc ', 'lzop': ' lzop -dc ', 2: ' gzip -dc ', 'gzip': ' gzip -dc ', 3: ' bzip -dc ', 'bzip': ' bzip -dc ', }.get (level, '') #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})') if 'ntfsclone' == tool: tool = f'| ntfsclone --restore-image --overwrite {part} -' elif 'partimage' == tool: tool = f'| partimage -f3 -B gui=no restore {part} stdin' elif 'partclone' in tool: # -C para que no compruebe tamaños tool = f'| partclone.restore -d0 -C -I -o {part}' elif 'dd' == tool: tool = f'| pv | dd conv=sync,noerror bs=1M of={part}' else: 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})') return f'{compressor} {imgfile} {mbuffer} {tool}'.strip() #/** # ogCreateDiskImage int_ndisk str_repo path_image [str_tools] [str_compressionlevel] #@brief Crea una imagen (copia de seguridad) de un disco completo. #@param int_ndisk nº de orden del disco #@param str_repo repositorio de imágenes (remoto o caché local) #@param path_image camino de la imagen (sin extensión) #@return (nada, por determinar) #@note repo = { REPO, CACHE } #@note Esta primera versión crea imágenes con dd comprimidas con gzip. #@exception OG_ERR_FORMAT formato incorrecto. #@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. #@exception OG_ERR_LOCKED particion bloqueada por otra operación. #@exception OG_ERR_IMAGE error al crear la imagen del sistema. #@warning En pruebas iniciales #@todo Gestión de bloqueos de disco #@todo Comprobar si debe desmontarse la caché local #@todo Comprobar que no se crea la imagen en el propio disco #*/ ## #/** # ogCreateImage int_ndisk int_npartition str_repo path_image [str_tools] [str_compressionlevel] #@brief Crea una imagen a partir de una partición. #@param int_ndisk nº de orden del disco #@param int_npartition nº de orden de la partición #@param str_repo repositorio de imágenes (remoto o caché local) #@param path_image camino de la imagen (sin extensión) #@param [opcional] str_tools herrmaienta de clonacion [partimage, partclone, ntfsclone] #@param [opcional] str_compressionlevel nivel de compresion. [0 -none-, 1-lzop-, 2-gzip] #@return (nada, por determinar) #@note repo = { REPO, CACHE } #@exception OG_ERR_FORMAT formato incorrecto. #@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. #@exception OG_ERR_PARTITION partición no accesible o no soportada. #@exception OG_ERR_LOCKED particion bloqueada por otra operación. #@exception OG_ERR_IMAGE error al crear la imagen del sistema. #@todo Comprobaciones, control de errores, definir parámetros, etc. #*/ ## def ogCreateImage (disk, par, container, imgfile, tool='partclone', level='gzip'): PART = DiskLib.ogDiskToDev (disk, par) if not PART: return None if FileSystemLib.ogIsLocked (disk, par): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_LOCKED, f'{ogGlobals.lang.MSG_ERR_LOCKED} {disk}, {par}') return None imgtype = 'img' # Extensión genérica de imágenes. imgdir = FileLib.ogGetParentPath (src=container, file=imgfile) if not imgdir: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f'{container} {imgfile}') return None bn = os.path.basename (imgfile) IMGFILE = f'{imgdir}/{bn}.{imgtype}' if ogIsImageLocked (IMGFILE): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_LOCKED, f'{ogGlobals.lang.MSG_IMAGE} {container}, {imgfile}') return None # Generar la instruccion a ejecutar antes de aplicar los bloqueos. program = ogCreateImageSyntax (PART, IMGFILE, tool=tool, level=level) # Desmontar partición, bloquear partición e imagen. FileSystemLib.ogUnmount (disk, par) if not FileSystemLib.ogLock (disk, par): return None if not ogLockImage (container, f'{imgfile}.{imgtype}'): return None # Crear Imagen. #trap p = subprocess.run (program, shell=True, check=True) errcode = p.returncode if 0 == errcode: i = ogGetImageInfo (IMGFILE) h = NetLib.ogGetHostname() with open (f'{IMGFILE}.info', 'w') as fd: fd.write (f'{i}:{h}\n') else: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_IMAGE, f'{disk} {par} {IMGFILE}') if os.path.exists (IMGFILE): os.unlink (IMGFILE) FileSystemLib.ogUnlock (disk, par) ogUnlockImage (container, f'{imgfile}.{imgtype}') return not errcode ## reverse to indicate success #/** # ogCreateMbrImage int_ndisk str_repo path_image #@brief Crea una imagen a partir del sector de arranque de un disco. #@param int_ndisk nº de orden del disco #@param str_repo repositorio de imágenes (remoto o caché local) #@param path_image camino de la imagen (sin extensión) #@return (nada, por determinar) #@note repo = { REPO, CACHE } #@exception OG_ERR_FORMAT formato incorrecto. #@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. #@exception OG_ERR_IMAGE error al crear la imagen del sistema. #*/ ## #/** # ogCreateBootLoaderImage int_ndisk str_repo path_image #@brief Crea una imagen del boot loader a partir del sector de arranque de un disco. #@param int_ndisk nº de orden del disco #@param str_repo repositorio de imágenes (remoto o caché local) #@param path_image camino de la imagen (sin extensión) #@return (nada, por determinar) #@note repo = { REPO, CACHE } #@exception OG_ERR_FORMAT formato incorrecto. #@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. #@exception OG_ERR_IMAGE error al crear la imagen del sistema. #*/ ## #/** # ogGetSizeParameters int_num_disk int_num_part str_repo [monolit|sync|diff] #@brief Devuelve el tamaño de los datos de un sistema de ficheros, el espacio necesario para la imagen y si cabe en el repositorio elegido. #@param int_disk numero de disco #@param int_part numero de particion #@param str_repo repositorio de imágenes { REPO, CACHE } #@param str_imageName Nombre de la imagen #@param str_imageType Tipo de imagen: monolit (por defecto), sync o diff. (parametro opcional) #@return SIZEDATA SIZEREQUIRED SIZEFREE ISENOUGHSPACE #@note si str_imageType= diff necesario /tmp/ogimg.info, que es creado por ogCreateInfoImage. #@note para el tamaño de la imagen no sigue enlaces simbólicos. #@exception OG_ERR_FORMAT formato incorrecto. #*/ ## #SIZEDATA, SIZEREQUIRED, SIZEFREE, ISENOUGHSPACE = ogGetSizeParameters (1, 1, 'REPO', 'myimg') def ogGetSizeParameters (disk, par, repo, imgname, imgtype=None): repo = repo.upper() ## imgtype se soporta como parametro pero se ignora mntdir = FileSystemLib.ogMount (disk, par) if not mntdir: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f'{disk} {par}') return None #if [ "$IMGTYPE" == "_DIFF_" ]; then cosas #else: sizedata = None df_out = subprocess.run (['df', '-k'], capture_output=True, text=True).stdout for l in df_out.splitlines(): if (re.search (f'{mntdir}$', l)): sizedata = int (l.split()[2]) break if sizedata is None: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_GENERIC, 'sizedata is None') return None #if [ "$IMGTYPE" == "_SYNC_" -o "$IMGTYPE" == "_DIFF_" ]; then cosas #else: factorgzip=55/100 factorlzop=65/100 sizerequired = sizedata * factorlzop #Comprobar espacio libre en el contenedor. sizefree = None if 'CACHE' == repo: CACHEPART = CacheLib.ogFindCache() if not CACHEPART: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTCACHE, '') return None cache_disk, cache_par = CACHEPART.split() sizefree = FileSystemLib.ogGetFreeSize (cache_disk, cache_par) if 'REPO' == repo: df_out = subprocess.run (['df', '-k'], capture_output=True, text=True).stdout for l in df_out.splitlines(): if (re.search (f'{ogGlobals.OGIMG}', l)): sizefree = int (l.split()[3]) break if sizefree is None: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_GENERIC, 'sizefree is None') return None # Comprobamos si existe una imagen con el mismo nombre en $REPO # En sincronizadas restamos tamaño de la imagen y en monoloticas de la .ant #case "${IMGTYPE}" in blah blah #*) imgext = 'img.ant' imgdir = FileLib.ogGetParentPath (src=repo, file=f'/{imgname}') bn = os.path.basename (f'/{imgname}') imgfile = FileLib.ogGetPath (file=f'{imgdir}/{bn}.{imgext}') if not imgfile: imgsize = 0 else: ls_out = subprocess.run (['ls', '-s', imgfile], capture_output=True, text=True).stdout imgsize = int (ls_out.split()[0]) sizefree = sizefree + imgsize if sizerequired < sizefree: isenoughspace = True else: isenoughspace = False return sizedata, sizerequired, sizefree, isenoughspace #/** # ogIsImageLocked [str_repo] path_image #@brief Comprueba si una imagen está bloqueada para uso exclusivo. #@param str_repo repositorio de imágenes (opcional) #@param path_image camino de la imagen (sin extensión) #@return Código de salida: 0 - bloqueado, 1 - sin bloquear o error. #@note repo = { REPO, CACHE } #@exception OG_ERR_FORMAT formato incorrecto. #*/ ## #ogIsImageLocked ('/opt/opengnsys/images/aula1/win7.img') #ogIsImageLocked ('REPO', '/aula1/win7.img') def ogIsImageLocked (container=None, imgfile=None): if container and imgfile: p = FileLib.ogGetPath (src=container, file=f'{imgfile}.lock') elif imgfile: p = FileLib.ogGetPath (file=f'{imgfile}.lock') else: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'{container} {imgfile}') return return os.path.exists (p) #/** # ogLockImage [str_repo] path_image #@brief Bloquea una imagen para uso exclusivo. #@param str_repo repositorio de imágenes (opcional) #@param path_image camino de la imagen (sin extensión) #@return Nada. #@note Se genera un fichero con extensión .lock #@note repo = { REPO, CACHE } #@exception OG_ERR_FORMAT formato incorrecto. #*/ ## def ogLockImage (container=None, imgfile=None): if not imgfile: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, '') return None if container: imgdir = FileLib.ogGetParentPath (src=container, file=imgfile) else: imgdir = FileLib.ogGetParentPath (file=imgfile) try: bn = os.path.basename (imgfile) open (f'{imgdir}/{bn}.lock', 'w').close() return True except: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTWRITE, f'{container} {imgfile}') return None #/** # ogRestoreDiskImage str_repo path_image int_npartition #@brief Restaura (recupera) una imagen de un disco completo. #@param str_repo repositorio de imágenes o caché local #@param path_image camino de la imagen #@param int_ndisk nº de orden del disco #@return (por determinar) #@warning Primera versión en pruebas #@todo Gestionar bloqueos de disco #@todo Comprobar que no se intenta restaurar de la caché sobre el mismo disco #@exception OG_ERR_FORMAT formato incorrecto. #@exception OG_ERR_NOTFOUND fichero de imagen o partición no detectados. #@exception OG_ERR_LOCKED partición bloqueada por otra operación. #@exception OG_ERR_IMAGE error al restaurar la imagen del sistema. #@exception OG_ERR_IMGSIZEPARTITION Tamaño de la particion es menor al tamaño de la imagen. #*/ ## #/** # ogRestoreImage str_repo path_image int_ndisk int_npartition #@brief Restaura una imagen de sistema de archivos en una partición. #@param str_repo repositorio de imágenes o caché local #@param path_image camino de la imagen #@param int_ndisk nº de orden del disco #@param int_npartition nº de orden de la partición #@return (por determinar) #@exception OG_ERR_FORMAT 1 formato incorrecto. #@exception OG_ERR_NOTFOUND 2 fichero de imagen o partición no detectados. #@exception OG_ERR_PARTITION 3 # Error en partición de disco. #@exception OG_ERR_LOCKED 4 partición bloqueada por otra operación. #@exception OG_ERR_IMAGE 5 error al restaurar la imagen del sistema. #@exception OG_ERR_IMGSIZEPARTITION 30 Tamaño de la particion es menor al tamaño de la imagen. #@todo Comprobar incongruencias partición-imagen, control de errores, definir parámetros, caché/repositorio, etc. #*/ ## #ogRestoreImage ('REPO', '/aula1/win7', '1', '1') def ogRestoreImage (repo, imgpath, disk, par): PART = DiskLib.ogDiskToDev (disk, par) if not PART: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f' {disk} {par}') return None imgtype = 'img' imgfile = FileLib.ogGetPath (repo, f'{imgpath}.{imgtype}') if not os.path.exists (imgfile): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f' {disk} {par}') return None if not ogGetImageInfo (imgfile): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_IMAGE, f' {repo} {imgpath}') return None # Error si la imagen no cabe en la particion. imgsize = ogGetImageSize (repo, imgpath) if not imgsize: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_IMAGE, f' {repo} {imgpath}') return None if not FileSystemLib.ogMount (disk, par): FileSystemLib.ogFormat (disk, par) partsize = DiskLib.ogGetPartitionSize (disk, par) if float (imgsize) > float (partsize): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_IMGSIZEPARTITION, f' {partsize} < {imgsize}') return None if ogIsImageLocked (container=None, imgfile=imgfile): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_LOCKED, f'{ogGlobals.lang.MSG_IMAGE} {repo}, {imgpath}.{imgtype}') return None if FileSystemLib.ogIsLocked (disk, par): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_LOCKED, f'{ogGlobals.lang.MSG_PARTITION} {disk}, {par}') return None program = ogRestoreImageSyntax (imgfile, PART) if not FileSystemLib.ogUnmount (disk, par): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f' {disk} {par}') return None if not FileSystemLib.ogLock (disk, par): print (f'not FileSystemLib.ogLock()') return None rc = None try: p = subprocess.run (program, shell=True, capture_output=True, text=True) rc = p.returncode if not rc: SystemLib.ogRaiseError ([], ogGlobalsOG_ERR_IMAGE, f'{imgfile}, {disk}, {par}') except: pass finally: FileSystemLib.ogUnlock (disk, par) return rc #/** # ogRestoreMbrImage str_repo path_image int_ndisk #@brief Restaura la imagen del sector de arranque de un disco. #@param str_repo repositorio de imágenes o caché local #@param path_image camino de la imagen #@param int_ndisk nº de orden del disco #@return (por determinar) #@exception OG_ERR_FORMAT formato incorrecto. #@exception OG_ERR_NOTFOUND fichero de imagen o partición no detectados. #@exception OG_ERR_IMAGE error al restaurar la imagen del sistema. #*/ ## #/** # ogRestoreBootLoaderImage str_repo path_image int_ndisk #@brief Restaura la imagen del boot loader del sector de arranque de un disco. #@param str_repo repositorio de imágenes o caché local #@param path_image camino de la imagen #@param int_ndisk nº de orden del disco #@return (por determinar) #@exception OG_ERR_FORMAT formato incorrecto. #@exception OG_ERR_NOTFOUND fichero de imagen o partición no detectados. #@exception OG_ERR_IMAGE error al restaurar la imagen del sistema. #*/ ## #/** # ogUnlockImage [str_repo] path_image #@brief Desbloquea una imagen con uso exclusivo. #@param str_repo repositorio de imágenes (opcional) #@param path_image camino de la imagen (sin extensión) #@return Nada. #@note repo = { REPO, CACHE } #@note Se elimina el fichero de bloqueo con extensión .lock #@exception OG_ERR_FORMAT formato incorrecto. #*/ ## #ogUnlockImage REPO /cucu.img def ogUnlockImage (container=None, imgfile=None): f = f'{imgfile}.lock' if container: p = FileLib.ogGetPath (src=container, file=f) else: p = FileLib.ogGetPath (file=f) if os.path.exists (p): os.unlink (p) #/** # ogGetImageInfo filename #@brief muestra información sobre la imagen monolitica. #@param 1 filename path absoluto del fichero imagen #@return cadena compuesta por clonacion:compresor:sistemaarchivos:tamañoKB #@exception OG_ERR_FORMAT formato incorrecto. #@exception OG_ERR_NOTFOUND fichero no encontrado. #@exception OG_ERR_IMAGE "Image format is not valid $IMGFILE" #@warning En pruebas iniciales #@TODO Definir sintaxis de salida (herramienta y compresor en minuscula) #@TODO Arreglar loop para ntfsclone #@TODO insertar parametros entrada tipo OG #*/ ## #ogGetImageInfo /opt/opengnsys/images/prueba.img ==> PARTCLONE:LZOP:NTFS:5642158" def ogGetImageInfo (imgfile): if not os.path.exists (imgfile): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, imgfile) return imgdetect = False filehead = f'/tmp/{os.path.basename (imgfile)}.infohead' compressor = subprocess.run (['file', imgfile], capture_output=True, text=True).stdout.split()[1] if compressor not in ['gzip', 'lzop']: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_IMAGE, f'Image format is not valid {imgfile}') return ## 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}"') 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 tools = fs = size = None if False == imgdetect: lc_all = os.getenv ('LC_ALL') os.environ['LC_ALL'] = 'C' partclone_info = subprocess.run (['partclone.info', filehead], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True).stdout #partclone_info = subprocess.run (['cat', '/tmp/foo-partclone'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True).stdout ## sacado de un email de alberto garcía uma.es #Partclone v0.3.13 http://partclone.org #Unknown mode #File system: NTFS #Device size: 29.2 GB = 7138019 Blocks #Space in use: 26.6 GB = 6485355 Blocks #Free Space: 2.7 GB = 652664 Blocks #Block size: 4096 Byte # #image format: 0002 #created on a: 64 bits platform #with partclone: v0.3.13 #bitmap mode: BIT #checksum algo: CRC32 #checksum size: 4 #blocks/checksum: 256 if lc_all is not None: os.environ["LC_ALL"] = lc_all else: del os.environ["LC_ALL"] if 'size' in partclone_info: tools = 'PARTCLONE' m = re.search (r'File system *: *(\S+)', partclone_info) fs = m.group(1) if m else '' sizefactor = 1000000 if 'GB' in partclone_info else 1024 m = re.search (r'Device size *: *(\S+)', partclone_info) size = float (m.group(1)) if m else 0 size = int (size * sizefactor) ## why is this? #if fs in ['HFS', 'HFSPLUS', 'FAT32']: # #FSPLUS=$(echo $PARTCLONEINFO | awk '{gsub(/\: /,"\n"); print toupper($9);}') # fsplus = 'PLUS' # if fsplus: # fs += fsplus ## 'HFS' -> 'HFSPLUS' imgdetect = True 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"') 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 #ntfsclone v2023.4.0 (libntfs-3g) #NTFS volume version: 3.1 #Cluster size: 4096 bytes #Image volume size: 104857600 bytes (105 MB) #Space in use: 52428800 bytes (52 MB) #Reading and restoring NTFS... #100.00 percent completed #Syncing ... #Successfully cloned image to device '/dev/loop2'. else: ntfscloneinfo = '' if 'ntfsclone' in ntfscloneinfo: tools = 'NTFSCLONE' m = re.search (r'Image volume size *: *(\S+)', ntfscloneinfo) size = float (m.group(1))/1000 if m else 0 fs = 'NTFS' imgdetect = True if False == imgdetect: partimageinfo = subprocess.run (['partimage', '-B', 'gui=no', 'imginfo', filehead], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True).stdout #partimageinfo = subprocess.run (['cat', '/tmp/foo-partimage'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True).stdout ## sacado de un email de alberto garcía uma.es #Volume number:.........0 #Volume size:...........1,27 MiB #Compression level: ....0 -> ninguno #Identificator:.........12442509728668372730=ACACACACCB9ECEFA #Filesystem:............ntfs #Description:...........Sin descripcion #Original device:......./dev/nvme0n1p2 #Original filepath:.... stdout #Flags:.................0: Bandera sin activar #Creation date:.........Mon Nov 11 21:00:22 2024 #Partition size:........476,84 GiB #Hostname:..............ING-LTR-083 #Compatible Version:....0.6.1 #Encryption algorithm:..0 -> ninguno #MBR saved count:.......0 partimageinfo = re.sub (r':\s*\.+', ' : ', partimageinfo) if 'Partition' in partimageinfo: tools = 'PARTIMAGE' m = re.search (r'Filesystem *: *(\S+)', partimageinfo) fs = m.group(1).upper() if m else '' m = re.search (r'Partition size *: *(\S+)', partimageinfo) size = m.group(1) if m else '' size = re.sub (r' [MGT]i?B$', '', size) size = float (size.replace (',', '.')) size = int (size*1024*1024) imgdetect = True if 'boot sector' in subprocess.run (['file', filehead], capture_output=True, text=True).stdout: tools = 'partclone.dd' fs = '' size = 0 imgdetect = True if not tools or not compressor or False == imgdetect: SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_IMAGE, f'Image format is not valid {imgfile}') return compressor = compressor.upper() return ':'.join ([tools, compressor, fs, str (size)]) #/** # ogGetImageProgram str_REPO str_imagen #@brief muestra información sobre la imagen monolitica. #@see ogGetImageInfo #@param 1 REPO o CACHE contenedor de la imagen #@param 2 filename nombre de la imagen sin extension #@return nombre del programa usado para generar la imagen #@exception OG_ERR_FORMAT formato incorrecto. #@exception OG_ERR_NOTFOUND fichero no encontrado. #@note ogGetImageProgram REPO imagenA -> partclone #*/ ## #ogGetImageProgram ('REPO', 'prueba') ==> 'PARTCLONE' def ogGetImageProgram (container, filename): imgfile = FileLib.ogGetPath (container, f'{filename}.img') if not os.path.exists (imgfile): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, imgfile) return None i = ogGetImageInfo (imgfile) return i.split (':')[0] #/** # ogGetImageCompressor str_REPO str_imagen #@brief muestra información sobre la imagen monolitica. #@see ogGetImageInfo #@param 1 REPO o CACHE contenedor de la imagen #@param 2 filename nombre de la imagen sin extension #@return tipo de compresión usada al generar la imagen #@exception OG_ERR_FORMAT formato incorrecto. #@exception OG_ERR_NOTFOUND fichero no encontrado. #@note ogGetImageCompressor REPO imagenA -> lzop #*/ ## #ogGetImageCompressor ('REPO', 'prueba') ==> 'LZOP' def ogGetImageCompressor (container, filename): imgfile = FileLib.ogGetPath (container, f'{filename}.img') if not os.path.exists (imgfile): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, imgfile) return None i = ogGetImageInfo (imgfile) return i.split (':')[1] #/** # ogGetImageType str_REPO str_imagen #@brief muestra información sobre el sistema de archivos de imagen monolitica. #@see ogGetImageInfo #@param 1 REPO o CACHE contenedor de la imagen #@param 2 filename nombre de la imagen sin extension #@return tipo de compresión usada al generar la imagen #@exception OG_ERR_FORMAT formato incorrecto. #@exception OG_ERR_NOTFOUND fichero no encontrado. #@note ogGetImageType REPO imagenA -> NTFS #*/ ## #ogGetImageType ('REPO', 'imgprueba') ==> 'NTFS' #ogGetImageType ('CACHE', 'testimg') ==> 'EXTFS' def ogGetImageType (repo, imgname): imgfile = FileLib.ogGetPath (src=repo, file=f'{imgname}.img') if not os.path.exists (imgfile): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, imgfile) return None i = ogGetImageInfo (imgfile) return i.split (':')[2] #/** # ogGetImageSize str_REPO str_imagen #@brief muestra información sobre el tamaño (KB) del sistema de archivos de imagen monolitica. #@see ogGetImageInfo #@param 1 REPO o CACHE contenedor de la imagen #@param 2 filename nombre de la imagen sin extension #@return tipo de compresión usada al generar la imagen #@exception OG_ERR_FORMAT formato incorrecto. #@exception OG_ERR_NOTFOUND fichero no encontrado. #@note ogGetImagesize REPO imagenA -> 56432234 > Kb #*/ ## #ogGetImageSize ('REPO', 'prueba') ==> '5642158' def ogGetImageSize (repo, imgname): imgfile = FileLib.ogGetPath (src=repo, file=f'{imgname}.img') if not os.path.exists (imgfile): SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, imgfile) return None i = ogGetImageInfo (imgfile) return i.split (':')[3] #/** # ogCreateGptImage int_ndisk str_repo path_image #@brief Crea una imagen de la tabla de particiones GPT de un disco. #@param int_ndisk nº de orden del disco #@param str_repo repositorio de imágenes (remoto o caché local) #@param path_image camino de la imagen (sin extensión) #@return (nada, por determinar) #@note repo = { REPO, CACHE } #@exception OG_ERR_FORMAT formato incorrecto. #@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. #@exception OG_ERR_IMAGE error al crear la imagen del sistema. #*/ ## #/** # ogRestoreGptImage str_repo path_image int_ndisk #@brief Restaura la imagen de la tabla de particiones GPT de un disco. #@param str_repo repositorio de imágenes o caché local #@param path_image camino de la imagen #@param int_ndisk nº de orden del disco #@return (por determinar) #@exception OG_ERR_FORMAT formato incorrecto. #@exception OG_ERR_NOTFOUND fichero de imagen o partición no detectados. #@exception OG_ERR_IMAGE error al restaurar la imagen del sistema. #*/ ##