import subprocess
import sys

from engine.SystemLib import *
from engine.DiskLib import *
from engine.CacheLib import *

def ogCheckFs(int_ndisk, int_nfilesys):
    # Si se solicita, mostrar ayuda.
    if int_ndisk == "help":
        ogHelp("ogCheckFs", "ogCheckFs int_ndisk int_nfilesys", "ogCheckFs 1 1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición.
    PART = ogDiskToDev(int_ndisk, int_nfilesys)
    if not PART:
        return

    TYPE = ogGetFsType(int_ndisk, int_nfilesys)
    if TYPE == "EXT[234]" or TYPE == "CACHE":
        PROG = "e2fsck"
        PARAMS = "-y"
        CODES = [1, 2]
    elif TYPE == "BTRFS":
        PROG = "btrfsck"
        CODES = [1]
    elif TYPE == "REISERFS":
        PROG = "fsck.reiserfs"
        PARAMS = "<<<\"Yes\""
        CODES = [1, 2]
    elif TYPE == "REISER4":
        PROG = "fsck.reiser4"
        PARAMS = "-ay"
    elif TYPE == "JFS":
        PROG = "fsck.jfs"
        CODES = [1, 2]
    elif TYPE == "XFS":
        PROG = "xfs_repair"
    elif TYPE == "F2FS":
        PROG = "fsck.f2fs"
    elif TYPE == "NTFS":
        PROG = "ntfsfix"
    elif TYPE == "EXFAT":
        PROG = "fsck.exfat"
    elif TYPE == "FAT32":
        PROG = "dosfsck"
        PARAMS = "-a"
        CODES = [1]
    elif TYPE == "FAT16":
        PROG = "dosfsck"
        PARAMS = "-a"
        CODES = [1]
    elif TYPE == "FAT12":
        PROG = "dosfsck"
        PARAMS = "-a"
        CODES = [1]
    elif TYPE == "HFS":
        PROG = "fsck.hfs"
        PARAMS = "-f"
    elif TYPE == "HFSPLUS":
        PROG = "fsck.hfs"
        PARAMS = "-f"
    elif TYPE == "UFS":
        PROG = "fsck.ufs"
    elif TYPE == "ZFS":
        PROG = "fsck.zfs"
    else:
        ogRaiseError(OG_ERR_PARTITION, f"{int_ndisk}, {int_nfilesys}")
        return

    # Error si el sistema de archivos esta montado o bloqueado.
    ogUnmount(int_ndisk, int_nfilesys)
    if ogIsMounted(int_ndisk, int_nfilesys):
        ogRaiseError(OG_ERR_PARTITION, f"{int_ndisk} {int_nfilesys}")
        return
    if ogIsLocked(int_ndisk, int_nfilesys):
        ogRaiseError(OG_ERR_LOCKED, f"{int_ndisk} {int_nfilesys}")
        return

    # Comprobar en modo uso exclusivo.
    ogLock(int_ndisk, int_nfilesys)
    try:
        result = subprocess.run([PROG, PARAMS, PART], capture_output=True)
        ERRCODE = result.returncode
    except FileNotFoundError:
        ogRaiseError(OG_ERR_NOTEXEC, PROG)
        ERRCODE = OG_ERR_NOTEXEC
    except:
        ogRaiseError(OG_ERR_PARTITION, f"{int_ndisk} {int_nfilesys}")
        ERRCODE = OG_ERR_PARTITION

    ogUnlock(int_ndisk, int_nfilesys)
    return ERRCODE

def ogExtendFs():
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 2 and sys.argv[1] == "help":
        ogHelp("ogExtendFs", "ogExtendFs int_ndisk int_nfilesys", "ogExtendFs 1 1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición.
    PART = ogDiskToDev(int(sys.argv[1]), int(sys.argv[2]))
    if not PART:
        return

    # Redimensionar al tamaño máximo según el tipo de partición.
    TYPE = ogGetFsType(int(sys.argv[1]), int(sys.argv[2]))
    if TYPE == "EXT[234]":
        PROG = "resize2fs"
        PARAMS = "-f"
    elif TYPE == "BTRFS":
        PROG = "btrfs"
        PARAMS = "filesystem resize max"
        DOMOUNT = True  # Debe estar montado.
    elif TYPE == "REISERFS" or TYPE == "REISER4":
        PROG = "resize_reiserfs"
        PARAMS = "-f"
    elif TYPE == "F2FS" or TYPE == "JFS" or TYPE == "NILFS2" or TYPE == "XFS" or TYPE == "EXFAT" or TYPE == "FAT32" or TYPE == "FAT16" or TYPE == "HFS" or TYPE == "HFSPLUS" or TYPE == "UFS":
        return  # No se reduce (por el momento).
    elif TYPE == "NTFS":
        PROG = "ntfsresize"
        PARAMS = "<<<\"y\" -f"
    else:
        ogRaiseError(OG_ERR_PARTITION, f"{int(sys.argv[1])} {int(sys.argv[2])} {TYPE}")
        return

    # Salida normal si no se va a aplicar la operación.
    if not PROG:
        return

    # Error si el sistema de archivos no se queda en el estado de montaje adecuado.
    if DOMOUNT:
        PART = ogMount(int(sys.argv[1]), int(sys.argv[2]))
        if not PART:
            return
    else:
        ogUnmount(int(sys.argv[1]), int(sys.argv[2]))
        if ogIsMounted(int(sys.argv[1]), int(sys.argv[2])):
            ogRaiseError(OG_ERR_PARTITION, f"{int(sys.argv[1])} {int(sys.argv[2])}")
            return

    # Error si el sistema de archivos está bloqueado.
    if ogIsLocked(int(sys.argv[1]), int(sys.argv[2])):
        ogRaiseError(OG_ERR_LOCKED, f"{int(sys.argv[1])} {int(sys.argv[2])}")
        return

    # Redimensionar en modo uso exclusivo.
    ogLock(int(sys.argv[1]), int(sys.argv[2]))
    try:
        subprocess.run([PROG, PARAMS, PART], capture_output=True)
        ERRCODE = 0
    except FileNotFoundError:
        ogRaiseError(OG_ERR_NOTEXEC, PROG)
        ERRCODE = OG_ERR_NOTEXEC
    except:
        ogRaiseError(OG_ERR_PARTITION, f"{int(sys.argv[1])} {int(sys.argv[2])}")
        ERRCODE = OG_ERR_PARTITION

    ogUnlock(int(sys.argv[1]), int(sys.argv[2]))
    return ERRCODE

def ogFormat(int_ndisk, int_nfilesys):
    if int_nfilesys.lower() == "cache":
        ogFormatCache()
    else:
        ogFormatFs(int_ndisk, int_nfilesys)

def ogFormatFs(int_ndisk, int_nfilesys, str_label=None):
    # Si se solicita, mostrar ayuda.
    if str_label == "help":
        ogHelp("ogFormatFs", "ogFormatFs int_ndisk int_nfilesys [str_label]", "ogFormatFs 1 1", "ogFormatFs 1 1 EXT4", "ogFormatFs 1 1 \"DATA\"", "ogFormatFs 1 1 EXT4 \"DATA\"")
        return

    # Error si no se reciben entre 2 y 4 parámetros.
    if not (2 <= len(sys.argv) <= 4):
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener fichero de dispositivo.
    PART = ogDiskToDev(int_ndisk, int_nfilesys)
    if not PART:
        return

    # Error si la partición está montada o bloqueada.
    if ogIsMounted(int_ndisk, int_nfilesys):
        ogRaiseError(OG_ERR_DONTFORMAT, f"{MSG_MOUNT}: {int_ndisk} {int_nfilesys}")
        return
    if ogIsLocked(int_ndisk, int_nfilesys):
        ogRaiseError(OG_ERR_LOCKED, f"{int_ndisk} {int_nfilesys}")
        return

    # Si no se indica el tipo de sistema de archivos, intentar obtenerlo.
    TYPE = ogGetFsType(int_ndisk, int_nfilesys)
    if not TYPE:
        TYPE = sys.argv[3]

    # Error, si no especifica el tipo de sistema de archivos a formatear.
    if not TYPE:
        ogRaiseError(OG_ERR_FORMAT, f"{int_ndisk} {int_nfilesys} ...")
        return

    # Elegir tipo de formato.
    if TYPE == "EXT2":
        PROG = "mkfs.ext2"
        PARAMS = "-F"
    elif TYPE == "EXT3":
        PROG = "mkfs.ext3"
        PARAMS = "-F"
    elif TYPE == "EXT4":
        PROG = "mkfs.ext4"
        PARAMS = "-F"
    elif TYPE == "BTRFS":
        PROG = "mkfs.btrfs"
        PARAMS = "-f"
    elif TYPE == "REISERFS":
        PROG = "mkfs.reiserfs"
        PARAMS = "-f"
        LABELPARAM = "-l"
    elif TYPE == "REISER4":
        PROG = "mkfs.reiser4"
        PARAMS = "-f <<<\"y\""
    elif TYPE == "XFS":
        PROG = "mkfs.xfs"
        PARAMS = "-f"
    elif TYPE == "JFS":
        PROG = "mkfs.jfs"
        PARAMS = "<<<\"y\""
    elif TYPE == "F2FS":
        PROG = "mkfs.f2fs"
        LABELPARAM = "-l"
    elif TYPE == "NILFS2":
        PROG = "mkfs.nilfs2"
        PARAMS = "-f"
    elif TYPE == "LINUX-SWAP":
        PROG = "mkswap"
    elif TYPE == "NTFS":
        PROG = "mkntfs"
        PARAMS = "-f"
    elif TYPE == "EXFAT":
        PROG = "mkfs.exfat"
        LABELPARAM = "-n"
    elif TYPE == "FAT32":
        PROG = "mkdosfs"
        PARAMS = "-F 32"
        LABELPARAM = "-n"
    elif TYPE == "FAT16":
        PROG = "mkdosfs"
        PARAMS = "-F 16"
        LABELPARAM = "-n"
    elif TYPE == "FAT12":
        PROG = "mkdosfs"
        PARAMS = "-F 12"
        LABELPARAM = "-n"
    elif TYPE == "HFS":
        PROG = "mkfs.hfs"
    elif TYPE == "HFSPLUS":
        PROG = "mkfs.hfsplus"
        LABELPARAM = "-v"
    elif TYPE == "UFS":
        PROG = "mkfs.ufs"
        PARAMS = "-O 2"
    else:
        ogRaiseError(OG_ERR_PARTITION, f"{int_ndisk} {int_nfilesys} {TYPE}")
        return

    # Etiquetas de particion.
    if not str_label:
        if sys.argv[4] == "CACHE":
            ogRaiseError(OG_ERR_FORMAT, f"{MSG_RESERVEDVALUE}: CACHE")
            return
        if len(sys.argv) >= 4:
            PARAMS = f"{PARAMS} {LABELPARAM or '-L'} {sys.argv[4]}"
    else:
        PARAMS = f"{PARAMS} {LABELPARAM or '-L'} {str_label}"

    # Formatear en modo uso exclusivo (desmontar siempre).
    ogLock(int_ndisk, int_nfilesys)
    try:
        subprocess.run([PROG, PARAMS, PART], capture_output=True)
        ERRCODE = 0
    except FileNotFoundError:
        ogRaiseError(OG_ERR_NOTEXEC, PROG)
        ERRCODE = OG_ERR_NOTEXEC
    except:
        ogRaiseError(OG_ERR_PARTITION, f"{int_ndisk} {int_nfilesys}")
        ERRCODE = OG_ERR_PARTITION

    ogUnlock(int_ndisk, int_nfilesys)
    return ERRCODE

def ogGetFsSize(int_ndisk, int_npartition, str_unit=None):
    # Si se solicita, mostrar ayuda.
    if str_unit == "help":
        ogHelp("ogGetFsSize", "ogGetFsSize int_ndisk int_npartition [str_unit]", "ogGetFsSize 1 1", "ogGetFsSize 1 1 KB")
        return

    # Error si no se reciben 2 o 3 parámetros.
    if not (2 <= len(sys.argv) <= 3):
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener unidad y factor de medida.
    UNIT = str_unit or "KB"
    FACTOR = 1
    if UNIT.upper() == "MB":
        FACTOR = 1024
    elif UNIT.upper() == "GB":
        FACTOR = 1024 * 1024
    elif UNIT.upper() == "TB":
        FACTOR = 1024 * 1024 * 1024
    elif UNIT.upper() != "KB":
        ogRaiseError(OG_ERR_FORMAT, f"{UNIT} != {{ KB, MB, GB, TB }}")
        return

    # Obtener el tamaño del sistema de archivo (si no está formateado; tamaño = 0).
    MNTDIR = ogMount(int_ndisk, int_npartition)
    if MNTDIR:
        result = subprocess.run(["df", "-BK", MNTDIR], capture_output=True, text=True)
        VALUE = result.stdout.split("\n")[1].split()[1]
        SIZE = float(VALUE) / FACTOR
    else:
        SIZE = 0

    # Devolver el tamaño (quitar decimales si son 0).
    return int(SIZE) if SIZE.is_integer() else SIZE

def ogGetFsType(int_ndisk, int_nfilesys):
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp("ogGetFsType", "ogGetFsType int_ndisk int_nfilesys", "ogGetFsType 1 1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición.
    PART = ogDiskToDev(int_ndisk, int_nfilesys)
    if not PART:
        return

    # Detectar tipo de sistema de archivo (independientemente del tipo de partición).
    if PART.startswith("/"):
        result = subprocess.run(["blkid", "-o", "export", PART], capture_output=True, text=True)
        TYPE = ""
        for line in result.stdout.split("\n"):
            if line.startswith("TYPE="):
                TYPE = line.split("=")[1].upper()
                break
    else:
        subprocess.run(["zfs", "mount", PART], stderr=subprocess.DEVNULL)
        result = subprocess.run(["mount"], capture_output=True, text=True)
        TYPE = ""
        for line in result.stdout.split("\n"):
            if line.startswith(PART):
                TYPE = line.split()[4].upper()
                break

    # Componer valores correctos.
    if TYPE == "EXT4":
        if f"{int_ndisk} {int_nfilesys}" == ogFindCache():
            if ogIsFormated(int_ndisk, int_nfilesys):
                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", "")

    if TYPE:
        return TYPE

def ogGetMountPoint(int_ndisk, int_nfilesys):
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp("ogGetMountPoint", "ogGetMountPoint int_ndisk int_nfilesys", "ogGetMountPoint 1 1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición.
    PART = ogDiskToDev(int_ndisk, int_nfilesys)
    if not PART:
        return

    # Devolver punto de montaje.
    result = subprocess.run(["findmnt", "-n", "-o", "TARGET", PART], capture_output=True, text=True)
    return result.stdout.strip()

def ogIsFormated(int_ndisk, int_nfilesys):
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp("ogIsFormated", "ogIsFormated int_ndisk int_nfilesys", "ogIsFormated 1 1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición.
    PART = ogDiskToDev(int_ndisk, int_nfilesys)
    if not PART:
        return

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

def ogIsLocked(int_ndisk, int_nfilesys):
    return ogIsPartitionLocked(int_ndisk, int_nfilesys)

def ogIsPartitionLocked(int_ndisk, int_npartition):
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp("ogIsPartitionLocked", "ogIsPartitionLocked int_ndisk int_npartition", "ogIsPartitionLocked 1 1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición.
    PART = ogDiskToDev(int_ndisk, int_npartition)
    if not PART:
        return

    # Comprobar existencia de fichero de bloqueo de la partición o de su disco.
    LOCKDISK = f"/var/lock/lock{ogDiskToDev(int_ndisk).replace('/', '-')}"
    LOCKPART = f"/var/lock/lock{PART.replace('/', '-')}"
    return os.path.isfile(LOCKDISK) or os.path.isfile(LOCKPART)

def ogIsMounted(int_ndisk, int_nfilesys):
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp("ogIsMounted", "ogIsMounted int_ndisk int_nfilesys", "ogIsMounted 1 1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener punto de montaje.
    MNTDIR = ogGetMountPoint(int_ndisk, int_nfilesys)
    return bool(MNTDIR)

def ogIsReadonly(int_ndisk, int_nfilesys):
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp("ogIsReadonly", "ogIsReadonly int_ndisk int_nfilesys", "ogIsReadonly 1 1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición.
    PART = ogDiskToDev(int_ndisk, int_nfilesys)
    if not PART:
        return

    # Comprobar si la partición está montada en modo de solo lectura.
    result = subprocess.run(["findmnt", "-n", "-o", "OPTIONS", PART], capture_output=True, text=True)
    options = result.stdout.strip().split(",")
    return "ro" in options

def ogIsWritable(int_ndisk, int_nfilesys):
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp("ogIsWritable", "ogIsWritable int_ndisk int_nfilesys", "ogIsWritable 1 1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición.
    PART = ogDiskToDev(int_ndisk, int_nfilesys)
    if not PART:
        return

    # Comprobar si la partición está montada en modo de escritura.
    result = subprocess.run(["findmnt", "-n", "-o", "OPTIONS", PART], capture_output=True, text=True)
    options = result.stdout.strip().split(",")
    return "rw" in options

def ogLock(int_ndisk, int_nfilesys):
    ogLockPartition(int_ndisk, int_nfilesys)

def ogLockPartition(int_ndisk, int_npartition):
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp("ogLockPartition", "ogLockPartition int_ndisk int_npartition", "ogLockPartition 1 1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición.
    PART = ogDiskToDev(int_ndisk, int_npartition)
    if not PART:
        return

    # Crear archivo de bloqueo exclusivo.
    LOCKFILE = f"/var/lock/lock{PART.replace('/', '-')}"
    open(LOCKFILE, 'a').close()

def ogMount():
    args = sys.argv[2:]
    if args == ["CACHE"] or args == ["cache"]:
        ogMountCache()
    elif args == ["CDROM"] or args == ["cdrom"]:
        ogMountCdrom()
    else:
        ogMountFs(*args)

def ogMountFirstFs(int_ndisk):
    # Obtener número de particiones del disco.
    NPARTS = ogGetPartitionsNumber(int_ndisk)
    for PART in range(1, NPARTS + 1):
        MNTDIR = ogMount(int_ndisk, PART)
        if MNTDIR:
            return MNTDIR
    ogRaiseError(OG_ERR_NOTFOUND, int_ndisk)
    return OG_ERR_NOTFOUND

def ogMountFs(int_ndisk, int_nfilesys):
    FUNCNAME = ogExecAndLog.__name__
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp(f"{FUNCNAME}", "{FUNCNAME} int_ndisk int_nfilesys", "{FUNCNAME} 1 1  =>  /mnt/sda1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición.
    PART = ogDiskToDev(int_ndisk, int_nfilesys)
    if not PART:
        return

    # Comprobar si el sistema de archivos ya está montada.
    MNTDIR = ogGetMountPoint(int_ndisk, int_nfilesys)
    # Si no, montarlo en un directorio de sistema.
    if not MNTDIR:
        # Error si la particion esta bloqueada.
        if ogIsLocked(int_ndisk, int_nfilesys):
            ogRaiseError(OG_ERR_LOCKED, f"{MSG_PARTITION}, {int_ndisk} {int_nfilesys}")
            return

        # El camino de un dispositivo normal comienza por el carácter "/".
        if PART.startswith("/"):
            # Crear punto de montaje o enlace simbólico para caché local.
            MNTDIR = PART.replace("/dev", "/mnt")
            DEBUG = "no"
            if f"{int_ndisk} {int_nfilesys}" == ogFindCache() and OGCAC:
                os.makedirs(OGCAC, exist_ok=True)
                os.symlink(OGCAC, MNTDIR)
            else:
                os.makedirs(MNTDIR, exist_ok=True)
            del DEBUG

            # Montar sistema de archivos.
            try:
                subprocess.run(["mount", PART, MNTDIR], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            except subprocess.CalledProcessError:
                try:
                    subprocess.run(["mount", PART, MNTDIR, "-o", "force,remove_hiberfile"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                except subprocess.CalledProcessError:
                    ogRaiseError(OG_ERR_PARTITION, f"{int_ndisk}, {int_nfilesys}")
                    return
            # Aviso de montaje de solo lectura.
            if ogIsReadonly(int_ndisk, int_nfilesys):
                ogEcho("warning", f"{FUNCNAME}: {MSG_MOUNTREADONLY}: \"{int_ndisk}, {int_nfilesys}\"")
        else:
            # Montar sistema de archivos ZFS (un ZPOOL no comienza por "/").
            try:
                subprocess.run(["zfs", "mount", PART], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            except subprocess.CalledProcessError:
                pass

    return MNTDIR

def ogMountCdrom():
    DEV = "/dev/cdrom"  # Por defecto
    MNTDIR = subprocess.run(["mount", "-l", "-t", "iso9660", DEV], capture_output=True, text=True)
    MNTDIR = MNTDIR.stdout.strip().split(" ")[2]
    if not MNTDIR:
        MNTDIR = DEV.replace("/dev", "/mnt")
        os.makedirs(MNTDIR, exist_ok=True)
        try:
            subprocess.run(["mount", "-t", "iso9660", DEV, MNTDIR], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        except subprocess.CalledProcessError:
            ogRaiseError(OG_ERR_PARTITION, "cdrom")
            return
    return MNTDIR

def ogReduceFs(int_ndisk, int_nfilesys):
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp("ogReduceFs", "ogReduceFs int_ndisk int_nfilesys", "ogReduceFs 1 1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición.
    PART = ogDiskToDev(int_ndisk, int_nfilesys)
    if not PART:
        return

    # Redimensionar según el tipo de partición.
    TYPE = ogGetFsType(int_ndisk, int_nfilesys)
    if TYPE == "EXT4":
        ogUnmount(int_ndisk, int_nfilesys)
        subprocess.run(["resize2fs", "-fpM", PART], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    elif TYPE == "BTRFS":
        MNTDIR = ogMount(int_ndisk, int_nfilesys)
        SIZE = subprocess.run(["btrfs", "filesystem", "show", MNTDIR], capture_output=True, text=True)
        SIZE = SIZE.stdout.strip().split(" ")[6]
        SIZE = int(float(SIZE) * 1.1 + 1)
        subprocess.run(["btrfs", "filesystem", "resize", str(SIZE), MNTDIR], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    elif TYPE in ["REISERFS", "REISER4"]:
        MNTDIR = ogMount(int_ndisk, int_nfilesys)
        SIZE = int(subprocess.run(["df", "-k", MNTDIR], capture_output=True, text=True).stdout.strip().split("\n")[1].split()[2])
        SIZE = SIZE * 110 // 100
        ogUnmount(int_ndisk, int_nfilesys)
        subprocess.run(["resize_reiserfs", "-s" + str(SIZE) + "K", PART], input="y\n", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    elif TYPE == "NTFS":
        ogUnmount(int_ndisk, int_nfilesys)
        MAXSIZE, SIZE = subprocess.run(["ntfsresize", "-fi", PART], capture_output=True, text=True)
        MAXSIZE = MAXSIZE.strip().split(" ")[3]
        SIZE = SIZE.strip().split(" ")[4]
        SIZE = int(float(SIZE) * 1.1 / 1024 + 1) * 1024
        RETVAL = 1
        while RETVAL != 0 and SIZE + EXTRASIZE < MAXSIZE:
            EXTRASIZE = subprocess.run(["ntfsresize", "-fns", str(SIZE), PART], capture_output=True, text=True)
            EXTRASIZE = int(EXTRASIZE.stdout.strip()) if EXTRASIZE.stdout.strip() else 0
            RETVAL = EXTRASIZE != 0
            SIZE += EXTRASIZE
        if SIZE < MAXSIZE:
            subprocess.run(["ntfsresize", "-fs", str(SIZE), PART], input="y\n", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    elif TYPE in ["FAT32", "FAT16"]:
        # No se reduce (por el momento).
        pass
    elif TYPE == "HFS" or TYPE == "HFSPLUS":
        # No se reduce (por el momento).
        pass
    elif TYPE == "UFS":
        # No se reduce (por el momento).
        pass
    else:
        ogRaiseError(OG_ERR_PARTITION, f"{int_ndisk}, {int_nfilesys}")

    # Devuelve tamaño del sistema de ficheros.
    return ogGetFsSize(int_ndisk, int_nfilesys)

def ogUnlock(int_ndisk, int_npartition):
    ogUnlockPartition(int_ndisk, int_npartition)

def ogUnlockPartition(int_ndisk, int_npartition):
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp("ogUnlockPartition", "ogUnlockPartition int_ndisk int_npartition", "ogUnlockPartition 1 1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición.
    PART = ogDiskToDev(int_ndisk, int_npartition)
    if not PART:
        return

    # Borrar archivo de bloqueo exclusivo.
    LOCKFILE = f"/var/lock/lock{PART.replace('/', '-')}"
    os.remove(LOCKFILE)

def ogUnmount():
    ogUnmountFs(*sys.argv[2:])

def ogUnmountFs(int_ndisk, int_npartition):
    FUNCNAME = ogUnmountFs.__name__
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp("ogUnmountFs", "ogUnmountFs int_ndisk int_npartition", "ogUnmountFs 1 1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición y punto de montaje.
    PART = ogDiskToDev(int_ndisk, int_npartition)
    MNTDIR = ogGetMountPoint(int_ndisk, int_npartition)

    # Si está montada, desmontarla.
    if MNTDIR:
        # Error si la particion está bloqueada.
        if ogIsPartitionLocked(int_ndisk, int_npartition):
            ogRaiseError(OG_ERR_LOCKED, f"{MSG_PARTITION}, {int_ndisk} {int_npartition}")
            return

        # Desmontar y borrar punto de montaje.
        try:
            subprocess.run(["umount", PART], check=True, stderr=subprocess.DEVNULL)
        except subprocess.CalledProcessError:
            ogEcho("warning", f"{FUNCNAME}: {MSG_DONTUNMOUNT}: \"{int_ndisk}, {int_npartition}\"")
        try:
            os.rmdir(MNTDIR)
        except OSError:
            os.remove(MNTDIR)
    else:
        ogEcho("warning", f"{MSG_DONTMOUNT}: \"{int_ndisk},{int_npartition}\"")
        ogUnmountFs(int_ndisk, int_npartition)

def ogUnmountAll(int_ndisk):
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp("ogUnmountAll", "ogUnmountAll int_ndisk", "ogUnmountAll 1")
        return

    # Error si no se recibe 1 parámetro.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición y punto de montaje.
    DISK = ogDiskToDev(int_ndisk)
    for PART in range(1, ogGetPartitionsNumber(int_ndisk) + 1):
        if ogGetFsType(int_ndisk, PART) != "CACHE":
            ogUnmount(int_ndisk, PART)

def ogUnsetDirtyBit(int_ndisk, int_nfilesys):
    # Si se solicita, mostrar ayuda.
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp("ogUnsetDirtyBit", "ogUnsetDirtyBit int_ndisk int_nfilesys", "ogUnsetDirtyBit 1 1")
        return

    # Error si no se reciben 2 parámetros.
    if len(sys.argv) != 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    # Obtener partición y punto de montaje.
    PART = ogDiskToDev(int_ndisk, int_nfilesys)
    if not PART:
        return

    # Realizar acciones específicas según el tipo de sistema de archivos.
    TYPE = ogGetFsType(int_ndisk, int_nfilesys)
    if TYPE == "NTFS":
        ogUnmount(int_ndisk, int_nfilesys)
        subprocess.run(["ntfsfix", "-d", PART], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    else:
        pass  # Add more specific actions for other file systems if needed.

def ogGetFreeSize(int_ndisk, int_npartition, str_SizeOutput):
    if len(sys.argv) == 3 and sys.argv[2] == "help":
        ogHelp("ogGetFreeSize", "ogGetFreeSize int_ndisk int_npartition str_SizeOutput", "ogGetFreeSize 1 1 GB")
        return

    if len(sys.argv) < 3:
        ogRaiseError(OG_ERR_FORMAT)
        return

    PART = ogDiskToDev(int_ndisk, int_npartition)
    if not PART:
        return

    unit = str_SizeOutput
    if not unit:
        unit = "GB"

    factor = 1.024 / 1000000
    if unit == "kB":
        factor = 1.024
    elif unit == "MB":
        factor = 1.024 / 1000

    result = subprocess.run(["df", PART], capture_output=True, text=True)
    output = result.stdout.strip().split("\n")[1].split()
    size = float(output[1]) * factor
    used = float(output[2]) * factor
    free = float(output[3]) * factor

    return free