#/**
#@file    FileSystemLib.py
#@brief   Librería o clase FileSystem
#@class   FileSystem
#@brief   Funciones para gestión de sistemas de archivos.
#@warning License: GNU GPLv3+
#*/

import subprocess
import sys
import os.path

import ogGlobals
import SystemLib
import DiskLib
import CacheLib


#/**
#         ogCheckFs int_ndisk int_nfilesys
#@brief   Comprueba el estado de un sistema de archivos.
#@param   int_ndisk      nº de orden del disco
#@param   int_nfilesys   nº de orden del sistema de archivos
#@return  (nada)
#@exception OG_ERR_FORMAT    Formato incorrecto.
#@exception OG_ERR_NOTFOUND  Disco o particion no corresponden con un dispositivo.
#@exception OG_ERR_PARTITION Partición desconocida o no accesible.
#@note    Requisitos: *fsck*
#@warning No se comprueban sistemas de archivos montados o bloqueados.
#@todo    Definir salidas.
#*/ ##
def ogCheckFs (disk, par):
    PART = DiskLib.ogDiskToDev (disk, par)
    if not PART: return

    data = {
        'EXT2':     { 'prog': 'e2fsck',        'params': '-y',   'codes': (1, 2),                },
        'EXT3':     { 'prog': 'e2fsck',        'params': '-y',   'codes': (1, 2),                },
        'EXT4':     { 'prog': 'e2fsck',        'params': '-y',   'codes': (1, 2),                },
        'CACHE':    { 'prog': 'e2fsck',        'params': '-y',   'codes': (1, 2),                },
        'BTRFS':    { 'prog': 'btrfsck',                         'codes': (1),                   },
        'REISERFS': { 'prog': 'fsck.reiserfs',                   'codes': (1, 2), 'input': 'Yes' },
        'REISER4':  { 'prog': 'fsck.reiser4',  'params': '-ay',                                  },
        'JFS':      { 'prog': 'fsck.jfs',                        'codes': (1, 2),                },
        'XFS':      { 'prog': 'xfs_repair',                                                      },
        'F2FS':     { 'prog': 'fsck.f2fs',                                                       },
        'NTFS':     { 'prog': 'ntfsfix',                                                         },
        'EXFAT':    { 'prog': 'fsck.exfat',                                                      },
        'FAT32':    { 'prog': 'dosfsck',       'params': '-a',   'codes': (1),                   },
        'FAT16':    { 'prog': 'dosfsck',       'params': '-a',   'codes': (1),                   },
        'FAT12':    { 'prog': 'dosfsck',       'params': '-a',   'codes': (1),                   },
        'HFS':      { 'prog': 'fsck.hfs',      'params': '-f',                                   },
        'HFSPLUS':  { 'prog': 'fsck.hfs',      'params': '-f',                                   },
        'UFS':      { 'prog': 'fsck.ufs',                                                        },
        'ZFS':      { 'prog': 'fsck.zfs',                                                        },
    }
    type = ogGetFsType (disk, par)
    if type in data:
        prog   = data[type]['prog']
        params = data[type]['params'] if 'params' in data[type] else ''
        codes  = data[type]['codes']  if 'codes'  in data[type] else []
        input  = data[type]['input']  if 'input'  in data[type] else None
    else:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f'{disk}, {par}, {type}')
        return None

    ogUnmount (disk, par)
    if ogIsMounted (disk, par):
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f'{disk} {par}')
        return None
    if ogIsLocked (disk, par):
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_LOCKED, f'{disk} {par}')
        return None

    ogLock (disk, par)
    rc = subprocess.run ([prog] + params.split() + [PART], input=input, text=True).returncode
    if 0 == rc or rc in codes:
        errcode = 0
    elif 127 == rc:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTEXEC, prog)
        errcode = ogGlobals.OG_ERR_NOTEXEC
    else:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f'{disk} {par}')
        errcode = ogGlobals.OG_ERR_PARTITION

    ogUnlock (disk, par)
    return not errcode      ## reverse to indicate success


#/**
#         ogExtendFs int_ndisk int_nfilesys
#@brief   Extiende un sistema de archivos al tamaño de su partición.
#@param   int_ndisk      nº de orden del disco
#@param   int_nfilesys   nº de orden del sistema de archivos
#@return  (nada)
#@exception OG_ERR_FORMAT   Formato incorrecto.
#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo.
#@exception OG_ERR_PARTITION Partición desconocida o no accesible.
#@note    Requisitos: *resize*
#*/ ##
def ogExtendFs (disk, par):
    PART = DiskLib.ogDiskToDev (disk, par)
    if not PART: return

    data = {
        'EXT2':     { 'prog': 'resize2fs',       'params': '-f',                                    },
        'EXT3':     { 'prog': 'resize2fs',       'params': '-f',                                    },
        'EXT4':     { 'prog': 'resize2fs',       'params': '-f',                                    },
        'BTRFS':    { 'prog': 'btrfs',           'params': 'filesystem resize max', 'domount': True },
        'REISERFS': { 'prog': 'resize_reiserfs', 'params': '-f',                                    },
        'REISER4':  { 'prog': 'resize_reiserfs', 'params': '-f',                                    },
        'NTFS':     { 'prog': 'ntfsresize',      'params': '-f',                    'input': 'y'    },
        'F2FS':     { 'unsupported': True },
        'JFS':      { 'unsupported': True },
        'NILFS2':   { 'unsupported': True }, # try "nilfs-resize"
        'XFS':      { 'unsupported': True },
        'EXFAT':    { 'unsupported': True },
        'FAT32':    { 'unsupported': True }, # try "fatresize"
        'FAT16':    { 'unsupported': True }, # try "fatresize"
        'HFS':      { 'unsupported': True },
        'HFSPLUS':  { 'unsupported': True },
        'UFS':      { 'unsupported': True },
    }

    type = ogGetFsType (disk, par)
    if type in data:
        prog    = data[type]['prog']    if 'prog'    in data[type] else None
        params  = data[type]['params']  if 'params'  in data[type] else None
        domount = data[type]['domount'] if 'domount' in data[type] else False
        input   = data[type]['input']   if 'input'   in data[type] else None
    else:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f'{disk} {par} {type}')
        return

    if not prog: return None

    if domount:
        PART = ogMount (disk, par)
        if not PART: return None
    else:
        ogUnmount (disk, par)
        if ogIsMounted (disk, par):
            SystemLib.ogRaiseError([], ogGlobals.OG_ERR_PARTITION, f'{disk} {par}')
            return None

# Error si el sistema de archivos está bloqueado.
    if ogIsLocked (disk, par):
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_LOCKED, f'{disk} {par}')
        return None

# Redimensionar en modo uso exclusivo.
    ogLock (disk, par)
    try:
        if input:
            rc = subprocess.run ([prog] + params.split() + [PART], input=input, text=True).returncode
        else:
            rc = subprocess.run ([prog] + params.split() + [PART]).returncode
    except FileNotFoundError:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTEXEC, prog)
        rc = ogGlobals.OG_ERR_NOTEXEC
    except:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f'{disk} {par}')
        rc = ogGlobals.OG_ERR_PARTITION

    ogUnlock (disk, par)
    return not rc      ## reverse to indicate success


#/**
#         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:
        return ogFormatFs (disk, par, fs=fs, label=label)


#/**
#         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, fs=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 fs:
        fs = ogGetFsType (disk, par)

    if not fs:
        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 fs not in data:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f"{disk} {par} {fs}")
        return

    d = data[fs]
    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}"

    ogLock (disk, par)
    subprocess.run (['umount', PART])
    try:
        if input:
            errcode = subprocess.run ([prog] + params.split (' ') + [PART]).returncode
        else:
            errcode = subprocess.run ([prog] + params.split (' ') + [PART], input=input, text=True).returncode
    except FileNotFoundError:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTEXEC, prog)
        errcode = ogGlobals.OG_ERR_NOTEXEC
    except:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f"{disk} {par}")
        errcode = ogGlobals.OG_ERR_PARTITION

    ogUnlock (disk, par)
    return not errcode      ## reverse to indicate success


#/**
#         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.
#*/ ##
def ogGetFsSize (disk, par, unit='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":
        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).
    mnt = 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:
        sz = 0

    return int (sz)


#/**
#         ogGetFsType int_ndisk int_nfilesys
#@brief   Devuelve el mnemonico con el tipo de sistema de archivos.
#@param   int_ndisk      nº de orden del disco
#@param   int_nfilesys   nº de orden del sistema de archivos
#@return  Mnemonico
#@note 	  Mnemonico: { EXT2, EXT3, EXT4, BTRFS, REISERFS, XFS, JFS, FAT12, FAT16, FAT32, NTFS, LINUX-SWAP, LINUX-LVM, LINUX-RAID, HFS, HFSPLUS, CACHE }
#@exception OG_ERR_FORMAT   Formato incorrecto.
#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo.
#*/ ##
def ogGetFsType(disk, part):
    PART = DiskLib.ogDiskToDev(disk, part)
    if not PART: return

    TYPE = None
    if PART.startswith("/"):
        out = subprocess.run(["blkid", "-o", "export", PART], capture_output=True, text=True).stdout.splitlines()
        for line in out:
            if line.startswith("TYPE="):
                TYPE = line.split("=")[1].upper()
                break
    else:
        try:
            subprocess.run(["zfs", "mount", PART])
        except FileNotFoundError:
            SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTEXEC, 'zfs')
            return
        out = subprocess.run(["mount"], capture_output=True, text=True).stdout.splitlines()
        for line in out:
            if line.startswith(PART):
                TYPE = line.split()[4].upper()
                break

    if not TYPE:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_NOTFOUND, f'{disk} {part}')
        return

    # Componer valores correctos.
    if TYPE == "EXT4":
        if f"{disk} {part}" == CacheLib.ogFindCache():
            if ogIsFormated(disk, part):
                TYPE = "CACHE"
    elif TYPE == "VFAT":
        result = subprocess.run(["blkid", "-po", "export", PART], capture_output=True, text=True)
        for line in result.stdout.split("\n"):
            if line.startswith("VERSION="):
                TYPE = line.split("=")[1].upper()
                break
    elif TYPE == "SWAP":
        TYPE = "LINUX-SWAP"
    elif TYPE.startswith("LVM"):
        TYPE = "LINUX-LVM"
    elif "RAID" in TYPE:
        TYPE = "LINUX-RAID"
    elif TYPE == "ZFS_MEMBER":
        TYPE = "ZVOL"
    elif "_MEMBER" in TYPE:
        TYPE = TYPE.replace("_MEMBER", "")

    return TYPE


#/**
#         ogGetMountPoint int_ndisk int_nfilesys
#@brief   Devuelve el punto de montaje de un sistema de archivos.
#@param   int_ndisk      nº de orden del disco
#@param   int_nfilesys   nº de orden del sistema de archivos
#@return  Punto de montaje
#@exception OG_ERR_FORMAT    Formato incorrecto.
#@exception OG_ERR_NOTFOUND  Disco o particion no corresponden con un dispositivo.
#@note    Requisitos: \c mount* \c awk
#*/ ##

def ogGetMountPoint(disk, par):
    PART = DiskLib.ogDiskToDev(disk, par)
    if not PART: return

    return subprocess.run(["findmnt", "-n", "-o", "TARGET", PART], capture_output=True, text=True).stdout.strip()



#/**
#         ogIsFormated int_ndisk int_nfilesys
#@brief   Comprueba si un sistema de archivos está formateado.
#@param   int_ndisk      nº de orden del disco o volumen.
#@param   int_nfilesys   nº de orden del sistema de archivos
#@return  Código de salida: True - formateado, False - sin formato o error.
#*/ ##
def ogIsFormated(disk, part):
    PART = DiskLib.ogDiskToDev (disk, part)
    if not PART:
        return

    # Revisar tipo de sistema de archivos.
    if PART.startswith("/"):
        out = subprocess.run(["blkid", "-s", "TYPE", PART], capture_output=True, text=True).stdout.strip()
        if 'swap'    in out: return False
        if '_member' in out: return False
        return bool(out)
    else:
        out = subprocess.run(["zfs", "list", "-Hp", "-o", "canmount", PART], capture_output=True, text=True).stdout.strip()
        return out == "on"



#/**
#         ogIsLocked int_ndisk int_npartition
#@see     ogIsPartitionLocked
#*/

def ogIsLocked(disk, par):
    return ogIsPartitionLocked(disk, par)


#/**
#         ogIsPartitionLocked int_ndisk int_npartition
#@brief   Comprueba si una partición o su disco están bloqueados por una operación de uso exclusivo.
#@param   int_ndisk      nº de orden del disco
#@param   int_npartition nº de orden de la partición
#@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 ogIsPartitionLocked(disk, par):
    DISK = DiskLib.ogDiskToDev(disk)
    PART = DiskLib.ogDiskToDev(disk, par)
    if not PART: return

    LOCKDISK = f"/var/lock/lock{DISK.replace('/', '-')}"
    LOCKPART = f"/var/lock/lock{PART.replace('/', '-')}"
    rc = os.path.isfile(LOCKDISK) or os.path.isfile(LOCKPART)
    return rc

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


#/**
#         ogIsReadonly int_ndisk int_nfilesys
#@brief   Comprueba si un sistema de archivos está montado solo de lectura.
#@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 solo de lectura, 1 - con escritura o no montado.
#@version 1.1.0 - Primera versión para OpenGnsys.
#@author  Ramon Gomez, ETSII Universidad de Sevilla
#@date    2016-01-20
#*/ ##

def ogIsReadonly(disk, par):
    PART = DiskLib.ogDiskToDev(disk, par)
    if not PART: return

    result = subprocess.run(["findmnt", "-n", "-o", "OPTIONS", PART], capture_output=True, text=True)
    options = result.stdout.strip().split(",")
    return "ro" in options


#/**
#         ogIsWritable int_ndisk int_nfilesys
#@brief   Comprueba si un sistema de archivos está montado de lectura y escritura.
#@param   int_ndisk      nº de orden del disco
#@param   int_nfilesys   nº de orden del sistema de archivos
#@return  Código de salida: 0 - lectura y escritura, 1 - solo lectura o no montado.
#*/ ##
def ogIsWritable (disk, par):
    PART = DiskLib.ogDiskToDev (disk, par)
    if not PART: return

    result = subprocess.run(["findmnt", "-n", "-o", "OPTIONS", PART], capture_output=True, text=True)
    options = result.stdout.strip().split(",")
    return "rw" in options


#/**
#         ogLock int_ndisk int_npartition
#@see     ogLockPartition
#*/
def ogLock(disk, par):
    return ogLockPartition(disk, par)

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

    LOCKFILE = f"/var/lock/lock{PART.replace('/', '-')}"
    try:
        open (LOCKFILE, 'w').close()
    except:
        return False
    return True


#/**
#         ogMount int_ndisk int_nfilesys
#@see     ogMountFs ogMountCache ogMountCdrom
#*/ ##
def ogMount(*args):
    if 1 == len (args):
        if 'cache' == args[0].lower():
            return DiskLib.ogMountCache()
        elif 'cdrom' == args[0].lower():
            return ogMountCdrom()
    elif 2 == len (args):
        return ogMountFs(args[0], args[1])


#/**
#         ogMountFirstFs int_ndisk
#@brief   Monta el primer sistema de archivos disponible en el disco.
#@param   int_ndisk      nº de orden del disco
#@return  Punto de montaje del primer sistema de archivos detectado
#*/ ##
def ogMountFirstFs (disk):
    NPARTS = DiskLib.ogGetPartitionsNumber (disk)
    for PART in range (1, NPARTS + 1):
        MNTDIR = ogMount (disk, PART)
        if MNTDIR:
            return MNTDIR
    SystemLib.ogRaiseError ('session', ogGlobals.OG_ERR_NOTFOUND, f'{disk}')
    return ogGlobals.OG_ERR_NOTFOUND

#/**
#         ogMountFs int_ndisk int_nfilesys
#@brief   Monta un sistema de archivos.
#@param   int_ndisk      nº de orden del disco
#@param   int_nfilesys   nº de orden del sistema de archivos
#@return  Punto de montaje
#@exception OG_ERR_FORMAT    Formato incorrecto.
#@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

    mntdir = ogGetMountPoint (disk, par)
    if mntdir: return mntdir

    if ogIsLocked (disk, par):
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_LOCKED, f'{ogGlobals.lang.MSG_PARTITION}, {disk} {par}')
        return

    # El camino de un dispositivo normal comienza por el carácter "/".
    if dev.startswith ('/'):
        # Crear punto de montaje o enlace simbólico para caché local.
        mntdir = dev.replace ('/dev', '/mnt')
        if f"{disk} {par}" == CacheLib.ogFindCache():
            os.makedirs(ogGlobals.OGCAC, exist_ok=True)
            try:
                os.symlink(ogGlobals.OGCAC, mntdir)
            except FileExistsError:
                pass
        else:
            os.makedirs(mntdir, exist_ok=True)

        # Montar sistema de archivos.
        try:
            rc = subprocess.run(['mount', dev, mntdir], check=True).returncode
        except subprocess.CalledProcessError:
            try:
                rc = subprocess.run(['mount', dev, mntdir, '-o', 'force,remove_hiberfile'], check=True).returncode
            except subprocess.CalledProcessError:
                SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f"{disk}, {par}")
                return

            if 0 == rc:
                pass
            elif 14 == rc:
                try:
                    subprocess.run (['ntfsfix', '-d', par], check=True)
                except subprocess.CalledProcessError:
                    SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f"{disk, par}")
                    #return
            else:
                try:
                    subprocess.run (['mount', par, mntdir, '-o', 'ro'], check=True)
                except subprocess.CalledProcessError:
                    SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f"{disk, par}")
                    #return

        # Aviso de montaje de solo lectura.
        if ogIsReadonly(disk, par):
            SystemLib.ogEcho ([], "warning", f'ogMountFs: {ogGlobals.lang.MSG_MOUNTREADONLY}: "{disk}, {par}"')
    else:
        # Montar sistema de archivos ZFS (un ZPOOL no comienza por "/").
        subprocess.run(['zfs', 'mount', dev])

    return mntdir



#/**
#         ogMountCdrom
#@brief   Monta dispositivo óptico por defecto
#@return  Punto de montaje
#@exception OG_ERR_FORMAT    Formato incorrecto.
#@exception OG_ERR_PARTITION Tipo de particion desconocido o no se puede montar.
#@version
#@author
#@date
#*/ ##
def ogMountCdrom():
    DEV = '/dev/cdrom'  # Por defecto
    outlines = subprocess.run (['mount'], capture_output=True, text=True).stdout.split ('\n')
    mntdir = ''
    for l in outlines:
        items = l.split (' ')
        if DEV == items[0]:
            mntdir = items[2]
            break

    if not mntdir:
        mntdir = DEV.replace ('/dev', '/mnt')
        os.makedirs (mntdir, exist_ok=True)
        try:
            subprocess.run (['mount', '-t', 'iso9660', DEV, mntdir], check=True)
        except subprocess.CalledProcessError:
            SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, 'cdrom')
            return None

    return mntdir


#/**
#         ogReduceFs int_ndisk int_nfilesys
#@brief   Reduce el tamaño del sistema de archivos, sin tener en cuenta el espacio libre.
#@param   int_ndisk      nº de orden del disco
#@param   int_nfilesys   nº de orden del sistema de archivos
#@return  int_tamañoKB - tamaño en KB
#@exception OG_ERR_FORMAT    Formato incorrecto.
#@exception OG_ERR_NOTFOUND  Disco o particion no corresponden con un dispositivo.
#@exception OG_ERR_PARTITION Partición desconocida o no accesible.
#@warning En Windows, se borran los ficheros de hiberanción y de paginación.
#@warning El sistema de archivos se amplía al mínimo + 10%.
#@note    Requisitos:	*resize*
#*/ ##
def ogReduceFs (disk, par):
    PART = DiskLib.ogDiskToDev (disk, par)
    if not PART: return

    # Redimensionar según el tipo de partición.
    type = ogGetFsType (disk, par)
    if type in ['EXT2', 'EXT3', 'EXT4']:
        ogUnmount (disk, par)
        rc = subprocess.run (['resize2fs', '-fpM', PART], capture_output=True, text=True).returncode
        if rc:
            SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f"{disk},{par}")
            return None
    elif 'BTRFS' == type:
        mntdir = ogMount (disk, par)
        # Calcular tamaño ocupado + 10%, redondeado + 1 (incluyendo letra de unidad).
        btrfs_lines = subprocess.run (['btrfs', 'filesystem', 'show', mntdir], capture_output=True, text=True).stdout.splitlines()
        for l in btrfs_lines:
            if 'devid' not in l: continue
            ## 'devid    2 size 8.89GiB used 1.00GiB path /dev/sda4'
            devid_str, devid, size_str, size, used_str, used, path_str, path = l.split()
            if PART != os.path.realpath (path): continue
            (sz, unit) = re.search ('^([^A-Z]+)([A-Z])', used).groups()
            sz = float (sz) * 1.1 + 1
            size = f'{str(sz)}{unit}'
            subprocess.run (['btrfs', 'filesystem', 'resize', size, mntdir], capture_output=True, text=True)
            break
    elif type in ['REISERFS', 'REISER4']:
        mntdir = ogMount (disk, par)
        df_lines = subprocess.run (['df', '-k', mntdir], capture_output=True, text=True).stdout.splitlines()
        for l in df_lines:
            if 'Filesystem' in l: continue
            fs, blocks, used, avail, use_pct, mntpt = l.split()
            size = str (int (used) * 1.1)
            ogUnmount (disk, par)
            subprocess.run (['resize_reiserfs', f'-s{size}K', PART], input='y\n', capture_output=True, text=True)
            break
    elif type == 'NTFS':
        ogUnmount (disk, par)
        ntfsresize_out = subprocess.run (['ntfsresize', '-fi', PART], capture_output=True, text=True).stdout
        nr_lines = ntfsresize_out.splitlines()
        maxsize = None
        size = None
        for l in nr_lines:
            if 'device size' in l:
                maxsize = float (l.split()[3])
            if 'resize at' in l:
                size = l.split()[4]
                size = int ((int (size) * 1.1 / 1024 + 1) * 1024)

        if not maxsize or not size:
            SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f'{disk},{par} ({ntfsresize_out})')
            return None

        extrasize = 0
        retval = 1
        while retval != 0 and size+extrasize < maxsize:
            nr = subprocess.run (['ntfsresize', '-fns', str(size), PART], capture_output=True, text=True)
            for l in nr.stdout.splitlines():
                if 'Needed relocations' not in l: continue
                extrasize = int ((int (l.split()[3])*1.1/1024+1)*1024)
                break
            retval = nr.returncode
            size += extrasize
        if size < maxsize:
            rc = subprocess.run (['ntfsresize', '-fs', str(size), PART], input='y\n', capture_output=True, text=True).returncode
            if rc:
                SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f"{disk},{par}")
                return None
    elif type in ['FAT32', 'FAT16', 'F2FS', 'JFS', 'NILFS2', 'XFS', 'EXFAT', 'HFS', 'HFSPLUS', 'UFS']:
        pass
    else:
        SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_PARTITION, f"{disk},{par}")
        return None

    return ogGetFsSize (disk, par)


#/**
#         ogUnlock int_ndisk int_npartition
#@see     ogUnlockPartition
#*/ ##
def ogUnlock (disk, par):
    return ogUnlockPartition (disk, par)

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

    LOCKFILE = f"/var/lock/lock{PART.replace('/', '-')}"
    if os.path.exists (LOCKFILE):
        os.remove (LOCKFILE)


#/**
#         ogUnmount int_ndisk int_npartition
#@see     ogUnmountFs
#*/ ##
def ogUnmount (disk, par):
    return ogUnmountFs (disk, par)

#/**
#         ogUnmountFs int_ndisk int_nfilesys
#@brief   Desmonta un sistema de archivos.
#@param   int_ndisk      nº de orden del disco
#@param   int_nfilesys   nº de orden del sistema de archivos
#@return  Nada
#@exception OG_ERR_FORMAT    Formato incorrecto.
#@exception OG_ERR_NOTFOUND  Disco o particion no corresponden con un dispositivo.
#@warning La partición no está previamente montada o no se puede desmontar.
#*/ ##

def ogUnmountFs(disk, par):
    PART = DiskLib.ogDiskToDev (disk, par)
    if not PART: return
    MNTDIR = ogGetMountPoint (disk, par)

    # Si está montada, desmontarla.
    if MNTDIR:
        # Error si la particion está bloqueada.
        if ogIsLocked (disk, par):
            SystemLib.ogRaiseError ([], ogGlobals.OG_ERR_LOCKED, f"{ogGlobals.lang.MSG_PARTITION}, {disk} {par}")
            return

        # Desmontar y borrar punto de montaje.
        try:
            subprocess.run(["umount", PART], check=True)
        except subprocess.CalledProcessError:
            SystemLib.ogEcho ([], "warning", f'ogUnmountFs: {ogGlobals.lang.MSG_DONTUNMOUNT}: "{disk}, {par}"')
        try:
            os.rmdir(MNTDIR)
        except:
            try:
                os.remove(MNTDIR)
            except:
                pass
        return True
    else:
        SystemLib.ogEcho ([], "warning", f'{ogGlobals.lang.MSG_DONTMOUNT}: "{disk},{par}"')
        return True


#/**
#         ogUnmountAll int_ndisk
#@brief   Desmonta todos los sistema de archivos de un disco, excepto el caché local.
#@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.
#@warning No se desmonta la partición marcada como caché local.
#*/ ##
def ogUnmountAll (disk):
    DISK = DiskLib.ogDiskToDev (disk)
    n = DiskLib.ogGetPartitionsNumber (disk)
    if not n: return None
    for PART in range (1, n+1):
        if 'CACHE' != ogGetFsType (disk, PART):
            ogUnmount (disk, PART)


#/**
#         ogUnsetDirtyBit int_ndisk int_npart
#@brief   Inhabilita el Dirty Bit del sistema de ficheros NTFS para evitar un CHKDSK en el primer arranque
#@param   int_ndisk      nº de orden del disco
#@param   int_npart      nº de orden de partición
#@return  Nada
#@exception OG_ERR_FORMAT    Formato incorrecto.
#*/ ##
def ogUnsetDirtyBit (disk, par):
    PART = DiskLib.ogDiskToDev (disk, par)
    if not PART: return None

    TYPE = ogGetFsType (disk, par)
    if 'NTFS' == TYPE:
        ogUnmount (disk, par)
        subprocess.run (['ntfsfix', '--clear-dirty', PART])


#/**
#         ogGetFreeSize int_disco int_partition str_SizeOutput
#@brief   muestra informacion del tamaño total, datos y libre.
#@param   int_ndisk     nº de orden del disco
#@param   int_npart     nº de orden de partición
#@param   str_unitSize      unidad mostrada
#@return  int_size:int_data:int_free
#@TODO  Componer corretcamente esta función.
#@exception OG_ERR_FORMAT    Formato incorrecto.
#*/ ##

def ogGetFreeSize(disk, part, unit='KB'):
    PART = DiskLib.ogDiskToDev (disk, part)
    if not PART: return

    unit2factor = {
        'kb': 1.024 / 1,
        'mb': 1.024 / 1000,
        'gb': 1.024 / 1000000,
    }
    if unit.lower() not in unit2factor:
        kk
    factor = unit2factor[unit.lower()]

    particion = ogMount (disk, part)
    if not particion:
        kk
    df = subprocess.run (['df'], capture_output=True, text=True).stdout
    df_part = list (filter (lambda l: particion in l, df.splitlines()))
    if not len (df_part):
        kk
    _, size, used, free, pct, mntpt = df_part[0].split()
    return float (free) * factor
