diff --git a/client/lib/engine/bin/DiskLib.py b/client/lib/engine/bin/DiskLib.py index d26ca4d..ddb7168 100755 --- a/client/lib/engine/bin/DiskLib.py +++ b/client/lib/engine/bin/DiskLib.py @@ -8,6 +8,7 @@ from pathlib import Path import ogGlobals import SystemLib import CacheLib +import FileSystemLib def parted(*args): parted_path = shutil.which("parted") @@ -707,32 +708,28 @@ def ogGetPartitionId(*args): print(ID) return -def ogGetPartitionSize(*args): - # Variables locales - PART = None - SIZE = None - # Si se solicita, mostrar ayuda. - if len(args) == 1 and args[0] == "help": - SystemLib.ogHelp('ogGetPartitionSize', 'ogGetPartitionSize int_ndisk int_npartition', 'ogGetPartitionSize 1 1 => 10000000') - return +#/** +# ogGetPartitionSize int_ndisk int_npartition +#@brief Muestra el tamano en KB de una particion determinada. +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@return int_partsize - Tamaño en KB de la partición. +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND disco o particion no detectado (no es un dispositivo). +#@note Requisitos: sfdisk, awk +#*/ ## +def ogGetPartitionSize (disk, par): + PART = ogDiskToDev (disk, par) + if PART is None: return - # Error si no se reciben 2 parámetros. - if len(args) != 2: - SystemLib.ogRaiseError(OG_ERR_FORMAT) - return + sz = subprocess.run (['partx', '-gbo', 'SIZE', PART], capture_output=True, text=True).stdout.strip() + if sz: return int (int (sz) / 1024) - # Devolver tamaño de partición, del volumen lógico o del sistema de archivos (para ZFS). - PART = ogDiskToDev(args[0], args[1]) - if PART is None: - return - SIZE = subprocess.getoutput(f"partx -gbo SIZE {PART} 2>/dev/null | awk '{{print int($1/1024)}}'") - if not SIZE: - SIZE = subprocess.getoutput(f"lvs --noheadings -o lv_size --units k {PART} | awk '{{printf \"%d\",$0}}'") - if not SIZE: - SIZE = ogGetFsSize(args[0], args[1]) - print(SIZE or 0) - return + sz = subprocess.run (['lvs', '--noheadings', '-o', 'lv_size', '--units', 'k', PART], capture_output=True, text=True).stdout.strip + if sz: return int (sz) + + return FileSystemLib.ogGetFsSize (disk, par) def ogGetPartitionsNumber(*args): # Variables locales diff --git a/client/lib/engine/bin/FileSystemLib.py b/client/lib/engine/bin/FileSystemLib.py index 384c969..ab7ead1 100755 --- a/client/lib/engine/bin/FileSystemLib.py +++ b/client/lib/engine/bin/FileSystemLib.py @@ -6,6 +6,7 @@ import ogGlobals import SystemLib import DiskLib import CacheLib +import FileSystemLib def ogCheckFs(int_ndisk, int_nfilesys): # Si se solicita, mostrar ayuda. @@ -171,170 +172,144 @@ def ogExtendFs(): ogUnlock(int(sys.argv[1]), int(sys.argv[2])) return ERRCODE -def ogFormat(int_ndisk, int_nfilesys): - if int_nfilesys.lower() == "cache": - ogFormatCache() + +#/** +# ogFormat int_ndisk int_nfilesys | CACHE +#@see ogFormatFs ogFormatCache +#*/ ## + +def ogFormat (disk, par=None, fs=None, label=None): + if disk.lower() == "cache": + return CacheLib.ogFormatCache() else: - ogFormatFs(int_ndisk, int_nfilesys) + return ogFormatFs (disk, par) -def ogFormatFs(int_ndisk, int_nfilesys, str_label=None): - # Si se solicita, mostrar ayuda. - if str_label == "help": - ogHelp("ogFormatFs", "ogFormatFs int_ndisk int_nfilesys [str_label]", "ogFormatFs 1 1", "ogFormatFs 1 1 EXT4", "ogFormatFs 1 1 \"DATA\"", "ogFormatFs 1 1 EXT4 \"DATA\"") + +#/** +# ogFormatFs int_ndisk int_nfilesys [type_fstype] [str_label] +#@brief Formatea un sistema de ficheros según el tipo de su partición. +#@param int_ndisk nº de orden del disco +#@param int_nfilesys nº de orden del sistema de archivos +#@param type_fstype mnemónico de sistema de ficheros a formatear (opcional al reformatear) +#@param str_label etiqueta de volumen (opcional) +#@return (por determinar) +#@exception OG_ERR_FORMAT Formato de ejecución incorrecto. +#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo. +#@exception OG_ERR_PARTITION Partición no accesible o desconocida. +#@note Requisitos: mkfs* +#@warning No formatea particiones montadas ni bloqueadas. +#@todo Definir salidas. +#*/ ## +def ogFormatFs (disk, par, type=None, label=None): + PART = DiskLib.ogDiskToDev (disk, par) + if not PART: return + + if ogIsMounted (disk, par): + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_DONTFORMAT, f'{ogGlobals.lang.MSG_MOUNT}: {disk} {par}') + return None + if ogIsLocked (disk, par): + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_LOCKED, f"{disk} {par}") + return None + + if not type: + type = ogGetFsType (disk, par) + + if not type: + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f"{disk} {par} ...") + return None + + data = { + 'EXT2': { 'PROG': 'mkfs.ext2', 'PARAMS': '-F' }, + 'EXT3': { 'PROG': 'mkfs.ext3', 'PARAMS': '-F' }, + 'EXT4': { 'PROG': 'mkfs.ext4', 'PARAMS': '-F' }, + 'BTRFS': { 'PROG': 'mkfs.btrfs', 'PARAMS': '-f' }, + 'REISERFS': { 'PROG': 'mkfs.reiserfs', 'PARAMS': '-f', 'LABELPARAM': '-l' }, + 'REISER4': { 'PROG': 'mkfs.reiser4', 'PARAMS': '-f', 'INPUT': 'y\n' }, + 'XFS': { 'PROG': 'mkfs.xfs', 'PARAMS': '-f' }, + 'JFS': { 'PROG': 'mkfs.jfs', 'INPUT': 'y\n' }, + 'F2FS': { 'PROG': 'mkfs.f2fs', 'LABELPARAM': '-l' }, + 'NILFS2': { 'PROG': 'mkfs.nilfs2', 'PARAMS': '-f' }, + 'LINUX-SWAP': { 'PROG': 'mkswap' }, + 'NTFS': { 'PROG': 'mkntfs', 'PARAMS': '-f' }, + 'EXFAT': { 'PROG': 'mkfs.exfat', 'LABELPARAM': '-n' }, + 'FAT32': { 'PROG': 'mkdosfs', 'PARAMS': '-F 32', 'LABELPARAM': '-n' }, + 'FAT16': { 'PROG': 'mkdosfs', 'PARAMS': '-F 16', 'LABELPARAM': '-n' }, + 'FAT12': { 'PROG': 'mkdosfs', 'PARAMS': '-F 12', 'LABELPARAM': '-n' }, + 'HFS': { 'PROG': 'mkfs.hfs' }, + 'HFSPLUS': { 'PROG': 'mkfs.hfsplus', 'LABELPARAM': '-v' }, + 'UFS': { 'PROG': 'mkfs.ufs', 'PARAMS': '-O 2' }, + } + if type not in data: + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f"{disk} {par} {type}") return - # Error si no se reciben entre 2 y 4 parámetros. - if not (2 <= len(sys.argv) <= 4): - ogRaiseError(OG_ERR_FORMAT) + d = data[type] + prog = d['PROG'] + params = d['PARAMS'] if 'PARAMS' in d else '' + labelparam = d['LABELPARAM'] if 'LABELPARAM' in d else '' + input = d['INPUT'] if 'INPUT' in d else '' + + if label == "CACHE": + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f"{ogGlobals.lang.MSG_RESERVEDVALUE}: CACHE") return + if label: + params = f"{params} {labelparam or '-L'} {label}" - # Obtener fichero de dispositivo. - PART = ogDiskToDev(int_ndisk, int_nfilesys) - if not PART: - return - - # Error si la partición está montada o bloqueada. - if ogIsMounted(int_ndisk, int_nfilesys): - ogRaiseError(OG_ERR_DONTFORMAT, f"{MSG_MOUNT}: {int_ndisk} {int_nfilesys}") - return - if ogIsLocked(int_ndisk, int_nfilesys): - ogRaiseError(OG_ERR_LOCKED, f"{int_ndisk} {int_nfilesys}") - return - - # Si no se indica el tipo de sistema de archivos, intentar obtenerlo. - TYPE = ogGetFsType(int_ndisk, int_nfilesys) - if not TYPE: - TYPE = sys.argv[3] - - # Error, si no especifica el tipo de sistema de archivos a formatear. - if not TYPE: - ogRaiseError(OG_ERR_FORMAT, f"{int_ndisk} {int_nfilesys} ...") - return - - # Elegir tipo de formato. - if TYPE == "EXT2": - PROG = "mkfs.ext2" - PARAMS = "-F" - elif TYPE == "EXT3": - PROG = "mkfs.ext3" - PARAMS = "-F" - elif TYPE == "EXT4": - PROG = "mkfs.ext4" - PARAMS = "-F" - elif TYPE == "BTRFS": - PROG = "mkfs.btrfs" - PARAMS = "-f" - elif TYPE == "REISERFS": - PROG = "mkfs.reiserfs" - PARAMS = "-f" - LABELPARAM = "-l" - elif TYPE == "REISER4": - PROG = "mkfs.reiser4" - PARAMS = "-f <<<\"y\"" - elif TYPE == "XFS": - PROG = "mkfs.xfs" - PARAMS = "-f" - elif TYPE == "JFS": - PROG = "mkfs.jfs" - PARAMS = "<<<\"y\"" - elif TYPE == "F2FS": - PROG = "mkfs.f2fs" - LABELPARAM = "-l" - elif TYPE == "NILFS2": - PROG = "mkfs.nilfs2" - PARAMS = "-f" - elif TYPE == "LINUX-SWAP": - PROG = "mkswap" - elif TYPE == "NTFS": - PROG = "mkntfs" - PARAMS = "-f" - elif TYPE == "EXFAT": - PROG = "mkfs.exfat" - LABELPARAM = "-n" - elif TYPE == "FAT32": - PROG = "mkdosfs" - PARAMS = "-F 32" - LABELPARAM = "-n" - elif TYPE == "FAT16": - PROG = "mkdosfs" - PARAMS = "-F 16" - LABELPARAM = "-n" - elif TYPE == "FAT12": - PROG = "mkdosfs" - PARAMS = "-F 12" - LABELPARAM = "-n" - elif TYPE == "HFS": - PROG = "mkfs.hfs" - elif TYPE == "HFSPLUS": - PROG = "mkfs.hfsplus" - LABELPARAM = "-v" - elif TYPE == "UFS": - PROG = "mkfs.ufs" - PARAMS = "-O 2" - else: - ogRaiseError(OG_ERR_PARTITION, f"{int_ndisk} {int_nfilesys} {TYPE}") - return - - # Etiquetas de particion. - if not str_label: - if sys.argv[4] == "CACHE": - ogRaiseError(OG_ERR_FORMAT, f"{MSG_RESERVEDVALUE}: CACHE") - return - if len(sys.argv) >= 4: - PARAMS = f"{PARAMS} {LABELPARAM or '-L'} {sys.argv[4]}" - else: - PARAMS = f"{PARAMS} {LABELPARAM or '-L'} {str_label}" - - # Formatear en modo uso exclusivo (desmontar siempre). - ogLock(int_ndisk, int_nfilesys) + ogLock (disk, par) + subprocess.run (['umount', PART]) try: - subprocess.run([PROG, PARAMS, PART], capture_output=True) - ERRCODE = 0 + if input: + errcode = subprocess.run ([prog, params, PART]) + else: + errcode = subprocess.run ([prog, params, PART], input=input, text=True) except FileNotFoundError: - ogRaiseError(OG_ERR_NOTEXEC, PROG) - ERRCODE = OG_ERR_NOTEXEC + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTEXEC, prog) + errcode = ogGlobals.OG_ERR_NOTEXEC except: - ogRaiseError(OG_ERR_PARTITION, f"{int_ndisk} {int_nfilesys}") - ERRCODE = OG_ERR_PARTITION + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f"{disk} {par}") + errcode = ogGlobals.OG_ERR_PARTITION - ogUnlock(int_ndisk, int_nfilesys) - return ERRCODE + ogUnlock (disk, par) + return errcode -def ogGetFsSize(int_ndisk, int_npartition, str_unit=None): - # Si se solicita, mostrar ayuda. - if str_unit == "help": - ogHelp("ogGetFsSize", "ogGetFsSize int_ndisk int_npartition [str_unit]", "ogGetFsSize 1 1", "ogGetFsSize 1 1 KB") - return - # Error si no se reciben 2 o 3 parámetros. - if not (2 <= len(sys.argv) <= 3): - ogRaiseError(OG_ERR_FORMAT) - return +#/** +# ogGetFsSize int_ndisk int_npartition [str_unit] +#@brief Muestra el tamanio del sistema de archivos indicado, permite definir la unidad de medida, por defecto GB +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@param str_unit unidad (opcional, por defecto: KB) +#@return float_size - Tamaño del sistema de archivos +#@note str_unit = { KB, MB, GB, TB } +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Disco o partición no corresponden con un dispositivo. +#*/ ## - # Obtener unidad y factor de medida. - UNIT = str_unit or "KB" - FACTOR = 1 - if UNIT.upper() == "MB": - FACTOR = 1024 - elif UNIT.upper() == "GB": - FACTOR = 1024 * 1024 - elif UNIT.upper() == "TB": - FACTOR = 1024 * 1024 * 1024 - elif UNIT.upper() != "KB": - ogRaiseError(OG_ERR_FORMAT, f"{UNIT} != {{ KB, MB, GB, TB }}") +def ogGetFsSize (disk, par, unit=None): + u = unit or "KB" + factor = 1 + if u.upper() == "MB": + factor = 1024 + elif u.upper() == "GB": + factor = 1024 * 1024 + elif u.upper() == "TB": + factor = 1024 * 1024 * 1024 + elif u.upper() != "KB": + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f"{unit} != {{ KB, MB, GB, TB }}") return # Obtener el tamaño del sistema de archivo (si no está formateado; tamaño = 0). - MNTDIR = ogMount(int_ndisk, int_npartition) - if MNTDIR: - result = subprocess.run(["df", "-BK", MNTDIR], capture_output=True, text=True) - VALUE = result.stdout.split("\n")[1].split()[1] - SIZE = float(VALUE) / FACTOR + mnt = FileSystemLib.ogMount (disk, par) + if mnt: + result = subprocess.run(["df", "-BK", mnt], capture_output=True, text=True) + val = result.stdout.split("\n")[1].split()[1] + val = val.replace ('K', '') + sz = int (val) / factor else: - SIZE = 0 + sz = 0 + + return int (sz) - # Devolver el tamaño (quitar decimales si son 0). - return int(SIZE) if SIZE.is_integer() else SIZE #/** # ogGetFsType int_ndisk int_nfilesys @@ -461,26 +436,24 @@ def ogIsLocked(disk, par): def ogIsPartitionLocked(disk, par): DISK = DiskLib.ogDiskToDev(disk) PART = DiskLib.ogDiskToDev(disk, par) + print (f'nati: ogIsPartitionLocked: DISK ({DISK}) PART ({PART})') if not PART: return LOCKDISK = f"/var/lock/lock{DISK.replace('/', '-')}" LOCKPART = f"/var/lock/lock{PART.replace('/', '-')}" - return os.path.isfile(LOCKDISK) or os.path.isfile(LOCKPART) + rc = os.path.isfile(LOCKDISK) or os.path.isfile(LOCKPART) + print (f'nati: ogIsPartitionLocked: LOCKDISK ({LOCKDISK}) LOCKPART ({LOCKPART}) rc ({rc})') + return rc -def ogIsMounted(int_ndisk, int_nfilesys): - # Si se solicita, mostrar ayuda. - if len(sys.argv) == 3 and sys.argv[2] == "help": - ogHelp("ogIsMounted", "ogIsMounted int_ndisk int_nfilesys", "ogIsMounted 1 1") - return - - # Error si no se reciben 2 parámetros. - if len(sys.argv) != 3: - ogRaiseError(OG_ERR_FORMAT) - return - - # Obtener punto de montaje. - MNTDIR = ogGetMountPoint(int_ndisk, int_nfilesys) - return bool(MNTDIR) +#/** +# ogIsMounted int_ndisk int_nfilesys +#@brief Comprueba si un sistema de archivos está montado. +#@param int_ndisk nº de orden del disco +#@param int_nfilesys nº de orden del sistema de archivos +#@return Código de salida: 0 - montado, 1 - sin montar o error. +#*/ ## +def ogIsMounted (disk, par): + return bool (ogGetMountPoint (disk, par)) #/** @@ -523,28 +496,31 @@ def ogIsWritable(int_ndisk, int_nfilesys): options = result.stdout.strip().split(",") return "rw" in options -def ogLock(int_ndisk, int_nfilesys): - ogLockPartition(int_ndisk, int_nfilesys) -def ogLockPartition(int_ndisk, int_npartition): - # Si se solicita, mostrar ayuda. - if len(sys.argv) == 3 and sys.argv[2] == "help": - ogHelp("ogLockPartition", "ogLockPartition int_ndisk int_npartition", "ogLockPartition 1 1") - return +#/** +# ogLock int_ndisk int_npartition +#@see ogLockPartition +#*/ +def ogLock(disk, par): + return ogLockPartition(disk, par) - # Error si no se reciben 2 parámetros. - if len(sys.argv) != 3: - ogRaiseError(OG_ERR_FORMAT) - return +#/** +# ogLockPartition int_ndisk int_npartition +#@brief Genera un fichero de bloqueo para una partición en uso exlusivo. +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo. +#@note El fichero de bloqueo se localiza en \c /var/lock/part, siendo \c part el dispositivo de la partición, sustituyendo el carácter "/" por "-". +#*/ ## +def ogLockPartition (disk, par): + PART = DiskLib.ogDiskToDev (disk, par) + if not PART: return - # Obtener partición. - PART = ogDiskToDev(int_ndisk, int_npartition) - if not PART: - return - - # Crear archivo de bloqueo exclusivo. LOCKFILE = f"/var/lock/lock{PART.replace('/', '-')}" - open(LOCKFILE, 'a').close() + open(LOCKFILE, 'w').close() + return True #/** @@ -580,7 +556,6 @@ def ogMountFirstFs(int_ndisk): #@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo. #@exception OG_ERR_PARTITION Tipo de particion desconocido o no se puede montar. #*/ ## - def ogMountFs (disk, par): dev = DiskLib.ogDiskToDev (disk, par) if not dev: return @@ -589,6 +564,7 @@ def ogMountFs (disk, par): if mntdir: return mntdir if ogIsLocked (disk, par): + print (f'nati: ogMountFs: is locked disk ({disk}) par ({par})') SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_LOCKED, f"{ogGlobals.lang.MSG_PARTITION}, {disk} {par}") return @@ -727,26 +703,28 @@ def ogReduceFs(int_ndisk, int_nfilesys): # Devuelve tamaño del sistema de ficheros. return ogGetFsSize(int_ndisk, int_nfilesys) -def ogUnlock(int_ndisk, int_npartition): - ogUnlockPartition(int_ndisk, int_npartition) -def ogUnlockPartition(int_ndisk, int_npartition): - # Si se solicita, mostrar ayuda. - if len(sys.argv) == 3 and sys.argv[2] == "help": - ogHelp("ogUnlockPartition", "ogUnlockPartition int_ndisk int_npartition", "ogUnlockPartition 1 1") - return +#/** +# ogUnlock int_ndisk int_npartition +#@see ogUnlockPartition +#*/ ## +def ogUnlock (disk, par): + return ogUnlockPartition (disk, par) - # Error si no se reciben 2 parámetros. - if len(sys.argv) != 3: - ogRaiseError(OG_ERR_FORMAT) - return +#/** +# ogUnlockPartition int_ndisk int_npartition +#@brief Elimina el fichero de bloqueo para una particion. +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo. +#@note El fichero de bloqueo se localiza en \c /var/lock/part, siendo \c part el dispositivo de la partición, sustituyendo el carácter "/" por "-". +#*/ ## +def ogUnlockPartition (disk, par): + PART = DiskLib.ogDiskToDev (disk, par) + if not PART: return - # Obtener partición. - PART = ogDiskToDev(int_ndisk, int_npartition) - if not PART: - return - - # Borrar archivo de bloqueo exclusivo. LOCKFILE = f"/var/lock/lock{PART.replace('/', '-')}" os.remove(LOCKFILE) @@ -756,7 +734,7 @@ def ogUnlockPartition(int_ndisk, int_npartition): #@see ogUnmountFs #*/ ## def ogUnmount (disk, par): - ogUnmountFs (disk, par) + return ogUnmountFs (disk, par) #/** # ogUnmountFs int_ndisk int_nfilesys @@ -793,8 +771,10 @@ def ogUnmountFs(disk, par): os.remove(MNTDIR) except: pass + return True else: SystemLib.ogEcho ([], "warning", f'{ogGlobals.lang.MSG_DONTMOUNT}: "{disk},{par}"') + return True #/** diff --git a/client/lib/engine/bin/ImageLib.py b/client/lib/engine/bin/ImageLib.py index 348b32c..936eaeb 100644 --- a/client/lib/engine/bin/ImageLib.py +++ b/client/lib/engine/bin/ImageLib.py @@ -267,6 +267,17 @@ def ogRestoreImageSyntax (imgfile, part, tool=None, level=None): #@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) #/** @@ -315,6 +326,68 @@ def ogRestoreImageSyntax (imgfile, part, tool=None, level=None): #@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: + print (f'nati: ogRestoreImage: running ({program})') + p = subprocess.run (program, shell=True, capture_output=True, text=True) + rc = p.returncode + print (f'nati: ogRestoreImage: rc ({rc}) stdout ({p.stdout}) stderr ({p.stderr})') + if not rc: + SystemLib.ogRaiseError ([], ogGlobalsOG_ERR_IMAGE, f'{imgfile}, {disk}, {par}') + except: + pass + finally: + FileSystemLib.ogUnlock (disk, par) + + return rc + #/** @@ -516,6 +589,14 @@ def ogGetImageInfo (imgfile): #@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 @@ -529,6 +610,15 @@ def ogGetImageInfo (imgfile): #@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 @@ -544,7 +634,7 @@ def ogGetImageInfo (imgfile): #ogGetImageType ('REPO', 'imgprueba') ==> 'NTFS' #ogGetImageType ('CACHE', 'testimg') ==> 'EXTFS' def ogGetImageType (repo, imgname): - imgfile = FileLib.ogGetPath (src=repo, file=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 @@ -565,6 +655,15 @@ def ogGetImageType (repo, imgname): #@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] #/** diff --git a/client/lib/engine/bin/NetLib.py b/client/lib/engine/bin/NetLib.py index 866196b..e3603fd 100755 --- a/client/lib/engine/bin/NetLib.py +++ b/client/lib/engine/bin/NetLib.py @@ -40,7 +40,7 @@ def ogChangeRepo(ip_repo, og_unit=None): try: mount = subprocess.run (['mount'], capture_output=True, text=True).stdout - ro = not not list (filter (lambda line: re.search (r'ogimages.*\bro,', line), mount.splitlines())) + ro = bool (list (filter (lambda line: re.search (r'ogimages.*\bro,', line), mount.splitlines()))) current_repo = ogGetRepoIp() new_repo = current_repo if ip_repo.upper() == "REPO" else ip_repo @@ -48,7 +48,7 @@ def ogChangeRepo(ip_repo, og_unit=None): subprocess.run(["umount", ogGlobals.OGIMG], check=True) - SystemLib.ogEcho (['session', 'log'], 'info', f'{ogGlobals.lang.MSG_HELP_ogChangeRepo} {new_repo}') + SystemLib.ogEcho (['session', 'log'], None, f'{ogGlobals.lang.MSG_HELP_ogChangeRepo} {new_repo}') options = _ogConnect_options() if not _ogConnect (new_repo, ogprotocol, 'ogimages', ogGlobals.OGIMG, options, ro): _ogConnect (current_repo, ogprotocol, 'ogimages', ogGlobals.OGIMG, options, ro) @@ -61,7 +61,7 @@ def ogChangeRepo(ip_repo, og_unit=None): SystemLib.ogEcho( ["session", "log"], - 'info', + None, f"Repository successfully changed to {new_repo}".strip(), ) @@ -164,28 +164,30 @@ def ogGetHostname(): #@note Usa las variables utilizadas por el initrd "/etc/net-ethX.conf #*/ ## def ogGetIpAddress(): - IP = "" - - if len(sys.argv) >= 2 and sys.argv[1] == "help": - SystemLib.ogHelp("ogGetIpAddress", "ogGetIpAddress", "ogGetIpAddress => 192.168.0.10") - return - if "IPV4ADDR" in os.environ: - IP = os.environ["IPV4ADDR"] - else: - # Obtener direcciones IP. - if "DEVICE" in os.environ: - IP = subprocess.run(["ip", "-o", "address", "show", "up", "dev", os.environ["DEVICE"]], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode().split() - else: - IP = subprocess.run(["ip", "-o", "address", "show", "up"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode().split() + ip = os.environ["IPV4ADDR"] + if '/' in ip: ip = ip.split ('/')[0] + return ip - IP = [addr.split("/")[0] for addr in IP if "inet" in addr] + extra_args = [] + if "DEVICE" in os.environ: + extra_args = [ "dev", os.environ["DEVICE"] ] + ipas = subprocess.run (['ip', '-json', 'address', 'show', 'up'] + extra_args, capture_output=True, text=True).stdout - # Mostrar solo la primera. - if IP: - print(IP[0]) + ipasj = json.loads (ipas) + addresses = [] + for e in ipasj: + if 'lo' == 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'] }) + + if 1 != len (addresses): + raise Exception ('more than one local IP address found') + return addresses[0] - return 0 #/** # ogGetMacAddress diff --git a/client/lib/engine/bin/ProtocolLib.py b/client/lib/engine/bin/ProtocolLib.py index b421307..9912e4c 100644 --- a/client/lib/engine/bin/ProtocolLib.py +++ b/client/lib/engine/bin/ProtocolLib.py @@ -246,9 +246,6 @@ def _clientip(): 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: @@ -642,7 +639,7 @@ def ogMcastReceiverPartition (disk, par, sess, tool, compressor): #*/ ## ## now ogCore takes this responsibility def ogMcastRequest (img, proto): - return + return True ########################################## @@ -754,7 +751,7 @@ def ogTorrentStart (disk=None, par=None, container=None, torrentfile=None, torre if 'peer' == mode: print ('Donwloading Torrent as peer') # Creamos el fichero de resumen por defecto - with open (f'{source}.bf', 'w') as fd: pass + open (f'{source}.bf', 'w').close() # ctorrent controla otro fichero -b ${SOURCE}.bfog subprocess.run (['ctorrent', '-f', '-c', '-X', f'sleep {time}; kill -2 $(pidof ctorrent)', '-C', '100', source, '-s', target, '-b', f'{source}.bfog']) elif 'leecher' == mode: @@ -763,7 +760,7 @@ def ogTorrentStart (disk=None, par=None, container=None, torrentfile=None, torre elif 'seeder' == mode: print ('MODE seeder ctorrent') # Creamos el fichero de resumen por defecto - with open (f'{source}.bf', 'w') as fd: pass + open (f'{source}.bf', 'w').close() # ctorrent controla otro fichero -b ${SOURCE}.bfog subprocess.run (['ctorrent', '-f', '-c', '-X', f'sleep {time}; kill -2 $(pidof ctorrent)', '-C', '100', source, '-s', target, '-b', f'{source}.bfog']) else: diff --git a/client/lib/engine/bin/SystemLib.py b/client/lib/engine/bin/SystemLib.py index c26eb42..66f0664 100755 --- a/client/lib/engine/bin/SystemLib.py +++ b/client/lib/engine/bin/SystemLib.py @@ -2,10 +2,14 @@ import subprocess import datetime from zoneinfo import ZoneInfo import sys -import os +import os import shutil import inspect +## for ogExecAndLog +from io import StringIO +from contextlib import redirect_stdout, redirect_stderr + import ogGlobals import StringLib @@ -14,9 +18,9 @@ import StringLib #OGLOGSESSION, OGLOGCOMMAND, OGLOGFILE, OG_ERR_LOCKED, OG_ERR_PARTITION, OG_ERR_FORMAT, OG_ERR_NOTEXEC, OG_ERR_NOTFOUND def _logtype2logfile (t): - if 'log' == t: return ogGlobals.OGLOGFILE - elif 'command' == t: return ogGlobals.OGLOGCOMMAND - elif 'session' == t: return ogGlobals.OGLOGSESSION + if 'log' == t.lower(): return ogGlobals.OGLOGFILE + elif 'command' == t.lower(): return ogGlobals.OGLOGCOMMAND + elif 'session' == t.lower(): return ogGlobals.OGLOGSESSION else: raise Exception (f'unknown log type ({t})') #/** # ogEcho [str_logtype ...] [str_loglevel] "str_message" ... @@ -51,58 +55,88 @@ def ogEcho (logtypes, loglevel, msg): else: raise Exception (f'unknown loglevel ({loglevel})') -def ogExecAndLog(*args): - # Variables locales - ISCOMMAND = False - ISLOG = False - ISSESSION = False - COMMAND = "" - CONTINUE = 1 - FILES = "" - REDIREC = "" - FUNCNAME = ogExecAndLog.__name__ - # Si se solicita, mostrar ayuda. - if len(args) > 0 and args[0] == "help": - ogHelp(f"{FUNCNAME} str_logfile ... str_command ...", - f"{FUNCNAME} COMMAND ls -al /") +#/** +# ogExecAndLog str_logfile ... str_command ... +#@brief Ejecuta un comando y guarda su salida en fichero de registro. +#@param str_logfile fichero de registro (pueden ser varios). +#@param str_command comando y comandos a ejecutar. +#@return Salida de ejecución del comando. +#@note str_logfile = { LOG, SESSION, COMMAND } +#*/ +#ogHelp (str_logfile ... str_command ...", +#ogHelp ([], ogMyLib.ogSomeMethod, *args, **kwargs) +#ogHelp ('command', ogMyLib.ogSomeMethod, *args, **kwargs) +#ogHelp (['command'], ogMyLib.ogSomeMethod, *args, **kwargs) +#ogHelp (['log', 'command'], ogMyLib.ogSomeMethod, *args, **kwargs) +def ogExecAndLog (logtypes, fun, *args, **kwargs): + logfiles = ['/dev/stdout'] + if type (logtypes) is list: + for l in logtypes: + logtypes = list (map (lambda x: x.lower(), logtypes)) + logfiles.append (_logtype2logfile (l)) + else: ## string + logtypes = logtypes.lower() + logfiles.append (_logtype2logfile (logtypes)) + + if not fun: + ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, 'no function provided') return - # Procesar parámetros. - while CONTINUE: - arg = args.pop(0).lower() - if arg == "command": - ISCOMMAND = True - continue - elif arg == "log": - ISLOG = True - continue - elif arg == "session": - ISSESSION = True - continue - else: - COMMAND = " ".join(args) - CONTINUE = 0 + ## the original bash code does something like this: + #if [ $ISCOMMAND ]; then + # > $OGLOGCOMMAND + # REDIREC="2>&1" + #fi + #eval $COMMAND $REDIREC | tee -a $FILES - # Error si no se recibe un comando que ejecutar. - if not COMMAND: - ogRaiseError(OG_ERR_FORMAT) - return + ## a hybrid bash/python pseudocode would end up being like the following: + #if 'command' in logtypes: + # rm $OGLOGCOMMAND + # touch $OGLOGCOMMAND + # + #if 'command' in logtypes: + # ## redirect both stdout and stderr + # eval $COMMAND 2>&1 | tee -a $FILES + #else: + # ## redirect stdout only + # eval $COMMAND | tee -a $FILES - # Componer lista de ficheros de registro. - if ISCOMMAND: - FILES = OGLOGCOMMAND - open(FILES, "w").close() - REDIREC = "2>&1" - if ISLOG: - FILES += " " + OGLOGFILE - if ISSESSION: - FILES += " " + OGLOGSESSION + import time + sout = serr = '' + if 'command' in logtypes: + os.unlink (ogGlobals.OGLOGCOMMAND) + open (ogGlobals.OGLOGCOMMAND, 'w').close() + print ('nati: ogExecAndLog: about to redirect stdout and stderr') + time.sleep (1) ## nati + with redirect_stdout (StringIO()) as r_stdout, redirect_stderr (StringIO()) as r_stderr: + rc = fun (*args, **kwargs) + sout = r_stdout.getvalue() + serr = r_stderr.getvalue() + print (f'nati: ogExecAndLog: end of redirections, rc ({rc}) sout ({sout}) serr ({serr})') + time.sleep (1) ## nati + else: + print ('nati: ogExecAndLog: about to redirect stdout only') + time.sleep (1) + with redirect_stdout (StringIO()) as r_stdout: + rc = fun (*args, **kwargs) + sout = r_stdout.getvalue() + print (f'nati: ogExecAndLog: end of redirections, rc ({rc}) sout ({sout})') + time.sleep (1) ## nati - # Ejecutar comando. - subprocess.call(f"{COMMAND} {REDIREC} | tee -a {FILES}", shell=True) - # Salida de error del comando ejecutado. - return subprocess.PIPESTATUS[0] + if sout or serr: + print ('nati: ogExecAndLog: sout or serr are true') + time.sleep (1) ## nati + for f in logfiles: + print (f'nati: ogExecAndLog: logging to logfile ({f})') + with open (f, 'a') as fd: + if sout: fd.write (f"ogExecAndLog: {fun.__name__} stdout:\n{sout}") + else: fd.write (f"ogExecAndLog: {fun.__name__} stdout: (none)\n") + if serr: fd.write (f"ogExecAndLog: {fun.__name__} stderr:\n{serr}") + else: fd.write (f"ogExecAndLog: {fun.__name__} stderr: (none)\n") + + print (f'nati: ogExecAndLog: returning rc ({rc})') + return rc #/** # ogGetCaller @@ -211,14 +245,14 @@ def ogRaiseError (logtypes, code, msg): CODE = ogGlobals.OG_ERR_GENERIC call_stack = [i[3] for i in inspect.stack()] - if len (call_stack) < 3: return ## shouldn't happen + if len (call_stack) < 2: return ## shouldn't happen call_stack.pop() ## remove '' call_stack.pop(0) ## remove 'ogRaiseError' str_call_stack = ' '.join (call_stack) if code == ogGlobals.OG_ERR_FORMAT or \ StringLib.ogCheckStringInGroup (str_call_stack, ogGlobals.NODEBUGFUNCTIONS) or \ - not StringLib.ogCheckStringInGroup (call_stack[0], ogGlobals.NODEBUGFUNCTIONS): + not (len(call_stack)>0 and StringLib.ogCheckStringInGroup (call_stack[0], ogGlobals.NODEBUGFUNCTIONS)): ogEcho (logtypes, "error", f"{str_call_stack.replace(' ', '<-')}: {MSG}") return code diff --git a/client/shared/scripts/restoreImage.py b/client/shared/scripts/restoreImage.py new file mode 100644 index 0000000..f937239 --- /dev/null +++ b/client/shared/scripts/restoreImage.py @@ -0,0 +1,117 @@ +#!/usr/bin/python3 +#/** +#@file restoreImage +#@brief Script de ejemplo para restaurar una imagen. +#@param $1 Repositorio (CACHE, REPO o dirección IP) +#@param $2 Nombre canónico de la imagen (sin extensión) +#@param $3 Número de disco +#@param $4 Número de particion +#@param $5 Protocolo (UNICAST, UNICAST-DIRECT, MULTICAST o MULTICAST-DIRECT) +#@param $6 Opciones del protocolo +#@exception OG_ERR_FORMAT 1 formato incorrecto. +#@exception OG_ERR_NOTFOUND 2 cambio de repositorio: repositorio no encontrado +#@exception OG_ERR_NOTFOUND 2 fichero de imagen o partición no detectados. +#@exception $OG_ERR_MCASTRECEIVERFILE 57 Error en la recepción Multicast de un fichero +#@exception $OG_ERR_PROTOCOLJOINMASTER 60 Error en la conexión de una sesión Unicast|Multicast con el Master +#**/ + +import os +import os.path +import sys +import re +import time + +import ogGlobals +import SystemLib +import NetLib +import StringLib +import FileLib +import ImageLib +import ProtocolLib + +t0 = time.time() +prog = os.path.basename (sys.argv[0]) + +#Load engine configurator from engine.cfg file. +#Carga el configurador del engine desde el fichero engine.cfg +# Valores por defecto: #IMGPROG="partclone" ; #IMGCOMP="lzop" ; #IMGEXT="img" #IMGREDUCE="TRUE" +## (ogGlobals se encarga) + +# Clear temporary file used as log track by httpdlog +# Limpia los ficheros temporales usados como log de seguimiento para httpdlog +open (ogGlobals.OGLOGCOMMAND, 'w').close() +if SystemLib.ogGetCaller() not in ['deployImage', 'restoreImageCustom']: + open (ogGlobals.OGLOGSESSION, 'w').close() + +SystemLib.ogEcho (['log', 'session'], None, f'[1] {ogGlobals.lang.MSG_SCRIPTS_START} {prog} ({sys.argv})') + +# Procesar parámetros de entrada +print (f'argv ({sys.argv}) len ({len (sys.argv)})') +if len (sys.argv) < 6: + SystemLib.ogRaiseError ('session', ogGlobals.OG_ERR_FORMAT, f'{ogGlobals.lang.MSG_FORMAT}: {prog} REPO|CACHE imagen ndisco nparticion [ UNICAST|MULTICAST opciones protocolo]') + sys.exit (1) + +_, repo, imgname, disk, par, *other = sys.argv +proto = other[0].upper() if len (other) > 0 else 'UNICAST' +protoopt = other[1] if len (other) > 1 else '' +repo = repo.upper() +# Si MCASTWAIT menos que tiempo de espera del servidor lo aumento +MCASTWAIT = ogGlobals.MCASTWAIT +if ':' in protoopt: + port, wait = protoopt.split (':') +else: + port, wait = ('', '') +if proto.startswith ('MULTICAST') and re.match (r'^-?\d+$', wait): + if int (MCASTWAIT or 0) < int (wait): + MCASTWAIT = int (wait) + 5 +imgtype = 'img' +print (f'repo ({repo}) imgname ({imgname}) disk ({disk}) par ({par}) proto ({proto}) protoopt ({protoopt}) MCASTWAIT ({MCASTWAIT})') + +# Si es una ip y es igual a la del equipo restaura desde cache +if repo == NetLib.ogGetIpAddress(): repo = 'CACHE' +# Si es una ip y es distinta a la del recurso samba cambiamos de REPO. +if StringLib.ogCheckIpAddress (repo) or 'REPO' == repo: + # Si falla el cambio -> salimos con error repositorio no valido + if not NetLib.ogChangeRepo (repo): + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, repo) + sys.exit (1) + REPO = 'REPO' + +# Comprobar que existe la imagen del origen. +imgfile = FileLib.ogGetPath (repo, f'{imgname}.{imgtype}') +imgdir = FileLib.ogGetParentPath (repo, imgname) +print (f'imgfile ({imgfile}) imgdir ({imgdir})') +if not imgfile or not imgdir: + SystemLib.ogRaiseError ('session', ogGlobals.OG_ERR_NOTFOUND, f'{repo}, {imgname}') + sys.exit (1) + +# Procesar protocolos de transferencia. +retval = 0 +if proto in ['UNICAST', 'UNICAST-DIRECT']: + SystemLib.ogEcho (['log', 'session'], None, f'[40] ogRestoreImage {repo} {imgname} {disk} {par} UNICAST') + retval = SystemLib.ogExecAndLog ('command', ImageLib.ogRestoreImage, repo, imgname, disk, par) +elif proto in ['MULTICAST', 'MULTICAST-DIRECT']: + tool = ImageLib.ogGetImageProgram ('REPO', imgname) + if not tool: + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_IMAGE, f'could not get tool used for image {imgname}') + sys.exit (1) + + compress = ImageLib.ogGetImageCompressor ('REPO', imgname) + if not compress: + SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_IMAGE, f'could not get compressor used for image {imgname}') + sys.exit (1) + + SystemLib.ogEcho (['log', 'session'], None, f'[40] ogMcastReceiverPartition {disk} {par} {port} {tool} {compress}') + if not ProtocolLib.ogMcastRequest (f'{imgname}.img', protoopt): + sys.exit (1) + retval = SystemLib.ogExecAndLog ('command', ProtocolLib.ogMcastReceiverPartition, disk, par, port, tool, compress) +else: + SystemLib.ogRaiseError ('session', ogGlobals.OG_ERR_FORMAT, f'{ogGlobals.lang.MSG_FORMAT}: {prog} REPO|CACHE imagen ndisco nparticion [ UNICAST|MULTICAST opciones protocolo]') + sys.exit (1) + +t = time.time() - t0 +SystemLib.ogEcho (['log', 'session'], None, f'[100] Duracion de la operacion {int (t/60)}m {int (t%60)}s') + +# Código de salida del comando prinicpal de restauración. +sys.exit (retval) +