#!/bin/bash
#/**
#@file    Cache.lib
#@brief   Librería o clase Cache
#@class   Cache
#@brief   Funciones para gestión de la caché local de disco.
#@version 1.1.1
#@warning License: GNU GPLv3+
#*/


#/**
#         ogCreateCache [int_ndisk] int_partsize
#@brief   Define la caché local, por defecto en partición 4 del disco 1.
#@param   int_ndisk  numero de disco donde crear la cache, si no se indica es el 1 por defecto
#@param   int_npart      número de partición (opcional, 4 por defecto)
#@param   int_partsize   tamaño de la partición (en KB)
#@return  (nada, por determinar)
#@exception OG_ERR_FORMAT   formato incorrecto.
#@note    Requisitos: sfdisk, parted, awk, sed
#@warning El tamaño de caché debe estar entre 50 MB y la mitad del disco.
#@warning La caché no puede solaparse con las particiones de datos.
#@version 0.9.1 - Definición de caché local.
#@author  Ramon Gomez, ETSII Universidad de Sevilla
#@date    2010/03/09
#@version 0.9.2 - Corrección definición de límites.
#@author  Ramon Gomez, ETSII Universidad de Sevilla
#@date    2010/06/01
#@version 1.0.4 - Soporte para discos GPT.
#@author  Universidad de Huelva
#@date    2012/03/13
#@version 1.0.5 - Posibilidad de crear la cache en cualquier disco duro
#@author  Universidad de Huelva
#@date    2012/09/18
#@version 1.1.0 - Posibilidad de crear la caché en cualquier partición.
#@author  Ramon Gomez, ETSII Universidad de Sevilla
#@date    2016/05/25
#@version 1.1.0 - Soporte discos con sectores de 4k
#@date    2017/01/09
#@version 1.0.6b - Al crear las particiones ordenamos los dispositivos en el fichero auxiliar.
#@author  Irina Gomez, ETSII Universidad de Sevilla
#@date    2017/01/09
#*/ ##
function ogCreateCache ()
{
# Variables locales.
local FINDCACHE IOSIZE NDSK SIZECACHE PART DISK START END ENDPREVPART SIZE MINSIZE MAXSIZE
local PTTYPE ID TMPFILE NVME_PREFIX
# Si se solicita, mostrar ayuda.
if [ "$*" == "help" ]; then
    ogHelp "$FUNCNAME" "$FUNCNAME [int_ndisk [int_npart]] int_partsize" \
           "$FUNCNAME 10000000" "$FUNCNAME 1 10000000" "$FUNCNAME 1 4 10000000"
    return
fi
# Si se recibe un parametro, sera el tamano de la cache
case $# in
    1)  # Error, si no es un entero positivo
        [[ $1 =~ ^[1-9][0-9]*$ ]] || ogRaiseError $OG_ERR_FORMAT "$1" || return $?
        NDSK=1
        PART=4
        SIZECACHE=$1
        ;;
    2)  # Error, si no son enteros positivos
        [[ $1 =~ ^[1-9][0-9]*$ ]] || ogRaiseError $OG_ERR_FORMAT "$1" || return $?
        [[ $2 =~ ^[1-9][0-9]*$ ]] || ogRaiseError $OG_ERR_FORMAT "$2" || return $?
        NDSK=$1
        PART=4
        SIZECACHE=$2
        ;;
    3)  # Error, si no son enteros positivos
        [[ $1 =~ ^[1-9][0-9]*$ ]] || ogRaiseError $OG_ERR_FORMAT "$1" || return $?
        [[ $2 =~ ^[1-9][0-9]*$ ]] || ogRaiseError $OG_ERR_FORMAT "$2" || return $?
        [[ $3 =~ ^[1-9][0-9]*$ ]] || ogRaiseError $OG_ERR_FORMAT "$3" || return $?
        NDSK=$1
        PART=$2
        SIZECACHE=$3
        ;;
    *)  ogRaiseError $OG_ERR_FORMAT
        return $?
        ;;
esac

TMPFILE=/tmp/sfdisk$$
DISK=$(ogDiskToDev $NDSK) || return $?

 # PATCH Para discos nvme la particion debe ser p1, p2, etc...en lugar de 1,2, sino falla sfdisk
NVME_PREFIX=""
if [[ $DISK == *"nvme"* ]]; then
        NVME_PREFIX="p"
fi


END=$[$(ogGetLastSector $NDSK 2>/dev/null)]  # Sector final del disco.
SIZE=$[$SIZECACHE*2]                            # Tamaño en sectores de 512 B.
# Inicio partición cache según el disco tenga sectores de 4k o menores
IOSIZE=$(fdisk -l $DISK | awk '/I\/O/ {print $4}')
if [ $IOSIZE -eq 4096 ]; then
    END=$[$END-8192]
    START=$[END-SIZE+2048-(END-SIZE)%2048]
else
    START=$[END-SIZE+1]
fi
ENDPREVPART=$[$(ogGetLastSector $NDSK $[PART-1] 2>/dev/null)]
# Error si tamaño no está entre límites permitidos o si se solapa con la partición anterior.
MINSIZE=25000                   # Error de formateo si tamaño < 50 MB.
MAXSIZE=$END                    # Para restringir tamaño > mitad del disco:  MAXSIZE=$[END/2]
if [ $SIZE -lt $MINSIZE -o $SIZE -gt $MAXSIZE -o $START -le $ENDPREVPART ]; then
    ogRaiseError $OG_ERR_FORMAT "$1" || return $?
fi

# Desmontar todos los sistemas de archivos del disco.
ogUnmountAll $NDSK 2>/dev/null
# Definir particiones y notificar al kernel.
# En el caso de ser disco GPT, de momento se borra la particion y se vuelve a crear,
# por lo que se pierden los datos.
PTTYPE=$(ogGetPartitionTableType $NDSK)
if [ -z "$PTTYPE" ]; then
    PTTYPE="MSDOS"                      # Por defecto para discos vacíos.
    ogCreatePartitionTable $NDSK $PTTYPE
fi
case "$(ogGetPartitionTableType $NDSK)" in
    GPT)
        # Si la tabla de particiones no es valida, volver a generarla.
        [ ! $(sgdisk -p $DISK &>/dev/null) ] || echo -e "2\nw\nY\n" | gdisk $DISK
        # Si existe la cache se borra previamente
        [ -n "$(ogFindCache)" ] && ogDeleteCache
        # Capturamos el codigo de particion GPT para cache
        # PATCH - Cuando es GPT, la particion con codigo CACHE (CA00) no existe y no puede crearse, se cambia por LINUX (8300)
        ID=$(ogTypeToId LINUX GPT)
        sgdisk $DISK -n$PART:$START:$END -c$PART:CACHE -t$PART:$ID 2>/dev/null
        ;;
    MSDOS)
        # Si la tabla de particiones no es valida, volver a generarla.
        parted -s $DISK print &>/dev/null || fdisk $DISK <<< "w"
        # Definir particiones y notificar al kernel.
        ID=$(ogTypeToId CACHE MSDOS)
        # Salvamos la configuración de las particiones e incluimos la cache.
        trap "rm -f $TMPFILE" 1 2 3 9 15
        sfdisk --dump $DISK | grep -v $DISK$PART > $TMPFILE
        echo  "$DISK$NVME_PREFIX$PART : start= $START, size= $SIZE, Id=$ID" >> $TMPFILE
        # Ordenamos las líneas de los dispositivos
        UNIT=$(grep unit $TMPFILE)
        grep ^/dev $TMPFILE|sort -o $TMPFILE
        sed -i "1i $UNIT\n" $TMPFILE
        # Guardamos nueva configuración en el disco.
        sfdisk --no-reread  $DISK < $TMPFILE
        rm -f $TMPFILE
        ;;
esac
# Actualiza la tabla de particiones en el kernel.
ogUpdatePartitionTable
}


#/**
#         ogDeleteCache
#@brief   Elimina la partición de caché local.
#@return  (nada, por determinar)
#@exception OG_ERR_FORMAT   formato incorrecto.
#@note    Requisitos: fdisk, sgdisk, partprobe
#@version 0.91 - Definición de caché local.
#@author  Ramon Gomez, ETSII Universidad de Sevilla
#@date    2010/03/11
#@version 1.0.4 - Soporte para discos GPT.
#@author  Universidad de Huelva
#@date    2012/03/13
#@version 1.0.6b - llamada correcta a ogUpdatePartitionTable
#@author  Antonio Doblas Universidad de Málaga
#@date    2016/11/16
#@version 1.1.0 - Sustituir "sfdisk" por "fdisk" para discos MSDOS.
#@author  Ramon Gomez, ETSII Universidad de Sevilla
#@date    2016/05/25
#*/ ##
function ogDeleteCache ()
{
# Variables locales.
local NDISK NPART DISK
# Si se solicita, mostrar ayuda.
if [ "$*" == "help" ]; then
    ogHelp "$FUNCNAME" "$FUNCNAME"
    return
fi
# Error si no se encuentra partición de caché.
read NDISK NPART <<<"$(ogFindCache)"
[ -n "$NDISK" -a -n "$NPART" ] || ogRaiseError $OG_ERR_PARTITION "$MSG_NOCACHE" || return $?
DISK=$(ogDiskToDev $NDISK)

# Desmontar todos los sistemas de archivos del disco.
ogUnmountAll $NDISK 2>/dev/null
case "$(ogGetPartitionTableType $NDISK)" in
    GPT)
        # Si la tabla de particiones no es valida, volver a generarla.
        [ ! $(sgdisk -p $DISK 2>&1 >/dev/null) ] || echo -e "2\nw\nY\n" | gdisk $DISK
        sgdisk $DISK -d$NPART 2>/dev/null 
        ;;
    MSDOS)
        # Si la tabla de particiones no es valida, volver a generarla.
        parted -s $DISK print &>/dev/null || fdisk $DISK <<< "w"
        # Eliminar la partición de caché.
        echo -e "d\n$NPART\nw" | fdisk $DISK 2>/dev/null 
        ;;
esac
# Borrar etiqueta de la caché.
rm -f /dev/disk/by-label/CACHE
#Actualiza la tabla de particiones en el kernel.
ogUpdatePartitionTable $NDISK
}


#/**
#         ogFindCache 
#@brief   Detecta la partición caché local.
#@param   No requiere parametros
#@return  int_ndisk int_npart - devuelve el par nº de disco-nº de partición .
#@warning Si no hay cache no devuelve nada
#@version 0.1 - Integracion para Opengnsys - EAC: FindCache() en ATA.lib -  HIDRA: DetectarCache.sh
#@author Ramon Gomez, ETSII Universidad de Sevilla 
#@Date    2008/06/19
#@author  Antonio J. Doblas Viso. Universidad de Malaga 
#@Date    2008/10/27
#@version 0.91 - Adaptacion a la cache local de OpenGnSys.
#@author  Ramon Gomez, ETSII Universidad de Sevilla
#@date    2010/03/16
#@version 1.0.5 - Obtener caché en discos GPT.
#@author  Alberto García, Universidad de Málaga y Ramon Gomez, ETSII Universidad de Sevilla
#@date    2014/05/28
#*/ ##
function ogFindCache ()
{
# Variables locales
local DISK PART
# Si se solicita, mostrar ayuda.
if [ "$*" == "help" ]; then
    ogHelp "$FUNCNAME" "$FUNCNAME" "$FUNCNAME  =>  1 4"
    return
fi
# Obtener el dispositivo del sistema de archivos etiquetado como "CACHE".
PART=$(blkid -L "CACHE")
# En discos nvme con particiones GPT la partición se detecta usando el tag PARTLABEL
PART=${PART:-$(blkid -t PARTLABEL=CACHE | awk -F: '{print $1}')}
# Si no se detecta, obtener particiones marcadas de tipo caché en discos MSDOS.
PART=${PART:-$(sfdisk -l 2>/dev/null | awk '$6~/ca|a7/ {print $1}')}

# Por último revisar todos los discos GPT y obtener las particiones etiquetadas como caché.
if [ -z "$PART" ]; then
    for DISK in $(ogDiskToDev); do
        # Nota: se añade espacio separador solo si existe valor previo.
        PART="${PART:+"$PART "}$(sgdisk -p $DISK 2>/dev/null | awk -v d=$DISK '$7~/CACHE/ {printf "%s%s",d,$1;}')"
    done
fi

# Devolver número de disco y número de partición de la 1ª partición encontrada.
ogDevToDisk ${PART%% *} 2>/dev/null
}


#/**
#         ogFormatCache
#@brief   Formatea el sistema de ficheros para la caché local.
#@return  (por determinar)
#@warning Prueba con formato Reiser.
#@attention
#@note    El sistema de archivos de la caché se queda montado.
#@version 0.1 -  Integracion para Opengnsys  - EAC: FormatCache() en ATA.lib
#@author  Antonio J. Doblas Viso. Universidad de Malaga
#@date   2008/10/27
#@version 0.91 - Creacion cache local.
#@author  Ramon Gomez, ETSII Universidad de Sevilla
#@date    2010-03-11
#@version 1.1.0 - llamada a updateBootCache.
#@author  Antonio J. Doblas Viso. Universidad de Malaga
#@date    2018-01-21

#*/ ##
function ogFormatCache ()
{
# Variables locales.
local DEV MNTDIR OPTIONS
# Si se solicita, mostrar ayuda.
if [ "$*" == "help" ]; then
    ogHelp "$FUNCNAME" "$FUNCNAME"
    return
fi

# Error si no hay definida partición de caché. 
DEV=$(ogFindCache) || ogRaiseError $OG_ERR_PARTITION "$MSG_NOCACHE" || return $? 
DEV=$(ogDiskToDev $DEV) || return $? 

# Formatear sistema de ficheros. 
ogUnmountCache 2>/dev/null 
OPTIONS="extent,large_file"
[[ $(uname -r) =~ ^5 ]] && OPTIONS+=",uninit_bg,^metadata_csum,^64bit"
mkfs.ext4 -q -F $DEV -L "CACHE" -O "$OPTIONS" 2>/dev/null || ogRaiseError $OG_ERR_PARTITION "CACHE" || return $?

# Crear estructura básica. 
MNTDIR=$(ogMountCache) 
mkdir -p $MNTDIR/$OGIMG 

# Incluir kernel e Initrd del ogLive
updateBootCache 2>&1>/dev/null
}


#/**
#         ogGetCacheSize
#@brief   Devuelve el tamaño definido para la partición de caché.
#@return  int_partsize   tamaño de la partición (en KB)
#@exception OG_ERR_PARTITION  No existe partición de caché.
#@version 0.1 -  Integracion para Opengnsys  -  EAC: InfoCache() en FileSystem.lib
#@author  Antonio J. Doblas Viso. Universidad de Malaga
#@date   2008/10/27
#@version 0.91 - Definicion de cache local.
#@author  Ramon Gomez, ETSII Universidad de Sevilla
#@date    2010/03/09
#*/ ##
function ogGetCacheSize ()
{
# Variables locales
local PART

# Si se solicita, mostrar ayuda.
if [ "$*" == "help" ]; then
    ogHelp "$FUNCNAME" "$FUNCNAME" "$FUNCNAME  =>  10000000"
    return
fi
# Error si no se encuentra partición de caché.
PART=$(ogFindCache) || ogRaiseError $OG_ERR_PARTITION "$MSG_NOCACHE" || return $?

# Devuelve tamaño de la partición de caché.
ogGetPartitionSize $PART
}


#/**
#         ogGetCacheSpace
#@brief   Devuelve el espacio de disco disponible para la partición de caché.
#@return  int_size   tamaño disponible (en KB)
#@note    El espacio disponible es el que hay entre el límite superior de la partición 3 del disco 1 y el final de dicho disco, y no puede ser superior a la mitad de dicho disco.
#@version 0.1 -  Integracion para Opengnsys  -  EAC: InfoCache() en FileSystem.lib
#@author  Antonio J. Doblas Viso. Universidad de Malaga
#@date   2008/10/27
#@version 0.91 - Definicion de cache local.
#@author  Ramon Gomez, ETSII Universidad de Sevilla
#@date    2010/03/09
#@version 1.0.5 - Uso de ogFindCache para detectar disco y particion
#@author  Universidad de Huelva
#@date    2012/09/18
#*/ ##
function ogGetCacheSpace ()
{
# Variables locales.
local NDISK DISK NPART SECTORS CYLS ENDPART3
# Si se solicita, mostrar ayuda.
if [ "$*" == "help" ]; then
    ogHelp "$FUNCNAME" "$FUNCNAME" "$FUNCNAME  =>  23165386"
    return
fi
# Parche UHU para usar ogFindCache en lugar de 1
# Error si no se encuentra partición de caché.
read NDISK NPART <<<"$(ogFindCache)"
[ -n "$NDISK" -a -n "$NPART" ] || ogRaiseError $OG_ERR_PARTITION "$MSG_NOCACHE" || return $?
DISK=$(ogDiskToDev $NDISK) || return $?

SECTORS=$(awk -v D=${DISK#/dev/} '{if ($4==D) {print $3*2}}' /proc/partitions)
CYLS=$(sfdisk -g $DISK | cut -f2 -d" ")
SECTORS=$[SECTORS/CYLS*CYLS-1]
ENDPART3=$(sfdisk -uS -l $DISK | awk -v P="${DISK}3" '{if ($1==P) print $3}')
# Mostrar espacio libre en KB (1 KB = 2 sectores)
if [ $ENDPART3 -gt $[SECTORS/2] ]; then
    echo $[(SECTORS-ENDPART3)/2]
else
    echo $[SECTORS/4]
fi
}


#/**
#         ogMountCache
#@brief   Monta la partición Cache y exporta la variable $OGCAC
#@param   sin parametros
#@return  path_mountpoint - Punto de montaje del sistema de archivos de cache.
#@warning Salidas de errores no determinada
#@version 0.1 -  Integracion para Opengnsys  -  EAC: MountCache() en FileSystem.lib - HIDRA: MontarCache.sh
#@author  Ramon Gomez, ETSII Universidad de Sevilla
#@date    2008/06/19
#@author  Antonio J. Doblas Viso. Universidad de Malaga
#@Date    2008/10/27
#@version 0.91 - Adaptacion a la cache local de OpenGnSys.
#@author  Ramon Gomez, ETSII Universidad de Sevilla
#@date    2010/03/16
#@version 1.0 - Correccion multiples montajes de cache.
#@author  Antonio J. Doblas Viso, Universidad de Malaga
#@date    2011/02/24
#*/ ##
function ogMountCache ()
{
# Si se solicita, mostrar ayuda.
if [ "$*" == "help" ]; then
    ogHelp "$FUNCNAME" "$FUNCNAME" "$FUNCNAME  ==>  /mnt/sda4"
    return
fi

ogMountFs $(ogFindCache) 2>/dev/null || ogRaiseError $OG_ERR_PARTITION "$MSG_NOCACHE" || return $?
}


#/**
#         ogUnmountCache
#@brief   Desmonta la particion Cache y elimina la variable $OGCAC
#@param   sin parametros
#@return  nada
#@warning Salidas de errores no determinada
#@version 0.1 -  Integracion para Opengnsys  -  EAC: UmountCache() en FileSystem.lib 
#@author  Antonio J. Doblas Viso. Universidad de Malaga
#@Date    2008/10/27
#@version 0.91 - Adaptacion a la cache local de OpenGnSys.
#@author  Ramon Gomez, ETSII Universidad de Sevilla
#@date    2010/03/16
#@version 1.0 - Correccion multiples montajes de cache.
#@author  Antonio J. Doblas Viso, Universidad de Malaga
#@date    2011/02/24
#*/ ##
function ogUnmountCache ()
{
# Variables locales.
local CACHE
# Si se solicita, mostrar ayuda.
if [ "$*" == "help" ]; then
    ogHelp "$FUNCNAME" "$FUNCNAME"
    return
fi

CACHE=$(ogFindCache) || ogRaiseError $OG_ERR_PARTITION "$MSG_NOCACHE"
ogIsMounted $CACHE || return 0
ogUnmountFs $CACHE
# Borrar enlace simbólico de /mnt/ParticiónCache.
rm -f $(ogDiskToDev $CACHE | sed 's/dev/mnt/')
}

