ogclone-engine/client/lib/engine/bin/ImageLib.py

833 lines
33 KiB
Python

#!/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:
cache_disk, cache_par = CacheLib.ogFindCache().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.
#*/ ##