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

350 lines
13 KiB
Python

import os
import re
import subprocess
import shutil
import sys
import platform
import ogGlobals
import SystemLib
import DiskLib
import FileSystemLib
import CacheLib
#/**
# ogCreateCache [int_ndisk] int_partsize
#@brief Define la caché local, por defecto en partición 4 del disco 1.
#@param int_ndisk numero de disco donde crear la cache, si no se indica es el 1 por defecto
#@param int_npart número de partición (opcional, 4 por defecto)
#@param int_partsize tamaño de la partición (en KB)
#@return (nada, por determinar)
#@exception OG_ERR_FORMAT formato incorrecto.
#@note Requisitos: sfdisk, parted, awk, sed
#@warning El tamaño de caché debe estar entre 50 MB y la mitad del disco.
#@warning La caché no puede solaparse con las particiones de datos.
#*/ ##
def ogCreateCache (ndsk=1, part=4, sizecache=0):
if not sizecache:
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, '')
return None
sizecache = int (sizecache)
DISK = DiskLib.ogDiskToDev (ndsk)
if not DISK: return None
# PATCH Para discos nvme la particion debe ser p1, p2, etc...en lugar de 1,2, sino falla sfdisk
NVME_PREFIX = ''
if 'nvme' in DISK:
NVME_PREFIX = 'p'
END = DiskLib.ogGetLastSector (ndsk)
SIZE = 2 * sizecache
# Inicio partición cache según el disco tenga sectores de 4k o menores
IOSIZE = 0
fdisk_out = subprocess.run (['fdisk', '-l', DISK], capture_output=True, text=True).stdout
for l in fdisk_out.splitlines():
items = l.split()
if len(items) < 4: continue
if 'I/O' == items[0]:
IOSIZE = int (items[3])
break
START = 0
if 4096 == IOSIZE:
END -= 8192
START = END - SIZE + 2048 - (END-SIZE)%2048
else:
START = END - SIZE + 1
ENDPREVPART = None
i = 1
while True:
prev_part = part - i
if prev_part <= 0: break
ENDPREVPART = DiskLib.ogGetLastSector (ndsk, prev_part)
if ENDPREVPART: break
i += 1
if not ENDPREVPART:
ENDPREVPART=0
#SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, ndsk)
#return None
# Error si tamaño no está entre límites permitidos o si se solapa con la partición anterior.
MINSIZE = 25000
MAXSIZE = END
if SIZE < MINSIZE or SIZE > MAXSIZE or START < ENDPREVPART:
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, ndsk)
return None
# Desmontar todos los sistemas de archivos del disco.
FileSystemLib.ogUnmountAll (ndsk)
# Definir particiones y notificar al kernel.
# En el caso de ser disco GPT, de momento se borra la particion y se vuelve a crear,
# por lo que se pierden los datos.
pttype = DiskLib.ogGetPartitionTableType (ndsk)
if not pttype:
pttype = 'MSDOS'
DiskLib.ogCreatePartitionTable (ndsk, pttype)
get_ptt = DiskLib.ogGetPartitionTableType (ndsk)
if 'GPT' == get_ptt:
# Si la tabla de particiones no es valida, volver a generarla.
if subprocess.run (['sgdisk', '-p', DISK]).returncode:
subprocess.run (['gdisk', DISK], input='2\nw\nY\n', text=True)
# Si existe la cache se borra previamente
if ogFindCache(): ogDeleteCache()
# Capturamos el codigo de particion GPT para cache
# PATCH - Cuando es GPT, la particion con codigo CACHE (CA00) no existe y no puede crearse, se cambia por LINUX (8300)
ID = DiskLib.ogTypeToId ('LINUX', 'GPT')
subprocess.run (['sgdisk', DISK, f'-n{part}:{START}:{END}', f'-c{part}:CACHE', f'-t{part}:{ID}'])
elif 'MSDOS' == get_ptt:
# Si la tabla de particiones no es valida, volver a generarla.
if subprocess.run (['parted', '-s', DISK, 'print']).returncode:
subprocess.run (['fdisk', DISK], input='w\n', text=True)
# Definir particiones y notificar al kernel.
ID = DiskLib.ogTypeToId ('CACHE', 'MSDOS')
# Salvamos la configuración de las particiones e incluimos la cache.
tmp = subprocess.run (['sfdisk', '--dump', DISK], capture_output=True, text=True).stdout.splitlines()
tmp = [ x for x in tmp if f'{DISK}{part}' not in x ]
tmp.append (f'{DISK}{NVME_PREFIX}{part} : start= {START}, size= {SIZE}, Id={ID}')
# Ordenamos las líneas de los dispositivos
UNIT = [ x for x in tmp if 'unit' in x ][0]
tmp = sorted ([ x for x in tmp if re.match ('^/dev', x) ])
tmp = [UNIT, ''] + tmp
# Guardamos nueva configuración en el disco.
i = '\n'.join(tmp)
subprocess.run (['sfdisk', '--no-reread', DISK], input=i, text=True)
# Actualiza la tabla de particiones en el kernel.
DiskLib.ogUpdatePartitionTable()
#/**
# ogDeleteCache
#@brief Elimina la partición de caché local.
#@return (nada, por determinar)
#@exception OG_ERR_FORMAT formato incorrecto.
#@note Requisitos: fdisk, sgdisk, partprobe
#*/ ##
def ogDeleteCache():
cachepart = ogFindCache()
if not cachepart:
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, ogGlobals.lang.MSG_NOCACHE)
return None
ndisk, npart = cachepart.split()
disk = DiskLib.ogDiskToDev (ndisk)
# Desmontar todos los sistemas de archivos del disco.
FileSystemLib.ogUnmountAll (ndisk)
ptt = DiskLib.ogGetPartitionTableType (ndisk)
if 'GPT' == ptt:
# Si la tabla de particiones no es valida, volver a generarla.
if subprocess.run (['sgdisk', '-p', disk]).returncode:
subprocess.run (['gdisk', disk], input='2\nw\nY\n', text=True)
subprocess.run (['sgdisk', disk, f'-d{npart}'])
elif 'MSDOS' == ptt:
# Si la tabla de particiones no es valida, volver a generarla.
if subprocess.run (['parted', '-s', disk, 'print']).returncode:
subprocess.run (['fdisk', disk], input='w', text=True)
# Eliminar la partición de caché.
subprocess.run (['fdisk', disk], input=f'd\n{npart}\nw', text=True)
# Borrar etiqueta de la caché.
if os.path.exists ('/dev/disk/by-label/CACHE'):
os.unlink ('/dev/disk/by-label/CACHE')
#Actualiza la tabla de particiones en el kernel.
DiskLib.ogUpdatePartitionTable()
#/**
# ogFindCache
#@brief Detecta la partición caché local.
#@param No requiere parametros
#@return int_ndisk int_npart - devuelve el par nº de disco-nº de partición .
#@warning Si no hay cache no devuelve nada
#*/ ##
def ogFindCache():
# Obtener el dispositivo del sistema de archivos etiquetado como "CACHE".
PART = subprocess.run (['blkid', '-L', 'CACHE'], capture_output=True, text=True).stdout.strip()
# En discos nvme con particiones GPT la partición se detecta usando el tag PARTLABEL
if not PART:
out = subprocess.run (['blkid', '-t', 'PARTLABEL=CACHE'], capture_output=True, text=True).stdout.strip()
PART = out.split (':')[0]
# Si no se detecta, obtener particiones marcadas de tipo caché en discos MSDOS.
if not PART:
out = subprocess.run (['sfdisk', '-l'], capture_output=True, text=True).stdout.splitlines()
for l in out:
elems = l.split (maxsplit=5)
if 6 > len (elems): continue
if 'ca' in elems[5] or 'a7' in elems[5]:
PART=elems[0]
break
# Por último revisar todos los discos GPT y obtener las particiones etiquetadas como caché.
if not PART:
PART = ''
for d in DiskLib.ogDiskToDev():
out = subprocess.run (['sgdisk', '-p', d], capture_output=True, text=True).stdout.splitlines()
for l in out:
elems = l.split (maxsplit=6)
if 7 > len (elems): continue
if 'CACHE' in elems[6]:
p = 'p' if 'nvme' in d else ''
PART += f' {d}{p}{elems[0]}'
if not PART: return
return DiskLib.ogDevToDisk (PART.split()[0]) # usar la 1ª partición encontrada.
#/**
# ogFormatCache
#@brief Formatea el sistema de ficheros para la caché local.
#@return (por determinar)
#@warning Prueba con formato Reiser.
#@attention
#@note El sistema de archivos de la caché se queda montado.
#*/ ##
def ogFormatCache():
cachepart = ogFindCache()
if cachepart is None:
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, ogGlobals.lang.MSG_NOCACHE)
return
cachepart = cachepart.split()
disk = DiskLib.ogDiskToDev (cachepart[0], cachepart[1])
if not disk: return
ogUnmountCache()
options = "extent,large_file"
if re.match("^5", platform.release()):
options += ",uninit_bg,^metadata_csum,^64bit"
try:
subprocess.run(["mkfs.ext4", "-q", "-F", disk, "-L", "CACHE", "-O", options], check=True)
except subprocess.CalledProcessError as e:
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, "CACHE")
return
# Crear estructura básica.
mntdir = ogMountCache()
j = '/'.join ([mntdir, ogGlobals.OGIMG]) ## os.path.join doesn't work: "If a segment […] is an absolute path, all previous segments are ignored"
#print (f'cucu mntdir ({mntdir}) OGIMG ({ogGlobals.OGIMG}) j ({j})')
os.makedirs (j, exist_ok=True)
# Incluir kernel e Initrd del ogLive
## como lo llamo sin especificar el path entero?
#subprocess.run (['scripts/updateBootCache.py']) ## TODO
#/**
# ogGetCacheSize
#@brief Devuelve el tamaño definido para la partición de caché.
#@return int_partsize tamaño de la partición (en KB)
#@exception OG_ERR_PARTITION No existe partición de caché.
#*/ ##
def ogGetCacheSize():
cachepart = ogFindCache()
if cachepart is None:
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, ogGlobals.lang.MSG_NOCACHE)
return None
disk, par = cachepart.split()
return DiskLib.ogGetPartitionSize (disk, par)
#/**
# ogGetCacheSpace
#@brief Devuelve el espacio de disco disponible para la partición de caché.
#@return int_size tamaño disponible (en KB)
#@note El espacio disponible es el que hay entre el límite superior de la partición 3 del disco 1 y el final de dicho disco, y no puede ser superior a la mitad de dicho disco.
#*/ ##
def ogGetCacheSpace():
cachepart = ogFindCache()
if not cachepart:
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, ogGlobals.lang.MSG_NOCACHE)
return None
cachepart = cachepart.split()
disk = DiskLib.ogDiskToDev (cachepart[0])
if not disk:
return None
sectors = 0
disk_bn = os.path.basename (disk)
with open ('/proc/partitions', 'r') as fd:
proc_parts = fd.read()
for l in proc_parts.splitlines():
items = l.split()
if len(items) < 4: continue
if items[3] == disk_bn:
sectors = 2 * int (items[2])
if not sectors: return None
sfdisk_out = subprocess.run (['sfdisk', '-g', disk], capture_output=True, text=True).stdout
cyls = int (sfdisk_out.split()[1])
sectors = sectors/cyls * cyls - 1
## the original code has a hard dependency on the existence of a third partition
## if the disk has sda1, sda2 and sda4, the code fails.
## this is an improved version
endpart3 = 0
for trypart in [3, 2, 1]:
sfdisk_out = subprocess.run (['sfdisk', '-uS', '-l', disk], capture_output=True, text=True).stdout
for l in sfdisk_out.splitlines():
items = l.split()
if len(items) < 6: continue
if f'{disk}{trypart}' == items[0]:
endpart3 = int (items[2])
break
if endpart3: break
if not endpart3: return None
# Mostrar espacio libre en KB (1 KB = 2 sectores)
if endpart3 > sectors // 2:
return (sectors - endpart3) // 2
else:
return sectors // 4
#/**
# ogMountCache
#@brief Monta la partición Cache y exporta la variable $OGCAC
#@param sin parametros
#@return path_mountpoint - Punto de montaje del sistema de archivos de cache.
#@warning Salidas de errores no determinada
#*/ ##
def ogMountCache():
c = ogFindCache()
if not c:
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, ogGlobals.lang.MSG_NOCACHE)
return None
c = c.split()
m = FileSystemLib.ogMountFs (c[0], c[1])
if not m:
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, ogGlobals.lang.MSG_NOCACHE)
return None
return m
#/**
# ogUnmountCache
#@brief Desmonta la particion Cache y elimina la variable $OGCAC
#@param sin parametros
#@return nada
#@warning Salidas de errores no determinada
#*/ ##
def ogUnmountCache():
cachepart = ogFindCache()
if cachepart is None:
SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, ogGlobals.lang.MSG_NOCACHE)
return
cachepart = cachepart.split()
if not FileSystemLib.ogIsMounted (cachepart[0], cachepart[1]): return True
FileSystemLib.ogUnmountFs (cachepart[0], cachepart[1])
# Eliminar el enlace simbólico de /mnt/ParticiónCache.
dev = DiskLib.ogDiskToDev (cachepart[0], cachepart[1])
dev = dev.replace ('dev', 'mnt')
if os.path.exists (dev): os.remove (dev)