#/**
#@file    DiskLib.py
#@brief   Librería o clase Disk
#@class   Disk
#@brief   Funciones para gestión de discos y particiones.
#@warning License: GNU GPLv3+
#*/

import filecmp
import subprocess
import shutil
import os
import re
import stat
from pathlib import Path

import ogGlobals
import SystemLib
import CacheLib
import FileSystemLib
import InventoryLib

# Función ficticia para lanzar parted con timeout, evitando cuelgues del programa.
def parted(*args):
    parted_path = shutil.which("parted")
    if parted_path:
        try:
            result = subprocess.run(
                [parted_path] + list(args),
                timeout=3,
                capture_output=True,
                text=True
            )
            return result.stdout
        except subprocess.TimeoutExpired:
            return "Error: Command 'parted' timed out"
    else:
        return "Error: 'parted' command not found"


#/**
#         ogGetIoSize int_ndisk
#@brief   Devuelve el tamaño de sector de un disco
#@param   int_ndisk      nº de orden del disco
#@return  Tamaño de sector
#*/ ##
def ogGetIoSize (disk):
    DISK = ogDiskToDev (disk)
    if not DISK: return None

    IOSIZE = None
    fdisk_out = subprocess.run (['fdisk', '-l', DISK], capture_output=True, text=True).stdout
    for l in fdisk_out.splitlines():
        if 'I/O' not in l: continue
        items = l.split()
        if len(items) < 4: continue
        IOSIZE = items[3]
        break

    if IOSIZE is None:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'Could not extract disk sector size from fdisk output')
        return None

    return int (IOSIZE)


#/**
#         ogCreatePartitions int_ndisk str_parttype:int_partsize ...
#@brief   Define el conjunto de particiones de un disco.
#@param   int_ndisk      nº de orden del disco
#@param   str_parttype   mnemónico del tipo de partición
#@param   int_partsize   tamaño de la partición (en KB)
#@return  (nada, por determinar)
#@exception OG_ERR_FORMAT    formato incorrecto.
#@exception OG_ERR_NOTFOUND  disco o partición no detectado (no es un dispositivo).
#@exception OG_ERR_PARTITION error en partición o en tabla de particiones.
#@attention El nº de partición se indica por el orden de los párametros \c parttype:partsize
#@attention Pueden definirse particiones vacías de tipo \c EMPTY
#@attention No puede definirse partición de cache y no se modifica si existe.
#@note    Requisitos: sfdisk, parted, partprobe, awk
#@todo    Definir atributos (arranque, oculta) y tamaños en MB, GB, etc.
#*/ ##
def ogCreatePartitions (disk, parts):
    ND = disk
    DISK = ogDiskToDev (ND)
    if not DISK: return None

    PTTYPE = ogGetPartitionTableType (disk)
    if not PTTYPE: PTTYPE = 'MSDOS'  # Por defecto para discos vacíos.
    if PTTYPE not in ['MSDOS', 'GPT']:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, PTTYPE)
        return None
    if 'MSDOS' == PTTYPE: CACHE_ID = ogTypeToId ('CACHE', 'MSDOS')
    else:                 CACHE_ID = ogTypeToId ('CACHE', 'GPT_GUID')

    # Se calcula el ultimo sector del disco (total de sectores usables)
    SECTORS = ogGetLastSector (disk)

    # Se recalcula el nº de sectores del disco si existe partición de caché.
    CACHESIZE = 0
    CACHEPART = CacheLib.ogFindCache()
    if CACHEPART:
        cache_disk, cache_part = CACHEPART.split()
        if int (ND) == int (cache_disk):
            CACHESIZE = int (CacheLib.ogGetCacheSize()) * 2

    # Sector de inicio (la partición 1 empieza en el sector 63).
    IOSIZE = ogGetIoSize (ND)
    if not IOSIZE:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'Failed to get disk sector size')
        return None

    if 4096 == IOSIZE:
        START = 4096
        SECTORS -= 8192
        if CACHESIZE:
            SECTORS = SECTORS - CACHESIZE + 2048 - (SECTORS - CACHESIZE) % 2048 - 1
    else:    ## 512
        if 'MSDOS' == PTTYPE: START = 63
        else:                 START = 2048
        if CACHESIZE:
            SECTORS -= CACHESIZE

    PART = 1

    sfdisk_input = 'unit: sectors\n\n'
    NVME_PREFIX = 'p' if 'nvme' in DISK else ''
    EXTSTART = EXTSIZE = 0
    for p in parts:
        # Leer formato de cada parámetro - Tipo:Tamaño
        TYPE, SIZE = p.split (':')
        if TYPE is None or 'None' == TYPE: TYPE='EMPTY'

        # Obtener identificador de tipo de partición válido.
        if 'GPT' == PTTYPE:
            ID = ogTypeToId (TYPE, 'GPT_GUID')
        else:
            ID = ogTypeToId (TYPE, PTTYPE)

        # Conservar los datos de la partición de caché.
        if f'{ND} {PART}' == CACHEPART and CACHESIZE:
            sfdisk_input += f'{DISK}{NVME_PREFIX}{PART} : start={SECTORS+1}, size={CACHESIZE}, type={CACHE_ID}\n'
            PART += 1

        try:
            SIZE = int (SIZE)
        except ValueError:
            SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, SIZE)
            return None

        if 'CACHE' == TYPE or not ID:
            SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, TYPE)
            return None

        # Comprobar tamaño numérico y convertir en sectores
        ORIG_SIZE = SIZE                        ## para reportar en un error
        if 512 == IOSIZE: SIZE *= 2
        else:             SIZE = (SIZE+3)//4    ## sumamos 3 para que la división entera "redondee al alza"

        # Comprobar si la partición es extendida.
        if '5' == ID:
            if 'GPT' == PTTYPE:
                SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, 'EXTENDED')
                return None

            ## la extendida debe ser:
            ## - si no estamos creando cache: /dev/sda4
            ## - si sí estamos creando cache: /dev/sda3
            ## de lo contrario, crea eg. esto:
            ## Device     Boot     Start       End   Sectors   Size Id Type
            ## /dev/sda1              63 222444728 222444666 106,1G 82 Linux swap / Solaris
            ## /dev/sda2       222444729 244889394  22444666  10,7G  5 Extended       <---------
            ## /dev/sda3       244889395 251334060   6444666   3,1G 83 Linux          <---------
            ## /dev/sda4       314103633 314572766    469134 229,1M ca unknown
            ## /dev/sda5       222446777 224891442   2444666   1,2G 83 Linux
            ## /dev/sda6       224891443 229558330   4666888   2,2G 83 Linux
            ## la sda2 es extendida, y las lógicas deberían ser sda5, sda6, sda7
            ## pero esto mete una sda3 como lógica, y luego va el cache que es primaria... WTF
            error_in_extended = False
            if     CACHEPART and PART != 3: error_in_extended = True
            if not CACHEPART and PART != 4: error_in_extended = True
            if                   PART  > 4: error_in_extended = True
            if error_in_extended:
                SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'No se puede definir la partición extendida en la partición "{PART}"')
                return None

            # El inicio de la primera partición logica es el de la extendida más 4x512
            EXTSTART = START+2048
            EXTSIZE = SIZE-2048
        # Incluir particiones lógicas dentro de la partición extendida.
        if 5 == PART:
            if 'GPT' == PTTYPE:
                pass    ## sin problema, la partición 5 es simplemente una más
            else:
                if not EXTSTART:
                    SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, 'la partición 5 es lógica pero no hay ninguna EXTENDED definida antes')
                    return None
                START = EXTSTART
                SECTORS = EXTSTART+EXTSIZE

        # Generar datos para la partición.
        # En el caso de que la partición sea EMPTY no se crea nada
        if 'EMPTY' != TYPE:
            sfdisk_input += f'{DISK}{NVME_PREFIX}{PART} : start={START}, size={SIZE}, type={ID}\n'

        START += SIZE

        if 'MSDOS' == PTTYPE and 4096 == IOSIZE and PART > 4:
            START += 2048

        # Error si se supera el nº total de sectores.
        if START > SECTORS:
            SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'partición "{TYPE}:{ORIG_SIZE}" no cabe')
            return None
        PART += 1

    # Si no se indican las 4 particiones primarias, definirlas como vacías, conservando la partición de caché.
    while PART <= 4:
        if f'{ND} {PART}' == CACHEPART and CACHESIZE:
            sfdisk_input += f'{DISK}{NVME_PREFIX}{PART} : start={SECTORS+1}, size={CACHESIZE}, type={CACHE_ID}\n'
        else:
            sfdisk_input += f'{DISK}{NVME_PREFIX}{PART} : start=0, size=0, type=0\n'
        PART += 1

    # Si se define partición extendida sin lógicas, crear particion 5 vacía.
    if 5 == PART and EXTSTART:
        ID = ogTypeToId ('WINDOWS', PTTYPE)
        sfdisk_input += f'{DISK}5 : start={EXTSTART}, SIZE={EXTSIZE}, type={ID}\n'

    # Desmontar los sistemas de archivos del disco antes de realizar las operaciones.
    FileSystemLib.ogUnmountAll (ND)
    if CACHESIZE: CacheLib.ogUnmountCache()

    # Si la tabla de particiones no es valida, volver a generarla.
    ogCreatePartitionTable (ND)

    # Definir particiones y notificar al kernel.
    p = subprocess.run (['sfdisk', DISK], input=sfdisk_input, text=True)
    subprocess.run (['partprobe', DISK])
    if CACHESIZE: CacheLib.ogMountCache()
    return not p.returncode


#/**
#         ogCreatePartitionTable int_ndisk [str_tabletype]
#@brief   Genera una tabla de particiones en caso de que no sea valida, si es valida no hace nada.
#@param   int_ndisk      nº de orden del disco
#@param   str_tabletype  tipo de tabla de particiones (opcional)
#@return  (por determinar)
#@exception OG_ERR_FORMAT   Formato incorrecto.
#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo.
#@note    tabletype: { MSDOS, GPT }, MSDOS por defecto
#@note    Requisitos: fdisk, gdisk, parted
#*/ ##
def ogCreatePartitionTable (disk, createptt=None):
    DISK = ogDiskToDev (disk)
    if not DISK: return None

    pttype = ogGetPartitionTableType (disk) or 'MSDOS'  # Por defecto para discos vacíos.
    createptt = createptt or pttype
    CREATE = None

# Si la tabla actual y la que se indica son iguales, se comprueba si hay que regenerarla.
    if createptt == pttype:
        if 'GPT' == pttype:
            try:
                result = subprocess.run (['sgdisk', '-p', DISK], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                if result.returncode:
                    CREATE = 'GPT'
            except subprocess.CalledProcessError:
                CREATE = 'GPT'
        elif 'MSDOS' == pttype:
            try:
                result = subprocess.run (['parted', '-s', DISK, 'print'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                if result.returncode:
                    CREATE = 'MSDOS'
            except subprocess.CalledProcessError:
                CREATE = 'MSDOS'
    else:
        CREATE = createptt.upper()

# Dependiendo del valor de CREATE, creamos la tabla de particiones en cada caso.
    if 'GPT' == CREATE:
        if 'MSDOS' == pttype:
            subprocess.run (['sgdisk', '-go', DISK])
        else:
            subprocess.run (['gdisk', DISK], input='2\nw\nY\n', text=True)
        subprocess.run (['partprobe', DISK])
    elif 'MSDOS' == CREATE:
        if 'GPT' == pttype:
            subprocess.run (['sgdisk', '-Z', DISK])
        # Crear y borrar una partición para que la tabla se genere bien.
        subprocess.run (['fdisk', DISK], input='o\nn\np\n\n\n\nd\n\nw', text=True)
        subprocess.run (['partprobe', DISK])

    return None


#/**
#         ogDeletePartitionTable ndisk
#@brief   Borra la tabla de particiones del disco.
#@param   int_ndisk      nº de orden del disco
#@return  la informacion propia del fdisk
#*/ ##
def ogDeletePartitionTable (disk):
    DISK = ogDiskToDev (disk)
    if not DISK: return None

    PTTYPE = ogGetPartitionTableType (disk)
    if 'GPT' == PTTYPE:
        subprocess.run (['sgdisk', '--clear', DISK])
    elif 'MSDOS' == PTTYPE:
        subprocess.run (['fdisk', DISK], input='o\nw', text=True)
    return

#/**
#         ogDevToDisk path_device | LABEL="str_label" | UUID="str_uuid"
#@brief   Devuelve el nº de orden de dicso (y partición) correspondiente al nombre de fichero de dispositivo o a la etiqueta o UUID del sistema de archivos asociado.
#@param   path_device  Camino del fichero de dispositivo.
#@param   str_label    etiqueta de sistema de archivos.
#@param   str_uuid     UUID de sistema de archivos.
#@return  int_ndisk (para dispositivo de disco)
#@return  int_ndisk int_npartition (para dispositivo de partición).
#@exception OG_ERR_FORMAT   Formato incorrecto.
#@exception OG_ERR_NOTFOUND Dispositivo no detectado.
#@note    Solo se acepta en cada llamada 1 de los 3 tipos de parámetros.
#*/ ##
def ogDevToDisk(arg_dev):
    CACHEFILE = "/var/cache/disks.cfg"

    if '=' in arg_dev:
        # arg_dev is "FOO=bar"
        cmd = None
        if   arg_dev.startswith("LABEL="):     cmd = ['blkid', '-L', arg_dev[6:]]
        elif arg_dev.startswith("PARTLABEL="): cmd = ['realpath', '/dev/disk/by-partlabel/'+arg_dev[11:]]
        elif arg_dev.startswith("PARTUUID="):  cmd = ['realpath', '/dev/disk/by-partuuid/'+arg_dev[10:]]
        elif arg_dev.startswith("UUID="):      cmd = ['blkid', '-U', arg_dev[5:]]
        if not cmd:
            SystemLib.ogRaiseError([], ogGlobals.OG_ERR_FORMAT, arg_dev)
            return
        DEV = subprocess.run (cmd, capture_output=True, text=True).stdout.strip()
    else:
        # arg_dev is "/dev/something"
        DEV = arg_dev

    if not os.path.exists(DEV):
        if '/dev/loop' != DEV[0:9]:
            SystemLib.ogRaiseError([], ogGlobals.OG_ERR_NOTFOUND, arg_dev)
        return

    # Error si no es fichero de bloques o directorio (para LVM).
    m = os.stat (DEV, follow_symlinks=True).st_mode
    if not stat.S_ISBLK (m) and not stat.S_ISDIR (m):
        if '/dev/loop' != DEV[0:9]:
            SystemLib.ogRaiseError([], ogGlobals.OG_ERR_NOTFOUND, arg_dev)
        return

    # Buscar en fichero de caché de discos.
    PART = None
    if os.path.exists(CACHEFILE):
        with open(CACHEFILE, 'r') as f:
            for line in f:
                parts = line.strip().split(':')
                if len(parts) == 2 and parts[1] == DEV:
                    PART = parts[0]
                    break

    if PART: return PART

    # Si no se encuentra, procesa todos los discos para devolver su nº de orden y de partición.
    disks = ogDiskToDev()
    n = 1
    for d in disks:
        NVME_PREFIX = "p" if "nvme" in d else ""
        if DEV.startswith(d):
            return f"{n} {DEV[len(d) + len(NVME_PREFIX):]}"
        n += 1

    if '/dev/loop' != DEV[0:9]:
        SystemLib.ogRaiseError([], ogGlobals.OG_ERR_NOTFOUND, arg_dev)
    return

def _getAllDisks():
    ret = []
    all_disks = subprocess.run("lsblk -n -e 1,2 -x NAME 2>/dev/null || lsblk -n -e 1,2", shell=True, capture_output=True, text=True).stdout.splitlines()
    for line in all_disks:
        parts = line.split()
        if parts[5] == "disk":
            parts[0].replace ('!', '/')
            ret.append(f"/dev/{parts[0]}")
    return ret

def _getAllVolGroups():
    vgs = subprocess.run(['vgs', '-a', '--noheadings'], capture_output=True, text=True).stdout.splitlines()
    ret = [f"/dev/{line.split()[0]}" for line in vgs]
    return ret

def _getMPath():
    ret = alldisks2remove = []
    try:
        mpath = subprocess.run(['multipath', '-l', '-v', '1'], capture_output=True, text=True).stdout.splitlines()
        ret = [f"/dev/mapper/{line.split()[0]}" for line in mpath]
        for line in subprocess.getoutput("multipath -ll").splitlines():
            if line.split()[5] == "ready":
                alldisks2remove.append (f"/dev/{line.split()[2]}")
    except FileNotFoundError:
        pass
    except subprocess.CalledProcessError:
        pass
    return ret, alldisks2remove

def _getAllZFSVols():
    zfsvols = subprocess.run(['blkid'], capture_output=True, text=True).stdout.splitlines()
    return [line.split(":")[0] for line in zfsvols if "zfs" in line]

#/**
#         ogDiskToDev [int_ndisk [int_npartition]]
#@brief   Devuelve la equivalencia entre el nº de orden del dispositivo (dicso o partición) y el nombre de fichero de dispositivo correspondiente.
#@param   int_ndisk      nº de orden del disco
#@param   int_npartition nº de orden de la partición
#@return  Para 0 parametros: Devuelve los nombres de ficheros  de los dispositivos sata/ata/usb linux encontrados.
#@return  Para 1 parametros: Devuelve la ruta del disco duro indicado.
#@return  Para 2 parametros: Devuelve la ruta de la particion indicada.
#@exception OG_ERR_FORMAT   Formato incorrecto.
#@exception OG_ERR_NOTFOUND Dispositivo no detectado.
#*/ ##
def ogDiskToDev (arg_disk=None, arg_part=None):
    CACHEFILE = "/var/cache/disks.cfg"

    try:
        if arg_part != None:
            arg_part = int (arg_part)
    except:
        SystemLib.ogRaiseError([], ogGlobals.OG_ERR_FORMAT, f"{arg_disk} {arg_part}")
        return

    try:
        if arg_disk != None:
            arg_disk = int (arg_disk)
    except:
        SystemLib.ogRaiseError([], ogGlobals.OG_ERR_FORMAT, arg_disk)
        return

    # Borrar fichero de caché de configuración si hay cambios en las particiones.
    proc_partitions = Path ('/proc/partitions').read_text()
    tmp_partitions  = Path ('/tmp/.partitions').read_text() if os.path.exists ('/tmp/.partitions') else ''
    if proc_partitions != tmp_partitions:
        # Guardar copia de las particiones definidas para comprobar cambios.
        shutil.copy2('/proc/partitions', '/tmp/.partitions')
        try:
            os.remove(CACHEFILE)
        except FileNotFoundError:
            pass

    # Si existe una correspondencia con disco/dispositivo en el caché; mostrarlo y salir.
    if arg_disk and os.path.exists (CACHEFILE):
        with open(CACHEFILE, 'r') as f:
            args_joined = ' '.join (map (str, filter (None, [arg_disk,arg_part])))
            for line in f:
                parts = line.strip().split(':')
                if len(parts) == 2 and parts[0] == args_joined:
                    return parts[1]

    # Continuar para detectar nuevos dispositivos.

    ALLDISKS = _getAllDisks()

    VOLGROUPS = _getAllVolGroups()
    ALLDISKS += VOLGROUPS

    MPATH, ALLDISKS_to_remove =_getMPath()
    for d in ALLDISKS_to_remove:
        if d in ALLDISKS: ALLDISKS.remove (d)

    ZFSVOLS = _getAllZFSVols()
    ALLDISKS += ZFSVOLS

    # No params: return all disks
    if arg_disk is None:
        return ALLDISKS

    # arg_disk is set: return it
    if arg_part is None:
        if arg_disk > len (ALLDISKS):
            SystemLib.ogRaiseError([], ogGlobals.OG_ERR_NOTFOUND, arg_disk)
            return
        disk = ALLDISKS[arg_disk-1]
        if not os.path.exists(disk):
            SystemLib.ogRaiseError([], ogGlobals.OG_ERR_NOTFOUND, arg_disk)
            return
        # Actualizar caché de configuración y mostrar dispositivo.
        with open(CACHEFILE, 'a') as f:
            f.write(f"{arg_disk}:{disk}\n")
        return disk

    # arg_disk and arg_part are set: there are several possibilities
    if arg_disk > len (ALLDISKS):
        SystemLib.ogRaiseError([], ogGlobals.OG_ERR_NOTFOUND, arg_disk)
        return
    disk = ALLDISKS[arg_disk-1]
    if not os.path.exists(disk):
        SystemLib.ogRaiseError([], ogGlobals.OG_ERR_NOTFOUND, arg_disk)
        return

    # Plain partition
    part = f"{disk}{arg_part}"
    if os.path.exists(part):
        # Actualizar caché de configuración y mostrar dispositivo.
        with open(CACHEFILE, 'a') as f:
            f.write(f"{arg_disk} {arg_part}:{part}\n")
        return part

    # RAID/Multipath (tener en cuenta enlace simbólico).
    part = f"{disk}p{arg_part}"
    if os.path.exists(part) and stat.S_ISBLK (os.stat (part, follow_symlinks=True).st_mode):
        # Actualizar caché de configuración y mostrar dispositivo.
        with open(CACHEFILE, 'a') as f:
            f.write(f"{arg_disk} {arg_part}:{part}\n")
        return part

    part = ""
    # Logical volume
    if disk in VOLGROUPS:
        lvscan = subprocess.run (['lvscan', '-a'], capture_output=True, text=True).stdout.splitlines()
        i = 0
        for line in lvscan:
            parts = line.split("'")
            if parts[1].startswith(f"{disk}/") and i == arg_part:
                part = parts[1]
                break
            i += 1

    # ZFS volume
    if disk in ZFSVOLS:
        subprocess.run(["zpool", "import", "-f", "-R", "/mnt", "-N", "-a"])
        zpool = subprocess.run(['blkid', '-s', 'LABEL', '-o', 'value', disk], capture_output=True, text=True).stdout
        zfs_list = subprocess.run(['zfs', 'list', '-Hp', '-o', 'name,canmount,mountpoint', '-r', zpool], capture_output=True, text=True).stdout.splitlines()
        i = 0
        for line in zfs_list:
            parts = line.split()
            if parts[1] == "on" and parts[2] != "none":
                if i == int(args[1]):
                    part = parts[0]
                    break
                i += 1

    if not part:
        SystemLib.ogRaiseError([], ogGlobals.OG_ERR_NOTFOUND, f"{arg_disk} {arg_part}")
        return

    # Actualizar caché de configuración y mostrar dispositivo.
    with open(CACHEFILE, 'a') as f:
        f.write(f"{arg_disk} {arg_part}:{part}\n")

    return part


#/**
#         ogGetDiskSize int_ndisk
#@brief   Muestra el tamaño en KB de un disco.
#@param   int_ndisk   nº de orden del disco
#@return  int_size  - Tamaño en KB del disco.
#@exception OG_ERR_FORMAT   formato incorrecto.
#@exception OG_ERR_NOTFOUND disco o particion no detectado (no es un dispositivo).
#@note    Requisitos: sfdisk, awk
#*/ ##
def ogGetDiskSize (disk):
    DISK = ogDiskToDev (disk)
    if not DISK: return None

    bn = os.path.basename (DISK)
    SIZE = None
    with open ('/proc/partitions', 'r') as fd:
        while True:
            l = fd.readline()
            if not l: break
            items = l.split()
            if len(items) < 4: continue
            if items[3] == bn:
                SIZE = int (items[2])
                break
    if not SIZE:
        vgs_out = subprocess.run (['vgs', '--noheadings', '--units=B', '-o', 'dev_size', DISK], capture_output=True, text=True).stdout
        items = vgs_out.split()
        SIZE = int (items[0]) // 1024

    return SIZE


#/**
#         ogGetDiskType path_device
#@brief   Muestra el tipo de disco (real, RAID, meta-disco, USB, etc.).
#@param   path_device  Dispositivo
#@exception OG_ERR_FORMAT   formato incorrecto.
#@exception OG_ERR_NOTFOUND disco no detectado o no es un dispositivo de bloques.
#@note    Requisitos: udevadm
#*/ ##
def ogGetDiskType (dev):
    DEV = os.path.basename (dev)

    bn = os.path.basename (DEV)
    MAJOR = None
    with open ('/proc/partitions', 'r') as fd:
        while True:
            l = fd.readline()
            if not l: break
            items = l.split()
            if len(items) < 4: continue
            if items[3] == bn:
                MAJOR = items[0]
                break

    TYPE = None
    with open ('/proc/devices', 'r') as fd:
        within_block_section = False
        while True:
            l = fd.readline()
            if not l: break

            if 'Block' in l:
                within_block_section = True
                continue
            if not within_block_section:
                continue

            items = l.split()
            if len(items) < 2: continue
            if items[0] == MAJOR:
                TYPE = items[1].upper()
                break

    # Devolver mnemónico del driver de dispositivo.
    if 'SD' == TYPE:
        TYPE = 'DISK'
        udevadm_out = subprocess.run (['udevadm', 'info', '-q', 'property', dev], capture_output=True, text=True).stdout
        for l in udevadm_out.splitlines():
            if 'ID_BUS=usb' == l[0:10]:
                TYPE = 'USB'
    elif 'BLKEXT' == TYPE:
        TYPE = 'NVM'
    elif 'SR' == TYPE or TYPE.startswith ('IDE'):
        TYPE = 'CDROM'  # FIXME Comprobar discos IDE.
    elif 'MD' == TYPE or TYPE.startswith ('CCISS'):
        TYPE = 'RAID'
    elif 'DEVICE-MAPPER' == TYPE:
        TYPE = 'MAPPER'  # FIXME Comprobar LVM y RAID.

    return TYPE


#/**
#         ogGetEsp
#@brief   Devuelve números de disco y partición para la partición EFI (ESP).
#*/ ##
def ogGetEsp():
    devices = subprocess.run (['blkid', '-o', 'device'], capture_output=True, text=True).stdout.splitlines()
    devices.sort()
    for d in devices:
        # Previene error para /dev/loop0
        PART = ogDevToDisk (d)
        if not PART: continue

        # En discos NVMe blkid devuelve una salida del tipo:
        #    >/dev/loop0
        #    >/dev/nvme0n1
        #    >/dev/nvme0n1p1
        # al analizar la particion nvme0n1, PART solo tiene un argumento y hace que ogGetPartitionId lance un error
        if len (PART) > 1:
            disk, par = PART.split()
            if ogGetPartitionId (disk, par) == ogTypeToId ('EFI', 'GPT'):
                return PART
    return None


#/**
#         ogGetLastSector int_ndisk [int_npart]
#@brief   Devuelve el último sector usable del disco o de una partición.
#@param   int_ndisk      nº de orden del disco
#@param   int_npart      nº de orden de la partición (opcional)
#@return  Último sector usable.
#@exception OG_ERR_FORMAT   Formato incorrecto.
#@exception OG_ERR_NOTFOUND Disco o partición no corresponde con un dispositivo.
#@note    Requisitos: sfdisk, sgdisk
#*/ ##
def ogGetLastSector (disk, par=None):
    DISK = ogDiskToDev (disk)
    if not DISK: return None
    env = os.environ
    env['LANG'] = 'C'
    last = None
    if par:
        PART = ogDiskToDev (disk, par)
        if not PART: return None
        sgdisk_out = subprocess.run (['sgdisk', '-p', DISK], env=env, capture_output=True, text=True).stdout
        for l in sgdisk_out.splitlines():
            items = l.split()
            if len(items) < 3: continue
            if str (par) != items[0]: continue
            last = int (items[2])
            break

    else:
        sgdisk_out = subprocess.run (['sgdisk', '-p', DISK], env=env, capture_output=True, text=True).stdout
        for l in sgdisk_out.splitlines():
            if 'last usable sector' not in l: continue
            items = l.split()
            last = int (items[-1])

    return last


#/**
#         ogGetPartitionActive int_ndisk
#@brief   Muestra que particion de un disco esta marcada como de activa.
#@param   int_ndisk   nº de orden del disco
#@exception OG_ERR_FORMAT Formato incorrecto.
#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo.
#@note    Requisitos: parted
#@todo    Queda definir formato para atributos (arranque, oculta, ...).
#*/ ##
def ogGetPartitionActive (disk):
    DISK = ogDiskToDev (disk)
    if DISK is None: return

    if 'LANG' in os.environ:
        lang = os.environ['LANG']

    ret = None
    os.environ['LANG'] = 'C'
    lines = subprocess.run (['parted', '--script', '--machine', DISK, 'print'], capture_output=True, text=True).stdout.splitlines()
    for line in lines:
        parts = line.split (':')
        if len (parts) < 6: continue
        if 'boot' in parts[6]:
            ret = parts[0]
            break

    if lang is None:
        if 'LANG' in os.environ: del os.environ['LANG']
    else:
        os.environ['LANG'] = lang

    return ret


#/**
#         ogGetPartitionId int_ndisk int_npartition
#@brief   Devuelve el mnemónico con el tipo de partición.
#@param   int_ndisk      nº de orden del disco
#@param   int_npartition nº de orden de la partición
#@return  Identificador de tipo de partición.
#@exception OG_ERR_FORMAT   Formato incorrecto.
#@exception OG_ERR_NOTFOUND Disco o partición no corresponde con un dispositivo.
#@note    Requisitos: sfdisk
#*/ ##
def ogGetPartitionId (disk, par):
    DISK = ogDiskToDev (disk)
    if DISK is None: return
    fsid = None

    pttype = ogGetPartitionTableType (disk)
    if 'GPT' == pttype:
        lines = subprocess.run (['sgdisk', '-p', DISK], capture_output=True, text=True).stdout.splitlines()
        start_index = next (i for i, line in enumerate(lines) if 'Number' in line)
        for l in lines[start_index:]:
            idx, start, end, sz, sz_units, code, *rest = l.split()
            if idx == str(par):
                fsid = code
                break
        if fsid == '8300' and f'{disk} {par}' == CacheLib.ogFindCache():
            fsid = 'CA00'
    elif 'MSDOS' == pttype:
        fsid = subprocess.run (['sfdisk', '--part-type', DISK, str(par)], capture_output=True, text=True).stdout.strip()
    elif 'LVM' == pttype:
        fsid = '10000'
    elif 'ZPOOL' == pttype:
        fsid = '10010'

    return fsid


#/**
#         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

    sz = subprocess.run (['partx', '-gbo', 'SIZE', PART], capture_output=True, text=True).stdout.strip()
    if sz: return int (int (sz) / 1024)

    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)


#/**
#         ogGetPartitionsNumber int_ndisk
#@brief   Detecta el numero de particiones del disco duro indicado.
#@param   int_ndisk      nº de orden del disco
#@return  Devuelve el numero paritiones del disco duro indicado
#@warning Salidas de errores no determinada
#@attention Requisitos: parted
#@note    Notas sin especificar
#*/ ##
def ogGetPartitionsNumber (disk):
    DISK = ogDiskToDev (disk)
    if not DISK: return None
    out = 0

    pttype = ogGetPartitionTableType (disk)
    if pttype in ['GPT', 'MSDOS']:
        partx_out = subprocess.run (['partx', '-gso', 'NR', DISK], capture_output=True, text=True).stdout
        lines = partx_out.splitlines()
        if len(lines):
            out = lines[-1].strip()
    elif 'LVM' == pttype:
        lvs_out = subprocess.run (['lvs', '--noheadings', DISK], capture_output=True, text=True).stdout
        lines = lvs_out.splitlines()
        out = len (lines)
    elif 'ZPOOL' == pttype:
        if subprocess.run (['zpool', 'list']).returncode:
            subprocess.run (['modprobe', 'zfs'])
        subprocess.run (['zpool', 'import', '-f', '-R', '/mnt', '-N', '-a'])
        blkid = subprocess.run (['blkid', '-s', 'LABEL', '-o', 'value', DISK], capture_output=True, text=True).stdout
        zfs_out = subprocess.run (['zfs', 'list', '-Hp', '-o', 'name,canmount,mountpoint', '-r', blkid], capture_output=True, text=True).stdout
        out = 0
        for l in zfs_out.splitlines():
            items = l.split()
            if len(items) < 3: continue
            if 'on' == items[1] and 'none' != items[2]: out += 1

    return int (out)


#/**
#         ogGetPartitionTableType int_ndisk
#@brief   Devuelve el tipo de tabla de particiones del disco (GPT o MSDOS)
#@param   int_ndisk       nº de orden del disco
#@return  str_tabletype - Tipo de tabla de paritiones
#@warning Salidas de errores no determinada
#@note    tabletype = { MSDOS, GPT }
#@note    Requisitos: blkid, parted, vgs
#*/ ##
def ogGetPartitionTableType (disk):
    DISK = ogDiskToDev (disk)
    if DISK is None: return

    m = os.stat (DISK, follow_symlinks=True).st_mode
    if stat.S_ISBLK (m):
        lines = subprocess.run (['parted', '--script', '--machine', DISK, 'print'], capture_output=True, text=True).stdout.splitlines()
        for l in lines:
            elems = l.split (':')
            if DISK == elems[0]:
                type = elems[5].upper()
                break

    # Comprobar si es volumen lógico.
    if os.path.isdir (DISK) and 0 == subprocess.run (['vgs', DISK]).returncode:
        type = 'LVM'

    # Comprobar si es pool de ZFS.
    if not type or 'UNKNOWN' == type:
        if 'zfs' in subprocess.run (['blkid', '-s', 'TYPE', DISK], capture_output=True, text=True).stdout:
            type = 'ZPOOL'

    return type


#/**
#         ogGetPartitionType int_ndisk int_npartition
#@brief   Devuelve el mnemonico con el tipo de partición.
#@param   int_ndisk      nº de orden del disco
#@param   int_npartition nº de orden de la partición
#@return  Mnemonico
#@note    Mnemonico: valor devuelto por ogIdToType.
#@exception OG_ERR_FORMAT   Formato incorrecto.
#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo.
#*/ ##
def ogGetPartitionType (disk, par):
    ID = ogGetPartitionId (disk, par)
    if ID is None: return None

    return ogIdToType (ID)


#/**
#         ogHidePartition int_ndisk int_npartition
#@brief   Oculta un apartición visible.
#@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 detectado (no es un dispositivo).
#@exception OG_ERR_PARTITION tipo de partición no reconocido.
#*/ ##
def ogHidePartition (disk, par):
    PART = ogDiskToDev (disk, par)
    if not PART: return None

    TYPE = ogGetPartitionType (disk, par)
    if   'NTFS'    == TYPE: NEWTYPE = 'HNTFS'
    elif 'FAT32'   == TYPE: NEWTYPE = 'HFAT32'
    elif 'FAT16'   == TYPE: NEWTYPE = 'HFAT16'
    elif 'FAT12'   == TYPE: NEWTYPE = 'HFAT12'
    elif 'WINDOWS' == TYPE: NEWTYPE = 'WIN-RESERV'
    else:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, TYPE)
        return None

    ogSetPartitionType (disk, par, NEWTYPE)


#/**
#         ogIdToType int_idpart
#@brief   Devuelve el identificador correspondiente a un tipo de partición.
#@param   int_idpart    identificador de tipo de partición.
#@return  str_parttype  mnemónico de tipo de partición.
#@exception OG_ERR_FORMAT   Formato incorrecto.
#*/ ##
def ogIdToType (ID):
    # Obtener valor hexadecimal de 4 caracteres rellenado con 0 por delante.
    ID = ID.zfill(4).lower()

    id2type = {
        '0000':    'EMPTY',
        '0001':    'FAT12',
        '0005':    'EXTENDED',
        '000f':    'EXTENDED',
        '0006':    'FAT16',
        '000e':    'FAT16',
        '0007':    'NTFS',
        '000b':    'FAT32',
        '000c':    'FAT32',
        '0011':    'HFAT12',
        '0012':    'COMPAQDIAG',
        '0016':    'HFAT16',
        '001e':    'HFAT16',
        '0017':    'HNTFS',
        '001b':    'HFAT32',
        '001c':    'HFAT32',
        '0042':    'WIN-DYNAMIC',
        '0082':    'LINUX-SWAP',
        '8200':    'LINUX-SWAP',
        '0083':    'LINUX',
        '8300':    'LINUX',
        '008e':    'LINUX-LVM',
        '8E00':    'LINUX-LVM',
        '00a5':    'FREEBSD',
        'a503':    'FREEBSD',
        '00a6':    'OPENBSD',
        '00a7':    'CACHE',  # (compatibilidad con Brutalix)
        '00af':    'HFS',
        'af00':    'HFS',
        '00be':    'SOLARIS-BOOT',
        'be00':    'SOLARIS-BOOT',
        '00bf':    'SOLARIS',
        'bf00145': 'SOLARIS',
        '00ca':    'CACHE',
        'ca00':    'CACHE',
        '00da':    'DATA',
        '00ee':    'GPT',
        '00ef':    'EFI',
        'ef00':    'EFI',
        '00fb':    'VMFS',
        '00fd':    'LINUX-RAID',
        'fd00':    'LINUX-RAID',
        '0700':    'WINDOWS',
        '0c01':    'WIN-RESERV',
        '7f00':    'CHROMEOS-KRN',
        '7f01':    'CHROMEOS',
        '7f02':    'CHROMEOS-RESERV',
        '8301':    'LINUX-RESERV',
        'a500':    'FREEBSD-DISK',
        'a501':    'FREEBSD-BOOT',
        'a502':    'FREEBSD-SWAP',
        'ab00':    'HFS-BOOT',
        'af01':    'HFS-RAID',
        'bf02':    'SOLARIS-SWAP',
        'bf03':    'SOLARIS-DISK',
        'ef01':    'MBR',
        'ef02':    'BIOS-BOOT',
        '10000':   'LVM-LV',
        '10010':   'ZFS-VOL',
    }
    if ID in id2type:
        return id2type[ID]
    return 'UNKNOWN'


#         ogIsDiskLocked int_ndisk
#@brief   Comprueba si un disco está bloqueado por una operación de uso exclusivo.
#@param   int_ndisk      nº de orden del disco
#@return  Código de salida: 0 - bloqueado, 1 - sin bloquear o error.
#@note    Los ficheros de bloqueo se localizan en \c /var/lock/dev, siendo \c dev el dispositivo de la partición o de su disco, sustituyendo el carácter "/" por "-".
#*/ ##
def ogIsDiskLocked (disk):
    DISK = ogDiskToDev (disk)
    if not DISK: return False
    return os.path.isfile (f'/var/lock/lock{DISK.replace("/", "-")}')


#/**
#         ogListPartitions int_ndisk
#@brief   Lista las particiones definidas en un disco.
#@param   int_ndisk  nº de orden del disco
#@return  str_parttype:int_partsize ...
#@exception OG_ERR_FORMAT   formato incorrecto.
#@exception OG_ERR_NOTFOUND disco o particion no detectado (no es un dispositivo).
#@note    Requisitos: \c parted \c awk
#@attention El nº de partición se indica por el orden de los párametros \c parttype:partsize
#@attention Las tuplas de valores están separadas por espacios.
#*/ ##
def ogListPartitions (disk):
    DISK = ogDiskToDev (disk)
    if not DISK: return None

    p = []
    NPARTS = ogGetPartitionsNumber (disk)
    for PART in range (1, NPARTS + 1):
        t = ogGetPartitionType (disk, PART)
        TYPE = ogGetPartitionType (disk, PART) or 'EMPTY'
        SIZE = ogGetPartitionSize (disk, PART) or 0
        p.append (f'{TYPE}:{SIZE}')

    return p


#/**
#         ogListPrimaryPartitions int_ndisk
#@brief   Metafunción que lista las particiones primarias no vacías de un disco.
#@param   int_ndisk  nº de orden del disco
#@see     ogListPartitions
#*/ ##
def ogListPrimaryPartitions (disk):
    PTTYPE = ogGetPartitionTableType (disk)
    if not PTTYPE: return None

    PARTS = ogListPartitions (disk)
    if not PARTS: return None

    if 'GPT' == PTTYPE:
        res = []
        for idx in range (len(PARTS),0,-1):
            item = PARTS[idx-1]
            if 0==len(res) and 'EMPTY:0' == item: continue
            res.insert (0, item)
        return res
    elif 'MSDOS' == PTTYPE:
        return PARTS[0:4]


#/**
#         ogListLogicalPartitions int_ndisk
#@brief   Metafunción que lista las particiones lógicas de una tabla tipo MSDOS.
#@param   int_ndisk  nº de orden del disco
#@see     ogListPartitions
#*/ ##
def ogListLogicalPartitions (disk):
    PTTYPE = ogGetPartitionTableType (disk)
    if not PTTYPE: return None

    PARTS = ogListPartitions (disk)
    if not PARTS: return None

    return PARTS[4:]


#/**
#         ogLockDisk int_ndisk
#@brief   Genera un fichero de bloqueo para un disco en uso exlusivo.
#@param   int_ndisk      nº de orden del disco
#@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/disk, siendo \c disk el dispositivo del disco, sustituyendo el carácter "/" por "-".
#*/ ##
def ogLockDisk (disk):
    DISK = ogDiskToDev (disk)
    if not DISK: return None
    open (f'/var/lock/lock{DISK.replace("/", "-")}', 'a').close()


#/**
#         ogSetPartitionActive int_ndisk int_npartition
#@brief   Establece cual es la partición activa de un disco.
#@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 partición no corresponden con un dispositivo.
#@note    Requisitos: parted
#*/ ##
def ogSetPartitionActive (disk, par):
    if InventoryLib.ogIsEfiActive():
        SystemLib.ogEcho (['session', 'log'], 'warning', f'EFI: {ogGlobals.lang.MSG_DONTUSE} ogSetPartitionActive')
        return

    DISK = ogDiskToDev (disk)
    if DISK is None: return

    PART = ogDiskToDev (disk, par)
    if PART is None: return

    subprocess.run (["parted", "-s", DISK, "set", par, "boot", "on"])
    return


#/**
#         ogSetPartitionId int_ndisk int_npartition hex_partid
#@brief   Cambia el identificador de la partición.
#@param   int_ndisk      nº de orden del disco
#@param   int_npartition nº de orden de la partición
#@param   hex_partid     identificador de tipo de partición
#@return  (nada)
#@exception OG_ERR_FORMAT     Formato incorrecto.
#@exception OG_ERR_NOTFOUND   Disco o partición no corresponden con un dispositivo.
#@exception OG_ERR_OUTOFLIMIT Valor no válido.
#@exception OG_ERR_PARTITION  Error al cambiar el id. de partición.
#@attention Requisitos: fdisk, sgdisk
#*/ ##
## for GPT partitions, id must be one of the valid types as reported by 'sgdisk --list-types', eg. 0700 or 8200
def ogSetPartitionId (disk, par, id):
    DISK = ogDiskToDev (disk)
    if not DISK: return None

    PART = ogDiskToDev (disk, par)
    if not PART: return None

# Error si el id. de partición no es hexadecimal.
    if not re.match ('^[0-9A-Fa-f]+$', id):
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_OUTOFLIMIT, id)
        return None

# Elección del tipo de partición.
    PTTYPE = ogGetPartitionTableType (disk)
    if 'GPT' == PTTYPE:
        p = subprocess.run (['sgdisk', f'-t{par}:{id.upper()}', DISK])
    elif 'MSDOS' == PTTYPE:
        p = subprocess.run (['sfdisk', '--id', DISK, par, id.upper()])
    else:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_OUTOFLIMIT, f'{disk},{PTTYPE}')
        return None

# MSDOS) Correcto si fdisk sin error o con error pero realiza Syncing
    if 0 == p.returncode:
        subprocess.run (['partprobe', DISK])
        return True
    else:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f'{disk},{part},{id}')
        return None


#/**
#         ogSetPartitionSize int_ndisk int_npartition int_size
#@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
#@param   int_size       tamaño de la partición (en KB)
#@return  (nada)
#@exception OG_ERR_FORMAT   formato incorrecto.
#@exception OG_ERR_NOTFOUND disco o particion no detectado (no es un dispositivo).
#@note    Requisitos: sfdisk, awk
#@todo    Compruebar que el tamaño sea numérico positivo y evitar que pueda solaparse con la siguiente partición.
#*/ ##
def ogSetPartitionSize (disk, par, size):
    DISK = ogDiskToDev (disk)
    if not DISK: return None

    PART = ogDiskToDev (disk, par)
    if not PART: return None

# Convertir tamaño en KB a sectores de 512 B.
    SIZE = 2 * int (size)
# Redefinir el tamaño de la partición.
    p = subprocess.run (['sfdisk', '-f', '-uS', f'-N{par}', DISK], input=f',{SIZE}', text=True)
    if p.returncode:
        ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f'{disk},{par}')
        return None
    subprocess.run (['partprobe', DISK])


#/**
#         ogSetPartitionType int_ndisk int_npartition str_type
#@brief   Cambia el identificador de la partición.
#@param   int_ndisk      nº de orden del disco
#@param   int_npartition nº de orden de la partición
#@param   str_type       mnemónico de tipo de partición
#@return  (nada)
#@attention Requisitos: fdisk, sgdisk
#*/ ##
def ogSetPartitionType (disk, par, t):
    DISK = ogDiskToDev (disk)
    if not DISK: return None

    PART = ogDiskToDev (disk, par)
    if not PART: return None

    PTTYPE = ogGetPartitionTableType (disk)
    if not PTTYPE: return None

    ID = ogTypeToId (t, PTTYPE)
    if not ID:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_FORMAT, f'{t},{PTTYPE}')
        return None

    ogSetPartitionId (disk, par, ID)


#/**
#         ogTypeToId str_parttype [str_tabletype]
#@brief   Devuelve el identificador correspondiente a un tipo de partición.
#@param   str_parttype  mnemónico de tipo de partición.
#@param   str_tabletype mnemónico de tipo de tabla de particiones (MSDOS por defecto).
#@return  int_idpart    identificador de tipo de partición.
#@exception OG_ERR_FORMAT   Formato incorrecto.
#@note    tabletype = { MSDOS, GPT },   (MSDOS, por defecto)
#*/ ##
#ogTypeToId ('LINUX')           =>  "83"
#ogTypeToId ('LINUX', 'MSDOS')  =>  "83"
def ogTypeToId (type, pttype='MSDOS'):
    data = {
        'GPT_GUID': {
            ## https://en.wikipedia.org/wiki/GUID_Partition_Table?useskin=vector
            'EMPTY':           '0',
            'WINDOWS':         'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7',
            'NTFS':            'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7',
            'FAT32':           'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7',
            'FAT16':           'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7',
            'FAT12':           'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7',
            'WIN-RESERV':      'E3C9E316-0B5C-4DB8-817D-F92DF00215AE',
            'LINUX-SWAP':      '0657FD6D-A4AB-43C4-84E5-0933C84B4F4F',
            'LINUX':           '0FC63DAF-8483-4772-8E79-3D69D8477DE4',
            'EXT2':            '0FC63DAF-8483-4772-8E79-3D69D8477DE4',
            'EXT3':            '0FC63DAF-8483-4772-8E79-3D69D8477DE4',
            'EXT4':            '0FC63DAF-8483-4772-8E79-3D69D8477DE4',
            'CACHE':           '0FC63DAF-8483-4772-8E79-3D69D8477DE4',
            'EFI':             'C12A7328-F81F-11D2-BA4B-00A0C93EC93B',
        },
        'GPT': {
            'EMPTY':           '0',
            'WINDOWS':         '0700',
            'NTFS':            '0700',
            'EXFAT':           '0700',
            'FAT32':           '0700',
            'FAT16':           '0700',
            'FAT12':           '0700',
            'HNTFS':           '0700',
            'HFAT32':          '0700',
            'HFAT16':          '0700',
            'HFAT12':          '0700',
            'WIN-RESERV':      '0C01',
            'CHROMEOS-KRN':    '7F00',
            'CHROMEOS':        '7F01',
            'CHROMEOS-RESERV': '7F02',
            'LINUX-SWAP':      '8200',
            'LINUX':           '8300',
            'EXT2':            '8300',
            'EXT3':            '8300',
            'EXT4':            '8300',
            'REISERFS':        '8300',
            'REISER4':         '8300',
            'XFS':             '8300',
            'JFS':             '8300',
            'LINUX-RESERV':    '8301',
            'LINUX-LVM':       '8E00',
            'FREEBSD-DISK':    'A500',
            'FREEBSD-BOOT':    'A501',
            'FREEBSD-SWAP':    'A502',
            'FREEBSD':         'A503',
            'HFS-BOOT':        'AB00',
            'HFS':             'AF00',
            'HFS+':            'AF00',
            'HFSPLUS':         'AF00',
            'HFS-RAID':        'AF01',
            'SOLARIS-BOOT':    'BE00',
            'SOLARIS':         'BF00',
            'SOLARIS-SWAP':    'BF02',
            'SOLARIS-DISK':    'BF03',
            'CACHE':           'CA00',
            'EFI':             'EF00',
            'LINUX-RAID':      'FD00',
        },
        'MSDOS': {
            'EMPTY':           '0',
            'FAT12':           '1',
            'EXTENDED':        '5',
            'FAT16':           '6',
            'WINDOWS':         '7',
            'NTFS':            '7',
            'EXFAT':           '7',
            'FAT32':           'b',
            'HFAT12':          '11',
            'HFAT16':          '16',
            'HNTFS':           '17',
            'HFAT32':          '1b',
            'LINUX-SWAP':      '82',
            'LINUX':           '83',
            'EXT2':            '83',
            'EXT3':            '83',
            'EXT4':            '83',
            'REISERFS':        '83',
            'REISER4':         '83',
            'XFS':             '83',
            'JFS':             '83',
            'LINUX-LVM':       '8e',
            'FREEBSD':         'a5',
            'OPENBSD':         'a6',
            'HFS':             'af',
            'HFS+':            'af',
            'SOLARIS-BOOT':    'be',
            'SOLARIS':         'bf',
            'CACHE':           'ca',
            'DATA':            'da',
            'GPT':             'ee',
            'EFI':             'ef',
            'VMFS':            'fb',
            'LINUX-RAID':      'fd',
        },
        'LVM': {
            'LVM-LV':          '10000',
        },
        'ZVOL': {
            'ZFS-VOL':         '10010',
        },
    }

    if pttype.upper() not in data:               return None
    if type.upper() not in data[pttype.upper()]: return None
    return data[pttype.upper()][type.upper()]


#/**
#         ogUnhidePartition int_ndisk int_npartition
#@brief   Hace visible una partición oculta.
#@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 detectado (no es un dispositivo).
#@exception OG_ERR_PARTITION tipo de partición no reconocido.
#*/ ##
def ogUnhidePartition (disk, par):
    PART = ogDiskToDev (disk, par)
    if not PART: return None

    TYPE = ogGetPartitionType (disk, par)
    if   'HNTFS'      == TYPE: NEWTYPE = 'NTFS'
    elif 'HFAT32'     == TYPE: NEWTYPE = 'FAT32'
    elif 'HFAT16'     == TYPE: NEWTYPE = 'FAT16'
    elif 'HFAT12'     == TYPE: NEWTYPE = 'FAT12'
    elif 'WIN-RESERV' == TYPE: NEWTYPE = 'WINDOWS'
    else:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, TYPE)
        return None

    ogSetPartitionType (disk, par, NEWTYPE)


#/**
#         ogUnlockDisk int_ndisk
#@brief   Elimina el fichero de bloqueo para un disco.
#@param   int_ndisk      nº de orden del disco
#@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/disk, siendo \c disk el dispositivo del disco, sustituyendo el carácter "/" por "-".
#*/ ##
def ogUnlockDisk (disk):
    DISK = ogDiskToDev (disk)
    if not DISK: return None
    os.remove (f'/var/lock/lock{DISK.replace("/", "-")}')


#/**
#         ogUpdatePartitionTable
#@brief   Fuerza al kernel releer la tabla de particiones de los discos duros
#@param   no requiere
#@return  informacion propia de la herramienta
#@note    Requisitos: \c partprobe
#@warning pendiente estructurar la funcion a opengnsys
#*/ ##
def ogUpdatePartitionTable():
    for disk in ogDiskToDev():
        subprocess.run(["partprobe", disk])
