From 3f5fac71eab295f06b08cc137f62042c0e447d5c Mon Sep 17 00:00:00 2001 From: Antonio Emmanuel Guerrero Silva Date: Tue, 9 Apr 2024 23:43:02 -0600 Subject: [PATCH 001/149] Initial version --- installer/ogboot_devel_installer.sh | 1879 +++++++++++++++++++++++++++ installer/ogboot_devel_uninstall.sh | 77 ++ 2 files changed, 1956 insertions(+) create mode 100755 installer/ogboot_devel_installer.sh create mode 100755 installer/ogboot_devel_uninstall.sh diff --git a/installer/ogboot_devel_installer.sh b/installer/ogboot_devel_installer.sh new file mode 100755 index 0000000..2548976 --- /dev/null +++ b/installer/ogboot_devel_installer.sh @@ -0,0 +1,1879 @@ +#!/bin/bash + +##################################################################### +####### Script instalador OpenGnsys +####### Autor: Luis Guillén +##################################################################### + + +##################################################################### +####### Funciones de configuración +##################################################################### + +# Devuelve en la variable PASSWORD la clave introducida por el usuario (o la indicada por defecto) +function enterPassword () +{ + local PASSWORD2 + local DEFAULT_PASSWORD="$1" + + while : ; do + stty -echo + read -r PASSWORD + stty echo + if [ -z "$PASSWORD" ]; then + # Si esta vacio ponemos el valor por defecto + PASSWORD="${PASSWORD:-$DEFAULT_PASSWORD}" + break + else + if [ -n "${PASSWORD//[a-zA-Z0-9]/}" ]; then # Comprobamos que sea un valor alfanumerico + echo -e "\\aERROR: Password must be alphanumeric, try again..." + else + echo -n -e "\\nConfirm password: " + stty -echo + read -r PASSWORD2 + stty echo + if [ "$PASSWORD" == "$PASSWORD2" ]; then + break + else + echo -e "\\aERROR: Passwords don't match, try again." + fi + fi + fi + echo -n -e "Please, enter a new password (${DEFAULT_PASSWORD}): " + done +} + +# Si la distribución no es la recomendada mostramos mensaje informativo. +function checkDistribution() +{ + local ADVISED VERSION + + ADVISED="18" + [ -r /etc/os-release ] && eval $(grep VERSION /etc/os-release) + + [[ "$VERSION" == "$ADVISED."* ]] && return + + echoAndLog "The OpenGnsys version 1.2.0 installation was tested with full functionality on Ubuntu 18.04 with PHP 7.2." + echo -n "Do you want to continue? [y/N]: " + read -r GO_ON + if [ "${GO_ON^^}" != "Y" ]; then + echoAndLog "We left the installation." && exit + fi +} + +# Recoge los datos de configuración introducidos por el usuario. +function userData () +{ + #### AVISO: Puede editar configuración de acceso por defecto. + #### WARNING: Edit default access configuration if you wish. + DEFAULT_MYSQLHOST="172.17.8.71" # IP por defecto de la base de datos + DEFAULT_MYSQL_ROOT_PASSWORD="passwordroot" # Clave por defecto root de MySQL + DEFAULT_OPENGNSYS_DB_USER="usuog" # Usuario por defecto de acceso a la base de datos + DEFAULT_OPENGNSYS_DB_PASSWD="passusuog" # Clave por defecto de acceso a la base de datos + DEFAULT_OPENGNSYS_CLIENT_PASSWD="og" # Clave por defecto de acceso del cliente + DEFAULT_OGLIVE="ogLive-bionic-5.4.0-40-generic-amd64-r20200629.85eceaf.iso " # Cliente ogLive + + echo -e "\\nOpenGnsys Installation" + echo "==============================" + + if [[ $- =~ s ]]; then + echo -e "\\nNot interactive mode: setting default configuration values.\\n" + MYSQLHOST="$DEFAULT_MYSQLHOST" + MYSQL_ROOT_PASSWORD="$DEFAULT_MYSQL_ROOT_PASSWORD" + OPENGNSYS_DB_USER="$DEFAULT_OPENGNSYS_DB_USER" + OPENGNSYS_DB_PASSWD="$DEFAULT_OPENGNSYS_DB_PASSWD" + OPENGNSYS_CLIENT_PASSWD="$DEFAULT_OPENGNSYS_CLIENT_PASSWD" + OGLIVE="$DEFAULT_OGLIVE" + return + fi + + # IP de Servidor MySQL + echo -n -e "\\nEnter IP or HOSTNAME for MySQL (${DEFAULT_MYSQLHOST}): " + enterPassword "$DEFAULT_MYSQL_HOST" + MYSQLHOST="$PASSWORD" + + # Clave root de MySQL + echo -n -e "\\nEnter root password for MySQL (${DEFAULT_MYSQL_ROOT_PASSWORD}): " + enterPassword "$DEFAULT_MYSQL_ROOT_PASSWORD" + MYSQL_ROOT_PASSWORD="$PASSWORD" + + # Usuario de acceso a la base de datos + while : ; do + echo -n -e "\\n\\nEnter username for OpenGnsys console (${DEFAULT_OPENGNSYS_DB_USER}): " + read -r OPENGNSYS_DB_USER + if [ -n "${OPENGNSYS_DB_USER//[a-zA-Z0-9]/}" ]; then # Comprobamos que sea un valor alfanumerico + echo -e "\\aERROR: Must be alphanumeric, try again..." + else + # Si esta vacio ponemos el valor por defecto + OPENGNSYS_DB_USER="${OPENGNSYS_DB_USER:-$DEFAULT_OPENGNSYS_DB_USER}" + break + fi + done + + # Clave de acceso a la base de datos + echo -n -e "\\nEnter password for OpenGnsys console (${DEFAULT_OPENGNSYS_DB_PASSWD}): " + enterPassword "$DEFAULT_OPENGNSYS_DB_PASSWD" + OPENGNSYS_DB_PASSWD="$PASSWORD" + + # Clave de acceso del cliente + echo -n -e "\\n\\nEnter root password for OpenGnsys client (${DEFAULT_OPENGNSYS_CLIENT_PASSWD}): " + enterPassword "$DEFAULT_OPENGNSYS_CLIENT_PASSWD" + OPENGNSYS_CLIENT_PASSWD="$PASSWORD" + unset PASSWORD + + # El ogclient sólo es compatible con ogLive de kernel 5.x. Se comenta la elección de ogLive. + # Selección de clientes ogLive para descargar. + #while : ; do + # echo -e "\\n\\nChoose ogLive client to install." + # echo -e "1) Kernel 5.4, 64-bit, EFI-compatible" + # echo -e "2) Kernel 3.2, 32-bit" + # echo -e "3) Both" + # echo -n -e "Please, type a valid number (1): " + # read -r OPT + # case "$OPT" in + # 1|"") OGLIVE="$DEFAULT_OGLIVE" + # break ;; + # 2) OGLIVE="ogLive-precise-3.2.0-23-generic-r5159.iso" + # break ;; + # 3) OGLIVE=" $DEFAULT_OGLIVE ogLive-precise-3.2.0-23-generic-r5159.iso"; + # break ;; + # *) echo -e "\\aERROR: unknown option, try again." + # esac + #done + OGLIVE="$DEFAULT_OGLIVE" + + echo -e "\\n==============================" +} + +# Asigna valores globales de configuración para el script. +function globalSetup () +{ + PROGRAMDIR=$(readlink -e "$(dirname "$0")") + PROGRAMNAME=$(basename "$0") + + # Comprobar si se ha descargado el paquete comprimido (REMOTE=0) o sólo el instalador (REMOTE=1). + OPENGNSYS_SERVER="opengnsys.es" + DOWNLOADURL="https://$OPENGNSYS_SERVER/trac/downloads" + if [ -d "$PROGRAMDIR/../installer" ]; then + REMOTE=0 + else + REMOTE=1 + fi + BRANCH="master" + CODE_URL="https://codeload.github.com/opengnsys/OpenGnsys/zip/$BRANCH" + API_URL="https://api.github.com/repos/opengnsys/OpenGnsys" + + # Directorios de instalación y destino de OpenGnsys. + WORKDIR=/tmp/opengnsys_installer + INSTALL_TARGET=/opt/opengnsys + INSTALL_OGBOOT_TARGET=/opt/ogboot + PATH=$PATH:$INSTALL_OGBOOT_TARGET/bin + + # Registro de incidencias. + OGLOGFILE=$INSTALL_OGBOOT_TARGET/log/${PROGRAMNAME%.sh}.log + LOG_FILE=/tmp/$(basename $OGLOGFILE) + + # Usuario del cliente para acceso remoto. + OPENGNSYS_CLIENT_USER="opengnsys" + # Nombre de la base datos y fichero SQL para su creación. + OPENGNSYS_DATABASE="ogAdmBD" + OPENGNSYS_DB_CREATION_FILE=ogboot/admin/Database/${OPENGNSYS_DATABASE}.sql +} + +# Generar variables de configuración del instalador +# Variables globales: +# - OSDISTRIB, OSVERSION - tipo y versión de la distribución GNU/Linux +# - DEPENDENCIES - array de dependencias que deben estar instaladas +# - UPDATEPKGLIST, INSTALLPKGS, CHECKPKGS - comandos para gestión de paquetes +# - INSTALLEXTRADEPS - instalar dependencias no incluidas en la distribución +# - STARTSERVICE, ENABLESERVICE - iniciar y habilitar un servicio +# - STOPSERVICE, DISABLESERVICE - parar y deshabilitar un servicio +# - APACHESERV, APACHECFGDIR, APACHESITESDIR, APACHEUSER, APACHEGROUP - servicio y configuración de Apache +# - APACHEENABLEMODS, APACHEENABLESSL, APACHEMAKECERT - habilitar módulos y certificado SSL +# - APACHEENABLEOG, APACHEOGSITE, - habilitar sitio web de OpenGnsys +# - PHPFPMSERV - servicio PHP FastCGI Process Manager para Apache +# - INETDSERV - servicio Inetd +# - DHCPSERV, DHCPCFGDIR - servicio y configuración de DHCP +# - MYSQLSERV, TMPMYCNF - servicio MySQL y fichero temporal con credenciales de acceso +# - MARIADBSERV - servicio MariaDB (sustituto de MySQL en algunas distribuciones) +# - RSYNCSERV, RSYNCCFGDIR - servicio y configuración de Rsync +# - SAMBASERV, SAMBACFGDIR - servicio y configuración de Samba +# - TFTPSERV, TFTPCFGDIR - servicio y configuración de TFTP/PXE +function autoConfigure() +{ +# Detectar sistema operativo del servidor (compatible con fichero os-release y con LSB). +if [ -f /etc/os-release ]; then + source /etc/os-release + OSDISTRIB="$ID" + OSVERSION="$VERSION_ID" +else + OSDISTRIB=$(lsb_release -is 2>/dev/null) + OSVERSION=$(lsb_release -rs 2>/dev/null) +fi +# Convertir distribución a minúsculas y obtener solo el 1er número de versión. +OSDISTRIB="${OSDISTRIB,,}" +OSVERSION="${OSVERSION%%.*}" + +# Configuración según la distribución GNU/Linux (usar minúsculas). +case "$OSDISTRIB" in + ubuntu|debian|linuxmint) + DEPENDENCIES=( subversion apache2 php php-ldap php-fpm mysql-client php-mysql isc-dhcp-server bittorrent tftp-hpa tftpd-hpa xinetd build-essential g++-multilib libmysqlclient-dev wget curl graphviz bittornado ctorrent samba rsync unzip netpipes debootstrap schroot squashfs-tools btrfs-tools procps arp-scan realpath php-curl gettext moreutils jq wakeonlan udpcast libev-dev libjansson-dev libssl-dev shim-signed grub-efi-amd64-signed gawk libdbi-dev libdbi1 libdbd-mysql automake liblz4-tool ) + UPDATEPKGLIST="apt-get update" + INSTALLPKG="apt-get -y install --force-yes" + CHECKPKG="dpkg -s \$package 2>/dev/null | grep Status | grep -qw install" + if which service &>/dev/null; then + STARTSERVICE="eval service \$service restart" + STOPSERVICE="eval service \$service stop" + else + STARTSERVICE="eval /etc/init.d/\$service restart" + STOPSERVICE="eval /etc/init.d/\$service stop" + fi + ENABLESERVICE="eval systemctl enable \$service.service" + DISABLESERVICE="eval systemctl disable \$service.service" + APACHESERV=apache2 + APACHECFGDIR=/etc/apache2 + APACHESITESDIR=sites-available + APACHEOGSITE=ogboot + APACHEUSER="www-data" + APACHEGROUP="www-data" + APACHEENABLEMODS="a2enmod ssl rewrite proxy_fcgi fastcgi actions alias" + APACHEENABLESSL="a2ensite default-ssl" + APACHEENABLEOG="a2ensite $APACHEOGSITE" + APACHEMAKECERT="make-ssl-cert generate-default-snakeoil --force-overwrite" + DHCPSERV=isc-dhcp-server + DHCPCFGDIR=/etc/dhcp + INETDSERV=xinetd + INETDCFGDIR=/etc/xinetd.d + MYSQLSERV=mysql + MYSQLHOST=172.17.8.74 + MYSQLPORT=3301 + MYSQLCFGDIR=/etc/mysql/mysql.conf.d + MARIADBSERV=mariadb + PHPFPMSERV=php-fpm + RSYNCSERV=rsync + RSYNCCFGDIR=/etc + SAMBASERV=smbd + SAMBACFGDIR=/etc/samba + TFTPCFGDIR=/var/lib/tftpboot + ;; + fedora|centos) + DEPENDENCIES=( subversion httpd mod_ssl php-ldap php-fpm mysql mysql-devel mysql-devel.i686 php-mysql dhcp tftp-server tftp xinetd binutils gcc gcc-c++ glibc-devel glibc-devel.i686 glibc-static glibc-static.i686 libstdc++-devel.i686 make wget curl doxygen graphviz ctorrent samba samba-client rsync unzip debootstrap schroot squashfs-tools python-crypto arp-scan procps-ng gettext moreutils jq net-tools udpcast libev-devel jansson-devel openssl-devel shim-x64 grub2-efi-x64 grub2-efi-x64-modules gawk libdbi-devel libdbi libdbi-dbd-mysql automake http://ftp.altlinux.org/pub/distributions/ALTLinux/5.1/branch/$(arch)/RPMS.classic/netpipes-4.2-alt1.$(arch).rpm ) + [ "$OSDISTRIB" == "centos" ] && UPDATEPKGLIST="yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-$OSVERSION.noarch.rpm http://rpms.remirepo.net/enterprise/remi-release-$OSVERSION.rpm" + INSTALLEXTRADEPS=( 'pushd /tmp; wget -t3 http://ftp.acc.umu.se/mirror/bittornado/BitTornado-0.3.18.tar.gz && tar xvzf BitTornado-0.3.18.tar.gz && cd BitTornado-CVS && python setup.py install && ln -fs btlaunchmany.py /usr/bin/btlaunchmany && ln -fs bttrack.py /usr/bin/bttrack; popd' ) + INSTALLPKG="yum install -y libstdc++ libstdc++.i686" + CHECKPKG="rpm -q --quiet \$package" + SYSTEMD=$(which systemctl 2>/dev/null) + if [ -n "$SYSTEMD" ]; then + STARTSERVICE="eval systemctl start \$service.service" + STOPSERVICE="eval systemctl stop \$service.service" + ENABLESERVICE="eval systemctl enable \$service.service" + DISABLESERVICE="eval systemctl disable \$service.service" + else + STARTSERVICE="eval service \$service start" + STOPSERVICE="eval service \$service stop" + ENABLESERVICE="eval chkconfig \$service on" + DISABLESERVICE="eval chkconfig \$service off" + fi + APACHESERV=httpd + APACHECFGDIR=/etc/httpd/conf.d + APACHEOGSITE=ogboot.conf + APACHEUSER="apache" + APACHEGROUP="apache" + APACHEREWRITEMOD="sed -i '/rewrite/s/^#//' $APACHECFGDIR/../*.conf" + DHCPSERV=dhcpd + DHCPCFGDIR=/etc/dhcp + INETDSERV=xinetd + INETDCFGDIR=/etc/xinetd.d + MYSQLSERV=mysqld + MYSQLHOST=172.17.8.74 + MYSQLPORT=3301 + MYSQLCFGDIR=/etc/my.cnf.d + MARIADBSERV=mariadb + PHPFPMSERV=php-fpm + RSYNCSERV=rsync + RSYNCCFGDIR=/etc + SAMBASERV=smb + SAMBACFGDIR=/etc/samba + TFTPSERV=tftp + TFTPCFGDIR=/var/lib/tftpboot + ;; + "") echo "ERROR: Unknown Linux distribution, please install \"lsb_release\" command." + exit 1 ;; + *) echo "ERROR: Distribution not supported by OpenGnsys." + exit 1 ;; +esac + +# Fichero de credenciales de acceso a MySQL. +TMPMYCNF=/tmp/.my.cnf.$$ +} + + +# Modificar variables de configuración tras instalar paquetes del sistema. +function autoConfigurePost() +{ +local f MKNETDIR + +# Configuraciones específicas para Samba y TFTP en Debian 6. +[ -z "$SYSTEMD" -a ! -e /etc/init.d/$SAMBASERV ] && SAMBASERV=samba +[ ! -e $TFTPCFGDIR ] && TFTPCFGDIR=/srv/tftp + +# Preparar arranque en red con Grub. +for f in grub-mknetdir grub2-mknetdir; do + if which $f &>/dev/null; then MKNETDIR=$f; fi +done +$MKNETDIR --net-directory=$TFTPCFGDIR --subdir=grub +} + + +# Cargar lista de paquetes del sistema y actualizar algunas variables de configuración +# dependiendo de la versión instalada. +function updatePackageList() +{ +local DHCPVERSION PHP7VERSION + +# Si es necesario, actualizar la lista de paquetes disponibles. +[ -n "$UPDATEPKGLIST" ] && eval $UPDATEPKGLIST + +# Configuración personallizada de algunos paquetes. +case "$OSDISTRIB" in + ubuntu|linuxmint) # Postconfiguación personalizada para Ubuntu. + # Configuración para DHCP v3. + DHCPVERSION=$(apt-cache show $(apt-cache pkgnames|egrep "dhcp.?-server$") | \ + awk '/Version/ {print substr($2,1,1);}' | \ + sort -n | tail -1) + if [ $DHCPVERSION = 3 ]; then + DEPENDENCIES=( ${DEPENDENCIES[@]/isc-dhcp-server/dhcp3-server} ) + DHCPSERV=dhcp3-server + DHCPCFGDIR=/etc/dhcp3 + fi + # Configuración para PHP 7 en Ubuntu. + if [ -z "$(apt-cache pkgnames php7)" ]; then + eval $INSTALLPKG software-properties-common + add-apt-repository -y ppa:ondrej/php + eval $UPDATEPKGLIST + PHP7VERSION=$(apt-cache pkgnames php7 | sort | head -1) + PHPFPMSERV="${PHP7VERSION}-fpm" + DEPENDENCIES=( ${DEPENDENCIES[@]//php/$PHP7VERSION} ) + fi + # Adaptar dependencias para libmysqlclient. + [ -z "$(apt-cache pkgnames libmysqlclient-dev)" ] && [ -n "$(apt-cache pkgnames libmysqlclient15)" ] && DEPENDENCIES=( ${DEPENDENCIES[@]//libmysqlclient-dev/libmysqlclient15} ) + # Paquete correcto para realpath. + [ -z "$(apt-cache pkgnames realpath)" ] && DEPENDENCIES=( ${DEPENDENCIES[@]//realpath/coreutils} ) + ;; + centos) # Postconfiguación personalizada para CentOS. + # Configuración para PHP 7. + PHP7VERSION=$(yum list -q php7\* 2>/dev/null | awk -F. '/^php/ {print $1; exit;}') + PHPFPMSERV="${PHP7VERSION}-${PHPFPMSERV}" + DEPENDENCIES=( ${PHP7VERSION} ${DEPENDENCIES[@]//php/$PHP7VERSION-php} ) + # Cambios a aplicar a partir de CentOS 7. + if [ $OSVERSION -ge 7 ]; then + # Sustituir MySQL por MariaDB. + DEPENDENCIES=( ${DEPENDENCIES[*]/mysql-/mariadb-} ) + # Instalar ctorrent de EPEL para CentOS 6 (no disponible en CentOS 7). + DEPENDENCIES=( ${DEPENDENCIES[*]/ctorrent/http://dl.fedoraproject.org/pub/epel/6/$(arch)/Packages/c/ctorrent-1.3.4-14.dnh3.3.2.el6.$(arch).rpm} ) + fi + ;; + fedora) # Postconfiguación personalizada para Fedora. + # Incluir paquetes específicos. + DEPENDENCIES=( ${DEPENDENCIES[@]} btrfs-progs ) + # Sustituir MySQL por MariaDB a partir de Fedora 20. + [ $OSVERSION -ge 20 ] && DEPENDENCIES=( ${DEPENDENCIES[*]/mysql-/mariadb-} ) + ;; +esac +} + + +##################################################################### +####### Algunas funciones útiles de propósito general: +##################################################################### + +function getDateTime() +{ + date "+%Y%m%d-%H%M%S" +} + +# Escribe a fichero y muestra por pantalla +function echoAndLog() +{ + local DATETIME=`getDateTime` + echo "$1" + echo "$DATETIME;$SSH_CLIENT;$1" >> $LOG_FILE +} + +# Escribe a fichero y muestra mensaje de error +function errorAndLog() +{ + local DATETIME=`getDateTime` + echo "ERROR: $1" + echo "$DATETIME;$SSH_CLIENT;ERROR: $1" >> $LOG_FILE +} + +# Escribe a fichero y muestra mensaje de aviso +function warningAndLog() +{ + local DATETIME=`getDateTime` + echo "Warning: $1" + echo "$DATETIME;$SSH_CLIENT;Warning: $1" >> $LOG_FILE +} + +# Comprueba si el elemento pasado en $2 está en el array $1 +function isInArray() +{ + if [ $# -ne 2 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + + local deps + local is_in_array=1 + local element="$2" + + echoAndLog "${FUNCNAME}(): checking if $2 is in $1" + eval "deps=( \"\${$1[@]}\" )" + + # Copia local del array del parámetro 1. + for (( i = 0 ; i < ${#deps[@]} ; i++ )); do + if [ "${deps[$i]}" = "${element}" ]; then + echoAndLog "isInArray(): $element found in array" + is_in_array=0 + fi + done + + if [ $is_in_array -ne 0 ]; then + echoAndLog "${FUNCNAME}(): $element NOT found in array" + fi + + return $is_in_array +} + + +##################################################################### +####### Funciones de manejo de paquetes Debian +##################################################################### + +function checkPackage() +{ + package=$1 + if [ -z $package ]; then + errorAndLog "${FUNCNAME}(): parameter required" + exit 1 + fi + echoAndLog "${FUNCNAME}(): checking if package $package exists" + eval $CHECKPKG + if [ $? -eq 0 ]; then + echoAndLog "${FUNCNAME}(): package $package exists" + return 0 + else + echoAndLog "${FUNCNAME}(): package $package doesn't exists" + return 1 + fi +} + +# Recibe array con dependencias +# por referencia deja un array con las dependencias no resueltas +# devuelve 1 si hay alguna dependencia no resuelta +function checkDependencies() +{ + if [ $# -ne 2 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + + echoAndLog "${FUNCNAME}(): checking dependences" + uncompletedeps=0 + + # copia local del array del parametro 1 + local deps + eval "deps=( \"\${$1[@]}\" )" + + declare -a local_notinstalled + + for (( i = 0 ; i < ${#deps[@]} ; i++ )) + do + checkPackage ${deps[$i]} + if [ $? -ne 0 ]; then + local_notinstalled[$uncompletedeps]=$package + let uncompletedeps=uncompletedeps+1 + fi + done + + # relleno el array especificado en $2 por referencia + for (( i = 0 ; i < ${#local_notinstalled[@]} ; i++ )) + do + eval "${2}[$i]=${local_notinstalled[$i]}" + done + + # retorna el numero de paquetes no resueltos + echoAndLog "${FUNCNAME}(): dependencies uncompleted: $uncompletedeps" + return $uncompletedeps +} + +# Recibe un array con las dependencias y lo instala +function installDependencies() +{ + if [ $# -ne 1 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + echoAndLog "${FUNCNAME}(): installing uncompleted dependencies" + + # copia local del array del parametro 1 + local deps + eval "deps=( \"\${$1[@]}\" )" + + local string_deps="" + for (( i = 0 ; i < ${#deps[@]} ; i++ )) + do + string_deps="$string_deps ${deps[$i]}" + done + + if [ -z "${string_deps}" ]; then + errorAndLog "${FUNCNAME}(): array of dependeces is empty" + exit 1 + fi + + OLD_DEBIAN_FRONTEND=$DEBIAN_FRONTEND # Debian/Ubuntu + export DEBIAN_FRONTEND=noninteractive + + echoAndLog "${FUNCNAME}(): now $string_deps will be installed" + eval $INSTALLPKG $string_deps + if [ $? -ne 0 ]; then + errorAndLog "${FUNCNAME}(): error installing dependencies" + return 1 + fi + + DEBIAN_FRONTEND=$OLD_DEBIAN_FRONTEND # Debian/Ubuntu + test grep -q "EPEL temporal" /etc/yum.repos.d/epel.repo 2>/dev/null || mv -f /etc/yum.repos.d/epel.repo.rpmnew /etc/yum.repos.d/epel.repo 2>/dev/null # CentOS/RedHat EPEL + + echoAndLog "${FUNCNAME}(): dependencies installed" +} + +# Hace un backup del fichero pasado por parámetro +# deja un -last y uno para el día +function backupFile() +{ + if [ $# -ne 1 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + + local file="$1" + local dateymd=`date +%Y%m%d` + + if [ ! -f "$file" ]; then + warningAndLog "${FUNCNAME}(): file $file doesn't exists" + return 1 + fi + + echoAndLog "${FUNCNAME}(): making $file backup" + + # realiza una copia de la última configuración como last + cp -a "$file" "${file}-LAST" + + # si para el día no hay backup lo hace, sino no + if [ ! -f "${file}-${dateymd}" ]; then + cp -a "$file" "${file}-${dateymd}" + fi + + echoAndLog "${FUNCNAME}(): $file backup success" +} + +##################################################################### +####### Funciones para el manejo de bases de datos +##################################################################### + +# This function set password to root +function mysqlSetRootPassword() +{ + if [ $# -ne 1 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + + local root_mysql="$1" + echoAndLog "${FUNCNAME}(): setting root password in MySQL server" + + mysqladmin -h $MYSQLHOST -u root password "$root_mysql" + if [ $? -ne 0 ]; then + errorAndLog "${FUNCNAME}(): error while setting root password in MySQL server" + return 1 + fi + echoAndLog "${FUNCNAME}(): root password saved!" + return 0 +} + +# Si el servicio mysql esta ya instalado cambia la variable de la clave del root por la ya existente +function mysqlGetRootPassword() +{ + local pass_mysql + local pass_mysql2 + # Comprobar si MySQL está instalado con la clave de root por defecto. + if mysql -h $MYSQLHOST -u root -p"$MYSQL_ROOT_PASSWORD" <<<"quit" 2>/dev/null; then + echoAndLog "${FUNCNAME}(): Using default mysql root password." + else + stty -echo + echo "There is a MySQL service already installed." + read -p "Enter MySQL root password: " pass_mysql + echo "" + read -p "Confrim password:" pass_mysql2 + echo "" + stty echo + if [ "$pass_mysql" == "$pass_mysql2" ] ;then + MYSQL_ROOT_PASSWORD="$pass_mysql" + return 0 + else + echo "The keys don't match. Do not configure the server's key," + echo "transactions in the database will give error." + return 1 + fi + fi +} + +# comprueba si puede conectar con mysql con el usuario root +function mysqlTestConnection() +{ + if [ $# -ne 1 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + + local root_password="$1" + echoAndLog "${FUNCNAME}(): checking connection to mysql..." + # Componer fichero con credenciales de conexión a MySQL. + touch $TMPMYCNF + chmod 600 $TMPMYCNF + cat << EOT > $TMPMYCNF +[client] +user=root +password=$root_password +EOT + echo "------------------------" + cat $TMPMYCNF + echo "------------------------" + # Comprobar conexión a MySQL. + echo "" | mysql --defaults-extra-file=$TMPMYCNF -h $MYSQLHOST + if [ $? -ne 0 ]; then + errorAndLog "${FUNCNAME}(): connection to mysql failed, check root password and if daemon is running!" + return 1 + else + echoAndLog "${FUNCNAME}(): connection success" + return 0 + fi + # Borrar el fichero temporal si termina el proceso de instalación. + trap "rm -f $TMPMYCNF" 0 1 2 3 6 9 15 +} + +# comprueba si la base de datos existe +function mysqlDbExists() +{ + if [ $# -ne 1 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + + local database="$1" + echoAndLog "${FUNCNAME}(): checking if $database exists..." + echo "show databases" | mysql --defaults-extra-file=$TMPMYCNF -h $MYSQLHOST | grep "^${database}$" + if [ $? -ne 0 ]; then + echoAndLog "${FUNCNAME}():database $database doesn't exists" + return 1 + else + echoAndLog "${FUNCNAME}():database $database exists" + return 0 + fi +} + +# Comprueba si la base de datos está vacía. +function mysqlCheckDbIsEmpty() +{ + if [ $# -ne 1 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + + local database="$1" + echoAndLog "${FUNCNAME}(): checking if $database is empty..." + num_tablas=`echo "show tables" | mysql --defaults-extra-file=$TMPMYCNF -h $MYSQLHOST "${database}" | wc -l` + if [ $? -ne 0 ]; then + errorAndLog "${FUNCNAME}(): error executing query, check database and root password" + exit 1 + fi + + if [ $num_tablas -eq 0 ]; then + echoAndLog "${FUNCNAME}():database $database is empty" + return 0 + else + echoAndLog "${FUNCNAME}():database $database has tables" + return 1 + fi + +} + +# Importa un fichero SQL en la base de datos. +# Parámetros: +# - 1: nombre de la BD. +# - 2: fichero a importar. +# Nota: el fichero SQL puede contener las siguientes palabras reservadas: +# - SERVERIP: se sustituye por la dirección IP del servidor. +# - DBUSER: se sustituye por usuario de conexión a la BD definido en este script. +# - DBPASSWD: se sustituye por la clave de conexión a la BD definida en este script. +function mysqlImportSqlFileToDb() +{ + if [ $# -ne 2 ]; then + errorAndLog "${FNCNAME}(): invalid number of parameters" + exit 1 + fi + + local database="$1" + local sqlfile="$2" + local tmpfile=$(mktemp) + local i=0 + local dev="" + local status + + if [ ! -f $sqlfile ]; then + errorAndLog "${FUNCNAME}(): Unable to locate $sqlfile!!" + return 1 + fi + + echoAndLog "${FUNCNAME}(): importing SQL file to ${database}..." + chmod 600 $tmpfile + for dev in ${DEVICE[*]}; do + if [ "${DEVICE[i]}" == "$DEFAULTDEV" ]; then + sed -e "s/SERVERIP/${SERVERIP[i]}/g" \ + -e "s/DBUSER/$OPENGNSYS_DB_USER/g" \ + -e "s/DBPASSWORD/$OPENGNSYS_DB_PASSWD/g" \ + $sqlfile > $tmpfile + fi + let i++ + done + echo "----------------------" + mysql --defaults-extra-file=$TMPMYCNF -h $MYSQLHOST --default-character-set=utf8 "${database}" < $tmpfile + status=$? + rm -f $tmpfile + if [ $status -ne 0 ]; then + errorAndLog "${FUNCNAME}(): error while importing $sqlfile in database $database" + return 1 + fi + echoAndLog "${FUNCNAME}(): file imported to database $database" + return 0 +} + +# Crea la base de datos +function mysqlCreateDb() +{ + if [ $# -ne 1 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + + local database="$1" + + echoAndLog "${FUNCNAME}(): creating database..." + mysqladmin --defaults-extra-file=$TMPMYCNF -h $MYSQLHOST create $database + if [ $? -ne 0 ]; then + errorAndLog "${FUNCNAME}(): error while creating database $database" + return 1 + fi + # Quitar modo ONLY_FULL_GROUP_BY de MySQL (ticket #730). + mysql --defaults-extra-file=$TMPMYCNF -h $MYSQLHOST -e "SET GLOBAL sql_mode=(SELECT TRIM(BOTH ',' FROM REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY','')));" + + echoAndLog "${FUNCNAME}(): database $database created" + return 0 +} + +# Comprueba si ya está definido el usuario de acceso a la BD. +function mysqlCheckUserExists() +{ + if [ $# -ne 1 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + + local userdb="$1" + + echoAndLog "${FUNCNAME}(): checking if $userdb exists..." + echo "select user from user where user='${userdb}'\\G" |mysql --defaults-extra-file=$TMPMYCNF -h $MYSQLHOST mysql | grep user + if [ $? -ne 0 ]; then + echoAndLog "${FUNCNAME}(): user doesn't exists" + return 1 + else + echoAndLog "${FUNCNAME}(): user already exists" + return 0 + fi + +} + +# Crea un usuario administrativo para la base de datos +function mysqlCreateAdminUserToDb() +{ + if [ $# -ne 3 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + + local database="$1" + local userdb="$2" + local passdb="$3" + + echoAndLog "${FUNCNAME}(): creating admin user ${userdb} to database ${database}" + + cat > $WORKDIR/create_${database}.sql </dev/null; then + curl --connect-timeout 10 -s "https://$OPENGNSYS_SERVER/" -o /dev/null && \ + curl --connect-timeout 10 -s "http://$OPENGNSYS_SERVER/" -o /dev/null + elif which wget &>/dev/null; then + wget --spider -q "https://$OPENGNSYS_SERVER/" && \ + wget --spider -q "http://$OPENGNSYS_SERVER/" + else + echoAndLog "${FUNCNAME}(): Cannot execute \"wget\" nor \"curl\"." + return 1 + fi +} + +# Convierte nº de bits (notación CIDR) en máscara de red (gracias a FriedZombie en openwrt.org). +cidr2mask () +{ + # Number of args to shift, 255..255, first non-255 byte, zeroes + set -- $[ 5 - ($1 / 8) ] 255 255 255 255 $[ (255 << (8 - ($1 % 8))) & 255 ] 0 0 0 + [ $1 -gt 1 ] && shift $1 || shift + echo ${1-0}.${2-0}.${3-0}.${4-0} +} + +# Obtener los parámetros de red de la interfaz por defecto. +function getNetworkSettings() +{ + # Arrays globales definidas: + # - DEVICE: nombres de dispositivos de red activos. + # - SERVERIP: IPs locales del servidor. + # - NETIP: IPs de redes. + # - NETMASK: máscaras de red. + # - NETBROAD: IPs de difusión de redes. + # - ROUTERIP: IPs de routers. + # Otras variables globales: + # - DEFAULTDEV: dispositivo de red por defecto. + # - DNSIP: IP del servidor DNS principal. + + local i=0 + local dev="" + + echoAndLog "${FUNCNAME}(): Detecting network parameters." + DEVICE=( $(ip -o link show up | awk '!/loopback/ {sub(/[:@].*/,"",$2); print $2}') ) + if [ -z "$DEVICE" ]; then + errorAndLog "${FUNCNAME}(): Network devices not detected." + exit 1 + fi + for dev in ${DEVICE[*]}; do + SERVERIP[i]=$(ip -o addr show dev "$dev" | awk '$3~/inet$/ {sub (/\/.*/, ""); print ($4); exit;}') + if [ -n "${SERVERIP[i]}" ]; then + NETMASK[i]=$( cidr2mask $(ip -o addr show dev "$dev" | awk '$3~/inet$/ {sub (/.*\//, "", $4); print ($4); exit;}') ) + NETBROAD[i]=$(ip -o addr show dev "$dev" | awk '$3~/inet$/ {print ($6); exit;}') + NETIP[i]=$(ip route list proto kernel | awk -v d="$dev" '$3==d && /src/ {sub (/\/.*/,""); print $1; exit;}') + ROUTERIP[i]=$(ip route list default | awk -v d="$dev" '$5==d {print $3; exit;}') + DEFAULTDEV=${DEFAULTDEV:-"$dev"} + fi + let i++ + done + DNSIP=$(systemd-resolve --status 2>/dev/null | awk '/DNS Servers:/ {print $3; exit;}') + [ -z "$DNSIP" ] && DNSIP=$(awk '/nameserver/ {print $2; exit;}' /etc/resolv.conf) + if [ -z "${NETIP[*]}" -o -z "${NETMASK[*]}" ]; then + errorAndLog "${FUNCNAME}(): Network not detected." + exit 1 + fi + + # Variables de ejecución de Apache + # - APACHE_RUN_USER + # - APACHE_RUN_GROUP + if [ -f $APACHECFGDIR/envvars ]; then + source $APACHECFGDIR/envvars + fi + APACHE_RUN_USER=${APACHE_RUN_USER:-"$APACHEUSER"} + APACHE_RUN_GROUP=${APACHE_RUN_GROUP:-"$APACHEGROUP"} + + echoAndLog "${FUNCNAME}(): Default network device: $DEFAULTDEV." +} + + +############################################################ +### Esqueleto para el Servicio pxe y contenedor tftpboot ### +############################################################ + +function tftpConfigure() +{ + echoAndLog "${FUNCNAME}(): Configuring TFTP service." + # Habilitar TFTP y reiniciar Inetd. + if [ -n "$TFTPSERV" ]; then + if [ -f $INETDCFGDIR/$TFTPSERV ]; then + perl -pi -e 's/disable.*/disable = no/' $INETDCFGDIR/$TFTPSERV + else + service=$TFTPSERV + $ENABLESERVICE; $STARTSERVICE + fi + fi + service=$INETDSERV + $ENABLESERVICE; $STARTSERVICE + + # comprobamos el servicio tftp + sleep 1 + testPxe +} + +# Comprueba que haya conexión al servicio TFTP/PXE. +function testPxe () +{ + echoAndLog "${FUNCNAME}(): Checking TFTP service... please wait." + echo "test" >$TFTPCFGDIR/testpxe + tftp -v 127.0.0.1 -c get testpxe /tmp/testpxe && echoAndLog "TFTP service is OK." || errorAndLog "TFTP service is down." + rm -f $TFTPCFGDIR/testpxe /tmp/testpxe +} + + +######################################################################## +## Configuración servicio Samba +######################################################################## + +# Configurar servicios Samba. +function smbConfigure() +{ + echoAndLog "${FUNCNAME}(): Configuring Samba service." + + backupFile $SAMBACFGDIR/smb.conf + + # Copiar plantailla de recursos para OpenGnsys + sed -e "s/OPENGNSYSDIR/${INSTALL_TARGET//\//\\/}/g" \ + $WORKDIR/ogboot/server/etc/smb-og.conf.tmpl > $SAMBACFGDIR/smb-og.conf + # Configurar y recargar Samba" + perl -pi -e "s/WORKGROUP/OPENGNSYS/; s/server string \=.*/server string \= OpenGnsys Samba Server/" $SAMBACFGDIR/smb.conf + if ! grep -q "smb-og" $SAMBACFGDIR/smb.conf; then + echo "include = $SAMBACFGDIR/smb-og.conf" >> $SAMBACFGDIR/smb.conf + fi + service=$SAMBASERV + $ENABLESERVICE; $STARTSERVICE + if [ $? -ne 0 ]; then + errorAndLog "${FUNCNAME}(): error while configure Samba" + return 1 + fi + # Crear clave para usuario de acceso a los recursos. + echo -ne "$OPENGNSYS_CLIENT_PASSWD\n$OPENGNSYS_CLIENT_PASSWD\n" | smbpasswd -a -s $OPENGNSYS_CLIENT_USER + + echoAndLog "${FUNCNAME}(): Added Samba configuration." + return 0 +} + + +######################################################################## +## Configuración servicio Rsync +######################################################################## + +# Configurar servicio Rsync. +function rsyncConfigure() +{ + echoAndLog "${FUNCNAME}(): Configuring Rsync service." + + backupFile $RSYNCCFGDIR/rsyncd.conf + + # Configurar acceso a Rsync. + sed -e "s/CLIENTUSER/$OPENGNSYS_CLIENT_USER/g" \ + $WORKDIR/ogboot/repoman/etc/rsyncd.conf.tmpl > $RSYNCCFGDIR/rsyncd.conf + # Habilitar Rsync y reiniciar Inetd. + if [ -n "$RSYNCSERV" ]; then + if [ -f /etc/default/rsync ]; then + perl -pi -e 's/RSYNC_ENABLE=.*/RSYNC_ENABLE=inetd/' /etc/default/rsync + fi + if [ -f $INETDCFGDIR/rsync ]; then + perl -pi -e 's/disable.*/disable = no/' $INETDCFGDIR/rsync + else + cat << EOT > $INETDCFGDIR/rsync +service rsync +{ + disable = no + socket_type = stream + wait = no + user = root + server = $(which rsync) + server_args = --daemon + log_on_failure += USERID + flags = IPv6 +} +EOT + fi + service=$RSYNCSERV $ENABLESERVICE + service=$INETDSERV $STARTSERVICE + fi + + echoAndLog "${FUNCNAME}(): Added Rsync configuration." + return 0 +} + + +######################################################################## +## Configuración servicio DHCP +######################################################################## + +# Configurar servicios DHCP. +function dhcpConfigure() +{ + echoAndLog "${FUNCNAME}(): Sample DHCP configuration." + + local errcode=0 + local i=0 + local dev="" + + backupFile $DHCPCFGDIR/dhcpd.conf + for dev in ${DEVICE[*]}; do + if [ -n "${SERVERIP[i]}" ]; then + backupFile $DHCPCFGDIR/dhcpd-$dev.conf + echo "1===========================================" + sed -e "s/SERVERIP/${SERVERIP[i]}/g" \ + -e "s/NETIP/${NETIP[i]}/g" \ + -e "s/NETMASK/${NETMASK[i]}/g" \ + -e "s/NETBROAD/${NETBROAD[i]}/g" \ + -e "s/ROUTERIP/${ROUTERIP[i]}/g" \ + -e "s/DNSIP/$DNSIP/g" \ + $WORKDIR/ogboot/server/etc/dhcpd.conf.tmpl > $DHCPCFGDIR/dhcpd-$dev.conf || errcode=1 + echo "$WORKDIR/ogboot/server/etc/dhcpd.conf.tmpl" + echo "2===========================================" + fi + let i++ + done + if [ $errcode -ne 0 ]; then + errorAndLog "${FUNCNAME}(): error while configuring DHCP server" + return 1 + fi + ln -f $DHCPCFGDIR/dhcpd-$DEFAULTDEV.conf $DHCPCFGDIR/dhcpd.conf + service=$DHCPSERV + $ENABLESERVICE; $STARTSERVICE + echoAndLog "${FUNCNAME}(): Sample DHCP configured in \"$DHCPCFGDIR\"." + return 0 +} + + +##################################################################### +####### Funciones específicas de la instalación de Opengnsys +##################################################################### + +# Copiar ficheros del OpenGnsys Web Console. +function installWebFiles() +{ + local COMPATDIR f + local SLIMFILE="slim-2.6.1.zip" + local SWAGGERFILE="swagger-ui-2.2.5.zip" + + echoAndLog "${FUNCNAME}(): Installing web files..." + # Copiar ficheros. + cp -a $WORKDIR/ogboot/admin/WebConsole/* $INSTALL_TARGET/www #*/ comentario para Doxygen. + if [ $? != 0 ]; then + errorAndLog "${FUNCNAME}(): Error copying web files." + exit 1 + fi + + # Descomprimir librerías: Slim y Swagger-UI. + unzip -o $WORKDIR/ogboot/admin/$SLIMFILE -d $INSTALL_TARGET/www/rest + unzip -o $WORKDIR/ogboot/admin/$SWAGGERFILE -d $INSTALL_TARGET/www/rest + + # Compatibilidad con dispositivos móviles. + COMPATDIR="$INSTALL_TARGET/www/principal" + for f in acciones administracion aula aulas hardwares imagenes menus repositorios softwares; do + sed 's/clickcontextualnodo/clicksupnodo/g' $COMPATDIR/$f.php > $COMPATDIR/$f.device.php + done + cp -a $COMPATDIR/imagenes.device.php $COMPATDIR/imagenes.device4.php + # Acceso al manual de usuario + ln -fs ../doc/userManual $INSTALL_TARGET/www/userManual + # Ficheros de log de la API REST. + touch $INSTALL_TARGET/log/{ogagent,remotepc,rest}.log + + echoAndLog "${FUNCNAME}(): Web files installed successfully." +} + +# Copiar ficheros en la zona de descargas de OpenGnsys Web Console. +function installDownloadableFiles() +{ + local VERSIONFILE OGVERSION FILENAME TARGETFILE + + # Obtener versión a descargar. + VERSIONFILE="$INSTALL_TARGET/doc/VERSION.json" + OGVERSION="$(jq -r ".ogagent // \"$INSTVERSION\"" $VERSIONFILE 2>/dev/null || echo "$INSTVERSION")" + FILENAME="ogagentpkgs-$OGVERSION.tar.gz" + TARGETFILE=$WORKDIR/$FILENAME + + # Descargar archivo comprimido, si es necesario. + if [ -s $PROGRAMDIR/$FILENAME ]; then + echoAndLog "${FUNCNAME}(): Moving $PROGRAMDIR/$FILENAME file to $(dirname $TARGETFILE)" + mv $PROGRAMDIR/$FILENAME $TARGETFILE + else + echoAndLog "${FUNCNAME}(): Downloading $FILENAME" + curl $DOWNLOADURL/$FILENAME -o $TARGETFILE + fi + if [ ! -s $TARGETFILE ]; then + errorAndLog "${FUNCNAME}(): Cannot download $FILENAME" + return 1 + fi + + # Descomprimir fichero en zona de descargas. + tar xvzf $TARGETFILE -C $INSTALL_TARGET/www/descargas + if [ $? != 0 ]; then + errorAndLog "${FUNCNAME}(): Error uncompressing archive." + exit 1 + fi +} + +# Configuración específica de Apache. +function installWebConsoleApacheConf() +{ + if [ $# -ne 2 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + + local path_ogboot_base="$1" + local path_apache2_confd="$2" + local CONSOLEDIR=${path_ogboot_base}/www + local sockfile + + if [ ! -d $path_apache2_confd ]; then + errorAndLog "${FUNCNAME}(): path to apache2 conf.d can not found, verify your server installation" + return 1 + fi + + mkdir -p $path_apache2_confd/{sites-available,sites-enabled} + + echoAndLog "${FUNCNAME}(): creating apache2 config file.." + + # Avtivar PHP-FPM. + echoAndLog "${FUNCNAME}(): configuring PHP-FPM" + service=$PHPFPMSERV + $ENABLESERVICE; $STARTSERVICE + sockfile=$(find /run/php -name "php*.sock" -type s -print 2>/dev/null | tail -1) + + # Activar módulos de Apache. + $APACHEENABLEMODS + # Activar HTTPS. + $APACHEENABLESSL + $APACHEMAKECERT + # Genera configuración de consola web a partir del fichero plantilla. + if [ -n "$(apachectl -v | grep "2\.[0-2]")" ]; then + # Configuración para versiones anteriores de Apache. + sed -e "s,CONSOLEDIR,$CONSOLEDIR,g" \ + $WORKDIR/ogboot/server/etc/apache-prev2.4.conf.tmpl > $path_apache2_confd/$APACHESITESDIR/${APACHEOGSITE} + else + # Configuración específica a partir de Apache 2.4 + if [ -n "$sockfile" ]; then + sed -e "s,CONSOLEDIR,$CONSOLEDIR,g" \ + -e "s,proxy:fcgi:.*,proxy:unix:${sockfile%% *}|fcgi://localhost\",g" \ + $WORKDIR/ogboot/server/etc/apache.conf.tmpl > $path_apache2_confd/$APACHESITESDIR/${APACHEOGSITE}.conf + else + sed -e "s,CONSOLEDIR,$CONSOLEDIR,g" \ + $WORKDIR/ogboot/server/etc/apache.conf.tmpl > $path_apache2_confd/$APACHESITESDIR/${APACHEOGSITE}.conf + fi + fi + $APACHEENABLEOG + if [ $? -ne 0 ]; then + errorAndLog "${FUNCNAME}(): config file can't be linked to apache conf, verify your server installation" + return 1 + fi + echoAndLog "${FUNCNAME}(): config file created and linked, restarting apache daemon" + service=$APACHESERV + $ENABLESERVICE; $STARTSERVICE + return 0 +} + + +# Crear documentación Doxygen para la consola web. +function makeDoxygenFiles() +{ + echoAndLog "${FUNCNAME}(): Making Doxygen web files..." + $WORKDIR/ogboot/installer/ogGenerateDoc.sh \ + $WORKDIR/ogboot/client/engine $INSTALL_TARGET/www + if [ ! -d "$INSTALL_TARGET/www/html" ]; then + errorAndLog "${FUNCNAME}(): unable to create Doxygen web files." + return 1 + fi + mv "$INSTALL_TARGET/www/html" "$INSTALL_TARGET/www/api" + echoAndLog "${FUNCNAME}(): Doxygen web files created successfully." +} + + +# Crea la estructura base de la instalación de ogBoot +function createDirs() +{ + if [ $# -ne 1 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + + local path_ogboot_base="$1" + + # Crear estructura de directorios. + echoAndLog "${FUNCNAME}(): creating directory paths in $path_ogboot_base" + mkdir -p $path_ogboot_base + mkdir -p $path_ogboot_base/bin + mkdir -p $path_ogboot_base/client/{cache,images,log} + mkdir -p $path_ogboot_base/doc + mkdir -p $path_ogboot_base/etc + mkdir -p $path_ogboot_base/lib + mkdir -p $path_ogboot_base/log/clients + ln -fs $path_ogboot_base/log /var/log/ogboot + mkdir -p $path_ogboot_base/sbin + mkdir -p $path_ogboot_base/www + mkdir -p $path_ogboot_base/images/groups + mkdir -p $TFTPCFGDIR + ln -fs $TFTPCFGDIR $path_ogboot_base/tftpboot + mkdir -p $path_ogboot_base/tftpboot/{menu.lst,grub} + if [ $? -ne 0 ]; then + errorAndLog "${FUNCNAME}(): error while creating dirs. Do you have write permissions?" + return 1 + fi + + # Crear usuario ficticio. + if id -u $OPENGNSYS_CLIENT_USER &>/dev/null; then + echoAndLog "${FUNCNAME}(): user \"$OPENGNSYS_CLIENT_USER\" is already created" + else + echoAndLog "${FUNCNAME}(): creating OpenGnsys user" + useradd $OPENGNSYS_CLIENT_USER 2>/dev/null + if [ $? -ne 0 ]; then + errorAndLog "${FUNCNAME}(): error creating OpenGnsys user" + return 1 + fi + fi + + # Mover el fichero de registro de instalación al directorio de logs. + echoAndLog "${FUNCNAME}(): moving installation log file" + mv $LOG_FILE $OGLOGFILE && LOG_FILE=$OGLOGFILE + chmod 600 $LOG_FILE + + echoAndLog "${FUNCNAME}(): directory paths created" + return 0 +} + +# Copia ficheros de configuración y ejecutables genéricos del servidor. +function copyServerFiles () +{ + if [ $# -ne 1 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + + local path_ogboot_base="$1" + + # Lista de ficheros y directorios origen y de directorios destino. + local SOURCES=( server/tftpboot \ + /usr/lib/shim/shimx64.efi.signed \ + /usr/lib/grub/x86_64-efi-signed/grubnetx64.efi.signed \ + server/bin \ + repoman/bin \ + server/lib \ + admin/Sources/Services/ogAdmRepoAux + installer/opengnsys_uninstall.sh \ + installer/opengnsys_update.sh \ + installer/opengnsys_export.sh \ + installer/opengnsys_import.sh \ + doc ) + local TARGETS=( tftpboot \ + tftpboot \ + tftpboot/grubx64.efi \ + bin \ + bin \ + lib \ + sbin \ + lib \ + lib \ + lib \ + lib \ + doc ) + + if [ ${#SOURCES[@]} != ${#TARGETS[@]} ]; then + errorAndLog "${FUNCNAME}(): inconsistent number of array items" + exit 1 + fi + + # Copiar ficheros. + echoAndLog "${FUNCNAME}(): copying files to server directories" + + pushd $WORKDIR/ogboot + local i + for (( i = 0; i < ${#SOURCES[@]}; i++ )); do + if [ -f "${SOURCES[$i]}" ]; then + echoAndLog "Copying ${SOURCES[$i]} to $path_ogboot_base/${TARGETS[$i]}" + cp -a "${SOURCES[$i]}" "${path_ogboot_base}/${TARGETS[$i]}" + elif [ -d "${SOURCES[$i]}" ]; then + echoAndLog "Copying content of ${SOURCES[$i]} to $path_ogboot_base/${TARGETS[$i]}" + cp -a "${SOURCES[$i]}"/* "${path_ogboot_base}/${TARGETS[$i]}" + else + warningAndLog "Unable to copy ${SOURCES[$i]} to $path_ogboot_base/${TARGETS[$i]}" + fi + done + + popd +} + +#################################################################### +### Funciones de compilación de código fuente de servicios +#################################################################### + +# Compilar los servicios de OpenGnsys +function ogServerCompilation () +{ + local ogserverUrl="https://codeload.github.com/opengnsys/ogServer/zip/$BRANCH" + local error=0 + + echoAndLog "${FUNCNAME}(): downloading ogServer code..." + + if ! (curl "${ogserverUrl}" -o ogserver.zip && \ + unzip -qo "ogserver.zip") + then + errorAndLog "${FUNCNAME}(): "\ + "error getting ogServer code from ${ogserverUrl}" + return 1 + fi + rm -f ogserver.zip + echoAndLog "${FUNCNAME}(): ogServer code was downloaded" + + echoAndLog "${FUNCNAME}(): Compiling OpenGnsys Server" + pushd "$WORKDIR/ogServer-${BRANCH#v}" + autoreconf -fi && ./configure && make && mv ogserver $INSTALL_TARGET/sbin + if [ $? -ne 0 ]; then + echoAndLog "${FUNCNAME}(): error while compiling OpenGnsys Server" + error=1 + fi + popd + + return $error +} + +#################################################################### +### Funciones de copia de la Interface de administración +#################################################################### + +# Copiar carpeta de Interface +function copyInterfaceAdm () +{ + local hayErrores=0 + + # Crear carpeta y copiar Interface + echoAndLog "${FUNCNAME}(): Copying Administration Interface Folder" + cp -ar $WORKDIR/ogboot/admin/Interface $INSTALL_TARGET/client/interfaceAdm + if [ $? -ne 0 ]; then + echoAndLog "${FUNCNAME}(): error while copying Administration Interface Folder" + hayErrores=1 + fi + + return $hayErrores +} + + +#################################################################### +### Funciones instalacion cliente opengnsys +#################################################################### + +function copyClientFiles() +{ + local errstatus=0 + + echoAndLog "${FUNCNAME}(): Copying OpenGnsys Client files." + cp -a $WORKDIR/ogboot/client/shared/* $INSTALL_OGBOOT_TARGET/client + if [ $? -ne 0 ]; then + errorAndLog "${FUNCNAME}(): error while copying client estructure" + errstatus=1 + fi + + echoAndLog "${FUNCNAME}(): Copying OpenGnsys Cloning Engine files." + mkdir -p $INSTALL_OGBOOT_TARGET/client/lib/engine/bin + cp -a $WORKDIR/ogboot/client/engine/*.lib* $INSTALL_OGBOOT_TARGET/client/lib/engine/bin + if [ $? -ne 0 ]; then + errorAndLog "${FUNCNAME}(): error while copying engine files" + errstatus=1 + fi + + if [ $errstatus -eq 0 ]; then + echoAndLog "${FUNCNAME}(): client copy files success." + else + errorAndLog "${FUNCNAME}(): client copy files with errors" + fi + + local ogclientUrl="https://codeload.github.com/opengnsys/ogClient/zip/$BRANCH" + + echoAndLog "${FUNCNAME}(): downloading ogClient code..." + + if ! (curl "${ogclientUrl}" -o ogclient.zip && \ + unzip -qo ogclient.zip && \ + mv "ogClient-${BRANCH#v}" $INSTALL_OGBOOT_TARGET/client/ogClient) + then + errorAndLog "${FUNCNAME}(): "\ + "error getting ogClient code from ${ogclientUrl}" + return 1 + fi + rm -f ogclient.zip + echoAndLog "${FUNCNAME}(): ogClient code was downloaded" + + return $errstatus +} + + +# Crear certificados para la firma de cargadores de arranque. +function createCerts () +{ + local SSLCFGDIR=$INSTALL_OGBOOT_TARGET/client/etc/ssl + echoAndLog "${FUNCNAME}(): creating certificate files" + mkdir -p $SSLCFGDIR/{certs,private} + openssl req -new -x509 -newkey rsa:2048 -keyout $SSLCFGDIR/private/opengnsys.key -out $SSLCFGDIR/certs/opengnsys.crt -nodes -days 3650 -subj "/CN=OpenGnsys/" + openssl x509 -in $SSLCFGDIR/certs/opengnsys.crt -out $SSLCFGDIR/certs/opengnsys.cer -outform DER + echoAndLog "${FUNCNAME}(): certificate successfully created"xmz8641 +} + + +# Crear cliente OpenGnsys. +function clientCreate() +{ + echo "-----------------------------2" + if [ $# -ne 1 ]; then + errorAndLog "${FUNCNAME}(): invalid number of parameters" + exit 1 + fi + + echo "-----------------------------3" + local FILENAME="$1" + local TARGETFILE=$INSTALL_OGBOOT_TARGET/lib/$FILENAME + + # Descargar cliente, si es necesario. + echo "PROGRAMDIR/FILENAME: $PROGRAMDIR/$FILENAME" + if [ -s $PROGRAMDIR/$FILENAME ]; then + echoAndLog "${FUNCNAME}(): Moving $PROGRAMDIR/$FILENAME file to $(dirname $TARGETFILE)" + mv $PROGRAMDIR/$FILENAME $TARGETFILE + else + echoAndLog "${FUNCNAME}(): Downloading $FILENAME" + oglivecli download $FILENAME + fi + echo "-----------------------------4" + if [ ! -s $TARGETFILE ]; then + errorAndLog "${FUNCNAME}(): Error loading $FILENAME" + return 1 + fi + + echo "-----------------------------5" + # Montar imagen, copiar cliente ogclient y desmontar. + echoAndLog "${FUNCNAME}(): Installing ogLive Client" + echo -ne "$OPENGNSYS_CLIENT_PASSWD\n$OPENGNSYS_CLIENT_PASSWD\n" | \ + oglivecli install $FILENAME + + echo "-----------------------------6" + echoAndLog "${FUNCNAME}(): Client generation success" +} + + +# Configuración básica de servicios de OpenGnsys +function openGnsysConfigure() +{ + local i=0 + local dev="" + local CONSOLEURL + + echoAndLog "${FUNCNAME}(): Copying init files." + cp -a $WORKDIR/ogboot/admin/Sources/Services/opengnsys.init /etc/init.d/opengnsys + cp -a $WORKDIR/ogboot/admin/Sources/Services/opengnsys.service \ + /lib/systemd/system/opengnsys.service + cp -a $WORKDIR/ogServer-${BRANCH#v}/cfg/ogserver.service \ + /lib/systemd/system/ogserver.service + cp -a $WORKDIR/ogboot/admin/Sources/Services/opengnsys.default /etc/default/opengnsys + # Deshabilitar servicios de BitTorrent si no están instalados. + if [ ! -e /usr/bin/bttrack ]; then + sed -i 's/RUN_BTTRACKER="yes"/RUN_BTTRACKER="no"/; s/RUN_BTSEEDER="yes"/RUN_BTSEEDER="no"/' \ + /etc/default/opengnsys + fi + echoAndLog "${FUNCNAME}(): Creating cron files." + echo "* * * * * root [ -x $INSTALL_OGBOOT_TARGET/bin/torrent-creator ] && $INSTALL_TARGET/bin/torrent-creator" > /etc/cron.d/torrentcreator + echo "5 * * * * root [ -x $INSTALL_OGBOOT_TARGET/bin/torrent-tracker ] && $INSTALL_TARGET/bin/torrent-tracker" > /etc/cron.d/torrenttracker + echo "* * * * * root [ -x $INSTALL_OGBOOT_TARGET/bin/deletepreimage ] && $INSTALL_TARGET/bin/deletepreimage" > /etc/cron.d/imagedelete + echo "* * * * * root [ -x $INSTALL_OGBOOT_TARGET/bin/ogagentqueue.cron ] && $INSTALL_TARGET/bin/ogagentqueue.cron" > /etc/cron.d/ogagentqueue + + echoAndLog "${FUNCNAME}(): Creating logrotate configuration files." + sed -e "s/$OPENGNSYSDIR/${INSTALL_OGBOOT_TARGET//\//\\/}/g" \ + $WORKDIR/ogboot/server/etc/logrotate.tmpl > /etc/logrotate.d/opengnsysServer + + sed -e "s/$OPENGNSYSDIR/${INSTALL_OGBOOT_TARGET//\//\\/}/g" \ + $WORKDIR/ogboot/repoman/etc/logrotate.tmpl > /etc/logrotate.d/opengnsysRepo + + echoAndLog "${FUNCNAME}(): Creating OpenGnsys config files." + for dev in ${DEVICE[*]}; do + if [ -n "${SERVERIP[i]}" ]; then + echo "{ + \"rest\" : { + \"ip\" : \"${SERVERIP[i]}\", + \"port\" : \"8888\", + \"api_token\": \"5a5ca1172136299640a9f47469237e0a\" + }, + \"database\" : { + \"ip\": \"$OPENGNSYS_SERVER\", + \"port\": \"3306\", + \"name\" : \"$OPENGNSYS_DATABASE\", + \"user\" : \"$OPENGNSYS_DB_USER\", + \"pass\" : \"$OPENGNSYS_DB_PASSWD\" + }, + \"wol\" : { + \"interface\" : \"$dev\" + } + }" | jq '.' > "$INSTALL_OGBOOT_TARGET"/etc/ogserver-"$dev".json + sed -e "s/SERVERIP/${SERVERIP[i]}/g" \ + $WORKDIR/ogboot/repoman/etc/ogAdmRepo.cfg.tmpl > $INSTALL_TARGET/etc/ogAdmRepo-$dev.cfg + CONSOLEURL="https://${SERVERIP[i]}/opengnsys" + sed -e "s/SERVERIP/${SERVERIP[i]}/g" \ + -e "s/DBUSER/$OPENGNSYS_DB_USER/g" \ + -e "s/DBPASSWORD/$OPENGNSYS_DB_PASSWD/g" \ + -e "s/DATABASE/$OPENGNSYS_DATABASE/g" \ + -e "s/OPENGNSYSURL/${CONSOLEURL//\//\\/}/g" \ + $INSTALL_OGBOOT_TARGET/www/controlacceso.php > $INSTALL_OGBOOT_TARGET/www/controlacceso-$dev.php + if [ "$dev" == "$DEFAULTDEV" ]; then + OPENGNSYS_CONSOLEURL="$CONSOLEURL" + OPENGNSYS_SERVERIP="${SERVERIP[i]}" + fi + fi + let i++ + done + ln -f $INSTALL_OGBOOT_TARGET/etc/ogserver-$DEFAULTDEV.json $INSTALL_OGBOOT_TARGET/etc/ogserver.json + ln -f $INSTALL_OGBOOT_TARGET/etc/ogAdmRepo-$DEFAULTDEV.cfg $INSTALL_OGBOOT_TARGET/etc/ogAdmRepo.cfg + ln -f $INSTALL_OGBOOT_TARGET/www/controlacceso-$DEFAULTDEV.php $INSTALL_OGBOOT_TARGET/www/controlacceso.php + + # Configuración del motor de clonación. + # - Zona horaria del servidor. + TZ=$(timedatectl status|awk -F"[:()]" '/Time.*zone/ {print $2}') + cat << EOT >> $INSTALL_OGBOOT_TARGET/client/etc/engine.cfg +# OpenGnsys Server timezone. +TZ="${TZ// /}" +EOT + + # Revisar permisos generales. + if [ -x $INSTALL_OGBOOT_TARGET/bin/checkperms ]; then + echoAndLog "${FUNCNAME}(): Checking permissions." + OPENGNSYS_DIR="$INSTALL_OGBOOT_TARGET" OPENGNSYS_USER="$OPENGNSYS_CLIENT_USER" APACHE_USER="$APACHE_RUN_USER" APACHE_GROUP="$APACHE_RUN_GROUP" checkperms + fi + + # Evitar inicio de duplicado en Ubuntu 14.04 (Upstart y SysV Init). + if [ -f /etc/init/${MYSQLSERV}.conf -a -n "$(which initctl 2>/dev/null)" ]; then + service=$MYSQLSERV + $DISABLESERVICE + fi + + # Actualizar tokens de autenticación e iniciar los servicios. + service="opengnsys" + $ENABLESERVICE + if [ -x $INSTALL_OGBOOT_TARGET/bin/settoken ]; then + echoAndLog "${FUNCNAME}(): Setting authentication tokens and starting OpenGnsys services." + $INSTALL_OGBOOT_TARGET/bin/settoken "$OPENGNSYS_DB_USER" + $INSTALL_OGBOOT_TARGET/bin/settoken -f + else + echoAndLog "${FUNCNAME}(): Starting OpenGnsys services." + $STARTSERVICE + fi + + # Enable and start ogServer systemd service + service="ogserver" + $ENABLESERVICE; $STARTSERVICE + + echoAndLog "${FUNCNAME}(): Creating ogClient config files." + sed -i -e 's/127.0.0.1/'$OPENGNSYS_SERVERIP'/' \ + -e 's/pass'.*$'/pass\": \"'$OPENGNSYS_CLIENT_PASSWD'\"/' \ + $INSTALL_OGBOOT_TARGET/client/ogClient/cfg/ogclient.json +} + + +##################################################################### +####### Función de resumen informativo de la instalación +##################################################################### + +function installationSummary() +{ + local VERSIONFILE REVISION + + # Crear fichero de versión y revisión, si no existe. + VERSIONFILE="$INSTALL_OGBOOT_TARGET/doc/VERSION.json" + [ -f $VERSIONFILE ] || echo '{ "project": "ogBoot" }' >$VERSIONFILE + # Incluir datos de revisión, si se está instalando desde el repositorio + # de código o si no está incluida en el fichero de versión. + if [ $REMOTE -eq 1 ] || [ -z "$(jq -r '.release' $VERSIONFILE)" ]; then + # Revisión: rAñoMesDía.Gitcommit (8 caracteres de fecha y 7 primeros de commit). + RELEASE=$(curl -s "$API_URL/branches/$BRANCH" | jq -r '"r" + (.commit.commit.committer.date | split("-") | join("")[:8]) + "." + (.commit.sha[:7])' 2>/dev/null) + # Obtener revisión para etiqueta de versión en vez de rama de código. + [ -z "$RELEASE" ] && RELEASE=$(curl -s $(curl -s "$API_URL/tags" | jq -r ".[] | select(.name==\"$BRANCH\").commit.url" 2>/dev/null) | jq -r '"r" + (.commit.committer.date | split("-") | join("")[:8]) + "." + .sha[:7]' 2>/dev/null) + jq ".release=\"$RELEASE\"" $VERSIONFILE | sponge $VERSIONFILE + fi + VERSION="$(jq -r '[.project, .version, .codename, .release] | join(" ")' $VERSIONFILE 2>/dev/null)" + + # Mostrar información. + echo + echoAndLog "ogBoot Installation Summary" + echo "==============================" + echoAndLog "Project version: $VERSION" + echoAndLog "Installation directory: $INSTALL_OGBOOT_TARGET" + echoAndLog "Installation log file: $LOG_FILE" + echoAndLog "Repository directory: $INSTALL_OGBOOT_TARGET/images" + echoAndLog "DHCP configuration directory: $DHCPCFGDIR" + echoAndLog "TFTP configuration directory: $TFTPCFGDIR" + echoAndLog "Installed ogLive client: $(oglivecli list | awk '{print $2}')" + echoAndLog "Samba configuration directory: $SAMBACFGDIR" + echoAndLog "Web Console URL: $OPENGNSYS_CONSOLEURL" + echoAndLog "Web Console access data: entered by the user" + if grep -q "^RUN_BTTRACK.*no" /etc/default/opengnsys; then + echoAndLog "BitTorrent service is disabled." + fi + echo + echoAndLog "Post-Installation Instructions:" + echo "===============================" + echoAndLog "You can improve server security by configuring firewall and SELinux," + echoAndLog " running \"$INSTALL_OGBOOT_TARGET/lib/security-config\" script as root." + echoAndLog "It's strongly recommended to synchronize this server with an NTP server." + echoAndLog "Review or edit all configuration files." + echoAndLog "Insert DHCP configuration data and restart service." + echoAndLog "Review syslog configuration and logrotate by syslog," +echo +} + +##################################################################### +##################################################################### +##################################################################### +##################################################################### +##################################################################### +##################################################################### +##################################################################### +####### Proceso de instalación de ogBoot +##################################################################### +##################################################################### +##################################################################### +##################################################################### +##################################################################### + + +# Sólo ejecutable por usuario root +if [ "$(whoami)" != 'root' ]; then + echo "ERROR: this program must run under root privileges!!" + exit 1 +fi + +globalSetup +# Comprobar instalación previa. +if cat $INSTALL_OGBOOT_TARGET/doc/VERSION.* &>/dev/null; then + echo "ERROR: OpenGnsys is already installed. Run \"$INSTALL_OGBOOT_TARGET/lib/ogboot-update.sh\" as root to update." + exit 2 +fi + +# Si la distribución no es la recomendada mostramos mensaje informativo. +checkDistribution + +echoAndLog "OpenGnsys installation begins at $(date)" +# Introducir datos de configuración y establecer variables globales. +userData + +echo "creando $WORKDIR" +mkdir -p $WORKDIR +pushd $WORKDIR + +# Detectar datos iniciales de auto-configuración del instalador. + +autoConfigure + +# Detectar parámetros de red y comprobar si hay conexión. +getNetworkSettings +if [ $? -ne 0 ]; then + errorAndLog "Error reading default network settings." + exit 1 +fi +checkNetworkConnection +if [ $? -ne 0 ]; then + errorAndLog "Error connecting to server. Causes:" + errorAndLog " - Network is unreachable, review devices parameters." + errorAndLog " - You are inside a private network, configure the proxy service." + errorAndLog " - Server is temporally down, try agian later." + exit 1 +fi + +# Detener servicios de OpenGnsys, si están activos previamente. +[ -f /etc/init.d/opengnsys ] && /etc/init.d/opengnsys stop + +# Actualizar repositorios +updatePackageList + +# Instalación de dependencias (paquetes de sistema operativo). +declare -a notinstalled +checkDependencies DEPENDENCIES notinstalled +if [ $? -ne 0 ]; then + installDependencies notinstalled + if [ $? -ne 0 ]; then + echoAndLog "Error while installing some dependeces, please verify your server installation before continue" + exit 1 + fi +fi +if [ -n "$INSTALLEXTRADEPS" ]; then + echoAndLog "Installing extra dependencies" + for (( i=0; i<${#INSTALLEXTRADEPS[*]}; i++ )); do + eval ${INSTALLEXTRADEPS[i]} + done +fi + +# Detectar datos de auto-configuración después de instalar paquetes. +autoConfigurePost + +# Arbol de directorios de OpenGnsys. +createDirs ${INSTALL_OGBOOT_TARGET} +if [ $? -ne 0 ]; then + errorAndLog "Error while creating directory paths!" + exit 1 +fi + +# Si es necesario, descarga el repositorio de código en directorio temporal +if [ $REMOTE -eq 1 ]; then + downloadCode $CODE_URL + if [ $? -ne 0 ]; then + errorAndLog "Error while getting code from the repository" + exit 1 + fi +else + ln -fs "$(dirname $PROGRAMDIR)" ogboot +fi +echo "*****************************************" +# Configuración de TFTP. +tftpConfigure + +# Configuración de Samba. +smbConfigure +if [ $? -ne 0 ]; then + errorAndLog "Error while configuring Samba server!" + exit 1 +fi + +# Configuración de Rsync. +rsyncConfigure + +# Configuración ejemplo DHCP. +dhcpConfigure +if [ $? -ne 0 ]; then + errorAndLog "Error while copying your dhcp server files!" + exit 1 +fi + +# Copiar ficheros de servicios OpenGnsys Server. +copyServerFiles ${INSTALL_OGBOOT_TARGET} +if [ $? -ne 0 ]; then + errorAndLog "Error while copying the server files!" + exit 1 +fi +INSTVERSION=$(jq -r '.version' $INSTALL_OGBOOT_TARGET/doc/VERSION.json) + +mysqlTestConnection "${MYSQL_ROOT_PASSWORD}" +if [ $? -ne 0 ]; then + errorAndLog "Error while connection to mysql" + exit 1 +fi +mysqlDbExists ${OPENGNSYS_DATABASE} +if [ $? -ne 0 ]; then + echoAndLog "Creating Web Console database" + mysqlCreateDb ${OPENGNSYS_DATABASE} + if [ $? -ne 0 ]; then + errorAndLog "Error while creating Web Console database" + exit 1 + fi +else + echoAndLog "Web Console database exists, ommiting creation" +fi + +mysqlCheckUserExists ${OPENGNSYS_DB_USER} +if [ $? -ne 0 ]; then + echoAndLog "Creating user in database" + mysqlCreateAdminUserToDb ${OPENGNSYS_DATABASE} ${OPENGNSYS_DB_USER} "${OPENGNSYS_DB_PASSWD}" + if [ $? -ne 0 ]; then + errorAndLog "Error while creating database user" + exit 1 + fi + +fi + +rm -f $TMPMYCNF + +# Creando configuración de Apache. +installWebConsoleApacheConf $INSTALL_OGBOOT_TARGET $APACHECFGDIR +if [ $? -ne 0 ]; then + errorAndLog "Error configuring Apache for OpenGnsys Admin" + exit 1 +fi + +popd + +# Crear la estructura de los accesos al servidor desde el cliente (shared) +copyClientFiles +if [ $? -ne 0 ]; then + errorAndLog "Error creating client structure" +fi + +# Crear certificado para firmar cargadores +createCerts + +# Crear la estructura del cliente de OpenGnsys. +for i in $OGLIVE; do + if ! clientCreate "$i"; then + errorAndLog "Error creating client $i" + exit 1 + fi +done + +# Configuración de servicios de OpenGnsys +openGnsysConfigure + +# Mostrar sumario de la instalación e instrucciones de post-instalación. +installationSummary + +rm -rf $WORKDIR +echoAndLog "OpenGnsys installation finished at $(date)" +exit 0 diff --git a/installer/ogboot_devel_uninstall.sh b/installer/ogboot_devel_uninstall.sh new file mode 100755 index 0000000..7d964ff --- /dev/null +++ b/installer/ogboot_devel_uninstall.sh @@ -0,0 +1,77 @@ + +#### AVISO: NO EDITAR variables de configuración. +#### WARNING: DO NOT EDIT configuration variables. +OPENGNSYS="/opt/ogboot" # Directorio de OpenGnsys +OGBOOT="/opt/ogboot" # Directorio de ogBoot +OGIMG="images" # Directorio de imágenes del repositorio +OPENGNSYS_CLIENT_USER="opengnsys" # Usuario Samba +OPENGNSYS_OLDDATABASE="ogBDAdmin" # Antigua base de datos +MYCNF=/tmp/.my.cnf.$$ # Fichero temporal con credenciales de acceso a la BD. +TFTPDIR=$(readlink $OPENGNSYS/tftpboot 2>/dev/null) # Directorio de PXE/TFTP + +# Sólo ejecutable por usuario root +if [ "$(whoami)" != 'root' ]; then + echo "ERROR: this program must run under root privileges!!" + exit 1 +fi + +# Solicitar confirmación para la desinstalación de OpenGnsys. +read -rp "WARNING: Files under $OPENGNSYS directory will be removed. Continue to uninstall? (y/n): " REPLY +if [ "${REPLY^^}" != "Y" ]; then + echo "Operation cancelled." + exit 0 +fi + +# Parar servicio. +echo "Uninstalling OpenGnsys services." +if [ -x /etc/init.d/opengnsys ]; then + /etc/init.d/opengnsys stop + if [ -n "$(which update-rc.d 2>/dev/null)" ]; then + update-rc.d -f opengnsys remove + else + chkconfig --del opengnsys + fi +fi + +#Parar ogserver +if [ -r /lib/systemd/system/ogserver.service ]; then + systemctl stop ogserver + systemctl disable ogserver + rm /lib/systemd/system/ogserver.service +fi + +# Quitar configuración específica de Apache. +[ -n "$(which a2dissite 2>/dev/null)" ] && a2dissite ogboot +rm -f /etc/{apache2/{sites-available,sites-enabled},httpd/conf.d}/ogboot* +for serv in apache2 httpd; do + [ -x /etc/init.d/$serv ] && /etc/init.d/$serv reload +done +# Eliminar ficheros. +echo "Deleting OpenGnsys files." +for dir in $OPENGNSYS/*; do + if [ "$dir" != "$OPENGNSYS/$OGIMG" ]; then + rm -fr "$dir" + fi +done +rm -f /etc/init.d/opengnsys /etc/default/opengnsys /var/log/opengnsys +rm -f /etc/cron.d/{opengnsys,torrentcreator,torrenttracker} +rm -f /etc/logrotate.d/opengnsys* +# Elminar recursos de OpenGnsys en Samba. +rm -f /etc/samba/smb-og.conf +perl -ni -e "print unless /smb-og.conf/" /etc/samba/smb.conf +for serv in smbd smb ; do + [ -x /etc/init.d/$serv ] && /etc/init.d/$serv reload +done +# Eliminar usuario de OpenGnsys. +smbpasswd -x $OPENGNSYS_CLIENT_USER +userdel $OPENGNSYS_CLIENT_USER +# Tareas manuales a realizar después de desinstalar. +echo "delete temporary files" +rm -rf /tmp/ogboot_installer +rm -rf /opt/ogboot +rm -rf /opt/opengnsys +echo "Manual tasks:" +echo "- You may stop or uninstall manually all other services" +echo " (DHCP, PXE, TFTP, NFS/Samba, Apache, MySQL)." +echo "- Delete repository directory \"$OPENGNSYS/$OGIMG\"" +[ -n "$TFTPDIR" ] && echo "- Delete PXE configuration directory \"$TFTPDIR\"" From bb440428de999be090fce9233ef2d2de6146c77d Mon Sep 17 00:00:00 2001 From: Antonio Emmanuel Guerrero Silva Date: Thu, 11 Apr 2024 00:39:39 -0600 Subject: [PATCH 002/149] All references to MySQL and WebConsole have been removed. --- installer/ogboot_devel_installer.sh | 644 +--------------------------- 1 file changed, 10 insertions(+), 634 deletions(-) diff --git a/installer/ogboot_devel_installer.sh b/installer/ogboot_devel_installer.sh index 2548976..84cc913 100755 --- a/installer/ogboot_devel_installer.sh +++ b/installer/ogboot_devel_installer.sh @@ -10,39 +10,6 @@ ####### Funciones de configuración ##################################################################### -# Devuelve en la variable PASSWORD la clave introducida por el usuario (o la indicada por defecto) -function enterPassword () -{ - local PASSWORD2 - local DEFAULT_PASSWORD="$1" - - while : ; do - stty -echo - read -r PASSWORD - stty echo - if [ -z "$PASSWORD" ]; then - # Si esta vacio ponemos el valor por defecto - PASSWORD="${PASSWORD:-$DEFAULT_PASSWORD}" - break - else - if [ -n "${PASSWORD//[a-zA-Z0-9]/}" ]; then # Comprobamos que sea un valor alfanumerico - echo -e "\\aERROR: Password must be alphanumeric, try again..." - else - echo -n -e "\\nConfirm password: " - stty -echo - read -r PASSWORD2 - stty echo - if [ "$PASSWORD" == "$PASSWORD2" ]; then - break - else - echo -e "\\aERROR: Passwords don't match, try again." - fi - fi - fi - echo -n -e "Please, enter a new password (${DEFAULT_PASSWORD}): " - done -} - # Si la distribución no es la recomendada mostramos mensaje informativo. function checkDistribution() { @@ -66,8 +33,6 @@ function userData () { #### AVISO: Puede editar configuración de acceso por defecto. #### WARNING: Edit default access configuration if you wish. - DEFAULT_MYSQLHOST="172.17.8.71" # IP por defecto de la base de datos - DEFAULT_MYSQL_ROOT_PASSWORD="passwordroot" # Clave por defecto root de MySQL DEFAULT_OPENGNSYS_DB_USER="usuog" # Usuario por defecto de acceso a la base de datos DEFAULT_OPENGNSYS_DB_PASSWD="passusuog" # Clave por defecto de acceso a la base de datos DEFAULT_OPENGNSYS_CLIENT_PASSWD="og" # Clave por defecto de acceso del cliente @@ -78,51 +43,11 @@ function userData () if [[ $- =~ s ]]; then echo -e "\\nNot interactive mode: setting default configuration values.\\n" - MYSQLHOST="$DEFAULT_MYSQLHOST" - MYSQL_ROOT_PASSWORD="$DEFAULT_MYSQL_ROOT_PASSWORD" - OPENGNSYS_DB_USER="$DEFAULT_OPENGNSYS_DB_USER" - OPENGNSYS_DB_PASSWD="$DEFAULT_OPENGNSYS_DB_PASSWD" OPENGNSYS_CLIENT_PASSWD="$DEFAULT_OPENGNSYS_CLIENT_PASSWD" OGLIVE="$DEFAULT_OGLIVE" return fi - # IP de Servidor MySQL - echo -n -e "\\nEnter IP or HOSTNAME for MySQL (${DEFAULT_MYSQLHOST}): " - enterPassword "$DEFAULT_MYSQL_HOST" - MYSQLHOST="$PASSWORD" - - # Clave root de MySQL - echo -n -e "\\nEnter root password for MySQL (${DEFAULT_MYSQL_ROOT_PASSWORD}): " - enterPassword "$DEFAULT_MYSQL_ROOT_PASSWORD" - MYSQL_ROOT_PASSWORD="$PASSWORD" - - # Usuario de acceso a la base de datos - while : ; do - echo -n -e "\\n\\nEnter username for OpenGnsys console (${DEFAULT_OPENGNSYS_DB_USER}): " - read -r OPENGNSYS_DB_USER - if [ -n "${OPENGNSYS_DB_USER//[a-zA-Z0-9]/}" ]; then # Comprobamos que sea un valor alfanumerico - echo -e "\\aERROR: Must be alphanumeric, try again..." - else - # Si esta vacio ponemos el valor por defecto - OPENGNSYS_DB_USER="${OPENGNSYS_DB_USER:-$DEFAULT_OPENGNSYS_DB_USER}" - break - fi - done - - # Clave de acceso a la base de datos - echo -n -e "\\nEnter password for OpenGnsys console (${DEFAULT_OPENGNSYS_DB_PASSWD}): " - enterPassword "$DEFAULT_OPENGNSYS_DB_PASSWD" - OPENGNSYS_DB_PASSWD="$PASSWORD" - - # Clave de acceso del cliente - echo -n -e "\\n\\nEnter root password for OpenGnsys client (${DEFAULT_OPENGNSYS_CLIENT_PASSWD}): " - enterPassword "$DEFAULT_OPENGNSYS_CLIENT_PASSWD" - OPENGNSYS_CLIENT_PASSWD="$PASSWORD" - unset PASSWORD - - # El ogclient sólo es compatible con ogLive de kernel 5.x. Se comenta la elección de ogLive. - # Selección de clientes ogLive para descargar. #while : ; do # echo -e "\\n\\nChoose ogLive client to install." # echo -e "1) Kernel 5.4, 64-bit, EFI-compatible" @@ -142,7 +67,6 @@ function userData () #done OGLIVE="$DEFAULT_OGLIVE" - echo -e "\\n==============================" } # Asigna valores globales de configuración para el script. @@ -164,7 +88,7 @@ function globalSetup () API_URL="https://api.github.com/repos/opengnsys/OpenGnsys" # Directorios de instalación y destino de OpenGnsys. - WORKDIR=/tmp/opengnsys_installer + WORKDIR=/tmp/ogboot_installer INSTALL_TARGET=/opt/opengnsys INSTALL_OGBOOT_TARGET=/opt/ogboot PATH=$PATH:$INSTALL_OGBOOT_TARGET/bin @@ -175,9 +99,6 @@ function globalSetup () # Usuario del cliente para acceso remoto. OPENGNSYS_CLIENT_USER="opengnsys" - # Nombre de la base datos y fichero SQL para su creación. - OPENGNSYS_DATABASE="ogAdmBD" - OPENGNSYS_DB_CREATION_FILE=ogboot/admin/Database/${OPENGNSYS_DATABASE}.sql } # Generar variables de configuración del instalador @@ -194,8 +115,6 @@ function globalSetup () # - PHPFPMSERV - servicio PHP FastCGI Process Manager para Apache # - INETDSERV - servicio Inetd # - DHCPSERV, DHCPCFGDIR - servicio y configuración de DHCP -# - MYSQLSERV, TMPMYCNF - servicio MySQL y fichero temporal con credenciales de acceso -# - MARIADBSERV - servicio MariaDB (sustituto de MySQL en algunas distribuciones) # - RSYNCSERV, RSYNCCFGDIR - servicio y configuración de Rsync # - SAMBASERV, SAMBACFGDIR - servicio y configuración de Samba # - TFTPSERV, TFTPCFGDIR - servicio y configuración de TFTP/PXE @@ -217,7 +136,7 @@ OSVERSION="${OSVERSION%%.*}" # Configuración según la distribución GNU/Linux (usar minúsculas). case "$OSDISTRIB" in ubuntu|debian|linuxmint) - DEPENDENCIES=( subversion apache2 php php-ldap php-fpm mysql-client php-mysql isc-dhcp-server bittorrent tftp-hpa tftpd-hpa xinetd build-essential g++-multilib libmysqlclient-dev wget curl graphviz bittornado ctorrent samba rsync unzip netpipes debootstrap schroot squashfs-tools btrfs-tools procps arp-scan realpath php-curl gettext moreutils jq wakeonlan udpcast libev-dev libjansson-dev libssl-dev shim-signed grub-efi-amd64-signed gawk libdbi-dev libdbi1 libdbd-mysql automake liblz4-tool ) + DEPENDENCIES=( subversion apache2 php php-ldap php-fpm isc-dhcp-server bittorrent tftp-hpa tftpd-hpa xinetd build-essential g++-multilib wget curl graphviz bittornado ctorrent samba rsync unzip netpipes debootstrap schroot squashfs-tools btrfs-tools procps arp-scan realpath php-curl gettext moreutils jq wakeonlan udpcast libev-dev libjansson-dev libssl-dev shim-signed grub-efi-amd64-signed gawk libdbi-dev libdbi1 automake liblz4-tool ) UPDATEPKGLIST="apt-get update" INSTALLPKG="apt-get -y install --force-yes" CHECKPKG="dpkg -s \$package 2>/dev/null | grep Status | grep -qw install" @@ -244,11 +163,6 @@ case "$OSDISTRIB" in DHCPCFGDIR=/etc/dhcp INETDSERV=xinetd INETDCFGDIR=/etc/xinetd.d - MYSQLSERV=mysql - MYSQLHOST=172.17.8.74 - MYSQLPORT=3301 - MYSQLCFGDIR=/etc/mysql/mysql.conf.d - MARIADBSERV=mariadb PHPFPMSERV=php-fpm RSYNCSERV=rsync RSYNCCFGDIR=/etc @@ -257,7 +171,7 @@ case "$OSDISTRIB" in TFTPCFGDIR=/var/lib/tftpboot ;; fedora|centos) - DEPENDENCIES=( subversion httpd mod_ssl php-ldap php-fpm mysql mysql-devel mysql-devel.i686 php-mysql dhcp tftp-server tftp xinetd binutils gcc gcc-c++ glibc-devel glibc-devel.i686 glibc-static glibc-static.i686 libstdc++-devel.i686 make wget curl doxygen graphviz ctorrent samba samba-client rsync unzip debootstrap schroot squashfs-tools python-crypto arp-scan procps-ng gettext moreutils jq net-tools udpcast libev-devel jansson-devel openssl-devel shim-x64 grub2-efi-x64 grub2-efi-x64-modules gawk libdbi-devel libdbi libdbi-dbd-mysql automake http://ftp.altlinux.org/pub/distributions/ALTLinux/5.1/branch/$(arch)/RPMS.classic/netpipes-4.2-alt1.$(arch).rpm ) + DEPENDENCIES=( subversion httpd mod_ssl php-ldap php-fpm dhcp tftp-server tftp xinetd binutils gcc gcc-c++ glibc-devel glibc-devel.i686 glibc-static glibc-static.i686 libstdc++-devel.i686 make wget curl doxygen graphviz ctorrent samba samba-client rsync unzip debootstrap schroot squashfs-tools python-crypto arp-scan procps-ng gettext moreutils jq net-tools udpcast libev-devel jansson-devel openssl-devel shim-x64 grub2-efi-x64 grub2-efi-x64-modules gawk libdbi-devel libdbi automake http://ftp.altlinux.org/pub/distributions/ALTLinux/5.1/branch/$(arch)/RPMS.classic/netpipes-4.2-alt1.$(arch).rpm ) [ "$OSDISTRIB" == "centos" ] && UPDATEPKGLIST="yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-$OSVERSION.noarch.rpm http://rpms.remirepo.net/enterprise/remi-release-$OSVERSION.rpm" INSTALLEXTRADEPS=( 'pushd /tmp; wget -t3 http://ftp.acc.umu.se/mirror/bittornado/BitTornado-0.3.18.tar.gz && tar xvzf BitTornado-0.3.18.tar.gz && cd BitTornado-CVS && python setup.py install && ln -fs btlaunchmany.py /usr/bin/btlaunchmany && ln -fs bttrack.py /usr/bin/bttrack; popd' ) INSTALLPKG="yum install -y libstdc++ libstdc++.i686" @@ -284,11 +198,6 @@ case "$OSDISTRIB" in DHCPCFGDIR=/etc/dhcp INETDSERV=xinetd INETDCFGDIR=/etc/xinetd.d - MYSQLSERV=mysqld - MYSQLHOST=172.17.8.74 - MYSQLPORT=3301 - MYSQLCFGDIR=/etc/my.cnf.d - MARIADBSERV=mariadb PHPFPMSERV=php-fpm RSYNCSERV=rsync RSYNCCFGDIR=/etc @@ -302,12 +211,7 @@ case "$OSDISTRIB" in *) echo "ERROR: Distribution not supported by OpenGnsys." exit 1 ;; esac - -# Fichero de credenciales de acceso a MySQL. -TMPMYCNF=/tmp/.my.cnf.$$ } - - # Modificar variables de configuración tras instalar paquetes del sistema. function autoConfigurePost() { @@ -355,8 +259,6 @@ case "$OSDISTRIB" in PHPFPMSERV="${PHP7VERSION}-fpm" DEPENDENCIES=( ${DEPENDENCIES[@]//php/$PHP7VERSION} ) fi - # Adaptar dependencias para libmysqlclient. - [ -z "$(apt-cache pkgnames libmysqlclient-dev)" ] && [ -n "$(apt-cache pkgnames libmysqlclient15)" ] && DEPENDENCIES=( ${DEPENDENCIES[@]//libmysqlclient-dev/libmysqlclient15} ) # Paquete correcto para realpath. [ -z "$(apt-cache pkgnames realpath)" ] && DEPENDENCIES=( ${DEPENDENCIES[@]//realpath/coreutils} ) ;; @@ -367,8 +269,6 @@ case "$OSDISTRIB" in DEPENDENCIES=( ${PHP7VERSION} ${DEPENDENCIES[@]//php/$PHP7VERSION-php} ) # Cambios a aplicar a partir de CentOS 7. if [ $OSVERSION -ge 7 ]; then - # Sustituir MySQL por MariaDB. - DEPENDENCIES=( ${DEPENDENCIES[*]/mysql-/mariadb-} ) # Instalar ctorrent de EPEL para CentOS 6 (no disponible en CentOS 7). DEPENDENCIES=( ${DEPENDENCIES[*]/ctorrent/http://dl.fedoraproject.org/pub/epel/6/$(arch)/Packages/c/ctorrent-1.3.4-14.dnh3.3.2.el6.$(arch).rpm} ) fi @@ -376,8 +276,6 @@ case "$OSDISTRIB" in fedora) # Postconfiguación personalizada para Fedora. # Incluir paquetes específicos. DEPENDENCIES=( ${DEPENDENCIES[@]} btrfs-progs ) - # Sustituir MySQL por MariaDB a partir de Fedora 20. - [ $OSVERSION -ge 20 ] && DEPENDENCIES=( ${DEPENDENCIES[*]/mysql-/mariadb-} ) ;; esac } @@ -578,264 +476,6 @@ function backupFile() echoAndLog "${FUNCNAME}(): $file backup success" } -##################################################################### -####### Funciones para el manejo de bases de datos -##################################################################### - -# This function set password to root -function mysqlSetRootPassword() -{ - if [ $# -ne 1 ]; then - errorAndLog "${FUNCNAME}(): invalid number of parameters" - exit 1 - fi - - local root_mysql="$1" - echoAndLog "${FUNCNAME}(): setting root password in MySQL server" - - mysqladmin -h $MYSQLHOST -u root password "$root_mysql" - if [ $? -ne 0 ]; then - errorAndLog "${FUNCNAME}(): error while setting root password in MySQL server" - return 1 - fi - echoAndLog "${FUNCNAME}(): root password saved!" - return 0 -} - -# Si el servicio mysql esta ya instalado cambia la variable de la clave del root por la ya existente -function mysqlGetRootPassword() -{ - local pass_mysql - local pass_mysql2 - # Comprobar si MySQL está instalado con la clave de root por defecto. - if mysql -h $MYSQLHOST -u root -p"$MYSQL_ROOT_PASSWORD" <<<"quit" 2>/dev/null; then - echoAndLog "${FUNCNAME}(): Using default mysql root password." - else - stty -echo - echo "There is a MySQL service already installed." - read -p "Enter MySQL root password: " pass_mysql - echo "" - read -p "Confrim password:" pass_mysql2 - echo "" - stty echo - if [ "$pass_mysql" == "$pass_mysql2" ] ;then - MYSQL_ROOT_PASSWORD="$pass_mysql" - return 0 - else - echo "The keys don't match. Do not configure the server's key," - echo "transactions in the database will give error." - return 1 - fi - fi -} - -# comprueba si puede conectar con mysql con el usuario root -function mysqlTestConnection() -{ - if [ $# -ne 1 ]; then - errorAndLog "${FUNCNAME}(): invalid number of parameters" - exit 1 - fi - - local root_password="$1" - echoAndLog "${FUNCNAME}(): checking connection to mysql..." - # Componer fichero con credenciales de conexión a MySQL. - touch $TMPMYCNF - chmod 600 $TMPMYCNF - cat << EOT > $TMPMYCNF -[client] -user=root -password=$root_password -EOT - echo "------------------------" - cat $TMPMYCNF - echo "------------------------" - # Comprobar conexión a MySQL. - echo "" | mysql --defaults-extra-file=$TMPMYCNF -h $MYSQLHOST - if [ $? -ne 0 ]; then - errorAndLog "${FUNCNAME}(): connection to mysql failed, check root password and if daemon is running!" - return 1 - else - echoAndLog "${FUNCNAME}(): connection success" - return 0 - fi - # Borrar el fichero temporal si termina el proceso de instalación. - trap "rm -f $TMPMYCNF" 0 1 2 3 6 9 15 -} - -# comprueba si la base de datos existe -function mysqlDbExists() -{ - if [ $# -ne 1 ]; then - errorAndLog "${FUNCNAME}(): invalid number of parameters" - exit 1 - fi - - local database="$1" - echoAndLog "${FUNCNAME}(): checking if $database exists..." - echo "show databases" | mysql --defaults-extra-file=$TMPMYCNF -h $MYSQLHOST | grep "^${database}$" - if [ $? -ne 0 ]; then - echoAndLog "${FUNCNAME}():database $database doesn't exists" - return 1 - else - echoAndLog "${FUNCNAME}():database $database exists" - return 0 - fi -} - -# Comprueba si la base de datos está vacía. -function mysqlCheckDbIsEmpty() -{ - if [ $# -ne 1 ]; then - errorAndLog "${FUNCNAME}(): invalid number of parameters" - exit 1 - fi - - local database="$1" - echoAndLog "${FUNCNAME}(): checking if $database is empty..." - num_tablas=`echo "show tables" | mysql --defaults-extra-file=$TMPMYCNF -h $MYSQLHOST "${database}" | wc -l` - if [ $? -ne 0 ]; then - errorAndLog "${FUNCNAME}(): error executing query, check database and root password" - exit 1 - fi - - if [ $num_tablas -eq 0 ]; then - echoAndLog "${FUNCNAME}():database $database is empty" - return 0 - else - echoAndLog "${FUNCNAME}():database $database has tables" - return 1 - fi - -} - -# Importa un fichero SQL en la base de datos. -# Parámetros: -# - 1: nombre de la BD. -# - 2: fichero a importar. -# Nota: el fichero SQL puede contener las siguientes palabras reservadas: -# - SERVERIP: se sustituye por la dirección IP del servidor. -# - DBUSER: se sustituye por usuario de conexión a la BD definido en este script. -# - DBPASSWD: se sustituye por la clave de conexión a la BD definida en este script. -function mysqlImportSqlFileToDb() -{ - if [ $# -ne 2 ]; then - errorAndLog "${FNCNAME}(): invalid number of parameters" - exit 1 - fi - - local database="$1" - local sqlfile="$2" - local tmpfile=$(mktemp) - local i=0 - local dev="" - local status - - if [ ! -f $sqlfile ]; then - errorAndLog "${FUNCNAME}(): Unable to locate $sqlfile!!" - return 1 - fi - - echoAndLog "${FUNCNAME}(): importing SQL file to ${database}..." - chmod 600 $tmpfile - for dev in ${DEVICE[*]}; do - if [ "${DEVICE[i]}" == "$DEFAULTDEV" ]; then - sed -e "s/SERVERIP/${SERVERIP[i]}/g" \ - -e "s/DBUSER/$OPENGNSYS_DB_USER/g" \ - -e "s/DBPASSWORD/$OPENGNSYS_DB_PASSWD/g" \ - $sqlfile > $tmpfile - fi - let i++ - done - echo "----------------------" - mysql --defaults-extra-file=$TMPMYCNF -h $MYSQLHOST --default-character-set=utf8 "${database}" < $tmpfile - status=$? - rm -f $tmpfile - if [ $status -ne 0 ]; then - errorAndLog "${FUNCNAME}(): error while importing $sqlfile in database $database" - return 1 - fi - echoAndLog "${FUNCNAME}(): file imported to database $database" - return 0 -} - -# Crea la base de datos -function mysqlCreateDb() -{ - if [ $# -ne 1 ]; then - errorAndLog "${FUNCNAME}(): invalid number of parameters" - exit 1 - fi - - local database="$1" - - echoAndLog "${FUNCNAME}(): creating database..." - mysqladmin --defaults-extra-file=$TMPMYCNF -h $MYSQLHOST create $database - if [ $? -ne 0 ]; then - errorAndLog "${FUNCNAME}(): error while creating database $database" - return 1 - fi - # Quitar modo ONLY_FULL_GROUP_BY de MySQL (ticket #730). - mysql --defaults-extra-file=$TMPMYCNF -h $MYSQLHOST -e "SET GLOBAL sql_mode=(SELECT TRIM(BOTH ',' FROM REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY','')));" - - echoAndLog "${FUNCNAME}(): database $database created" - return 0 -} - -# Comprueba si ya está definido el usuario de acceso a la BD. -function mysqlCheckUserExists() -{ - if [ $# -ne 1 ]; then - errorAndLog "${FUNCNAME}(): invalid number of parameters" - exit 1 - fi - - local userdb="$1" - - echoAndLog "${FUNCNAME}(): checking if $userdb exists..." - echo "select user from user where user='${userdb}'\\G" |mysql --defaults-extra-file=$TMPMYCNF -h $MYSQLHOST mysql | grep user - if [ $? -ne 0 ]; then - echoAndLog "${FUNCNAME}(): user doesn't exists" - return 1 - else - echoAndLog "${FUNCNAME}(): user already exists" - return 0 - fi - -} - -# Crea un usuario administrativo para la base de datos -function mysqlCreateAdminUserToDb() -{ - if [ $# -ne 3 ]; then - errorAndLog "${FUNCNAME}(): invalid number of parameters" - exit 1 - fi - - local database="$1" - local userdb="$2" - local passdb="$3" - - echoAndLog "${FUNCNAME}(): creating admin user ${userdb} to database ${database}" - - cat > $WORKDIR/create_${database}.sql < $SAMBACFGDIR/smb-og.conf # Configurar y recargar Samba" - perl -pi -e "s/WORKGROUP/OPENGNSYS/; s/server string \=.*/server string \= OpenGnsys Samba Server/" $SAMBACFGDIR/smb.conf + perl -pi -e "s/WORKGROUP/OPENGNSYS/; s/server string \=.*/server string \= ogBoot Samba Server/" $SAMBACFGDIR/smb.conf if ! grep -q "smb-og" $SAMBACFGDIR/smb.conf; then echo "include = $SAMBACFGDIR/smb-og.conf" >> $SAMBACFGDIR/smb.conf fi @@ -1108,72 +748,6 @@ function dhcpConfigure() ####### Funciones específicas de la instalación de Opengnsys ##################################################################### -# Copiar ficheros del OpenGnsys Web Console. -function installWebFiles() -{ - local COMPATDIR f - local SLIMFILE="slim-2.6.1.zip" - local SWAGGERFILE="swagger-ui-2.2.5.zip" - - echoAndLog "${FUNCNAME}(): Installing web files..." - # Copiar ficheros. - cp -a $WORKDIR/ogboot/admin/WebConsole/* $INSTALL_TARGET/www #*/ comentario para Doxygen. - if [ $? != 0 ]; then - errorAndLog "${FUNCNAME}(): Error copying web files." - exit 1 - fi - - # Descomprimir librerías: Slim y Swagger-UI. - unzip -o $WORKDIR/ogboot/admin/$SLIMFILE -d $INSTALL_TARGET/www/rest - unzip -o $WORKDIR/ogboot/admin/$SWAGGERFILE -d $INSTALL_TARGET/www/rest - - # Compatibilidad con dispositivos móviles. - COMPATDIR="$INSTALL_TARGET/www/principal" - for f in acciones administracion aula aulas hardwares imagenes menus repositorios softwares; do - sed 's/clickcontextualnodo/clicksupnodo/g' $COMPATDIR/$f.php > $COMPATDIR/$f.device.php - done - cp -a $COMPATDIR/imagenes.device.php $COMPATDIR/imagenes.device4.php - # Acceso al manual de usuario - ln -fs ../doc/userManual $INSTALL_TARGET/www/userManual - # Ficheros de log de la API REST. - touch $INSTALL_TARGET/log/{ogagent,remotepc,rest}.log - - echoAndLog "${FUNCNAME}(): Web files installed successfully." -} - -# Copiar ficheros en la zona de descargas de OpenGnsys Web Console. -function installDownloadableFiles() -{ - local VERSIONFILE OGVERSION FILENAME TARGETFILE - - # Obtener versión a descargar. - VERSIONFILE="$INSTALL_TARGET/doc/VERSION.json" - OGVERSION="$(jq -r ".ogagent // \"$INSTVERSION\"" $VERSIONFILE 2>/dev/null || echo "$INSTVERSION")" - FILENAME="ogagentpkgs-$OGVERSION.tar.gz" - TARGETFILE=$WORKDIR/$FILENAME - - # Descargar archivo comprimido, si es necesario. - if [ -s $PROGRAMDIR/$FILENAME ]; then - echoAndLog "${FUNCNAME}(): Moving $PROGRAMDIR/$FILENAME file to $(dirname $TARGETFILE)" - mv $PROGRAMDIR/$FILENAME $TARGETFILE - else - echoAndLog "${FUNCNAME}(): Downloading $FILENAME" - curl $DOWNLOADURL/$FILENAME -o $TARGETFILE - fi - if [ ! -s $TARGETFILE ]; then - errorAndLog "${FUNCNAME}(): Cannot download $FILENAME" - return 1 - fi - - # Descomprimir fichero en zona de descargas. - tar xvzf $TARGETFILE -C $INSTALL_TARGET/www/descargas - if [ $? != 0 ]; then - errorAndLog "${FUNCNAME}(): Error uncompressing archive." - exit 1 - fi -} - -# Configuración específica de Apache. function installWebConsoleApacheConf() { if [ $# -ne 2 ]; then @@ -1233,22 +807,6 @@ function installWebConsoleApacheConf() return 0 } - -# Crear documentación Doxygen para la consola web. -function makeDoxygenFiles() -{ - echoAndLog "${FUNCNAME}(): Making Doxygen web files..." - $WORKDIR/ogboot/installer/ogGenerateDoc.sh \ - $WORKDIR/ogboot/client/engine $INSTALL_TARGET/www - if [ ! -d "$INSTALL_TARGET/www/html" ]; then - errorAndLog "${FUNCNAME}(): unable to create Doxygen web files." - return 1 - fi - mv "$INSTALL_TARGET/www/html" "$INSTALL_TARGET/www/api" - echoAndLog "${FUNCNAME}(): Doxygen web files created successfully." -} - - # Crea la estructura base de la instalación de ogBoot function createDirs() { @@ -1386,7 +944,7 @@ function ogServerCompilation () echoAndLog "${FUNCNAME}(): Compiling OpenGnsys Server" pushd "$WORKDIR/ogServer-${BRANCH#v}" - autoreconf -fi && ./configure && make && mv ogserver $INSTALL_TARGET/sbin + autoreconf -fi && ./configure && make && mv ogserver $INSTALL_OGBOOT_TARGET/sbin if [ $? -ne 0 ]; then echoAndLog "${FUNCNAME}(): error while compiling OpenGnsys Server" error=1 @@ -1396,27 +954,6 @@ function ogServerCompilation () return $error } -#################################################################### -### Funciones de copia de la Interface de administración -#################################################################### - -# Copiar carpeta de Interface -function copyInterfaceAdm () -{ - local hayErrores=0 - - # Crear carpeta y copiar Interface - echoAndLog "${FUNCNAME}(): Copying Administration Interface Folder" - cp -ar $WORKDIR/ogboot/admin/Interface $INSTALL_TARGET/client/interfaceAdm - if [ $? -ne 0 ]; then - echoAndLog "${FUNCNAME}(): error while copying Administration Interface Folder" - hayErrores=1 - fi - - return $hayErrores -} - - #################################################################### ### Funciones instalacion cliente opengnsys #################################################################### @@ -1488,7 +1025,7 @@ function clientCreate() echo "-----------------------------3" local FILENAME="$1" - local TARGETFILE=$INSTALL_OGBOOT_TARGET/lib/$FILENAME + local TARGETFILE=$INSTALL_TARGET/lib/$FILENAME # Descargar cliente, si es necesario. echo "PROGRAMDIR/FILENAME: $PROGRAMDIR/$FILENAME" @@ -1515,122 +1052,6 @@ function clientCreate() echoAndLog "${FUNCNAME}(): Client generation success" } - -# Configuración básica de servicios de OpenGnsys -function openGnsysConfigure() -{ - local i=0 - local dev="" - local CONSOLEURL - - echoAndLog "${FUNCNAME}(): Copying init files." - cp -a $WORKDIR/ogboot/admin/Sources/Services/opengnsys.init /etc/init.d/opengnsys - cp -a $WORKDIR/ogboot/admin/Sources/Services/opengnsys.service \ - /lib/systemd/system/opengnsys.service - cp -a $WORKDIR/ogServer-${BRANCH#v}/cfg/ogserver.service \ - /lib/systemd/system/ogserver.service - cp -a $WORKDIR/ogboot/admin/Sources/Services/opengnsys.default /etc/default/opengnsys - # Deshabilitar servicios de BitTorrent si no están instalados. - if [ ! -e /usr/bin/bttrack ]; then - sed -i 's/RUN_BTTRACKER="yes"/RUN_BTTRACKER="no"/; s/RUN_BTSEEDER="yes"/RUN_BTSEEDER="no"/' \ - /etc/default/opengnsys - fi - echoAndLog "${FUNCNAME}(): Creating cron files." - echo "* * * * * root [ -x $INSTALL_OGBOOT_TARGET/bin/torrent-creator ] && $INSTALL_TARGET/bin/torrent-creator" > /etc/cron.d/torrentcreator - echo "5 * * * * root [ -x $INSTALL_OGBOOT_TARGET/bin/torrent-tracker ] && $INSTALL_TARGET/bin/torrent-tracker" > /etc/cron.d/torrenttracker - echo "* * * * * root [ -x $INSTALL_OGBOOT_TARGET/bin/deletepreimage ] && $INSTALL_TARGET/bin/deletepreimage" > /etc/cron.d/imagedelete - echo "* * * * * root [ -x $INSTALL_OGBOOT_TARGET/bin/ogagentqueue.cron ] && $INSTALL_TARGET/bin/ogagentqueue.cron" > /etc/cron.d/ogagentqueue - - echoAndLog "${FUNCNAME}(): Creating logrotate configuration files." - sed -e "s/$OPENGNSYSDIR/${INSTALL_OGBOOT_TARGET//\//\\/}/g" \ - $WORKDIR/ogboot/server/etc/logrotate.tmpl > /etc/logrotate.d/opengnsysServer - - sed -e "s/$OPENGNSYSDIR/${INSTALL_OGBOOT_TARGET//\//\\/}/g" \ - $WORKDIR/ogboot/repoman/etc/logrotate.tmpl > /etc/logrotate.d/opengnsysRepo - - echoAndLog "${FUNCNAME}(): Creating OpenGnsys config files." - for dev in ${DEVICE[*]}; do - if [ -n "${SERVERIP[i]}" ]; then - echo "{ - \"rest\" : { - \"ip\" : \"${SERVERIP[i]}\", - \"port\" : \"8888\", - \"api_token\": \"5a5ca1172136299640a9f47469237e0a\" - }, - \"database\" : { - \"ip\": \"$OPENGNSYS_SERVER\", - \"port\": \"3306\", - \"name\" : \"$OPENGNSYS_DATABASE\", - \"user\" : \"$OPENGNSYS_DB_USER\", - \"pass\" : \"$OPENGNSYS_DB_PASSWD\" - }, - \"wol\" : { - \"interface\" : \"$dev\" - } - }" | jq '.' > "$INSTALL_OGBOOT_TARGET"/etc/ogserver-"$dev".json - sed -e "s/SERVERIP/${SERVERIP[i]}/g" \ - $WORKDIR/ogboot/repoman/etc/ogAdmRepo.cfg.tmpl > $INSTALL_TARGET/etc/ogAdmRepo-$dev.cfg - CONSOLEURL="https://${SERVERIP[i]}/opengnsys" - sed -e "s/SERVERIP/${SERVERIP[i]}/g" \ - -e "s/DBUSER/$OPENGNSYS_DB_USER/g" \ - -e "s/DBPASSWORD/$OPENGNSYS_DB_PASSWD/g" \ - -e "s/DATABASE/$OPENGNSYS_DATABASE/g" \ - -e "s/OPENGNSYSURL/${CONSOLEURL//\//\\/}/g" \ - $INSTALL_OGBOOT_TARGET/www/controlacceso.php > $INSTALL_OGBOOT_TARGET/www/controlacceso-$dev.php - if [ "$dev" == "$DEFAULTDEV" ]; then - OPENGNSYS_CONSOLEURL="$CONSOLEURL" - OPENGNSYS_SERVERIP="${SERVERIP[i]}" - fi - fi - let i++ - done - ln -f $INSTALL_OGBOOT_TARGET/etc/ogserver-$DEFAULTDEV.json $INSTALL_OGBOOT_TARGET/etc/ogserver.json - ln -f $INSTALL_OGBOOT_TARGET/etc/ogAdmRepo-$DEFAULTDEV.cfg $INSTALL_OGBOOT_TARGET/etc/ogAdmRepo.cfg - ln -f $INSTALL_OGBOOT_TARGET/www/controlacceso-$DEFAULTDEV.php $INSTALL_OGBOOT_TARGET/www/controlacceso.php - - # Configuración del motor de clonación. - # - Zona horaria del servidor. - TZ=$(timedatectl status|awk -F"[:()]" '/Time.*zone/ {print $2}') - cat << EOT >> $INSTALL_OGBOOT_TARGET/client/etc/engine.cfg -# OpenGnsys Server timezone. -TZ="${TZ// /}" -EOT - - # Revisar permisos generales. - if [ -x $INSTALL_OGBOOT_TARGET/bin/checkperms ]; then - echoAndLog "${FUNCNAME}(): Checking permissions." - OPENGNSYS_DIR="$INSTALL_OGBOOT_TARGET" OPENGNSYS_USER="$OPENGNSYS_CLIENT_USER" APACHE_USER="$APACHE_RUN_USER" APACHE_GROUP="$APACHE_RUN_GROUP" checkperms - fi - - # Evitar inicio de duplicado en Ubuntu 14.04 (Upstart y SysV Init). - if [ -f /etc/init/${MYSQLSERV}.conf -a -n "$(which initctl 2>/dev/null)" ]; then - service=$MYSQLSERV - $DISABLESERVICE - fi - - # Actualizar tokens de autenticación e iniciar los servicios. - service="opengnsys" - $ENABLESERVICE - if [ -x $INSTALL_OGBOOT_TARGET/bin/settoken ]; then - echoAndLog "${FUNCNAME}(): Setting authentication tokens and starting OpenGnsys services." - $INSTALL_OGBOOT_TARGET/bin/settoken "$OPENGNSYS_DB_USER" - $INSTALL_OGBOOT_TARGET/bin/settoken -f - else - echoAndLog "${FUNCNAME}(): Starting OpenGnsys services." - $STARTSERVICE - fi - - # Enable and start ogServer systemd service - service="ogserver" - $ENABLESERVICE; $STARTSERVICE - - echoAndLog "${FUNCNAME}(): Creating ogClient config files." - sed -i -e 's/127.0.0.1/'$OPENGNSYS_SERVERIP'/' \ - -e 's/pass'.*$'/pass\": \"'$OPENGNSYS_CLIENT_PASSWD'\"/' \ - $INSTALL_OGBOOT_TARGET/client/ogClient/cfg/ogclient.json -} - - ##################################################################### ####### Función de resumen informativo de la instalación ##################################################################### @@ -1679,22 +1100,13 @@ function installationSummary() echoAndLog "Review or edit all configuration files." echoAndLog "Insert DHCP configuration data and restart service." echoAndLog "Review syslog configuration and logrotate by syslog," -echo } -##################################################################### -##################################################################### -##################################################################### -##################################################################### -##################################################################### ##################################################################### ##################################################################### ####### Proceso de instalación de ogBoot ##################################################################### ##################################################################### -##################################################################### -##################################################################### -##################################################################### # Sólo ejecutable por usuario root @@ -1706,14 +1118,14 @@ fi globalSetup # Comprobar instalación previa. if cat $INSTALL_OGBOOT_TARGET/doc/VERSION.* &>/dev/null; then - echo "ERROR: OpenGnsys is already installed. Run \"$INSTALL_OGBOOT_TARGET/lib/ogboot-update.sh\" as root to update." + echo "ERROR: ogBoot is already installed. Run \"$INSTALL_OGBOOT_TARGET/lib/ogboot-update.sh\" as root to update." exit 2 fi # Si la distribución no es la recomendada mostramos mensaje informativo. checkDistribution -echoAndLog "OpenGnsys installation begins at $(date)" +echoAndLog "ogBoot installation begins at $(date)" # Introducir datos de configuración y establecer variables globales. userData @@ -1783,7 +1195,6 @@ if [ $REMOTE -eq 1 ]; then else ln -fs "$(dirname $PROGRAMDIR)" ogboot fi -echo "*****************************************" # Configuración de TFTP. tftpConfigure @@ -1812,38 +1223,6 @@ if [ $? -ne 0 ]; then fi INSTVERSION=$(jq -r '.version' $INSTALL_OGBOOT_TARGET/doc/VERSION.json) -mysqlTestConnection "${MYSQL_ROOT_PASSWORD}" -if [ $? -ne 0 ]; then - errorAndLog "Error while connection to mysql" - exit 1 -fi -mysqlDbExists ${OPENGNSYS_DATABASE} -if [ $? -ne 0 ]; then - echoAndLog "Creating Web Console database" - mysqlCreateDb ${OPENGNSYS_DATABASE} - if [ $? -ne 0 ]; then - errorAndLog "Error while creating Web Console database" - exit 1 - fi -else - echoAndLog "Web Console database exists, ommiting creation" -fi - -mysqlCheckUserExists ${OPENGNSYS_DB_USER} -if [ $? -ne 0 ]; then - echoAndLog "Creating user in database" - mysqlCreateAdminUserToDb ${OPENGNSYS_DATABASE} ${OPENGNSYS_DB_USER} "${OPENGNSYS_DB_PASSWD}" - if [ $? -ne 0 ]; then - errorAndLog "Error while creating database user" - exit 1 - fi - -fi - -rm -f $TMPMYCNF - -# Creando configuración de Apache. -installWebConsoleApacheConf $INSTALL_OGBOOT_TARGET $APACHECFGDIR if [ $? -ne 0 ]; then errorAndLog "Error configuring Apache for OpenGnsys Admin" exit 1 @@ -1868,12 +1247,9 @@ for i in $OGLIVE; do fi done -# Configuración de servicios de OpenGnsys -openGnsysConfigure - # Mostrar sumario de la instalación e instrucciones de post-instalación. installationSummary rm -rf $WORKDIR -echoAndLog "OpenGnsys installation finished at $(date)" +echoAndLog "ogBoot installation finished at $(date)" exit 0 From 0a49e7968347cc4dc9d115f2241f9de05818b0ea Mon Sep 17 00:00:00 2001 From: Antonio Emmanuel Guerrero Silva Date: Thu, 11 Apr 2024 20:57:11 -0600 Subject: [PATCH 003/149] refs #197 Code cleanup --- installer/ogboot_devel_installer.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/installer/ogboot_devel_installer.sh b/installer/ogboot_devel_installer.sh index 84cc913..ddd9d69 100755 --- a/installer/ogboot_devel_installer.sh +++ b/installer/ogboot_devel_installer.sh @@ -5,7 +5,6 @@ ####### Autor: Luis Guillén ##################################################################### - ##################################################################### ####### Funciones de configuración ##################################################################### @@ -39,7 +38,6 @@ function userData () DEFAULT_OGLIVE="ogLive-bionic-5.4.0-40-generic-amd64-r20200629.85eceaf.iso " # Cliente ogLive echo -e "\\nOpenGnsys Installation" - echo "==============================" if [[ $- =~ s ]]; then echo -e "\\nNot interactive mode: setting default configuration values.\\n" @@ -491,14 +489,12 @@ function downloadCode() local url="$1" echoAndLog "${FUNCNAME}(): downloading code..." - echo "======================///////////////////////////////////////===============================" echo "Current PATH: $(pwd)" curl "${url}" -o opengnsys.zip && unzip -q opengnsys.zip && mv "OpenGnsys-${BRANCH#v}" ogboot if [ $? -ne 0 ]; then errorAndLog "${FUNCNAME}(): error getting OpenGnsys code from $url" return 1 fi - echo "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%" rm -f opengnsys.zip echoAndLog "${FUNCNAME}(): code was downloaded" return 0 @@ -719,7 +715,6 @@ function dhcpConfigure() for dev in ${DEVICE[*]}; do if [ -n "${SERVERIP[i]}" ]; then backupFile $DHCPCFGDIR/dhcpd-$dev.conf - echo "1===========================================" sed -e "s/SERVERIP/${SERVERIP[i]}/g" \ -e "s/NETIP/${NETIP[i]}/g" \ -e "s/NETMASK/${NETMASK[i]}/g" \ @@ -728,7 +723,6 @@ function dhcpConfigure() -e "s/DNSIP/$DNSIP/g" \ $WORKDIR/ogboot/server/etc/dhcpd.conf.tmpl > $DHCPCFGDIR/dhcpd-$dev.conf || errcode=1 echo "$WORKDIR/ogboot/server/etc/dhcpd.conf.tmpl" - echo "2===========================================" fi let i++ done From 4fe104eb05ec5288750cd9579546604526fb3b53 Mon Sep 17 00:00:00 2001 From: lgromero Date: Thu, 2 May 2024 11:00:36 +0200 Subject: [PATCH 004/149] refs #273 adds client directory to ogboot installer --- client/LICENSE.en.txt | 674 + client/README.es.txt | 12 + client/engine/Boot.lib | 2973 +++ client/engine/Cache.lib | 439 + client/engine/Disk.lib | 1715 ++ client/engine/File.lib | 422 + client/engine/FileSystem.lib | 1205 ++ client/engine/Image.lib | 1209 ++ client/engine/Inventory.lib | 528 + client/engine/Net.lib | 345 + client/engine/PostConf.lib | 543 + client/engine/PostConfEAC.lib | 699 + client/engine/Protocol.lib | 1216 ++ client/engine/README.es.txt | 38 + client/engine/Registry.lib | 455 + client/engine/Rsync.lib | 900 + client/engine/String.lib | 122 + client/engine/System.lib | 339 + client/engine/ToolsGNU.c | 147 + client/engine/UEFI.lib | 679 + client/shared/README.es.txt | 29 + client/shared/bin/EACInterfaces | Bin 0 -> 9682 bytes client/shared/bin/browser | Bin 0 -> 554987 bytes client/shared/bin/grub-probe1.99_i686 | Bin 0 -> 358216 bytes client/shared/bin/grub-probe1.99_x86_64 | Bin 0 -> 336600 bytes client/shared/bin/ogAdmClient | Bin 0 -> 687449 bytes client/shared/bin/poweroffconf | 82 + client/shared/bin/rsync-31 | Bin 0 -> 1417064 bytes client/shared/bin/runtest | 246 + client/shared/etc/engine.cfg | 56 + client/shared/etc/engine.json | 804 + client/shared/etc/es.qmap | Bin 0 -> 102808 bytes client/shared/etc/init/default.sh | 37 + client/shared/etc/lang.ca_ES.UTF-8.conf | 1 + client/shared/etc/lang.ca_ES.conf | 398 + client/shared/etc/lang.en_GB.UTF-8.conf | 1 + client/shared/etc/lang.en_GB.conf | 385 + client/shared/etc/lang.es_ES.UTF-8.conf | 1 + client/shared/etc/lang.es_ES.conf | 385 + client/shared/etc/preinit/default.sh | 25 + client/shared/etc/preinit/fileslinks.sh | 53 + client/shared/etc/preinit/loadenviron.sh | 147 + client/shared/etc/preinit/loadmodules.sh | 23 + client/shared/etc/preinit/metadevs.sh | 28 + client/shared/etc/preinit/mountrepo.sh | 54 + client/shared/etc/preinit/otherservices.sh | 36 + client/shared/etc/preinit/poweroff.sh | 40 + .../themes/OpenGnsys/background-original.png | Bin 0 -> 136 bytes .../lib/burg/themes/OpenGnsys/background.png | Bin 0 -> 74953 bytes .../shared/lib/burg/themes/OpenGnsys/extended | 79 + .../themes/OpenGnsys/icons/hover_debian.png | Bin 0 -> 3715 bytes .../OpenGnsys/icons/hover_elementary.png | Bin 0 -> 5496 bytes .../themes/OpenGnsys/icons/hover_freebsd.png | Bin 0 -> 4416 bytes .../themes/OpenGnsys/icons/hover_haiku.png | Bin 0 -> 1912 bytes .../themes/OpenGnsys/icons/hover_linux.png | Bin 0 -> 33041 bytes .../OpenGnsys/icons/hover_opengnsys.png | Bin 0 -> 11389 bytes .../themes/OpenGnsys/icons/hover_opensuse.png | Bin 0 -> 4429 bytes .../burg/themes/OpenGnsys/icons/hover_os.png | Bin 0 -> 4586 bytes .../burg/themes/OpenGnsys/icons/hover_osx.png | Bin 0 -> 2457 bytes .../themes/OpenGnsys/icons/hover_recovery.png | Bin 0 -> 4442 bytes .../themes/OpenGnsys/icons/hover_restart.png | Bin 0 -> 5057 bytes .../themes/OpenGnsys/icons/hover_shutdown.png | Bin 0 -> 4539 bytes .../themes/OpenGnsys/icons/hover_ubuntu.png | Bin 0 -> 14374 bytes .../themes/OpenGnsys/icons/hover_windows.png | Bin 0 -> 20160 bytes .../OpenGnsys/icons/hover_windows10.png | Bin 0 -> 5101 bytes .../themes/OpenGnsys/icons/hover_windows7.png | Bin 0 -> 18024 bytes .../OpenGnsys/icons/hover_windows_metro.png | Bin 0 -> 1583 bytes .../lib/burg/themes/OpenGnsys/icons/icons | 21 + .../themes/OpenGnsys/icons/normal_debian.png | Bin 0 -> 3451 bytes .../OpenGnsys/icons/normal_elementary.png | Bin 0 -> 5204 bytes .../themes/OpenGnsys/icons/normal_freebsd.png | Bin 0 -> 4080 bytes .../themes/OpenGnsys/icons/normal_haiku.png | Bin 0 -> 1652 bytes .../themes/OpenGnsys/icons/normal_linux.png | Bin 0 -> 33691 bytes .../OpenGnsys/icons/normal_opengnsys.png | Bin 0 -> 11119 bytes .../OpenGnsys/icons/normal_opensuse.png | Bin 0 -> 4059 bytes .../burg/themes/OpenGnsys/icons/normal_os.png | Bin 0 -> 4242 bytes .../themes/OpenGnsys/icons/normal_osx.png | Bin 0 -> 2066 bytes .../OpenGnsys/icons/normal_recovery.png | Bin 0 -> 4081 bytes .../themes/OpenGnsys/icons/normal_restart.png | Bin 0 -> 4718 bytes .../OpenGnsys/icons/normal_shutdown.png | Bin 0 -> 4182 bytes .../themes/OpenGnsys/icons/normal_ubuntu.png | Bin 0 -> 14036 bytes .../themes/OpenGnsys/icons/normal_windows.png | Bin 0 -> 20637 bytes .../OpenGnsys/icons/normal_windows10.png | Bin 0 -> 4791 bytes .../OpenGnsys/icons/normal_windows7.png | Bin 0 -> 17786 bytes .../OpenGnsys/icons/normal_windows_metro.png | Bin 0 -> 1280 bytes .../themes/OpenGnsys/images/000-70opaque.png | Bin 0 -> 109 bytes .../themes/OpenGnsys/images/button-bg.png | Bin 0 -> 146 bytes .../OpenGnsys/images/button-hover-bg.png | Bin 0 -> 146 bytes .../OpenGnsys/images/button-hover-l.png | Bin 0 -> 525 bytes .../OpenGnsys/images/button-hover-r.png | Bin 0 -> 671 bytes .../burg/themes/OpenGnsys/images/button-l.png | Bin 0 -> 725 bytes .../burg/themes/OpenGnsys/images/button-r.png | Bin 0 -> 562 bytes .../OpenGnsys/images/button-tools-hover.png | Bin 0 -> 1550 bytes .../themes/OpenGnsys/images/button-tools.png | Bin 0 -> 1398 bytes .../themes/OpenGnsys/images/container-b.png | Bin 0 -> 153 bytes .../themes/OpenGnsys/images/container-bg.png | Bin 0 -> 146 bytes .../themes/OpenGnsys/images/container-bl.png | Bin 0 -> 154 bytes .../themes/OpenGnsys/images/container-br.png | Bin 0 -> 154 bytes .../themes/OpenGnsys/images/container-l.png | Bin 0 -> 155 bytes .../themes/OpenGnsys/images/container-r.png | Bin 0 -> 148 bytes .../themes/OpenGnsys/images/container-t.png | Bin 0 -> 159 bytes .../OpenGnsys/images/container-title-bg.png | Bin 0 -> 144 bytes .../OpenGnsys/images/container-title-l.png | Bin 0 -> 139 bytes .../OpenGnsys/images/container-title-r.png | Bin 0 -> 139 bytes .../OpenGnsys/images/container-title-t.png | Bin 0 -> 151 bytes .../OpenGnsys/images/container-title-tl.png | Bin 0 -> 181 bytes .../OpenGnsys/images/container-title-tr.png | Bin 0 -> 188 bytes .../themes/OpenGnsys/images/container-tl.png | Bin 0 -> 154 bytes .../themes/OpenGnsys/images/container-tr.png | Bin 0 -> 154 bytes .../burg/themes/OpenGnsys/images/dialog-b.png | Bin 0 -> 135 bytes .../themes/OpenGnsys/images/dialog-bg.png | Bin 0 -> 144 bytes .../themes/OpenGnsys/images/dialog-bl.png | Bin 0 -> 177 bytes .../themes/OpenGnsys/images/dialog-bl.xcf | Bin 0 -> 889 bytes .../themes/OpenGnsys/images/dialog-br.png | Bin 0 -> 168 bytes .../themes/OpenGnsys/images/dialog-lr.png | Bin 0 -> 131 bytes .../themes/OpenGnsys/images/dialog-spacer.png | Bin 0 -> 140 bytes .../burg/themes/OpenGnsys/images/dialog-t.png | Bin 0 -> 135 bytes .../themes/OpenGnsys/images/dialog-tl.png | Bin 0 -> 181 bytes .../themes/OpenGnsys/images/dialog-tr.png | Bin 0 -> 188 bytes .../OpenGnsys/images/progressbar-bg-b.png | Bin 0 -> 156 bytes .../OpenGnsys/images/progressbar-bg-bl.png | Bin 0 -> 169 bytes .../OpenGnsys/images/progressbar-bg-br.png | Bin 0 -> 215 bytes .../OpenGnsys/images/progressbar-bg-l.png | Bin 0 -> 346 bytes .../OpenGnsys/images/progressbar-bg-r.png | Bin 0 -> 165 bytes .../OpenGnsys/images/progressbar-bg-t.png | Bin 0 -> 155 bytes .../OpenGnsys/images/progressbar-bg-tl.png | Bin 0 -> 183 bytes .../OpenGnsys/images/progressbar-bg-tr.png | Bin 0 -> 186 bytes .../OpenGnsys/images/progressbar-bg.png | Bin 0 -> 152 bytes .../themes/OpenGnsys/images/text-line-l.png | Bin 0 -> 254 bytes .../themes/OpenGnsys/images/text-line-r.png | Bin 0 -> 260 bytes .../lib/burg/themes/OpenGnsys/images/tick.png | Bin 0 -> 253 bytes .../themes/OpenGnsys/images/txt-about.png | Bin 0 -> 905 bytes .../burg/themes/OpenGnsys/images/txt-help.png | Bin 0 -> 423 bytes .../themes/OpenGnsys/images/txt-select.png | Bin 0 -> 4367 bytes .../themes/OpenGnsys/images/txt-tools.png | Bin 0 -> 474 bytes .../OpenGnsys/images/ubuntu-glow-96.png | Bin 0 -> 9155 bytes client/shared/lib/burg/themes/OpenGnsys/menus | 188 + client/shared/lib/burg/themes/OpenGnsys/style | 158 + client/shared/lib/burg/themes/OpenGnsys/theme | 231 + .../lib/engine/tests/Modify/Cache.shtest | 93 + .../lib/engine/tests/NoModify/File1.shtest | 75 + .../lib/engine/tests/NoModify/Lock1.shtest | 85 + .../lib/engine/tests/NoModify/Net1.shtest | 21 + client/shared/lib/engine/tests/README | 6 + client/shared/lib/engine/tests/crearTestDisk1 | 160 + client/shared/lib/engine/tests/crearTestLock2 | 82 + client/shared/lib/fonts/DejaVuSans-Bold.ttf | Bin 0 -> 466696 bytes .../lib/fonts/DejaVuSans-BoldOblique.ttf | Bin 0 -> 441736 bytes .../shared/lib/fonts/DejaVuSans-Oblique.ttf | Bin 0 -> 434576 bytes client/shared/lib/fonts/DejaVuSans.ttf | Bin 0 -> 493564 bytes .../shared/lib/fonts/DejaVuSansMono-Bold.ttf | Bin 0 -> 229460 bytes .../lib/fonts/DejaVuSansMono-BoldOblique.ttf | Bin 0 -> 177780 bytes .../lib/fonts/DejaVuSansMono-Oblique.ttf | Bin 0 -> 184896 bytes client/shared/lib/fonts/DejaVuSansMono.ttf | Bin 0 -> 237788 bytes client/shared/lib/fonts/DejaVuSerif-Bold.ttf | Bin 0 -> 201516 bytes .../lib/fonts/DejaVuSerif-BoldOblique.ttf | Bin 0 -> 180948 bytes .../shared/lib/fonts/DejaVuSerif-Oblique.ttf | Bin 0 -> 179872 bytes client/shared/lib/fonts/DejaVuSerif.ttf | Bin 0 -> 210416 bytes client/shared/lib/fonts/README | 21 + client/shared/lib/fonts/UTBI____.pfa | 1172 + client/shared/lib/fonts/UTB_____.pfa | 1134 + client/shared/lib/fonts/UTI_____.pfa | 1165 + client/shared/lib/fonts/UTRG____.pfa | 1126 + client/shared/lib/fonts/Vera.ttf | Bin 0 -> 65932 bytes client/shared/lib/fonts/VeraBI.ttf | Bin 0 -> 63208 bytes client/shared/lib/fonts/VeraBd.ttf | Bin 0 -> 58716 bytes client/shared/lib/fonts/VeraIt.ttf | Bin 0 -> 63684 bytes client/shared/lib/fonts/VeraMoBI.ttf | Bin 0 -> 55032 bytes client/shared/lib/fonts/VeraMoBd.ttf | Bin 0 -> 49052 bytes client/shared/lib/fonts/VeraMoIt.ttf | Bin 0 -> 54508 bytes client/shared/lib/fonts/VeraMono.ttf | Bin 0 -> 49224 bytes client/shared/lib/fonts/VeraSe.ttf | Bin 0 -> 60280 bytes client/shared/lib/fonts/VeraSeBd.ttf | Bin 0 -> 58736 bytes client/shared/lib/fonts/c0419bt_.pfb | Bin 0 -> 40766 bytes client/shared/lib/fonts/c0582bt_.pfb | Bin 0 -> 39511 bytes client/shared/lib/fonts/c0583bt_.pfb | Bin 0 -> 40008 bytes client/shared/lib/fonts/c0611bt_.pfb | Bin 0 -> 39871 bytes client/shared/lib/fonts/c0632bt_.pfb | Bin 0 -> 33799 bytes client/shared/lib/fonts/c0633bt_.pfb | Bin 0 -> 35229 bytes client/shared/lib/fonts/c0648bt_.pfb | Bin 0 -> 34869 bytes client/shared/lib/fonts/c0649bt_.pfb | Bin 0 -> 35118 bytes client/shared/lib/fonts/cour.pfa | 1954 ++ client/shared/lib/fonts/courb.pfa | 1966 ++ client/shared/lib/fonts/courbi.pfa | 1940 ++ client/shared/lib/fonts/couri.pfa | 1893 ++ client/shared/lib/fonts/cursor.pfa | 954 + client/shared/lib/fonts/fixed_120_50.qpf | Bin 0 -> 3109 bytes client/shared/lib/fonts/fixed_70_50.qpf | Bin 0 -> 2567 bytes client/shared/lib/fonts/helvetica_100_50.qpf | Bin 0 -> 3046 bytes client/shared/lib/fonts/helvetica_100_50i.qpf | Bin 0 -> 3052 bytes client/shared/lib/fonts/helvetica_100_75.qpf | Bin 0 -> 3040 bytes client/shared/lib/fonts/helvetica_100_75i.qpf | Bin 0 -> 3081 bytes client/shared/lib/fonts/helvetica_120_50.qpf | Bin 0 -> 3301 bytes client/shared/lib/fonts/helvetica_120_50i.qpf | Bin 0 -> 3560 bytes client/shared/lib/fonts/helvetica_120_75.qpf | Bin 0 -> 3326 bytes client/shared/lib/fonts/helvetica_120_75i.qpf | Bin 0 -> 3759 bytes client/shared/lib/fonts/helvetica_140_50.qpf | Bin 0 -> 3860 bytes client/shared/lib/fonts/helvetica_140_50i.qpf | Bin 0 -> 4208 bytes client/shared/lib/fonts/helvetica_140_75.qpf | Bin 0 -> 4035 bytes client/shared/lib/fonts/helvetica_140_75i.qpf | Bin 0 -> 4498 bytes client/shared/lib/fonts/helvetica_180_50.qpf | Bin 0 -> 5179 bytes client/shared/lib/fonts/helvetica_180_50i.qpf | Bin 0 -> 5778 bytes client/shared/lib/fonts/helvetica_180_75.qpf | Bin 0 -> 5712 bytes client/shared/lib/fonts/helvetica_180_75i.qpf | Bin 0 -> 5977 bytes client/shared/lib/fonts/helvetica_240_50.qpf | Bin 0 -> 7691 bytes client/shared/lib/fonts/helvetica_240_50i.qpf | Bin 0 -> 8333 bytes client/shared/lib/fonts/helvetica_240_75.qpf | Bin 0 -> 7912 bytes client/shared/lib/fonts/helvetica_240_75i.qpf | Bin 0 -> 8588 bytes client/shared/lib/fonts/helvetica_80_50.qpf | Bin 0 -> 2735 bytes client/shared/lib/fonts/helvetica_80_50i.qpf | Bin 0 -> 2742 bytes client/shared/lib/fonts/helvetica_80_75.qpf | Bin 0 -> 2745 bytes client/shared/lib/fonts/helvetica_80_75i.qpf | Bin 0 -> 2750 bytes client/shared/lib/fonts/japanese_230_50.qpf | Bin 0 -> 263331 bytes client/shared/lib/fonts/l047013t.pfa | 1346 ++ client/shared/lib/fonts/l047016t.pfa | 1356 ++ client/shared/lib/fonts/l047033t.pfa | 1353 ++ client/shared/lib/fonts/l047036t.pfa | 1361 ++ client/shared/lib/fonts/l048013t.pfa | 1233 ++ client/shared/lib/fonts/l048016t.pfa | 1269 ++ client/shared/lib/fonts/l048033t.pfa | 1267 ++ client/shared/lib/fonts/l048036t.pfa | 1260 ++ client/shared/lib/fonts/l049013t.pfa | 1598 ++ client/shared/lib/fonts/l049016t.pfa | 1582 ++ client/shared/lib/fonts/l049033t.pfa | 1735 ++ client/shared/lib/fonts/l049036t.pfa | 1613 ++ client/shared/lib/fonts/micro_40_50.qpf | Bin 0 -> 1602 bytes client/shared/lib/fonts/unifont_160_50.qpf | Bin 0 -> 1215089 bytes client/shared/lib/grub4dos/COPYING | 340 + .../lib/grub4dos/ChangeLog_GRUB4DOS.txt | 585 + .../lib/grub4dos/Get_Source_of_This_Build.txt | 11 + .../shared/lib/grub4dos/README_GRUB4DOS.txt | 3866 ++++ client/shared/lib/grub4dos/badgrub.exe | Bin 0 -> 235441 bytes client/shared/lib/grub4dos/bootlace.com | Bin 0 -> 37092 bytes client/shared/lib/grub4dos/config.sys | 6 + client/shared/lib/grub4dos/default | 46 + client/shared/lib/grub4dos/example.menu.lst | 108 + client/shared/lib/grub4dos/grldr | Bin 0 -> 220049 bytes client/shared/lib/grub4dos/grldr.mbr | Bin 0 -> 9216 bytes client/shared/lib/grub4dos/grub.exe | Bin 0 -> 236977 bytes client/shared/lib/grub4dos/grub.pif | Bin 0 -> 967 bytes .../lib/grub4dos/grub4dos-0.4.5b/COPYING | 340 + .../grub4dos-0.4.5b/ChangeLog_GRUB4DOS.txt | 704 + .../grub4dos-0.4.5b/ChangeLog_chenall.txt | 645 + .../grub4dos-0.4.5b/README_GRUB4DOS.txt | 4138 ++++ .../grub4dos-0.4.5b/README_GRUB4DOS_CN.txt | 4314 ++++ .../lib/grub4dos/grub4dos-0.4.5b/badgrub.exe | Bin 0 -> 289455 bytes .../lib/grub4dos/grub4dos-0.4.5b/bootlace.com | Bin 0 -> 37092 bytes .../lib/grub4dos/grub4dos-0.4.5b/config.sys | 6 + .../lib/grub4dos/grub4dos-0.4.5b/default | 46 + .../shared/lib/grub4dos/grub4dos-0.4.5b/grldr | Bin 0 -> 273039 bytes .../lib/grub4dos/grub4dos-0.4.5b/grldr.mbr | Bin 0 -> 9216 bytes .../lib/grub4dos/grub4dos-0.4.5b/grub.exe | Bin 0 -> 292015 bytes .../lib/grub4dos/grub4dos-0.4.5b/grub.pif | Bin 0 -> 967 bytes .../lib/grub4dos/grub4dos-0.4.5b/hmload.com | Bin 0 -> 1856 bytes .../lib/grub4dos/grub4dos-0.4.5b/menu.lst | 132 + .../lib/grub4dos/grub4dos-0.4.6a/COPYING | 340 + .../Get_Source_of_This_Build.txt | 8 + .../lib/grub4dos/grub4dos-0.4.6a/badgrub.exe | Bin 0 -> 347277 bytes .../lib/grub4dos/grub4dos-0.4.6a/bootlace.com | Bin 0 -> 43520 bytes .../grub4dos/grub4dos-0.4.6a/bootlace64.com | Bin 0 -> 43520 bytes .../lib/grub4dos/grub4dos-0.4.6a/eltorito.sys | Bin 0 -> 1724 bytes .../shared/lib/grub4dos/grub4dos-0.4.6a/grldr | Bin 0 -> 330861 bytes .../lib/grub4dos/grub4dos-0.4.6a/grldr.mbr | Bin 0 -> 8192 bytes .../lib/grub4dos/grub4dos-0.4.6a/grldr.pbr | Bin 0 -> 5632 bytes .../lib/grub4dos/grub4dos-0.4.6a/grldr_cd.bin | Bin 0 -> 512 bytes .../lib/grub4dos/grub4dos-0.4.6a/grub.exe | Bin 0 -> 349837 bytes .../lib/grub4dos/grub4dos-0.4.6a/grub.pif | Bin 0 -> 967 bytes .../lib/grub4dos/grub4dos-0.4.6a/hmload.com | Bin 0 -> 1856 bytes .../lib/grub4dos/grub4dos-0.4.6a/ipxegrldr | Bin 0 -> 228045 bytes .../grub4dos-0.4.6a/sample/config.sys | 6 + .../grub4dos/grub4dos-0.4.6a/sample/default | 46 + .../grub4dos/grub4dos-0.4.6a/sample/menu.lst | 151 + client/shared/lib/grub4dos/hmload.com | Bin 0 -> 1856 bytes client/shared/lib/httpd/10-cgi.conf | 18 + client/shared/lib/httpd/LogCommand.sh | 41 + client/shared/lib/httpd/LogSession.sh | 30 + client/shared/lib/httpd/bandwidth.sh | 12 + client/shared/lib/httpd/cache.sh | 22 + client/shared/lib/httpd/httpd-log.sh | 17 + client/shared/lib/httpd/httpd-menu.sh | 14 + client/shared/lib/httpd/httpd-runengine.sh | 10 + client/shared/lib/httpd/lighttpd.conf | 167 + client/shared/lib/httpd/oglive.css | 11 + .../lib/locale/ca/LC_MESSAGES/browser.mo | Bin 0 -> 1021 bytes .../lib/locale/en/LC_MESSAGES/browser.mo | Bin 0 -> 2251 bytes client/shared/lib/modules/psmouse.ko | Bin 0 -> 78700 bytes client/shared/lib/os-probes/10zvol-test | 14 + client/shared/lib/os-probes/50mounted-tests | 99 + .../shared/lib/os-probes/init/10filesystems | 39 + client/shared/lib/os-probes/mounted/05efi | 71 + client/shared/lib/os-probes/mounted/10freedos | 23 + client/shared/lib/os-probes/mounted/10qnx | 21 + client/shared/lib/os-probes/mounted/20macosx | 30 + .../shared/lib/os-probes/mounted/20microsoft | 140 + client/shared/lib/os-probes/mounted/30utility | 33 + client/shared/lib/os-probes/mounted/40lsb | 48 + client/shared/lib/os-probes/mounted/70hurd | 16 + client/shared/lib/os-probes/mounted/80minix | 28 + client/shared/lib/os-probes/mounted/83haiku | 35 + .../lib/os-probes/mounted/90linux-distro | 138 + client/shared/lib/os-probes/mounted/90solaris | 19 + .../shared/lib/os-probes/mounted/efi/10elilo | 24 + .../lib/os-probes/mounted/efi/20microsoft | 28 + .../lib/os-probes/mounted/efi/31part-x-y | 28 + client/shared/lib/pci.ids | 17910 ++++++++++++++++ client/shared/lib/pictures/oglogo.png | Bin 0 -> 736 bytes client/shared/lib/qtlib/libQtCore.so.4 | Bin 0 -> 3222280 bytes client/shared/lib/qtlib/libQtGui.so.4 | Bin 0 -> 11154352 bytes client/shared/lib/qtlib/libQtNetwork.so.4 | Bin 0 -> 1116108 bytes client/shared/lib/qtlib/libQtWebKit.so.4 | Bin 0 -> 21958488 bytes client/shared/lib/qtlib/libcrypto.so.1.0.0 | Bin 0 -> 1742840 bytes client/shared/lib/qtlib/libssl.so.1.0.0 | Bin 0 -> 354520 bytes .../lib/qtplugins/imageformats/libqjpeg.so | Bin 0 -> 239404 bytes .../shared/scripts/ImagenesSincronizadas.lib | 368 + client/shared/scripts/README.es.txt | 53 + client/shared/scripts/bootLinux | 1 + client/shared/scripts/bootOs | 47 + client/shared/scripts/bootOsCustom.template | 92 + client/shared/scripts/bootWindows | 1 + client/shared/scripts/buildToOrder | 79 + client/shared/scripts/cloneRemoteFromMaster | 324 + client/shared/scripts/configureOs | 175 + .../shared/scripts/configureOsCustom.template | 81 + client/shared/scripts/createBaseImage | 166 + client/shared/scripts/createDiffImage | 201 + client/shared/scripts/createImage | 182 + .../shared/scripts/createImageCustom.template | 35 + client/shared/scripts/createLogicalPartitions | 3 + client/shared/scripts/createPrimaryPartitions | 3 + client/shared/scripts/deployImage | 246 + client/shared/scripts/formatFs | 19 + client/shared/scripts/generateMenuDefault | 81 + client/shared/scripts/getFsType | 3 + client/shared/scripts/getIpAddress | 3 + client/shared/scripts/getOsVersion | 3 + client/shared/scripts/grubSyntax | 483 + client/shared/scripts/initCache | 111 + client/shared/scripts/installOfflineMode | 60 + client/shared/scripts/launchOgagentInstaller | 145 + client/shared/scripts/listHardwareInfo | 24 + client/shared/scripts/listPartitions | 3 + client/shared/scripts/listPrimaryPartitions | 3 + client/shared/scripts/listSoftwareInfo | 35 + client/shared/scripts/menuBrowser | 11 + client/shared/scripts/ogCrearImagenBasica | 167 + client/shared/scripts/ogCrearSoftIncremental | 185 + client/shared/scripts/ogRestaurarImagenBasica | 206 + .../shared/scripts/ogRestaurarSoftIncremental | 209 + client/shared/scripts/poweroff | 39 + client/shared/scripts/reboot | 54 + client/shared/scripts/remoteConsole | 22 + client/shared/scripts/restoreBaseImage | 175 + client/shared/scripts/restoreDiffImage | 159 + client/shared/scripts/restoreImage | 106 + .../scripts/restoreImageCustom.template | 37 + client/shared/scripts/runAplicationX.sh | 8 + client/shared/scripts/runhttplog.sh | 15 + client/shared/scripts/samples/configureGroup | 65 + .../scripts/samples/firstRunOnceWindows | 68 + client/shared/scripts/samples/smartPartition | 62 + client/shared/scripts/sendFileMcast | 56 + client/shared/scripts/setBootMode | 47 + client/shared/scripts/updateBootCache | 65 + client/shared/scripts/updateCache | 315 + 364 files changed, 89013 insertions(+) create mode 100644 client/LICENSE.en.txt create mode 100644 client/README.es.txt create mode 100755 client/engine/Boot.lib create mode 100755 client/engine/Cache.lib create mode 100755 client/engine/Disk.lib create mode 100755 client/engine/File.lib create mode 100755 client/engine/FileSystem.lib create mode 100755 client/engine/Image.lib create mode 100755 client/engine/Inventory.lib create mode 100755 client/engine/Net.lib create mode 100755 client/engine/PostConf.lib create mode 100755 client/engine/PostConfEAC.lib create mode 100755 client/engine/Protocol.lib create mode 100644 client/engine/README.es.txt create mode 100755 client/engine/Registry.lib create mode 100755 client/engine/Rsync.lib create mode 100755 client/engine/String.lib create mode 100755 client/engine/System.lib create mode 100644 client/engine/ToolsGNU.c create mode 100755 client/engine/UEFI.lib create mode 100644 client/shared/README.es.txt create mode 100755 client/shared/bin/EACInterfaces create mode 100755 client/shared/bin/browser create mode 100755 client/shared/bin/grub-probe1.99_i686 create mode 100755 client/shared/bin/grub-probe1.99_x86_64 create mode 100755 client/shared/bin/ogAdmClient create mode 100755 client/shared/bin/poweroffconf create mode 100755 client/shared/bin/rsync-31 create mode 100755 client/shared/bin/runtest create mode 100644 client/shared/etc/engine.cfg create mode 100644 client/shared/etc/engine.json create mode 100644 client/shared/etc/es.qmap create mode 100755 client/shared/etc/init/default.sh create mode 120000 client/shared/etc/lang.ca_ES.UTF-8.conf create mode 100644 client/shared/etc/lang.ca_ES.conf create mode 120000 client/shared/etc/lang.en_GB.UTF-8.conf create mode 100644 client/shared/etc/lang.en_GB.conf create mode 120000 client/shared/etc/lang.es_ES.UTF-8.conf create mode 100644 client/shared/etc/lang.es_ES.conf create mode 100755 client/shared/etc/preinit/default.sh create mode 100755 client/shared/etc/preinit/fileslinks.sh create mode 100755 client/shared/etc/preinit/loadenviron.sh create mode 100755 client/shared/etc/preinit/loadmodules.sh create mode 100755 client/shared/etc/preinit/metadevs.sh create mode 100755 client/shared/etc/preinit/mountrepo.sh create mode 100755 client/shared/etc/preinit/otherservices.sh create mode 100755 client/shared/etc/preinit/poweroff.sh create mode 100644 client/shared/lib/burg/themes/OpenGnsys/background-original.png create mode 100755 client/shared/lib/burg/themes/OpenGnsys/background.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/extended create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_debian.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_elementary.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_freebsd.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_haiku.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_linux.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_opengnsys.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_opensuse.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_os.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_osx.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_recovery.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_restart.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_shutdown.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_ubuntu.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_windows.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_windows10.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_windows7.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/hover_windows_metro.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/icons create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_debian.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_elementary.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_freebsd.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_haiku.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_linux.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_opengnsys.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_opensuse.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_os.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_osx.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_recovery.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_restart.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_shutdown.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_ubuntu.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_windows.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_windows10.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_windows7.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/icons/normal_windows_metro.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/000-70opaque.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/button-bg.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/button-hover-bg.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/button-hover-l.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/button-hover-r.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/button-l.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/button-r.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/button-tools-hover.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/button-tools.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-b.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-bg.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-bl.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-br.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-l.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-r.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-t.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-title-bg.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-title-l.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-title-r.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-title-t.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-title-tl.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-title-tr.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-tl.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/container-tr.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/dialog-b.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/dialog-bg.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/dialog-bl.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/dialog-bl.xcf create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/dialog-br.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/dialog-lr.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/dialog-spacer.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/dialog-t.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/dialog-tl.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/dialog-tr.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/progressbar-bg-b.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/progressbar-bg-bl.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/progressbar-bg-br.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/progressbar-bg-l.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/progressbar-bg-r.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/progressbar-bg-t.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/progressbar-bg-tl.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/progressbar-bg-tr.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/progressbar-bg.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/text-line-l.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/text-line-r.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/tick.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/txt-about.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/txt-help.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/txt-select.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/txt-tools.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/images/ubuntu-glow-96.png create mode 100644 client/shared/lib/burg/themes/OpenGnsys/menus create mode 100644 client/shared/lib/burg/themes/OpenGnsys/style create mode 100644 client/shared/lib/burg/themes/OpenGnsys/theme create mode 100644 client/shared/lib/engine/tests/Modify/Cache.shtest create mode 100644 client/shared/lib/engine/tests/NoModify/File1.shtest create mode 100644 client/shared/lib/engine/tests/NoModify/Lock1.shtest create mode 100644 client/shared/lib/engine/tests/NoModify/Net1.shtest create mode 100644 client/shared/lib/engine/tests/README create mode 100755 client/shared/lib/engine/tests/crearTestDisk1 create mode 100755 client/shared/lib/engine/tests/crearTestLock2 create mode 100644 client/shared/lib/fonts/DejaVuSans-Bold.ttf create mode 100644 client/shared/lib/fonts/DejaVuSans-BoldOblique.ttf create mode 100644 client/shared/lib/fonts/DejaVuSans-Oblique.ttf create mode 100644 client/shared/lib/fonts/DejaVuSans.ttf create mode 100644 client/shared/lib/fonts/DejaVuSansMono-Bold.ttf create mode 100644 client/shared/lib/fonts/DejaVuSansMono-BoldOblique.ttf create mode 100644 client/shared/lib/fonts/DejaVuSansMono-Oblique.ttf create mode 100644 client/shared/lib/fonts/DejaVuSansMono.ttf create mode 100644 client/shared/lib/fonts/DejaVuSerif-Bold.ttf create mode 100644 client/shared/lib/fonts/DejaVuSerif-BoldOblique.ttf create mode 100644 client/shared/lib/fonts/DejaVuSerif-Oblique.ttf create mode 100644 client/shared/lib/fonts/DejaVuSerif.ttf create mode 100644 client/shared/lib/fonts/README create mode 100644 client/shared/lib/fonts/UTBI____.pfa create mode 100644 client/shared/lib/fonts/UTB_____.pfa create mode 100644 client/shared/lib/fonts/UTI_____.pfa create mode 100644 client/shared/lib/fonts/UTRG____.pfa create mode 100644 client/shared/lib/fonts/Vera.ttf create mode 100644 client/shared/lib/fonts/VeraBI.ttf create mode 100644 client/shared/lib/fonts/VeraBd.ttf create mode 100644 client/shared/lib/fonts/VeraIt.ttf create mode 100644 client/shared/lib/fonts/VeraMoBI.ttf create mode 100644 client/shared/lib/fonts/VeraMoBd.ttf create mode 100644 client/shared/lib/fonts/VeraMoIt.ttf create mode 100644 client/shared/lib/fonts/VeraMono.ttf create mode 100644 client/shared/lib/fonts/VeraSe.ttf create mode 100644 client/shared/lib/fonts/VeraSeBd.ttf create mode 100644 client/shared/lib/fonts/c0419bt_.pfb create mode 100644 client/shared/lib/fonts/c0582bt_.pfb create mode 100644 client/shared/lib/fonts/c0583bt_.pfb create mode 100644 client/shared/lib/fonts/c0611bt_.pfb create mode 100644 client/shared/lib/fonts/c0632bt_.pfb create mode 100644 client/shared/lib/fonts/c0633bt_.pfb create mode 100644 client/shared/lib/fonts/c0648bt_.pfb create mode 100644 client/shared/lib/fonts/c0649bt_.pfb create mode 100644 client/shared/lib/fonts/cour.pfa create mode 100644 client/shared/lib/fonts/courb.pfa create mode 100644 client/shared/lib/fonts/courbi.pfa create mode 100644 client/shared/lib/fonts/couri.pfa create mode 100644 client/shared/lib/fonts/cursor.pfa create mode 100644 client/shared/lib/fonts/fixed_120_50.qpf create mode 100644 client/shared/lib/fonts/fixed_70_50.qpf create mode 100644 client/shared/lib/fonts/helvetica_100_50.qpf create mode 100644 client/shared/lib/fonts/helvetica_100_50i.qpf create mode 100644 client/shared/lib/fonts/helvetica_100_75.qpf create mode 100644 client/shared/lib/fonts/helvetica_100_75i.qpf create mode 100644 client/shared/lib/fonts/helvetica_120_50.qpf create mode 100644 client/shared/lib/fonts/helvetica_120_50i.qpf create mode 100644 client/shared/lib/fonts/helvetica_120_75.qpf create mode 100644 client/shared/lib/fonts/helvetica_120_75i.qpf create mode 100644 client/shared/lib/fonts/helvetica_140_50.qpf create mode 100644 client/shared/lib/fonts/helvetica_140_50i.qpf create mode 100644 client/shared/lib/fonts/helvetica_140_75.qpf create mode 100644 client/shared/lib/fonts/helvetica_140_75i.qpf create mode 100644 client/shared/lib/fonts/helvetica_180_50.qpf create mode 100644 client/shared/lib/fonts/helvetica_180_50i.qpf create mode 100644 client/shared/lib/fonts/helvetica_180_75.qpf create mode 100644 client/shared/lib/fonts/helvetica_180_75i.qpf create mode 100644 client/shared/lib/fonts/helvetica_240_50.qpf create mode 100644 client/shared/lib/fonts/helvetica_240_50i.qpf create mode 100644 client/shared/lib/fonts/helvetica_240_75.qpf create mode 100644 client/shared/lib/fonts/helvetica_240_75i.qpf create mode 100644 client/shared/lib/fonts/helvetica_80_50.qpf create mode 100644 client/shared/lib/fonts/helvetica_80_50i.qpf create mode 100644 client/shared/lib/fonts/helvetica_80_75.qpf create mode 100644 client/shared/lib/fonts/helvetica_80_75i.qpf create mode 100644 client/shared/lib/fonts/japanese_230_50.qpf create mode 100644 client/shared/lib/fonts/l047013t.pfa create mode 100644 client/shared/lib/fonts/l047016t.pfa create mode 100644 client/shared/lib/fonts/l047033t.pfa create mode 100644 client/shared/lib/fonts/l047036t.pfa create mode 100644 client/shared/lib/fonts/l048013t.pfa create mode 100644 client/shared/lib/fonts/l048016t.pfa create mode 100644 client/shared/lib/fonts/l048033t.pfa create mode 100644 client/shared/lib/fonts/l048036t.pfa create mode 100644 client/shared/lib/fonts/l049013t.pfa create mode 100644 client/shared/lib/fonts/l049016t.pfa create mode 100644 client/shared/lib/fonts/l049033t.pfa create mode 100644 client/shared/lib/fonts/l049036t.pfa create mode 100644 client/shared/lib/fonts/micro_40_50.qpf create mode 100644 client/shared/lib/fonts/unifont_160_50.qpf create mode 100644 client/shared/lib/grub4dos/COPYING create mode 100644 client/shared/lib/grub4dos/ChangeLog_GRUB4DOS.txt create mode 100644 client/shared/lib/grub4dos/Get_Source_of_This_Build.txt create mode 100644 client/shared/lib/grub4dos/README_GRUB4DOS.txt create mode 100644 client/shared/lib/grub4dos/badgrub.exe create mode 100644 client/shared/lib/grub4dos/bootlace.com create mode 100644 client/shared/lib/grub4dos/config.sys create mode 100644 client/shared/lib/grub4dos/default create mode 100644 client/shared/lib/grub4dos/example.menu.lst create mode 100644 client/shared/lib/grub4dos/grldr create mode 100644 client/shared/lib/grub4dos/grldr.mbr create mode 100644 client/shared/lib/grub4dos/grub.exe create mode 100644 client/shared/lib/grub4dos/grub.pif create mode 100644 client/shared/lib/grub4dos/grub4dos-0.4.5b/COPYING create mode 100644 client/shared/lib/grub4dos/grub4dos-0.4.5b/ChangeLog_GRUB4DOS.txt create mode 100644 client/shared/lib/grub4dos/grub4dos-0.4.5b/ChangeLog_chenall.txt create mode 100644 client/shared/lib/grub4dos/grub4dos-0.4.5b/README_GRUB4DOS.txt create mode 100644 client/shared/lib/grub4dos/grub4dos-0.4.5b/README_GRUB4DOS_CN.txt create mode 100644 client/shared/lib/grub4dos/grub4dos-0.4.5b/badgrub.exe create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.5b/bootlace.com create mode 100644 client/shared/lib/grub4dos/grub4dos-0.4.5b/config.sys create mode 100644 client/shared/lib/grub4dos/grub4dos-0.4.5b/default create mode 100644 client/shared/lib/grub4dos/grub4dos-0.4.5b/grldr create mode 100644 client/shared/lib/grub4dos/grub4dos-0.4.5b/grldr.mbr create mode 100644 client/shared/lib/grub4dos/grub4dos-0.4.5b/grub.exe create mode 100644 client/shared/lib/grub4dos/grub4dos-0.4.5b/grub.pif create mode 100644 client/shared/lib/grub4dos/grub4dos-0.4.5b/hmload.com create mode 100644 client/shared/lib/grub4dos/grub4dos-0.4.5b/menu.lst create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/COPYING create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/Get_Source_of_This_Build.txt create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/badgrub.exe create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/bootlace.com create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/bootlace64.com create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/eltorito.sys create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/grldr create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/grldr.mbr create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/grldr.pbr create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/grldr_cd.bin create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/grub.exe create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/grub.pif create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/hmload.com create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/ipxegrldr create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/sample/config.sys create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/sample/default create mode 100755 client/shared/lib/grub4dos/grub4dos-0.4.6a/sample/menu.lst create mode 100644 client/shared/lib/grub4dos/hmload.com create mode 100644 client/shared/lib/httpd/10-cgi.conf create mode 100755 client/shared/lib/httpd/LogCommand.sh create mode 100755 client/shared/lib/httpd/LogSession.sh create mode 100755 client/shared/lib/httpd/bandwidth.sh create mode 100755 client/shared/lib/httpd/cache.sh create mode 100755 client/shared/lib/httpd/httpd-log.sh create mode 100755 client/shared/lib/httpd/httpd-menu.sh create mode 100755 client/shared/lib/httpd/httpd-runengine.sh create mode 100644 client/shared/lib/httpd/lighttpd.conf create mode 100644 client/shared/lib/httpd/oglive.css create mode 100644 client/shared/lib/locale/ca/LC_MESSAGES/browser.mo create mode 100644 client/shared/lib/locale/en/LC_MESSAGES/browser.mo create mode 100644 client/shared/lib/modules/psmouse.ko create mode 100755 client/shared/lib/os-probes/10zvol-test create mode 100755 client/shared/lib/os-probes/50mounted-tests create mode 100755 client/shared/lib/os-probes/init/10filesystems create mode 100755 client/shared/lib/os-probes/mounted/05efi create mode 100755 client/shared/lib/os-probes/mounted/10freedos create mode 100755 client/shared/lib/os-probes/mounted/10qnx create mode 100755 client/shared/lib/os-probes/mounted/20macosx create mode 100755 client/shared/lib/os-probes/mounted/20microsoft create mode 100755 client/shared/lib/os-probes/mounted/30utility create mode 100755 client/shared/lib/os-probes/mounted/40lsb create mode 100755 client/shared/lib/os-probes/mounted/70hurd create mode 100755 client/shared/lib/os-probes/mounted/80minix create mode 100755 client/shared/lib/os-probes/mounted/83haiku create mode 100755 client/shared/lib/os-probes/mounted/90linux-distro create mode 100755 client/shared/lib/os-probes/mounted/90solaris create mode 100755 client/shared/lib/os-probes/mounted/efi/10elilo create mode 100755 client/shared/lib/os-probes/mounted/efi/20microsoft create mode 100755 client/shared/lib/os-probes/mounted/efi/31part-x-y create mode 100644 client/shared/lib/pci.ids create mode 100644 client/shared/lib/pictures/oglogo.png create mode 100755 client/shared/lib/qtlib/libQtCore.so.4 create mode 100755 client/shared/lib/qtlib/libQtGui.so.4 create mode 100755 client/shared/lib/qtlib/libQtNetwork.so.4 create mode 100755 client/shared/lib/qtlib/libQtWebKit.so.4 create mode 100755 client/shared/lib/qtlib/libcrypto.so.1.0.0 create mode 100755 client/shared/lib/qtlib/libssl.so.1.0.0 create mode 100755 client/shared/lib/qtplugins/imageformats/libqjpeg.so create mode 100755 client/shared/scripts/ImagenesSincronizadas.lib create mode 100644 client/shared/scripts/README.es.txt create mode 120000 client/shared/scripts/bootLinux create mode 100755 client/shared/scripts/bootOs create mode 100755 client/shared/scripts/bootOsCustom.template create mode 120000 client/shared/scripts/bootWindows create mode 100755 client/shared/scripts/buildToOrder create mode 100755 client/shared/scripts/cloneRemoteFromMaster create mode 100755 client/shared/scripts/configureOs create mode 100644 client/shared/scripts/configureOsCustom.template create mode 100755 client/shared/scripts/createBaseImage create mode 100755 client/shared/scripts/createDiffImage create mode 100755 client/shared/scripts/createImage create mode 100644 client/shared/scripts/createImageCustom.template create mode 100755 client/shared/scripts/createLogicalPartitions create mode 100755 client/shared/scripts/createPrimaryPartitions create mode 100755 client/shared/scripts/deployImage create mode 100755 client/shared/scripts/formatFs create mode 100755 client/shared/scripts/generateMenuDefault create mode 100755 client/shared/scripts/getFsType create mode 100755 client/shared/scripts/getIpAddress create mode 100755 client/shared/scripts/getOsVersion create mode 100755 client/shared/scripts/grubSyntax create mode 100755 client/shared/scripts/initCache create mode 100755 client/shared/scripts/installOfflineMode create mode 100755 client/shared/scripts/launchOgagentInstaller create mode 100755 client/shared/scripts/listHardwareInfo create mode 100755 client/shared/scripts/listPartitions create mode 100755 client/shared/scripts/listPrimaryPartitions create mode 100755 client/shared/scripts/listSoftwareInfo create mode 100755 client/shared/scripts/menuBrowser create mode 100755 client/shared/scripts/ogCrearImagenBasica create mode 100755 client/shared/scripts/ogCrearSoftIncremental create mode 100755 client/shared/scripts/ogRestaurarImagenBasica create mode 100755 client/shared/scripts/ogRestaurarSoftIncremental create mode 100755 client/shared/scripts/poweroff create mode 100755 client/shared/scripts/reboot create mode 100755 client/shared/scripts/remoteConsole create mode 100755 client/shared/scripts/restoreBaseImage create mode 100755 client/shared/scripts/restoreDiffImage create mode 100755 client/shared/scripts/restoreImage create mode 100644 client/shared/scripts/restoreImageCustom.template create mode 100755 client/shared/scripts/runAplicationX.sh create mode 100755 client/shared/scripts/runhttplog.sh create mode 100755 client/shared/scripts/samples/configureGroup create mode 100644 client/shared/scripts/samples/firstRunOnceWindows create mode 100644 client/shared/scripts/samples/smartPartition create mode 100755 client/shared/scripts/sendFileMcast create mode 100755 client/shared/scripts/setBootMode create mode 100755 client/shared/scripts/updateBootCache create mode 100755 client/shared/scripts/updateCache diff --git a/client/LICENSE.en.txt b/client/LICENSE.en.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/client/LICENSE.en.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/client/README.es.txt b/client/README.es.txt new file mode 100644 index 0000000..7d2c03b --- /dev/null +++ b/client/README.es.txt @@ -0,0 +1,12 @@ +OpenGnsys Client README +========================= + + +Este directorio contiene la estructura de datos del cliente OpenGnsys. + +- boot-tools herramientas para generar la distribución de OpenGnsys Client. +- browser código fuente del cliente gráfico OpenGnsys Browser. +- engine funciones del motor (se instalará en el servidor en + /opt/opengneys/client/lib/engine). +- shared estructura principal del cliente exportado por NFS o Samba + (se instalará en el servidor en /opt/opengneys/client). diff --git a/client/engine/Boot.lib b/client/engine/Boot.lib new file mode 100755 index 0000000..04eaded --- /dev/null +++ b/client/engine/Boot.lib @@ -0,0 +1,2973 @@ +#!/bin/bash +#/** +#@file Boot.lib +#@brief Librería o clase Boot +#@class Boot +#@brief Funciones para arranque y post-configuración de sistemas de archivos. +#@version 1.1.0 +#@warning License: GNU GPLv3+ +#*/ + + +#/** +# ogBoot int_ndisk int_nfilesys [str_kernel str_initrd str_krnlparams] +#@brief Inicia el proceso de arranque de un sistema de archivos. +#@param int_ndisk nº de orden del disco +#@param int_nfilesys nº de orden del sistema de archivos +#@param str_krnlparams parámetros de arranque del kernel (opcional) +#@return (activar el sistema de archivos). +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar. +#@exception OG_ERR_NOTOS La partición no tiene instalado un sistema operativo. +#@note En Linux, si no se indican los parámetros de arranque se detectan de la opción por defecto del cargador GRUB. +#@note En Linux, debe arrancarse la partición del directorio \c /boot +#@version 0.1 - Integración para OpenGnSys. - EAC: HDboot; BootLinuxEX en Boot.lib +#@author Antonio J. Doblas Viso, Universidad de Malaga +#@date 2008-10-27 +#@version 0.9 - Adaptación para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-11 +#@version 1.0.4 - Soporta modo de arranque Windows (parámetro de inicio "winboot"). +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2012-04-12 +#@version 1.0.6 - Selección a partir de tipo de sistema operativo (en vez de S.F.) y arrancar Linux con /boot separado. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2015-06-05 +#@version 1.1.0 - Nuevo parámetro opcional con opciones de arranque del Kernel. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2015-07-15 +#@version 1.1.1 - UEFI: Permite iniciar linux recien instalados (ticket #802 #890) +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2019-03-13 +#*/ ## +function ogBoot () +{ +# Variables locales. +local PART TYPE MNTDIR PARAMS KERNEL INITRD APPEND FILE LOADER f +local EFIDISK EFIPART EFIDIR BOOTLABEL BOOTLOADER BOOTNO DIRGRUB b + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys [str_kernel str_initrd str_kernelparams]" \ + "$FUNCNAME 1 1" "$FUNCNAME 1 2 \"/boot/vmlinuz /boot/initrd.img root=/dev/sda2 ro\"" + return +fi +# Error si no se reciben 2 o 3 parámetros. +[ $# == 2 ] || [ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Detectar tipo de sistema de archivos y montarlo. +PART=$(ogDiskToDev $1 $2) || return $? +TYPE=$(ogGetOsType $1 $2) || return $? +# Error si no puede montar sistema de archivos. +MNTDIR=$(ogMount $1 $2) || return $? + +case "$TYPE" in + Linux|Android) + # Si no se indican, obtiene los parámetros de arranque para Linux. + PARAMS="${3:-$(ogLinuxBootParameters $1 $2 2>/dev/null)}" + # Si no existe y el UEFI buscar en particion ESP + [ -z "$PARAMS" ] && ogIsEfiActive && PARAMS="$(ogLinuxBootParameters $(ogGetEsp))" + # Si no existe, buscar sistema de archivo /boot en /etc/fstab. + if [ -z "$PARAMS" -a -e $MNTDIR/etc/fstab ]; then + # Localizar S.F. /boot en /etc/fstab del S.F. actual. + PART=$(ogDevToDisk $(awk '$1!="#" && $2=="/boot" {print $1}' $MNTDIR/etc/fstab)) + # Montar S.F. de /boot. + MNTDIR=$(ogMount $PART) || return $? + # Buscar los datos de arranque. + PARAMS=$(ogLinuxBootParameters $PART) || exit $? + fi + read -e KERNEL INITRD APPEND <<<"$PARAMS" + # Si no hay kernel, no hay sistema operativo. + [ -n "$KERNEL" -a -e "$MNTDIR/$KERNEL" ] || ogRaiseError $OG_ERR_NOTOS "$1 $2 ($TYPE)" || return $? + # Arrancar de partición distinta a la original. + [ -e "$MNTDIR/etc" ] && APPEND=$(echo $APPEND | awk -v P="$PART " '{sub (/root=[-+=_/a-zA-Z0-9]* /,"root="P);print}') + # Comprobar tipo de sistema. + if ogIsEfiActive; then + # Comprobar si el Kernel está firmado. + if ! file -k "$MNTDIR/$KERNEL" | grep -q "EFI app"; then + ogRaiseError $OG_ERR_NOTOS "$1 $2 ($TYPE, EFI)" + return $? + fi + + BOOTLABEL=$(printf "Part-%02d-%02d" $1 $2) + BOOTLOADER="shimx64.efi" + # Obtener parcición EFI. + read -e EFIDISK EFIPART <<<"$(ogGetEsp)" + # TODO: Comprobamos que existe la BOOTLABEL, si no buscamos por sistema operativo + if [ "$(ogGetPath $EFIDISK $EFIPART EFI/$BOOTLABEL)" == "" ]; then + OSVERSION="$(ogGetOsVersion $1 $2)" + case $OSVERSION in + *SUSE*) + BOOTLABEL="opensuse" + ;; + *Fedora*) + BOOTLABEL="fedora" + ;; + *Ubuntu*) + BOOTLABEL="ubuntu" + ;; + *) + ogRaiseError $OG_ERR_NOTFOUND "$EFIDISK $EFIPART Boot loader"; return $? + ;; + esac + fi + + # Crear orden de arranque (con unos valores por defecto). + ogNvramAddEntry $BOOTLABEL "/EFI/$BOOTLABEL/Boot/$BOOTLOADER" + # Marcar próximo arranque y reiniciar. + ogNvramSetNext "$BOOTLABEL" + reboot + else + # Arranque BIOS: configurar kernel Linux con los parámetros leídos de su GRUB. + kexec -l "${MNTDIR}${KERNEL}" --append="$APPEND" --initrd="${MNTDIR}${INITRD}" + kexec -e & + fi + ;; + Windows) + # Comprobar tipo de sistema. + if ogIsEfiActive; then + BOOTLABEL=$(printf "Part-%02d-%02d" $1 $2) + # Obtener parcición EFI. + read -e EFIDISK EFIPART <<<"$(ogGetEsp)" + [ -n "$EFIPART" ] || ogRaiseError $OG_ERR_PARTITION "ESP" || return $? + EFIDIR=$(ogMount $EFIDISK $EFIPART) || exit $? + # Comprobar cargador (si no existe buscar por defecto en ESP). + LOADER=$(ogGetPath $EFIDIR/EFI/$BOOTLABEL/Boot/bootmgfw.efi) + [ -z "$LOADER" ] && BOOTLABEL=Microsoft && LOADER=$(ogGetPath $EFIDIR/EFI/Microsoft/Boot/bootmgfw.efi) + [ -n "$LOADER" ] || ogRaiseError $OG_ERR_NOTOS "$1 $2 ($TYPE, EFI)" || return $? + + # Crear orden de arranque (con unos valores por defecto). + ogNvramAddEntry $BOOTLABEL "/EFI${LOADER#*EFI}" + # Marcar próximo arranque y reiniciar. + ogNvramSetNext "$BOOTLABEL" + reboot + else + # Arranque BIOS: comprueba si hay un cargador de Windows. + for f in io.sys ntldr bootmgr; do + FILE="$(ogGetPath $1 $2 $f 2>/dev/null)" + [ -n "$FILE" ] && LOADER="$f" + done + [ -n "$LOADER" ] || ogRaiseError $OG_ERR_NOTOS "$1 $2 ($TYPE)" || return $? + if [ "$winboot" == "kexec" ]; then + # Modo de arranque en caliente (con kexec). + cp $OGLIB/grub4dos/* $MNTDIR # */ (Comentario Doxygen) + kexec -l $MNTDIR/grub.exe --append=--config-file="root (hd$[$1-1],$[$2-1]); chainloader (hd$[$1-1],$[$2-1])/$LOADER; tpm --init" + kexec -e & + else + # Modo de arranque por reinicio (con reboot). + dd if=/dev/zero of=${MNTDIR}/ogboot.me bs=1024 count=3 + dd if=/dev/zero of=${MNTDIR}/ogboot.firstboot bs=1024 count=3 + dd if=/dev/zero of=${MNTDIR}/ogboot.secondboot bs=1024 count=3 + if [ -z "$(ogGetRegistryValue $MNTDIR SOFTWARE '\Microsoft\Windows\CurrentVersion\Run\ogcleannboot')" ]; then + ogAddRegistryValue $MNTDIR SOFTWARE '\Microsoft\Windows\CurrentVersion\Run\ogcleanboot' + ogSetRegistryValue $MNTDIR SOFTWARE '\Microsoft\Windows\CurrentVersion\Run\ogcleanboot' "cmd /c del c:\ogboot.*" + fi + # Activar la partición. + ogSetPartitionActive $1 $2 + reboot + fi + fi + ;; + MacOS) + # Modo de arranque por reinicio. + # Nota: el cliente tiene que tener configurado correctamente Grub. + touch ${MNTDIR}/boot.mac &>/dev/null + reboot + ;; + GrubLoader) + # Reiniciar. + #reboot + ;; + *) ogRaiseError $OG_ERR_NOTOS "$1 $2 ${TYPE:+($TYPE)}" + return $? + ;; +esac +} + + +#/** +# ogGetWindowsName int_ndisk int_nfilesys +#@brief Muestra el nombre del equipo en el registro de Windows. +#@param int_ndisk nº de orden del disco +#@param int_nfilesys nº de orden del sistema de archivos +#@return str_name - nombre del equipo +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar. +#@version 0.9 - Adaptación para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-23 +#*/ ## +function ogGetWindowsName () +{ +# Variables locales. +local MNTDIR + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition" \ + "$FUNCNAME 1 1 ==> PRACTICA-PC" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Montar el sistema de archivos. +MNTDIR=$(ogMount $1 $2) || return $? + +# Obtener dato del valor de registro. +ogGetRegistryValue $MNTDIR system '\ControlSet001\Control\ComputerName\ComputerName\ComputerName' +} + + +#/** +# ogLinuxBootParameters int_ndisk int_nfilesys +#@brief Muestra los parámetros de arranque de un sistema de archivos Linux. +#@param int_ndisk nº de orden del disco +#@param int_nfilesys nº de orden del sistema de archivos +#@return str_kernel str_initrd str_parameters ... +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar. +#@warning Función básica usada por \c ogBoot +#@version 0.9 - Primera adaptación para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-11 +#@version 0.9.2 - Soporta partición /boot independiente. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010-07-20 +#@version 1.0.5 - Mejoras en tratamiento de GRUB2. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2013-05-14 +#@version 1.0.6 - Detectar instalaciones sobre EFI. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2014-09-15 +#*/ ## +function ogLinuxBootParameters () +{ +# Variables locales. +local MNTDIR CONFDIR CONFFILE f + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys" \ + "$FUNCNAME 1 2 ==> /vmlinuz-3.5.0-21-generic /initrd.img-3.5.0-21-generic root=/dev/sda2 ro splash" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Detectar id. de tipo de partición y codificar al mnemonico. +MNTDIR=$(ogMount $1 $2) || return $? + +# Fichero de configuración de GRUB. +CONFDIR=$MNTDIR # Sistema de archivos de arranque (/boot). +[ -d $MNTDIR/boot ] && CONFDIR=$MNTDIR/boot # Sist. archivos raíz con directorio boot. +for f in $MNTDIR/{,boot/}{{grubMBR,grubPARTITION}/boot/,}{grub{2,},{,efi/}EFI/*}/{menu.lst,grub.cfg}; do + [ -r $f ] && CONFFILE=$f +done +[ -n "$CONFFILE" ] || ogRaiseError $OG_ERR_NOTFOUND "grub.cfg" || return $? + +# Toma del fichero de configuracion los valores del kernel, initrd +# y parámetros de arranque usando las cláusulas por defecto +# ("default" en GRUB1, "set default" en GRUB2) +# y los formatea para que sean compatibles con \c kexec . */ +# /* (comentario Doxygen) +awk 'BEGIN {cont=-1;} + $1~/^default$/ {sub(/=/," "); def=$2;} + $1~/^set$/ && $2~/^default/ { gsub(/[="]/," "); def=$3; + if (def ~ /saved_entry/) def=0; + } + $1~/^(title|menuentry)$/ {cont++} + $1~/^set$/ && $2~/^root=.\(hd'$[1-1]',(msdos|gpt)'$2'\).$/ { if (def==0) def=cont; } + $1~/^(kernel|linux(16|efi)?)$/ { if (def==cont) { + kern=$2; + sub($1,""); sub($1,""); sub(/^[ \t]*/,""); app=$0 + } # /* (comentario Doxygen) + } + $1~/^initrd(16|efi)?$/ {if (def==cont) init=$2} + END {if (kern!="") printf("%s %s %s", kern,init,app)} + ' $CONFFILE +# */ (comentario Doxygen) +} + + +#/** +# ogSetWindowsName int_ndisk int_nfilesys str_name +#@brief Establece el nombre del equipo en el registro de Windows. +#@param int_ndisk nº de orden del disco +#@param int_nfilesys nº de orden del sistema de archivos +#@param str_name nombre asignado +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar. +#@exception OG_ERR_OUTOFLIMIT Nombre Netbios con más de 15 caracteres. +#@version 0.9 - Adaptación a OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-24 +#@version 1.0.5 - Establecer restricción de tamaño de nombre Netbios. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2013-03-20 +#*/ ## +function ogSetWindowsName () +{ +# Variables locales. +local PART MNTDIR NAME + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_filesys str_name" \ + "$FUNCNAME 1 1 PRACTICA-PC" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Error si el nombre supera los 15 caracteres. +[ ${#3} -le 15 ] || ogRaiseError $OG_ERR_OUTOFLIMIT "\"${3:0:15}...\"" || return $? + +# Montar el sistema de archivos. +MNTDIR=$(ogMount $1 $2) || return $? + +# Asignar nombre. +NAME="$3" + +# Modificar datos de los valores de registro. +ogSetRegistryValue $MNTDIR system '\ControlSet001\Control\ComputerName\ComputerName\ComputerName' "$NAME" 2>/dev/null +ogSetRegistryValue $MNTDIR system '\ControlSet001\Services\Tcpip\Parameters\Hostname' "$NAME" 2>/dev/null +ogSetRegistryValue $MNTDIR system '\ControlSet001\Services\Tcpip\Parameters\HostName' "$NAME" 2>/dev/null +ogSetRegistryValue $MNTDIR system '\ControlSet001\services\Tcpip\Parameters\Hostname' "$NAME" 2>/dev/null +ogSetRegistryValue $MNTDIR system '\ControlSet001\Services\Tcpip\Parameters\NV Hostname' "$NAME" 2>/dev/null +ogSetRegistryValue $MNTDIR system '\ControlSet001\Services\Tcpip\Parameters\NV HostName' "$NAME" 2>/dev/null +ogSetRegistryValue $MNTDIR system '\ControlSet001\services\Tcpip\Parameters\NV Hostname' "$NAME" 2>/dev/null +} + + +#/** +# ogSetWinlogonUser int_ndisk int_npartition str_username +#@brief Establece el nombre de usuario por defecto en la entrada de Windows. +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@param str_username nombre de usuario por defecto +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar. +#@version 0.9.2 - Adaptación a OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010-07-20 +#*/ ## +function ogSetWinlogonUser () +{ +# Variables locales. +local PART MNTDIR NAME + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition str_username" \ + "$FUNCNAME 1 1 USUARIO" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Montar el sistema de archivos. +MNTDIR=$(ogMount $1 $2) || return $? + +# Asignar nombre. +NAME="$3" + +# Modificar datos en el registro. +ogSetRegistryValue $MNTDIR SOFTWARE '\Microsoft\Windows NT\CurrentVersion\Winlogon\DefaultUserName' "$3" +} + + +#/** +# ogBootMbrXP int_ndisk +#@brief Genera un nuevo Master Boot Record en el disco duro indicado, compatible con los SO tipo Windows +#@param int_ndisk nº de orden del disco +#@return salida del programa my-sys +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar. +#@version 0.9 - Adaptación a OpenGnSys. +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date 2009-09-24 +#*/ ## + +function ogBootMbrXP () +{ +# Variables locales. +local DISK + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk " \ + "$FUNCNAME 1" + return +fi +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +DISK="$(ogDiskToDev $1)" || return $? +ms-sys -z -f $DISK +ms-sys -m -f $DISK +} + + +#/** +# ogBootMbrGeneric int_ndisk +#@brief Genera un nuevo Codigo de arranque en el MBR del disco indicado, compatible con los SO tipo Windows, Linux. +#@param int_ndisk nº de orden del disco +#@return salida del programa my-sys +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Tipo de partición desconocido o no se puede montar. +#@version 0.9 - Adaptación a OpenGnSys. +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date 2009-09-24 +#*/ ## + +function ogBootMbrGeneric () +{ +# Variables locales. +local DISK + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk " \ + "$FUNCNAME 1 " + return +fi +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || return $(ogRaiseError $OG_ERR_FORMAT; echo $?) + +DISK="$(ogDiskToDev $1)" || return $? +ms-sys -z -f $DISK +ms-sys -s -f $DISK + +# Firma necesaria para Windows equipos UEFI +SIGNATURE=0x$(cat /proc/sys/kernel/random/uuid | cut -d '-' -f1) +ms-sys -S $SIGNATURE $DISK +} + + + + +#/** +# ogFixBootSector int_ndisk int_parition +#@brief Corrige el boot sector de una particion activa para MS windows/dos -fat-ntfs +#@param int_ndisk nº de orden del disco +#@param int_partition nº de particion +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar. +#@version 0.9 - Adaptación a OpenGnSys. +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date 2009-09-24 +#*/ ## + +function ogFixBootSector () +{ +# Variables locales. +local PARTYPE DISK PART FILE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_partition " \ + "$FUNCNAME 1 1 " + return +fi + +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || return $(ogRaiseError $OG_ERR_FORMAT; echo $?) + +#TODO, solo si la particion existe +#TODO, solo si es ntfs o fat +PARTYPE=$(ogGetPartitionId $1 $2) +case "$PARTYPE" in + 1|4|6|7|b|c|e|f|17|700|EF00) + ;; + *) + return $(ogRaiseError $OG_ERR_PARTITION; echo $?) + ;; +esac + +ogUnmount $1 $2 || return $(ogRaiseError $OG_ERR_PARTITION; echo $?) + +#Preparando instruccion +let DISK=$1-1 +PART=$2 +FILE=/tmp/temp$$ +cat > $FILE < ${MOUNT}/tmp.boot.ini; mv ${MOUNT}/tmp.boot.ini ${MOUNT}/boot.ini + return 0 +fi + +ogUnmount $1 $2 || return $(ogRaiseError $OG_ERR_PARTITION; echo $?) + + +#Preparando instruccion Windows Resume Application +cat > $FILE < $FILE < $FILE < $FILE < $FILE < $FILE < $FILE < $FILE <stico de memoria de Windows +EOF +timeout --foreground --signal=SIGKILL 5s spartlnx.run -cui -nm -w -f $FILE + +rm -f $FILE +} + + + +#/** +# ogWindowsRegisterPartition int_ndisk int_partiton str_volume int_disk int_partition +#@brief Registra una partición en windows con un determinado volumen. +#@param int_ndisk nº de orden del disco a registrar +#@param int_partition nº de particion a registrar +#@param str_volumen volumen a resgistar +#@param int_ndisk_windows nº de orden del disco donde esta windows +#@param int_partition_windows nº de particion donde esta windows +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar. +#@version 0.9 - Adaptación a OpenGnSys. +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date 2009-09-24 +#*/ ## +function ogWindowsRegisterPartition () +{ +# Variables locales. +local PART DISK FILE REGISTREDDISK REGISTREDPART REGISTREDVOL VERSION SYSTEMROOT + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk_TO_registre int_partition_TO_registre str_NewVolume int_disk int_parition " \ + "$FUNCNAME 1 1 c: 1 1" + return +fi + +# Error si no se reciben 5 parámetros. +[ $# == 5 ] || return $(ogRaiseError $OG_ERR_FORMAT; echo $?) + +REGISTREDDISK=$1 +REGISTREDPART=$2 +REGISTREDVOL=$(echo $3 | cut -c1 | tr '[:lower:]' '[:upper:]') +DISK=$4 +PART=$5 +FILE=/tmp/temp$$ + +ogDiskToDev $REGISTREDDISK $REGISTREDPART || return $(ogRaiseError $OG_ERR_PARTITION "particion a registrar "; echo $?) +ogDiskToDev $DISK $PART || return $(ogRaiseError $OG_ERR_PARTITION "particion de windows"; echo $?) + +ogGetOsType $DISK $PART | grep "Windows" || return $(ogRaiseError $OG_ERR_NOTOS "no es windows"; echo $?) + +VERSION=$(ogGetOsVersion $DISK $PART) + +#Systemroot + +if ogGetPath $DISK $PART WINDOWS +then + SYSTEMROOT="Windows" +elif ogGetPath $DISK $PART WINNT +then + SYSTEMROOT="winnt" +else + return $(ogRaiseError $OG_ERR_NOTOS; echo $?) +fi + +ogUnmount $DISK $PART +let DISK=$DISK-1 +let REGISTREDDISK=$REGISTREDDISK-1 +#Preparando instruccion Windows Boot Manager +cat > $FILE <> /etc/default/grub + echo "GRUB_DISABLE_LINUX_UUID=\"true\"" >> /etc/default/grub + + + #Preparar configuración segunda etapa: crear ubicacion + mkdir -p ${SECONDSTAGE}${PREFIXSECONDSTAGE}/boot/grub/ + #Preparar configuración segunda etapa: crear cabecera del fichero (ignorar errores) + sed -i 's/^set -e/#set -e/' /etc/grub.d/00_header + # (ogLive 5.0) Si 'pkgdatadir' está vacía ponemos valor de otros ogLive + sed -i '/grub-mkconfig_lib/i\pkgdatadir=${pkgdatadir:-"${datarootdir}/grub"}' /etc/grub.d/00_header + /etc/grub.d/00_header > ${SECONDSTAGE}${PREFIXSECONDSTAGE}/boot/grub/grub.cfg 2>/dev/null + + #Preparar configuración segunda etapa: crear entrada del sistema operativo + grubSyntax "$KERNELPARAM" >> ${SECONDSTAGE}${PREFIXSECONDSTAGE}/boot/grub/grub.cfg + + # Renombramos la configuración de grub antigua + [ -f ${SECONDSTAGE}/boot/grub/grub.cfg ] && mv ${SECONDSTAGE}/boot/grub/grub.cfg ${SECONDSTAGE}/boot/grub/grub.cfg$BACKUPNAME + +fi + +#Instalar el grub +grub-install --force ${EFIOPTGRUB} --root-directory=${SECONDSTAGE}${PREFIXSECONDSTAGE} $FIRSTSTAGE +EVAL=$? + +# Movemos el grubx64.efi +if ogIsEfiActive; then + mv ${EFISECONDSTAGE}/EFI/$EFISUBDIR/EFI/BOOT/* ${EFISECONDSTAGE}/EFI/$EFISUBDIR/Boot + rm -rf ${EFISECONDSTAGE}/EFI/$EFISUBDIR/EFI + cp /usr/lib/shim/shimx64.efi.signed ${EFISECONDSTAGE}/EFI/$EFISUBDIR/Boot/shimx64.efi + # Nombre OpenGnsys para cargador + cp ${EFISECONDSTAGE}/EFI/$EFISUBDIR/Boot/{grubx64.efi,ogloader.efi} + + # Creamos entrada NVRAM y la ponemos en segundo lugar + ogNvramAddEntry grub /EFI/grub/Boot/shimx64.efi + GRUBENTRY=$(ogNvramList| awk '{if ($2=="grub") print $1}') + NEWORDER="$(ogNvramGetOrder|awk -v ENTRY=$GRUBENTRY '{gsub(",", " "); printf "%x %x %s\n", $1 , ENTRY , substr($0, index($0,$2))}')" + ogNvramSetOrder $NEWORDER +fi +return $EVAL + +} + + +#/** +# ogGrubInstallPartition int_disk_SECONDSTAGE int_partition_SECONDSTAGE bolean_Check_Os_installed_and_Configure_2ndStage +#@brief Instala y actualiza el gestor grub en el bootsector de la particion indicada +#@param int_disk_SecondStage +#@param int_part_SecondStage +#@param bolean_Check_Os_installed_and_Configure_2ndStage true | false[default] +#@param str "kernel param " +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@version 1.0.2 - Primeras pruebas. +#@author Antonio J. Doblas Viso. Universidad de Malaga. +#@date 2011-10-29 +#@version 1.0.3 - Soporte para linux de 32 y 64 bits +#@author Antonio J. Doblas Viso. Universidad de Malaga. +#@date 2012-03-13 +#@version 1.0.3 - Ficheros de configuracion independientes segun ubicación de la priemra etapa +#@author Antonio J. Doblas Viso. Universidad de Malaga. +#@date 2012-03-13 +#@version 1.1.1 - #802 Equipos EFI: Se crea el grub.cfg de la partición EFI +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2019-01-08 +#@version 1.1.1 - #890 UEFI: el grub.cfg original es necesario para obtener los datos del kernel efi: se mueve al final. +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2019-03-05 +#*/ ## + +function ogGrubInstallPartition () +{ + +# Variables locales. +local PART DISK VERSION FIRSTAGE SECONSTAGE CHECKOS KERNELPARAM BACKUPNAME +local EFIDISK EFIPART EFISECONDSTAGE EFISUBDIR EFIOPTGRUB EFIBOOTDIR + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage bolean_Configure_2ndStage \"param param \" " \ + "$FUNCNAME 1 1 FALSE " \ + "$FUNCNAME 1 1 TRUE \"nomodeset irqpoll pci=noacpi quiet splash \" " + return +fi + +# Error si no se reciben 2 parámetros. +[ $# -ge 2 ] || return $(ogRaiseError $OG_ERR_FORMAT; echo $?) + +DISK=$1; PART=$2; +CHECKOS=${3:-"FALSE"} +KERNELPARAM=$4 +BACKUPNAME=".backup.og" + +#error si no es linux. +VERSION=$(ogGetOsVersion $DISK $PART) +echo $VERSION | grep "Linux" || return $(ogRaiseError $OG_ERR_NOTOS "no es linux"; echo $?) + +#Localizar primera etapa del grub +FIRSTSTAGE=$(ogDiskToDev $DISK $PART) + +#localizar disco segunda etapa del grub +SECONDSTAGE=$(ogMount $DISK $PART) + +#Localizar directorio segunda etapa del grub +PREFIXSECONDSTAGE="/boot/grubPARTITION" + +# Si es EFI instalamos el grub en la ESP +EFIOPTGRUB="" +# Desde el bootdir uefi y bios buscan el grub.cfg en subdirectorios distintos. +EFIBOOTDIR="" +if ogIsEfiActive; then + read EFIDISK EFIPART <<< $(ogGetEsp) + # Comprobamos que exista ESP y el directorio para ubuntu + EFISECONDSTAGE=$(ogMount $EFIDISK $EFIPART) + if [ $? -ne 0 ]; then + ogFormat $EFIDISK $EFIPART FAT32 + EFISECONDSTAGE=$(ogMount $EFIDISK $EFIPART) || ogRaiseError $OG_ERR_PARTITION "ESP" || return $? + fi + EFISUBDIR=$(printf "Part-%02d-%02d" $DISK $PART) + # Borramos la configuración anterior + [ -d ${EFISECONDSTAGE}/EFI/$EFISUBDIR ] && rm -rf ${EFISECONDSTAGE}/EFI/$EFISUBDIR + mkdir -p ${EFISECONDSTAGE}/EFI/$EFISUBDIR/Boot + EFIOPTGRUB=" --removable --no-nvram --uefi-secure-boot --target $(ogGetArch)-efi --efi-directory=${EFISECONDSTAGE}/EFI/$EFISUBDIR " + EFIBOOTDIR="/boot" +fi + +# Si Reconfigurar segunda etapa (grub.cfg) == FALSE +if [ "${CHECKOS^^}" == "FALSE" ] && [ -f ${SECONDSTAGE}/boot/grub/grub.cfg -o -f ${SECONDSTAGE}/boot/grub/grub.cfg$BACKUPNAME ] +then + # Si no se reconfigura se utiliza el grub.cfg orginal + [ -f ${SECONDSTAGE}/boot/grub/grub.cfg$BACKUPNAME ] && mv ${SECONDSTAGE}/boot/grub/grub.cfg$BACKUPNAME ${SECONDSTAGE}/boot/grub/grub.cfg + # Si no se reconfigure se borra los ficheros previos de configuración específicos de opengnsys. + [ -d ${SECONDSTAGE}${PREFIXSECONDSTAGE} ] && rm -fr ${SECONDSTAGE}${PREFIXSECONDSTAGE} + # Reactivamos el grub con el grub.cfg original. + PREFIXSECONDSTAGE="" +else + # SI Reconfigurar segunda etapa (grub.cfg) == TRUE + + if ogIsEfiActive; then + # UEFI: grubSintax necesita grub.cfg para detectar los kernels: si no existe recupero backup. + if ! [ -f ${SECONDSTAGE}/boot/grub/grub.cfg ]; then + [ -f ${SECONDSTAGE}/boot/grub/grub.cfg$BACKUPNAME ] && mv ${SECONDSTAGE}/boot/grub/grub.cfg$BACKUPNAME ${SECONDSTAGE}/boot/grub/grub.cfg + fi + else + #Evitar detectar modo recovery - mover grub.cfg original a grub.cfg.backup + mv ${SECONDSTAGE}/boot/grub/grub.cfg ${SECONDSTAGE}/boot/grub/grub.cfg$BACKUPNAME + fi + + #Configur la sintaxis grub para evitar menus de "recovery" en el OGLive + echo "GRUB_DISABLE_RECOVERY=\"true\"" >> /etc/default/grub + echo "GRUB_DISABLE_LINUX_UUID=\"true\"" >> /etc/default/grub + + #Preparar configuración segunda etapa: crear ubicacion + mkdir -p ${SECONDSTAGE}${PREFIXSECONDSTAGE}/boot/grub/ + #Preparar configuración segunda etapa: crear cabecera del fichero (ingnorar errores) + sed -i 's/^set -e/#set -e/' /etc/grub.d/00_header + # (ogLive 5.0) Si 'pkgdatadir' está vacía ponemos valor de otros ogLive + sed -i '/grub-mkconfig_lib/i\pkgdatadir=${pkgdatadir:-"${datarootdir}/grub"}' /etc/grub.d/00_header + /etc/grub.d/00_header > ${SECONDSTAGE}${PREFIXSECONDSTAGE}/boot/grub/grub.cfg 2>/dev/null + #Preparar configuración segunda etapa: crear entrada del sistema operativo + grubSyntax $DISK $PART "$KERNELPARAM" >> ${SECONDSTAGE}${PREFIXSECONDSTAGE}/boot/grub/grub.cfg + +fi +#Instalar el grub +grub-install --force ${EFIOPTGRUB} --root-directory=${SECONDSTAGE}${PREFIXSECONDSTAGE} $FIRSTSTAGE +EVAL=$? + +# Movemos el grubx64.efi +if ogIsEfiActive; then + mv ${EFISECONDSTAGE}/EFI/$EFISUBDIR/EFI/BOOT/* ${EFISECONDSTAGE}/EFI/$EFISUBDIR/Boot + rm -rf ${EFISECONDSTAGE}/EFI/$EFISUBDIR/EFI + cp /usr/lib/shim/shimx64.efi.signed ${EFISECONDSTAGE}/EFI/$EFISUBDIR/Boot/shimx64.efi + # Nombre OpenGnsys para cargador + cp ${EFISECONDSTAGE}/EFI/$EFISUBDIR/Boot/{grubx64.efi,ogloader.efi} +fi + +return $EVAL +} + + +#/** +# ogConfigureFstab int_ndisk int_nfilesys +#@brief Configura el fstab según particiones existentes +#@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 No se encuentra el fichero fstab a procesar. +#@warning Puede haber un error si hay más de 1 partición swap. +#@version 1.0.5 - Primera versión para OpenGnSys. Solo configura la SWAP +#@author Antonio J. Doblas Viso. Universidad de Malaga. +#@date 2013-03-21 +#@version 1.0.6b - correccion. Si no hay partición fisica para la SWAP, eliminar entrada del fstab. +#@author Antonio J. Doblas Viso. Universidad de Malaga. +#@date 2016-11-03 +#@version 1.1.1 - Se configura la partición ESP (para sistemas EFI) (ticket #802) +#@author Irina Gómez, ETSII Universidad de Sevilla +#@date 2018-12-13 +#*/ ## +function ogConfigureFstab () +{ +# Variables locales. +local FSTAB DEFROOT PARTROOT DEFSWAP PARTSWAP +local EFIDISK EFIPART EFIDEV EFIOPT + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys" \ + "$FUNCNAME 1 1" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Error si no se encuentra un fichero etc/fstab en el sistema de archivos. +FSTAB=$(ogGetPath $1 $2 /etc/fstab) 2>/dev/null +[ -n "$FSTAB" ] || ogRaiseError $OG_ERR_NOTFOUND "$1,$2,/etc/fstab" || return $? + +# Hacer copia de seguridad del fichero fstab original. +cp -a ${FSTAB} ${FSTAB}.backup +# Dispositivo del raíz en fichero fstab: 1er campo (si no tiene "#") con 2º campo = "/". +DEFROOT=$(awk '$1!~/#/ && $2=="/" {print $1}' ${FSTAB}) +PARTROOT=$(ogDiskToDev $1 $2) +# Configuración de swap (solo 1ª partición detectada). +PARTSWAP=$(blkid -t TYPE=swap | awk -F: 'NR==1 {print $1}') +if [ -n "$PARTSWAP" ] +then + # Dispositivo de swap en fichero fstab: 1er campo (si no tiene "#") con 3er campo = "swap". + DEFSWAP=$(awk '$1!~/#/ && $3=="swap" {print $1}' ${FSTAB}) + if [ -n "$DEFSWAP" ] + then + echo "Hay definicion de SWAP en el FSTAB $DEFSWAP -> modificamos fichero con nuevo valor $DEFSWAP->$PARTSWAP" # Mensaje temporal. + sed "s|$DEFSWAP|$PARTSWAP|g ; s|$DEFROOT|$PARTROOT|g" ${FSTAB}.backup > ${FSTAB} + else + echo "No hay definicion de SWAP y si hay partición SWAP -> moficamos fichero" # Mensaje temporal. + sed "s|$DEFROOT|$PARTROOT|g" ${FSTAB}.backup > ${FSTAB} + echo "$PARTSWAP none swap sw 0 0" >> ${FSTAB} + fi +else + echo "No hay partición SWAP -> configuramos FSTAB" # Mensaje temporal. + sed "/swap/d" ${FSTAB}.backup > ${FSTAB} +fi +# Si es un sistema EFI incluimos partición ESP (Si existe la modificamos) +if ogIsEfiActive; then + read EFIDISK EFIPART <<< $(ogGetEsp) + EFIDEV=$(ogDiskToDev $EFIDISK $EFIPART) + + # Opciones de la partición ESP: si no existe ponemos un valor por defecto + EFIOPT=$(awk '$1!~/#/ && $2=="/boot/efi" {print $3"\t"$4"\t"$5"\t"$6 }' ${FSTAB}) + [ "$EFIOPT" == "" ] && EFIOPT='vfat\tumask=0077\t0\t1' + + sed -i /"boot\/efi"/d ${FSTAB} + echo -e "$EFIDEV\t/boot/efi\t$EFIOPT" >> ${FSTAB} +fi +} + +#/** +# ogSetLinuxName int_ndisk int_nfilesys [str_name] +#@brief Establece el nombre del equipo en los ficheros hostname y hosts. +#@param int_ndisk nº de orden del disco +#@param int_nfilesys nº de orden del sistema de archivos +#@param str_name nombre asignado (opcional) +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar. +#@note Si no se indica nombre, se asigna un valor por defecto. +#@version 1.0.5 - Primera versión para OpenGnSys. +#@author Antonio J. Doblas Viso. Universidad de Malaga. +#@date 2013-03-21 +#*/ ## +function ogSetLinuxName () +{ +# Variables locales. +local MNTDIR ETC NAME + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys [str_name]" \ + "$FUNCNAME 1 1" "$FUNCNAME 1 1 practica-pc" + return +fi +# Error si no se reciben 2 o 3 parámetros. +case $# in + 2) # Asignar nombre automático (por defecto, "pc"). + NAME="$(ogGetHostname)" + NAME=${NAME:-"pc"} ;; + 3) # Asignar nombre del 3er parámetro. + NAME="$3" ;; + *) # Formato de ejecución incorrecto. + ogRaiseError $OG_ERR_FORMAT + return $? +esac + +# Montar el sistema de archivos. +MNTDIR=$(ogMount $1 $2) || return $? + +ETC=$(ogGetPath $1 $2 /etc) + +if [ -d "$ETC" ]; then + #cambio de nombre en hostname + echo "$NAME" > $ETC/hostname + #Opcion A para cambio de nombre en hosts + #sed "/127.0.1.1/ c\127.0.1.1 \t $HOSTNAME" $ETC/hosts > /tmp/hosts && cp /tmp/hosts $ETC/ && rm /tmp/hosts + #Opcion B componer fichero de hosts + cat > $ETC/hosts <&2; echo $?) + + # Archivo de configuracion del grub + DIRMOUNT=$(ogMount $1 $2) + GRUBGFC="$DIRMOUNT/boot/grubMBR/boot/grub/grub.cfg" + + # Error si no existe archivo del grub + [ -r $GRUBGFC ] || return $(ogRaiseError log session $OG_ERR_NOTFOUND "$GRUBGFC" 1>&2; echo $?) + + # Si existe la entrada de opengnsys, se borra + grep -q "menuentry Opengnsys" $GRUBGFC && sed -ie "/menuentry Opengnsys/,+6d" $GRUBGFC + + # Tipo de tabla de particiones + PARTTABLETYPE=$(ogGetPartitionTableType $1 | tr [:upper:] [:lower:]) + + # Localizacion de la cache + read NUMDISK NUMPART <<< $(ogFindCache) + let NUMDISK=$NUMDISK-1 + # kernel y sus opciones. Pasamos a modo usuario + KERNEL="/boot/${oglivedir}/ogvmlinuz $(sed -e s/^.*linuz//g -e s/ogactiveadmin=[a-z]*//g /proc/cmdline)" + + # Configuracion offline si existe parametro + echo "$@" |grep offline &>/dev/null && STATUS=offline + echo "$@" |grep online &>/dev/null && STATUS=online + [ -z "$STATUS" ] || KERNEL="$(echo $KERNEL | sed s/"ogprotocol=[a-z]* "/"ogprotocol=local "/g ) ogstatus=$STATUS" + + # Numero de línea de la primera entrada del grub. + NUMLINE=$(grep -n -m 1 "^menuentry" $GRUBGFC|cut -d: -f1) + # Texto de la entrada de opengnsys +MENUENTRY="menuentry "OpenGnsys" --class opengnsys --class gnu --class os { \n \ +\tinsmod part_$PARTTABLETYPE \n \ +\tinsmod ext2 \n \ +\tset root='(hd${NUMDISK},$PARTTABLETYPE${NUMPART})' \n \ +\tlinux $KERNEL \n \ +\tinitrd /boot/${oglivedir}/oginitrd.img \n \ +}" + + + # Insertamos la entrada de opengnsys antes de la primera entrada existente. + sed -i "${NUMLINE}i\ $MENUENTRY" $GRUBGFC + + # Ponemos que la entrada por defecto sea la primera. + sed -i s/"set.*default.*$"/"set default=\"0\""/g $GRUBGFC + + # Si me dan valor para timeout lo cambio en el grub. + [ $TIMEOUT ] && sed -i s/timeout=.*$/timeout=$TIMEOUT/g $GRUBGFC +} + +#/** +# ogGrubHidePartitions num_disk num_part +#@brief ver ogBootLoaderHidePartitions +#@see ogBootLoaderHidePartitions +#*/ ## +function ogGrubHidePartitions () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition [ num_disk_partdata num_partdata ]" \ + "$FUNCNAME 1 2" \ + "$FUNCNAME 1 2 1 3" + return + fi + ogBootLoaderHidePartitions $@ + return $? +} + +#/** +# ogBurgHidePartitions num_disk num_part +#@brief ver ogBootLoaderHidePartitions +#@see ogBootLoaderHidePartitions +#*/ ## +function ogBurgHidePartitions () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition [ num_disk_partdata num_partdata ]" \ + "$FUNCNAME 1 2" \ + "$FUNCNAME 1 2 1 3" + return + fi + ogBootLoaderHidePartitions $@ + return $? +} + +#/** +# ogBootLoaderHidePartitions num_disk num_part +#@brief Configura el grub/burg para que oculte las particiones de windows que no se esten iniciando. +#@param 1 Numero de disco +#@param 2 Numero de particion +#@param 3 Numero de disco de la partición de datos (no ocultar) +#@param 4 Numero de particion de datos (no ocultar) +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception No existe archivo de configuracion del grub/burg. +#@version 1.1 Se comprueban las particiones de Windows con blkid (y no con grub.cfg) +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2015-11-17 +#@version 1.1 Se generaliza la función para grub y burg +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2017-10-20 +#@version 1.1.1 Se incluye comentarios en codigo para autodocuemtnacion con Doxygen +#@author Antonio J. Doblas Viso, EVLT Univesidad de Malaga. +#@date 2018-07-05 +#@version Se permite una partición de datos que no se ocultará. Soporta más de un disco. Compatible con grub.cfg creado por ogLive 5.0 +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2019-08-26 +#*/ + +function ogBootLoaderHidePartitions () +{ + local FUNC DIRMOUNT GFCFILE PARTTABLETYPE WINENTRY WINPART ENTRY LINE PART PARTDATA TEXT PARTHIDDEN HIDDEN + + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$MSG_SEE ogGrubHidePartitions ogBurgHidePartitions" + return + fi + + # Nombre de la función que llama a esta. + FUNC="${FUNCNAME[@]:1}" + FUNC="${FUNC%%\ *}" + + # Error si no se reciben 2 parámetros. + [ $# -lt 2 ] && return $(ogRaiseError session $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME num_disk num_part [ num_disk_partdata num_partdata ]"; echo $?) + # Si no existe $4 pongo un valor imposible para la partición de datos + [ $# -eq 4 ] && PARTDATA=$(ogDiskToDev $3 $4) || PARTDATA=0 + + # Archivo de configuracion del grub + DIRMOUNT=$(ogMount $1 $2) + # La función debe ser llamanda desde ogGrubHidePartitions or ogBurgHidePartitions. + case "$FUNC" in + ogGrubHidePartitions) + CFGFILE="$DIRMOUNT/boot/grubMBR/boot/grub/grub.cfg" + ;; + ogBurgHidePartitions) + CFGFILE="$DIRMOUNT/boot/burg/burg.cfg" + ;; + *) + ogRaiseError $OG_ERR_FORMAT "Use ogGrubHidePartitions or ogBurgHidePartitions." + return $? + ;; + esac + + # Error si no existe archivo del grub + [ -r $CFGFILE ] || return $(ogRaiseError log session $OG_ERR_NOTFOUND "$CFGFILE" 1>&2; echo $?) + + # Si solo hay una particion de Windows me salgo + [ $(fdisk -l $(ogDiskToDev) | grep 'NTFS' |wc -l) -eq 1 ] && return 0 + + # Elimino llamadas a parttool, se han incluido en otras ejecuciones de esta funcion. + sed -i '/parttool/d' $CFGFILE + + PARTTABLETYPE=$(ogGetPartitionTableType $1 | tr [:upper:] [:lower:]) + +# /* (comentario de bloque para Doxygen) + # Entradas de Windows: numero de linea y particion. De mayor a menor. + WINENTRY=$(awk '/menuentry.*Windows/ {gsub(/\)\"/, ""); gsub(/^.*dev/,""); print NR":/dev"$1} ' $CFGFILE | sed -e '1!G;h;$!d') + #*/ (comentario para bloque Doxygen) + # Particiones de Windows, pueden no estar en el grub. + WINPART=$(fdisk -l $(ogDiskToDev)|awk '/NTFS/ {print $1}'|sed '1!G;h;$!d') + + + # Modifico todas las entradas de Windows. + for ENTRY in $WINENTRY; do + LINE=${ENTRY%:*} + PART=${ENTRY#*:} + + # En cada entrada, oculto o muestro cada particion. + TEXT="" + for PARTHIDDEN in $WINPART; do + # Muestro la particion de la entrada actual y la de datos. + [ "$PARTHIDDEN" == "$PART" -o "$PARTHIDDEN" == "$PARTDATA" ] && HIDDEN="-" || HIDDEN="+" + read NUMDISK NUMPART <<< $(ogDevToDisk $PARTHIDDEN) + + TEXT="\tparttool (hd$((NUMDISK-1)),$PARTTABLETYPE$NUMPART) hidden$HIDDEN \n$TEXT" + done + + sed -i "${LINE}a\ $TEXT" $CFGFILE + done + + # Activamos la particion que se inicia en todas las entradas de windows. + sed -i "/chainloader/i\\\tparttool \$\{root\} boot+" $CFGFILE +} + +#/** +# ogGrubDeleteEntry num_disk num_part num_disk_delete num_part_delete +#@brief ver ogBootLoaderDeleteEntry +#@see ogBootLoaderDeleteEntry +#*/ +function ogGrubDeleteEntry () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition int_disk_delete int_npartition_delete" \ + "$FUNCNAME 1 6 2 1" + return + fi + ogBootLoaderDeleteEntry $@ + return $? +} + +#/** +# ogBurgDeleteEntry num_disk num_part num_disk_delete num_part_delete +#@brief ver ogBootLoaderDeleteEntry +#@see ogBootLoaderDeleteEntry +#*/ +function ogBurgDeleteEntry () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition int_disk_delete int_npartition_delete" \ + "$FUNCNAME 1 6 2 1" + return + fi + ogBootLoaderDeleteEntry $@ + return $? +} + +#/** +# ogRefindDeleteEntry num_disk_delete num_part_delete +#@brief ver ogBootLoaderDeleteEntry +#@see ogBootLoaderDeleteEntry +#*/ +function ogRefindDeleteEntry () +{ + local EFIDISK EFIPART + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_disk_delete int_npartition_delete" \ + "$FUNCNAME 2 1" + return + fi + read EFIDISK EFIPART <<< $(ogGetEsp) + ogBootLoaderDeleteEntry $EFIDISK $EFIPART $@ + return $? +} + +#/** +# ogBootLoaderDeleteEntry num_disk num_part num_part_delete +#@brief Borra en el grub las entradas para el inicio en una particion. +#@param 1 Numero de disco donde esta el grub +#@param 2 Numero de particion donde esta el grub +#@param 3 Numero del disco del que borramos las entradas +#@param 4 Numero de la particion de la que borramos las entradas +#@note Tiene que ser llamada desde ogGrubDeleteEntry, ogBurgDeleteEntry o ogRefindDeleteEntry +#@return (nada) +#@exception OG_ERR_FORMAT Use ogGrubDeleteEntry or ogBurgDeleteEntry. +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND No existe archivo de configuracion del grub. +#@version 1.1 Se generaliza la función para grub y burg +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2017-10-20 +#*/ ## + +function ogBootLoaderDeleteEntry () +{ + local FUNC DIRMOUNT CFGFILE LABEL MENUENTRY DELETEENTRY ENDENTRY ENTRY + + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$MSG_SEE ogBurgDeleteEntry, ogGrubDeleteEntry or ogRefindDeleteEntry" + return + fi + + # Si el número de parámetros menos que 4 nos salimos + [ $# -lt 4 ] && return $(ogRaiseError session $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME num_disk num_part num_disk_delete num_part_delete"; echo $?) + + + # Nombre de la función que llama a esta. + FUNC="${FUNCNAME[@]:1}" + FUNC="${FUNC%%\ *}" + + # Archivo de configuracion del grub + DIRMOUNT=$(ogMount $1 $2) + # La función debe ser llamanda desde ogGrubDeleteEntry, ogBurgDeleteEntry or ogRefindDeleteEntry. + case "$FUNC" in + ogGrubDeleteEntry) + CFGFILE="$DIRMOUNT/boot/grubMBR/boot/grub/grub.cfg" + ;; + ogBurgDeleteEntry) + CFGFILE="$DIRMOUNT/boot/burg/burg.cfg" + ;; + ogRefindDeleteEntry) + CFGFILE="$DIRMOUNT/EFI/refind/refind.conf" + ;; + *) + ogRaiseError $OG_ERR_FORMAT "Use ogGrubDeleteEntry, ogBurgDeleteEntry or ogRefindDeleteEntry." + return $? + ;; + esac + + # Dispositivo + if [ "$(basename $CFGFILE)" == "refind.conf" ]; then + LABEL=$(printf "Part-%02d-%02d" $3 $4) + else + LABEL=$(ogDiskToDev $3 $4) + fi + + # Error si no existe archivo de configuración + [ -r $CFGFILE ] || ogRaiseError log session $OG_ERR_NOTFOUND "$CFGFILE" || return $? + + # Numero de linea de cada entrada. + MENUENTRY="$(grep -n -e menuentry $CFGFILE| cut -d: -f1 | sed '1!G;h;$!d' )" + + # Entradas que hay que borrar. + DELETEENTRY=$(grep -n menuentry.*$LABEL $CFGFILE| cut -d: -f1) + + # Si no hay entradas para borrar me salgo con aviso + [ "$DELETEENTRY" != "" ] || ogRaiseError log session $OG_ERR_NOTFOUND "Menuentry $LABEL" || return $? + + # Recorremos el fichero del final hacia el principio. + ENDENTRY="$(wc -l $CFGFILE|cut -d" " -f1)" + for ENTRY in $MENUENTRY; do + # Comprobamos si hay que borrar la entrada. + if ogCheckStringInGroup $ENTRY "$DELETEENTRY" ; then + let ENDENTRY=$ENDENTRY-1 + sed -i -e $ENTRY,${ENDENTRY}d $CFGFILE + fi + + # Guardamos el número de línea de la entrada, que sera el final de la siguiente. + ENDENTRY=$ENTRY + done +} + +#/** +# ogBurgInstallMbr int_disk_GRUBCFG int_partition_GRUBCFG +#@param bolean_Check_Os_installed_and_Configure_2ndStage true | false[default] +#@brief Instala y actualiza el gestor grub en el MBR del disco duro donde se encuentra el fichero grub.cfg. Admite sistemas Windows. +#@param int_disk_SecondStage +#@param int_part_SecondStage +#@param bolean_Check_Os_installed_and_Configure_2ndStage true | false[default] +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Partición no soportada +#@version 1.1.0 - Primeras pruebas instalando BURG. Codigo basado en el ogGrubInstallMBR. +#@author Antonio J. Doblas Viso. Universidad de Malaga. +#@date 2017-06-23 +#@version 1.1.0 - Redirección del proceso de copiado de archivos y de la instalacion del binario +#@author Antonio J. Doblas Viso. Universidad de Malaga. +#@date 2018-01-21 +#@version 1.1.0 - Refactorizar fichero de configuacion +#@author Antonio J. Doblas Viso. Universidad de Malaga. +#@date 2018-01-24 +#@version 1.1.1 - Se incluye comentarios en codigo para autodocuemtnacion con Doxygen +#@author Antonio J. Doblas Viso. Universidad de Malaga. +#@date 2018-07-05 +#*/ ## + +function ogBurgInstallMbr () +{ + +# Variables locales. +local BINARYAVAILABLE PART DISK DEVICE MOUNTDISK FIRSTAGE SECONSTAGE PREFIXSECONDSTAGE CHECKOS KERNELPARAM BACKUPNAME FILECFG + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage bolean_Configure_2ndStage \"param param \" " \ + "$FUNCNAME 1 1 FALSE " \ + "$FUNCNAME 1 1 TRUE \"nomodeset irqpoll pci=noacpi quiet splash \" " + return +fi + +# Error si no se reciben 2 parametros. +[ $# -ge 2 ] || return $(ogRaiseError $OG_ERR_FORMAT; echo $?) + +#Error si no tenemos el binario burg +BINARYAVAILABLE=$(burg-install -v &>/dev/null && echo "YES" ||echo "NO") +if [ "$BINARYAVAILABLE" == NO ]; then + if [ -e $OGLIB/burg/burg.tgz ]; then + cd / ; tar xzvf $OGLIB/burg/burg.tgz --strip 1 &>/dev/null + else + return $(ogRaiseError $OG_ERR_NOTEXEC "Binary burg not found"; echo $?) + fi +fi + +DISK=$1; PART=$2; +CHECKOS=${3:-"FALSE"} +KERNELPARAM=$4 +BACKUPNAME=".backup.og" + +#Controlar disco no uefi +ogIsEfiActive && return $(ogRaiseError $OG_ERR_NOTBIOS " : grub4dos solo soporta PC con bios legacy"; echo $?) +#Controlar particionado tipo msdos +ogCheckStringInGroup $(ogGetPartitionTableType $DISK) "MSDOS" || return $(ogRaiseError $OG_ERR_NOMSDOS ": grub2dos requiere particionado tipo MSDOS"; echo $?) +#Controlar existencia de disco y particion +DEVICE=$(ogDiskToDev $DISK) || ogRaiseError $OG_ERR_NOTFOUND || return $? +MOUNTDISK=$(ogMount $DISK $PART) || ogRaiseError $OG_ERR_PARTITION "$MSG_ERROR " || return $? +#Controlar particion segunda etapa del burg +ogCheckStringInGroup $(ogGetFsType $DISK $PART) "CACHE EXT4 EXT3 EXT2" || return $(ogRaiseError $OG_ERR_PARTITION "burg.cfg soporta solo particiones linux"; echo $?) +#Controlar acceso de escritura a la particion segunda etapa del burg +ogIsReadonly $DISK $PART && return $(ogRaiseError $OG_ERR_NOTWRITE ": $DISK $PART" || echo $?) + +#Asigar la primera etapa del grub en el primer disco duro +FIRSTSTAGE=$(ogDiskToDev 1) +#Localizar disco segunda etapa del grub +SECONDSTAGE=$(ogMount $DISK $PART) + +#Preparar el directorio principal de la segunda etapa (y copia los binarios) +[ -d ${SECONDSTAGE}/boot/burg/ ] || mkdir -p ${SECONDSTAGE}/boot/burg/; cp -prv /boot/burg/* ${SECONDSTAGE}/boot/burg/ 2>&1>/dev/null; cp -prv $OGLIB/burg/themes ${SECONDSTAGE}/boot/burg/ 2>&1>/dev/null; #*/ ## (comentario Dogygen) #*/ ## (comentario Dogygen) +#Copiar el tema de opengnsys +mkdir -p ${SECONDSTAGE}/boot/burg/themes/OpenGnsys +cp -prv "$OGLIB/burg/themes" "${SECONDSTAGE}/boot/burg/" 2>&1>/dev/null + +# No configurar la segunda etapa (grub.cfg). Parámetro FALSE +if [ -f ${SECONDSTAGE}/boot/burg/burg.cfg -o -f ${SECONDSTAGE}/boot/burg/burg.cfg$BACKUPNAME ]; +then + if [ "$CHECKOS" == "false" -o "$CHECKOS" == "FALSE" ] + then + burg-install --force --root-directory=${SECONDSTAGE} $FIRSTSTAGE 2>&1>/dev/null + return $? + fi +fi + +# Configurrar la segunda etapa (burg.cfg) == tercer parámetro TRUE + +#llamar a updateBootCache para que aloje la primera fase del ogLive +updateBootCache + +#Configur la sintaxis grub para evitar menus de "recovery" en el OGLive +echo "GRUB_DISABLE_RECOVERY=\"true\"" >> /etc/default/grub +echo "GRUB_DISABLE_LINUX_UUID=\"true\"" >> /etc/default/grub + +#Preparar configuración segunda etapa: crear ubicacion +mkdir -p ${SECONDSTAGE}${PREFIXSECONDSTAGE}/boot/burg/ +#Preparar configuración segunda etapa: crear cabecera del fichero +FILECFG=${SECONDSTAGE}${PREFIXSECONDSTAGE}/boot/burg/burg.cfg +#/* ## (comentario Dogygen) +cat > "$FILECFG" << EOF + +set theme_name=OpenGnsys +set gfxmode=1024x768 + + +set locale_dir=(\$root)/boot/burg/locale + +set default=0 +set timeout=25 +set lang=es + + +insmod ext2 +insmod gettext + + + + +if [ -s \$prefix/burgenv ]; then + load_env +fi + + + +if [ \${prev_saved_entry} ]; then + set saved_entry=\${prev_saved_entry} + save_env saved_entry + set prev_saved_entry= + save_env prev_saved_entry + set boot_once=true +fi + +function savedefault { + if [ -z \${boot_once} ]; then + saved_entry=\${chosen} + save_env saved_entry + fi +} +function select_menu { + if menu_popup -t template_popup theme_menu ; then + free_config template_popup template_subitem menu class screen + load_config \${prefix}/themes/\${theme_name}/theme \${prefix}/themes/custom/theme_\${theme_name} + save_env theme_name + menu_refresh + fi +} + +function toggle_fold { + if test -z $theme_fold ; then + set theme_fold=1 + else + set theme_fold= + fi + save_env theme_fold + menu_refresh +} +function select_resolution { + if menu_popup -t template_popup resolution_menu ; then + menu_reload_mode + save_env gfxmode + fi +} + + +if test -f \${prefix}/themes/\${theme_name}/theme ; then + insmod coreui + menu_region.text + load_string '+theme_menu { -OpenGnsys { command="set theme_name=OpenGnsys" }}' + load_config \${prefix}/themes/conf.d/10_hotkey + load_config \${prefix}/themes/\${theme_name}/theme \${prefix}/themes/custom/theme_\${theme_name} + insmod vbe + insmod png + insmod jpeg + set gfxfont="Unifont Regular 16" + menu_region.gfx + vmenu resolution_menu + controller.ext +fi + + +EOF +#*/ ## (comentario Dogygen) + +#Preparar configuración segunda etapa: crear entrada del sistema operativo +grubSyntax "$KERNELPARAM" >> "$FILECFG" +#Instalar el burg +burg-install --force --root-directory=${SECONDSTAGE} $FIRSTSTAGE 2>&1>/dev/null +} + +#/** +# ogGrubDefaultEntry int_disk_GRUGCFG int_partition_GRUBCFG int_disk_default_entry int_npartition_default_entry +#@brief ver ogBootLoaderDefaultEntry +#@see ogBootLoaderDefaultEntry +#*/ ## +function ogGrubDefaultEntry () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition int_disk_default_entry int_npartition_default_entry" \ + "$FUNCNAME 1 6 1 1" + return + fi + ogBootLoaderDefaultEntry $@ + return $? +} + +#/** +# ogBurgDefaultEntry int_disk_BURGCFG int_partition_BURGCFG int_disk_default_entry int_npartition_default_entry +#@brief ver ogBootLoaderDefaultEntry +#@see ogBootLoaderDefaultEntry +#*/ ## +function ogBurgDefaultEntry () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition int_disk_default_entry int_npartition_default_entry" \ + "$FUNCNAME 1 6 1 1" + return + fi + ogBootLoaderDefaultEntry $@ + return $? +} + + +#/** +# ogRefindDefaultEntry int_disk_default_entry int_npartition_default_entry +#@brief ver ogBootLoaderDefaultEntry +#@see ogBootLoaderDefaultEntry +#*/ ## +function ogRefindDefaultEntry () +{ + local EFIDISK EFIPART + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_disk_default_entry int_npartition_default_entry" \ + "$FUNCNAME 1 1" + return + fi + + read EFIDISK EFIPART <<< $(ogGetEsp) + ogBootLoaderDefaultEntry $EFIDISK $EFIPART $@ + return $? +} + +#/** +# ogBootLoaderDefaultEntry int_disk_CFG int_partition_CFG int_disk_default_entry int_npartition_default_entry +#@brief Configura la entrada por defecto de Burg +#@param int_disk_SecondStage +#@param int_part_SecondStage +#@param int_disk_default_entry +#@param int_part_default_entry +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Partición errónea o desconocida (ogMount). +#@exception OG_ERR_OUTOFLIMIT Param $3 no es entero. +#@exception OG_ERR_NOTFOUND Fichero de configuración no encontrado: burg.cfg. +#@version 1.1.0 - Define la entrada por defecto del Burg +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2017-08-09 +#@version 1.1 Se generaliza la función para grub y burg +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2018-01-04 +#*/ ## +function ogBootLoaderDefaultEntry () +{ + +# Variables locales. +local PART FUNC DIRMOUNT LABEL CFGFILE DEFAULTENTRY MENUENTRY MSG + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$MSG_SEE ogGrubDefaultEntry, ogBurgDefaultEntry or ogRefindDefaultEntry." + return +fi + +# Nombre de la función que llama a esta. +FUNC="${FUNCNAME[@]:1}" +FUNC="${FUNC%%\ *}" + +# Error si no se reciben 3 parametros. +[ $# -eq 4 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage int_disk_default_entry int_partitions_default_entry" || return $? + +# Error si no puede montar sistema de archivos. +DIRMOUNT=$(ogMount $1 $2) || return $? + +# Comprobamos que exista fichero de configuración +# La función debe ser llamanda desde ogGrubDefaultEntry, ogBurgDefaultEntry or ogRefindDefaultEntry. +case "$FUNC" in + ogGrubDefaultEntry) + CFGFILE="$DIRMOUNT/boot/grubMBR/boot/grub/grub.cfg" + ;; + ogBurgDefaultEntry) + CFGFILE="$DIRMOUNT/boot/burg/burg.cfg" + ;; + ogRefindDefaultEntry) + CFGFILE="$DIRMOUNT/EFI/refind/refind.conf" + ;; + *) + ogRaiseError $OG_ERR_FORMAT "Use ogGrubDefaultEntry, ogBurgDefaultEntry or ogRefindDefaultEntry." + return $? + ;; +esac + +# Error si no existe archivo de configuración +[ -r $CFGFILE ] || ogRaiseError $OG_ERR_NOTFOUND "$CFGFILE" || return $? + +# Dispositivo +if [ "$(basename $CFGFILE)" == "refind.conf" ]; then + LABEL=$(printf "Part-%02d-%02d" $3 $4) +else + LABEL=$(ogDiskToDev $3 $4) +fi + +# Número de línea de la entrada por defecto en CFGFILE (primera de la partición). +DEFAULTENTRY=$(grep -n -m 1 menuentry.*$LABEL $CFGFILE| cut -d: -f1) + +# Si no hay entradas para borrar me salgo con aviso +[ "$DEFAULTENTRY" != "" ] || ogRaiseError session log $OG_ERR_NOTFOUND "No menuentry $LABEL" || return $? + +# Número de la de linea por defecto en el menú de usuario +MENUENTRY="$(grep -n -e menuentry $CFGFILE| cut -d: -f1 | grep -n $DEFAULTENTRY |cut -d: -f1)" + +if [ "$(basename $CFGFILE)" == "refind.conf" ]; then + sed -i /default_selection.*$/d $CFGFILE + sed -i "1 i\default_selection $MENUENTRY" $CFGFILE +else + # En grub y burg las líneas empiezan a contar desde cero + let MENUENTRY=$MENUENTRY-1 + sed --regexp-extended -i s/"set default=\"?[0-9]*\"?$"/"set default=\"$MENUENTRY\""/g $CFGFILE +fi +MSG="MSG_HELP_$FUNC" +echo "${!MSG%%\.}: $@" +} + +#/** +# ogGrubOgliveDefaultEntry num_disk num_part +#@brief ver ogBootLoaderOgliveDefaultEntry +#@see ogBootLoaderOgliveDefaultEntry +#*/ ## +function ogGrubOgliveDefaultEntry () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage" \ + "$FUNCNAME 1 6" + return + fi + ogBootLoaderOgliveDefaultEntry $@ + return $? +} + +#/** +# ogBurgOgliveDefaultEntry num_disk num_part +#@brief ver ogBootLoaderOgliveDefaultEntry +#@see ogBootLoaderOgliveDefaultEntry +#*/ ## +function ogBurgOgliveDefaultEntry () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage" \ + "$FUNCNAME 1 6" + return + fi + ogBootLoaderOgliveDefaultEntry $@ + return $? +} + + +#/** +# ogRefindOgliveDefaultEntry +#@brief ver ogBootLoaderOgliveDefaultEntry +#@see ogBootLoaderOgliveDefaultEntry +#*/ ## +function ogRefindOgliveDefaultEntry () +{ + local EFIDISK EFIPART + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" \ + "$FUNCNAME" + return + fi + + read EFIDISK EFIPART <<< $(ogGetEsp) + ogBootLoaderOgliveDefaultEntry $EFIDISK $EFIPART + return $? +} + + +#/** +# ogBootLoaderOgliveDefaultEntry +#@brief Configura la entrada de ogLive como la entrada por defecto de Burg. +#@param int_disk_SecondStage +#@param int_part_SecondStage +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Partición errónea o desconocida (ogMount). +#@exception OG_ERR_NOTFOUND Fichero de configuración no encontrado: burg.cfg. +#@exception OG_ERR_NOTFOUND Entrada de OgLive no encontrada en burg.cfg. +#@version 1.1.0 - Primeras pruebas con Burg +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2017-08-09 +#@version 1.1 Se generaliza la función para grub y burg +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2018-01-04 +#*/ ## +function ogBootLoaderOgliveDefaultEntry () +{ + +# Variables locales. +local FUNC PART CFGFILE NUMENTRY MSG + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$MSG_SEE ogGrubOgliveDefaultEntry, ogBurgOgliveDefaultEntry or ogRefindOgliveDefaultEntry" \ + return +fi + +# Nombre de la función que llama a esta. +FUNC="${FUNCNAME[@]:1}" +FUNC="${FUNC%%\ *}" + +# Error si no se reciben 2 parametros. +[ $# -eq 2 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage" || return $? + +# Error si no puede montar sistema de archivos. +PART=$(ogMount $1 $2) || return $? +# La función debe ser llamanda desde ogGrubOgliveDefaultEntry, ogBurgOgliveDefaultEntry or ogRefindOgliveDefaultEntry. +case "$FUNC" in + ogGrubOgliveDefaultEntry) + CFGFILE="$PART/boot/grubMBR/boot/grub/grub.cfg" + ;; + ogBurgOgliveDefaultEntry) + CFGFILE="$PART/boot/burg/burg.cfg" + ;; + ogRefindOgliveDefaultEntry) + CFGFILE="$PART/EFI/refind/refind.conf" + ;; + *) + ogRaiseError $OG_ERR_FORMAT "Use ogGrubOgliveDefaultEntry, ogBurgOgliveDefaultEntry or ogRefindOgliveDefaultEntry." + return $? + ;; +esac + +# Comprobamos que exista fichero de configuración +[ -f $CFGFILE ] || ogRaiseError $OG_ERR_NOTFOUND "$CFGFILE" || return $? + +# Detectamos cual es la entrada de ogLive +NUMENTRY=$(grep ^menuentry $CFGFILE| grep -n "OpenGnsys Live"|cut -d: -f1) + +# Si no existe entrada de ogLive nos salimos +[ -z "$NUMENTRY" ] && (ogRaiseError $OG_ERR_NOTFOUND "menuentry OpenGnsys Live in $CFGFILE" || return $?) + +if [ "$(basename $CFGFILE)" == "refind.conf" ]; then + sed -i /default_selection.*$/d $CFGFILE + + sed -i "1 i\default_selection $NUMENTRY" $CFGFILE +else + let NUMENTRY=$NUMENTRY-1 + sed --regexp-extended -i s/"set default=\"?[0-9]+\"?"/"set default=\"$NUMENTRY\""/g $CFGFILE +fi + +MSG="MSG_HELP_$FUNC" +echo "${!MSG%%\.}: $@" +} + + +#/** +# ogGrubSecurity int_disk_GRUBCFG int_partition_GRUBCFG [user] [password] +#@brief Configura grub.cfg para que sólo permita editar entrada o acceder a línea de comandos al usuario especificado +#@param int_disk_SecondStage +#@param int_part_SecondStage +#@param user (default root) +#@param password (default "", no puede entrar) +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar (ogMount). +#@exception OG_ERR_NOTFOUND No encuentra archivo de configuración del grub. +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2019-12-17 +#*/ ## +function ogGrubSecurity () +{ +# Variables locales. +local SECONDSTAGE GRUBGFC FILE USER PASSWD ENCRYPTPASSWD + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage [USER] [PASSWORD]" \ + "$FUNCNAME 1 1 " \ + "$FUNCNAME 1 2 user clave" + return +fi + +# Error si no se reciben 2 parámetros. +[ $# -ge 2 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage [USER] [PASSWORD]"|| return $? + +#localizar disco segunda etapa del grub +SECONDSTAGE=$(ogMount "$1" "$2") || return $? + +GRUBGFC=$(ls $SECONDSTAGE/{,boot/}{{grubMBR,grubPARTITION}/boot/,}{grub{,2},{,efi/}EFI/*}/{menu.lst,grub.cfg,grub.cfg.backup.og} 2>/dev/null) + +# comprobamos que exista el archivo de configuración. +[ -n "$GRUBGFC" ] || ogRaiseError $OG_ERR_NOTFOUND "grub.cfg" || return $? + +USER=${3:-root} +PASSWD=${4:-""} + +ENCRYPTPASSWD=$(echo -e "$PASSWD\n$PASSWD"|grub-mkpasswd-pbkdf2|sed -e 1,2d -e s/^.*grub/grub/) + +for FILE in $GRUBGFC; do + # Eliminamos configuración anterior + sed -i -e /superusers/d -e /password_pbkdf2/d $FILE + + # Configuramos grub.cfg para que sólo permita editar o entrar en línea de comandos al usuario especificado + [ "$PASSWD" == "" ] || sed -i "1i\password_pbkdf2 $USER $ENCRYPTPASSWD" $FILE + sed -i "1i\set superusers=\"$USER\"" $FILE + + # Permitimos que se seleccionen las entradas + sed -i /"menuentry "/s/"{"/"--unrestricted {"/ $FILE +done +} + + +#/** +# ogGrubSetTheme num_disk num_part str_theme +#@brief ver ogBootLoaderSetTheme +#@see ogBootLoaderSetTheme +#*/ ## +function ogGrubSetTheme () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage str_themeName" \ + "$FUNCNAME 1 4 ThemeBasic"\ + "$FUNCNAME \$(ogFindCache) ThemeBasic" + return + fi + ogBootLoaderSetTheme $@ + return $? +} + +#/** +# ogBurgSetTheme num_disk num_part str_theme +#@brief ver ogBootLoaderSetTheme +#@see ogBootLoaderSetTheme +#*/ ## +function ogBurgSetTheme () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage str_themeName" \ + "$FUNCNAME 1 4 ThemeBasic" \ + "$FUNCNAME \$(ogFindCache) ThemeBasic" + echo "Temas disponibles:\ $(ls $OGCAC/boot/burg/themes/)" + + return + fi + ogBootLoaderSetTheme $@ + return $? +} + + +#/** +# ogRefindSetTheme str_theme +#@brief ver ogBootLoaderSetTheme +#@see ogBootLoaderSetTheme +#*/ ## +function ogRefindSetTheme () { + local PART DIRTHEME CFGFILE + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME str_themeName" \ + "$FUNCNAME ThemeBasic" + echo -e "\nThemes in $OGLIB/refind:\n$(ls $OGLIB/refind/themes/ 2>/dev/null)" + + return + fi + + # Detectamos partición ESP + read EFIDISK EFIPART <<< $(ogGetEsp) + + PART=$(ogMount $EFIDISK $EFIPART) || return $? + DIRTHEME="$PART/EFI/refind/themes" + CFGFILE="$PART/EFI/refind/refind.conf" + + # Para utilizar ogBootLoaderSetTheme es necesario la entrada set theme_name + if [ -f $CFGFILE ]; then + sed -i '1 i\set theme_name=none' $CFGFILE + else + ogRaiseError $OG_ERR_NOTFOUND "$CFGFILE" || return $? + fi + # Creamos el directorio para los temas + [ -d $DIRTHEME ] || mkdir $DIRTHEME + + ogBootLoaderSetTheme $EFIDISK $EFIPART $@ + return $? +} + + +#/** +# ogBootLoaderSetTheme +#@brief asigna un tema al BURG +#@param int_disk_SecondStage +#@param int_part_SecondStage +#@param str_theme_name +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Partición errónea o desconocida (ogMount). +#@exception OG_ERR_NOTFOUND Fichero de configuración no encontrado: grub.cfg burg.cfg refind.conf. +#@exception OG_ERR_NOTFOUND Entrada deltema no encontrada en burg.cfg. +#@exception OG_ERR_NOTFOUND Fichero de configuración del tema no encontrado: theme.conf (sólo refind). +#@note El tema debe situarse en OGLIB/BOOTLOADER/themes +#@version 1.1.0 - Primeras pruebas con Burg. grub no soportado. +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2018-01-24 +#@version 1.1.1 - Soporta rEFInd (ticket #802 #888). +#@author Irina Gomez. Universidad de Sevilla +#@date 2019-03-22 +#*/ ## +function ogBootLoaderSetTheme () +{ + +# Variables locales. +local FUNC PART CFGFILE THEME NEWTHEME BOOTLOADER MSG NEWTHEMECFG + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$MSG_SEE ogGrubSetTheme, ogBurgSetTheme or ogRefindSetTheme." + return +fi + + +NEWTHEME="$3" + +# Nombre de la función que llama a esta. +FUNC="${FUNCNAME[@]:1}" +FUNC="${FUNC%%\ *}" + + + +# Error si no se reciben 3 parametros. +[ $# -eq 3 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage str_themeName" || return $? + +# Error si no puede montar sistema de archivos. +PART=$(ogMount $1 $2) || return $? +# La función debe ser llamanda desde ogGrubSetTheme, ogBurgSetTheme or ogRefindSetTheme. +case "$FUNC" in + ogGrubSetTheme) + BOOTLOADER="grub" + BOOTLOADERDIR="boot/grubMBR" + CFGFILE="$PART/boot/grubMBR/boot/grub/grub.cfg" + ogRaiseError $OG_ERR_FORMAT "ogGrubSetTheme not sopported" + return $? + ;; + ogBurgSetTheme) + BOOTLOADER="burg" + BOOTLOADERDIR="boot/burg" + CFGFILE="$PART/boot/burg/burg.cfg" + ;; + ogRefindSetTheme) + BOOTLOADER="refind" + BOOTLOADERDIR="EFI/refind" + CFGFILE="$PART/EFI/refind/refind.conf" + ;; + *) + ogRaiseError $OG_ERR_FORMAT "Use ogGrubSetTheme, ogBurgSetTheme or ogRefindSetTheme." + return $? + ;; +esac + +# Comprobamos que exista fichero de configuración +[ -f $CFGFILE ] || ogRaiseError $OG_ERR_NOTFOUND "$CFGFILE" || return $? + +# Detectamos cual es el tema asignado +THEME=$(grep "set theme_name=" $CFGFILE | grep ^set | cut -d= -f2) +# Si no existe entrada de theme_name nos salimos +[ -z "$THEME" ] && (ogRaiseError $OG_ERR_NOTFOUND "theme_name in $CFGFILE" || return $?) + +#Actualizamos el tema del servidor a la particion +if [ -d $OGLIB/$BOOTLOADER/themes/$NEWTHEME ]; then + # Para refind es necesario que exista theme.conf en el directorio del tema. + if [ "$BOOTLOADER" == "refind" ]; then + NEWTHEMECFG="$OGLIB/$BOOTLOADER/themes/$NEWTHEME/theme.conf" + [ -f $NEWTHEMECFG ] || ogRaiserError $OG_ERR_NOTFOUND "theme.conf" || return $? + grep -v "^#" $NEWTHEMECFG >> $CFGFILE + # eliminamos "set theme" es de grub y no de refind + sed -i '/theme_name/d' $CFGFILE + fi + cp -pr $OGLIB/$BOOTLOADER/themes/$NEWTHEME $PART/$BOOTLOADERDIR/themes/ +fi + +#Verificamos que el tema esta en la particion +if ! [ -d $PART/$BOOTLOADERDIR/themes/$NEWTHEME ]; then + ogRaiseError $OG_ERR_NOTFOUND "theme_name=$NEWTHEME in $PART/$BOOTLOADERDIR/themes/" || return $? +fi + +#Cambiamos la entrada el fichero de configuración. +sed --regexp-extended -i s/"set theme_name=$THEME"/"set theme_name=$NEWTHEME"/g $CFGFILE + + +} + +#/** +# ogGrubSetAdminKeys num_disk num_part str_theme +#@brief ver ogBootLoaderSetTheme +#@see ogBootLoaderSetTheme +#*/ ## +function ogGrubSetAdminKeys () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage str_bolean" \ + "$FUNCNAME 1 4 FALSE "\ + "$FUNCNAME \$(ogFindCache) ThemeBasic" + return + fi + ogBootLoaderSetAdminKeys $@ + return $? +} + +#/** +# ogBurgSetAdminKeys num_disk num_part str_bolean +#@brief ver ogBootLoaderSetAdminKeys +#@see ogBootLoaderSetAdminKeys +#*/ ## +function ogBurgSetAdminKeys () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage str_bolean" \ + "$FUNCNAME 1 4 TRUE" \ + "$FUNCNAME \$(ogFindCache) FALSE" + return + fi + ogBootLoaderSetAdminKeys $@ + return $? +} + + + +#/** +# ogBootLoaderSetAdminKeys +#@brief Activa/Desactica las teclas de administracion +#@param int_disk_SecondStage +#@param int_part_SecondStage +#@param Boolean TRUE/FALSE +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Partición errónea o desconocida (ogMount). +#@exception OG_ERR_NOTFOUND Fichero de configuración no encontrado: grub.cfg burg.cfg. +#@exception OG_ERR_NOTFOUND Entrada deltema no encontrada en burg.cfg. +#@version 1.1.0 - Primeras pruebas con Burg. grub no soportado. +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2018-01-24 +#*/ ## +function ogBootLoaderSetAdminKeys () +{ + +# Variables locales. +local FUNC PART CFGFILE BOOTLOADER BOOTLOADERDIR CFGFILE MSG + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$MSG_SEE ogGrubSetSetAdminKeys ogBurgSetSetAdminKeys" + return +fi + + +# Nombre de la función que llama a esta. +FUNC="${FUNCNAME[@]:1}" +FUNC="${FUNC%%\ *}" + + +# Error si no se reciben 2 parametros. +[ $# -eq 3 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage str_bolean" || return $? + +# Error si no puede montar sistema de archivos. +PART=$(ogMount $1 $2) || return $? +# La función debe ser llamanda desde ogGrubSetAdminKeys or ogBurgSetAdminKeys. +case "$FUNC" in + ogGrubSetAdminKeys) + BOOTLOADER="grub" + BOOTLOADERDIR="grubMBR" + CFGFILE="$PART/boot/grubMBR/boot/grub/grub.cfg" + ogRaiseError $OG_ERR_FORMAT "ogGrubSetAdminKeys not sopported" + return $? + ;; + ogBurgSetAdminKeys) + BOOTLOADER="burg" + BOOTLOADERDIR="burg" + CFGFILE="$PART/boot/burg/burg.cfg" + ;; + *) + ogRaiseError $OG_ERR_FORMAT "Use ogGrubSetAdminKeys" + return $? + ;; +esac + + +# Comprobamos que exista fichero de configuración +[ -f $CFGFILE ] || ogRaiseError $OG_ERR_NOTFOUND "$CFGFILE" || return $? + + +case "$3" in + true|TRUE) + [ -f ${OGCAC}/boot/$BOOTLOADERDIR/themes/conf.d/10_hotkey.disabled ] && mv ${OGCAC}/boot/$BOOTLOADERDIR/themes/conf.d/10_hotkey.disabled ${OGCAC}/boot/$BOOTLOADERDIR/themes/conf.d/10_hotkey + ;; + false|FALSE) + [ -f ${OGCAC}/boot/$BOOTLOADERDIR/themes/conf.d/10_hotkey ] && mv ${OGCAC}/boot/$BOOTLOADERDIR/themes/conf.d/10_hotkey ${OGCAC}/boot/$BOOTLOADERDIR/themes/conf.d/10_hotkey.disabled + ;; + *) + ogRaiseError $OG_ERR_FORMAT "str bolean unknow " + return $? + ;; +esac +} + + + +#/** +# ogGrubSetTimeOut num_disk num_part int_timeout_seconds +#@brief ver ogBootLoaderSetTimeOut +#@see ogBootLoaderSetTimeOut +#*/ ## +function ogGrubSetTimeOut () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage int_timeout_seconds" \ + "$FUNCNAME 1 4 50 "\ + "$FUNCNAME \$(ogFindCache) 50" + return + fi + ogBootLoaderSetTimeOut $@ + return $? +} + +#/** +# ogBurgSetTimeOut num_disk num_part str_bolean +#@brief ver ogBootLoaderSetTimeOut +#@see ogBootLoaderSetTimeOut +#*/ ## +function ogBurgSetTimeOut () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage str_timeout_seconds" \ + "$FUNCNAME 1 4 50" \ + "$FUNCNAME \$(ogFindCache) 50" + return + fi + ogBootLoaderSetTimeOut $@ + return $? +} + + +#/** +# ogRefindSetTimeOut int_timeout_second +#@brief ver ogBootLoaderSetTimeOut +#@see ogBootLoaderSetTimeOut +#*/ ## +function ogRefindSetTimeOut () +{ + local EFIDISK EFIPART + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_timeout_seconds" \ + "$FUNCNAME 50" + return + fi + + read EFIDISK EFIPART <<< $(ogGetEsp) + ogBootLoaderSetTimeOut $EFIDISK $EFIPART $@ + return $? +} + +#/** +# ogBootLoaderSetTimeOut +#@brief Define el tiempo (segundos) que se muestran las opciones de inicio +#@param int_disk_SecondStage +#@param int_part_SecondStage +#@param int_timeout_seconds +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Partición errónea o desconocida (ogMount). +#@exception OG_ERR_NOTFOUND Fichero de configuración no encontrado: grub.cfg burg.cfg. +#@exception OG_ERR_NOTFOUND Entrada deltema no encontrada en burg.cfg. +#@version 1.1.0 - Primeras pruebas con Burg. GRUB solo si está instalado en MBR +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2018-01-24 +#*/ ## +function ogBootLoaderSetTimeOut () +{ + +# Variables locales. +local FUNC PART CFGFILE TIMEOUT BOOTLOADER BOOTLOADERDIR CFGFILE MSG + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$MSG_SEE ogGrubSetTimeOut, ogBurgSetTimeOut or ogRefindSetTimeOut" + return +fi + +ogCheckStringInReg $3 "^[0-9]{1,10}$" && TIMEOUT="$3" || ogRaiseError $OG_ERR_FORMAT "param 3 is not a integer" + +# Nombre de la función que llama a esta. +FUNC="${FUNCNAME[@]:1}" +FUNC="${FUNC%%\ *}" + +# Error si no se reciben 3 parametros. +[ $# -eq 3 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage int_timeout_seconds" || return $? + +# Error si no puede montar sistema de archivos. +PART=$(ogMount $1 $2) || return $? +# La función debe ser llamanda desde ogGrubSetTimeOut, ogBurgSetTimeOut or ogRefindSetTimeOut. +case "$FUNC" in + ogGrubSetTimeOut) + BOOTLOADER="grub" + BOOTLOADERDIR="boot/grubMBR" + CFGFILE="$PART/boot/grubMBR/boot/grub/grub.cfg" + ;; + ogBurgSetTimeOut) + BOOTLOADER="burg" + BOOTLOADERDIR="boot/burg" + CFGFILE="$PART/boot/burg/burg.cfg" + ;; + ogRefindSetTimeOut) + BOOTLOADER="refind" + BOOTLOADERDIR="EFI/refind" + CFGFILE="$PART/EFI/refind/refind.conf" + ;; + *) + ogRaiseError $OG_ERR_FORMAT "Use ogGrubSetTimeOut, ogBurgSetTimeOut or ogRefindSetTimeOut." + return $? + ;; +esac + +# Comprobamos que exista fichero de configuración +[ -f $CFGFILE ] || ogRaiseError $OG_ERR_NOTFOUND "$CFGFILE" || return $? + +# Asignamos el timeOut. +if [ "$BOOTLOADER" == "refind" ]; then + sed -i s/timeout.*$/"timeout $TIMEOUT"/g $CFGFILE +else + sed -i s/timeout=.*$/timeout=$TIMEOUT/g $CFGFILE +fi +} + + +#/** +# ogGrubSetResolution num_disk num_part int_resolution +#@brief ver ogBootLoaderSetResolution +#@see ogBootLoaderSetResolution +#*/ ## +function ogGrubSetResolution () +{ + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage [str_resolution]" \ + "$FUNCNAME 1 4 1024x768" \ + "$FUNCNAME \$(ogFindCache) 1024x768" \ + "$FUNCNAME 1 4" + return + fi + ogBootLoaderSetResolution $@ + return $? +} + +#/** +# ogBurgSetResolution num_disk num_part str_bolean +#@brief ver ogBootLoaderSetResolution +#@see ogBootLoaderSetResolution +#*/ ## +function ogBurgSetResolution () + { + # Si se solicita, mostrar ayuda. + if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage [str_resolution]" \ + "$FUNCNAME 1 4 1024x768" \ + "$FUNCNAME \$(ogFindCache) 1024x768" \ + "$FUNCNAME 1 4" + return + fi + ogBootLoaderSetResolution $@ + return $? +} + + + +#/** +# ogBootLoaderSetResolution +#@brief Define la resolucion que usuara el thema del gestor de arranque +#@param int_disk_SecondStage +#@param int_part_SecondStage +#@param str_resolution (Opcional) +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Partición errónea o desconocida (ogMount). +#@exception OG_ERR_NOTFOUND Fichero de configuración no encontrado: grub.cfg burg.cfg. +#@version 1.1.0 - Primeras pruebas con Burg. grub no soportado. +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2018-01-24 +#*/ ## +function ogBootLoaderSetResolution () +{ + +# Variables locales. +local FUNC PART CFGFILE RESOLUTION NEWRESOLUTION DEFAULTRESOLUTION BOOTLOADER BOOTLOADERDIR CFGFILE MSG + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$MSG_SEE ogGrubSetResolution, ogBurgSetResolution or ogRefindSetResolution." + return +fi + + +# Nombre de la función que llama a esta. +FUNC="${FUNCNAME[@]:1}" +FUNC="${FUNC%%\ *}" + + +# Error si no se reciben 2 parametros. +[ $# -ge 2 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_ndiskSecondStage int_partitionSecondStage [str_resolution]" || return $? + +# Error si no puede montar sistema de archivos. +PART=$(ogMount $1 $2) || return $? +# La función debe ser llamanda desde ogGrugSetResolution, ogBurgSetResolution or ogRefindSetResolution. +case "$FUNC" in + ogGrubSetResolution) + BOOTLOADER="grub" + BOOTLOADERDIR="grubMBR" + CFGFILE="$PART/boot/grubMBR/boot/grub/grub.cfg" + ogRaiseError $OG_ERR_FORMAT "ogGrubSetResolution not sopported" + return $? + ;; + ogBurgSetResolution) + BOOTLOADER="burg" + BOOTLOADERDIR="burg" + CFGFILE="$PART/boot/burg/burg.cfg" + ;; + *) + ogRaiseError $OG_ERR_FORMAT "Use GrugSetResolution, ogBurgSetResolution or ogRefindSetResolution." + return $? + ;; +esac + +DEFAULTRESOLUTION=1024x768 + +# Comprobamos que exista fichero de configuración +[ -f $CFGFILE ] || ogRaiseError $OG_ERR_NOTFOUND "$CFGFILE" || return $? + +#controlar variable a consierar vga (default template) o video (menu) +#Si solo dos parametros autoconfiguracion basado en el parametro vga de las propiedad menu. si no hay menu asignado es 788 por defecto +if [ $# -eq 2 ] ; then + if [ -n $video ]; then + NEWRESOLUTION=$(echo "$video" | cut -f2 -d: | cut -f1 -d-) + fi + if [ -n $vga ] ; then + case "$vga" in + 788|789|814) + NEWRESOLUTION=800x600 + ;; + 791|792|824) + NEWRESOLUTION=1024x768 + ;; + 355) + NEWRESOLUTION=1152x864 + ;; + 794|795|829) + NEWRESOLUTION=1280x1024 + ;; + esac + fi +fi + +if [ $# -eq 3 ] ; then + #comprobamos que el parametro 3 cumple formato NNNNxNNNN + ogCheckStringInReg $3 "[0-9]{3,4}[x][0-9]{3,4}\$" && NEWRESOLUTION="$3" || ogRaiseError $OG_ERR_FORMAT "param 3 is not a valid resolution: 800x600, 1024x768, 1152x864, 1280x1024, 1600x1200" +fi + +# Si no existe NEWRESOLUCION asignamos la defaulT +[ -z "$NEWRESOLUTION" ] && NEWRESOLUTION=$DEFAULRESOLUTION + +#Cambiamos la entrada el fichero de configuración. +sed -i s/gfxmode=.*$/gfxmode=$NEWRESOLUTION/g $CFGFILE +} + + + + +#/** +# ogBootLoaderSetResolution +#@brief Define la resolucion que usuara el thema del gestor de arranque +#@param int_resolution1 +#@param int_resolution2 (Opcional) +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Partición errónea o desconocida (ogMount). +#@exception OG_ERR_NOTFOUND Fichero de configuración no encontrado: grub.cfg burg.cfg. +#*/ ## +function ogRefindSetResolution () { +local PART CFGFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_resolution1 [int_resolution2]" \ + "$FUNCNAME 1366 768" \ + "$FUNCNAME 1" + return +fi + + # Error si no se reciben 2 parametros. +[ $# -ge 1 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_resolution1 [int_resolution2]" || return $? + +# Error si no puede montar sistema de archivos. +PART=$(ogMount $(ogGetEsp)) || return $? + +# Comprobamos que exista fichero de configuración +CFGFILE=$PART/EFI/refind/refind.conf +[ -f $CFGFILE ] || ogRaiseError $OG_ERR_NOTFOUND "$CFGFILE" || return $? + +# Borramos resolucion anterior y configuramos la nueva +sed -i /^resolution/d $CFGFILE + +sed -i "1 i\resolution $1 $2" $CFGFILE +} + +# ogRefindInstall bool_autoconfig +#@brief Instala y actualiza el gestor rEFInd en la particion EFI +#@param bolean_Check__auto_config true | false[default] +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND No se encuentra la partición ESP. +#@exception OG_ERR_NOTFOUND No se encuentra shimx64.efi.signed. +#@exception OG_ERR_NOTFOUND No se encuentra refind-install o refind en OGLIB +#@exception OG_ERR_PARTITION No se puede montar la partición ESP. +#@note Refind debe estar instalado en el ogLive o compartido en OGLIB +#@version 1.1.0 - Primeras pruebas. +#@author Juan Carlos Garcia. Universidad de ZAragoza. +#@date 2017-06-26 +#@version 1.1.1 - Usa refind-install. Obtiene partición con ogGetEsp. Configura Part-X-Y y ogLive. +#@author Irina Gomez. Universidad de Sevilla. +#@date 2019-03-22 +#*/ ## +function ogRefindInstall () { +# Variables locales. +local CONFIG EFIDISK EFIPART EFIDEVICE EFIMNT EFIDIR SHIM REFINDDIR +local CACHEDEVICE OGLIVE OGLIVEDIR CMDLINE OGICON CFGFILE DEVICES +local LNXCFGFILE NUMENTRY DIR + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME boolean_autoconfig " \ + "$FUNCNAME TRUE" + return +fi + +# Recogemos parametros +CONFIG=${1:-"FALSE"} + +read -e EFIDISK EFIPART <<< $(ogGetEsp) +EFIDEVICE=$(ogDiskToDev $EFIDISK $EFIPART) || ogRaiseError $OG_ERR_NOTFOUND "ESP" || return $? +EFIMNT=$(ogMount $EFIDISK $EFIPART) || ogRaiseError $OG_ERR_PARTITION "$MSG_ERROR mount ESP" || return $? +EFIDIR="$EFIMNT/EFI" +[ -d $EFIDIR ] || mkdir $EFIDIR + +# Comprobamos que exista shimx64 +SHIM=$(ogGetPath /usr/lib/shim/shimx64.efi.signed) +[ "$SHIM" == "" ] && return $(ogRaiseError $OG_ERR_NOTFOUND "shimx64.efi.signed") + +# Si existe configuración anterior de refind la borro +[ -d "$EFIDIR/refind" ] && rm -rf $EFIDIR/refind + +# Instalamos rEFInd. +refind-install --yes --alldrivers --root $EFIMNT --shim $SHIM + +# Firmo refind con certificado de OpenGnsys +mv $EFIDIR/refind/grubx64.efi $EFIDIR/refind/grubx64.efi-unsigned +sbsign --key $OGETC/ssl/private/opengnsys.key --cert $OGETC/ssl/certs/opengnsys.crt --output $EFIDIR/refind/grubx64.efi $EFIDIR/refind/grubx64.efi-unsigned + +# Copio los certificados +cp /etc/refind.d/keys/* $EFIDIR/refind/keys +# Copio certificado opengnsys +cp $OGETC/ssl/certs/opengnsys.* $EFIDIR/refind/keys + +# Ponemos la entrada en NVRAM en el segundo lugar del orden de arranque +NEWORDER="$(ogNvramGetOrder|awk '{gsub(",", " "); printf "%x %x %s\n", $2, $1, substr($0, index($0,$3))}')" +ogNvramSetOrder $NEWORDER + +# Borramos configuración linux +[ -f $EFIMNT/boot/refind_linux.conf ] && mv $EFIMNT/boot/refind_linux.conf{,.ogbackup} + +# Eliminamos punto de motaje (por si ejecutamos más de una vez) +umount $EFIMNT/boot/efi + +# Para la configuración del ogLive +ogMountCache &>/dev/null +if [ $? -eq 0 ]; then + # Detectamos si hay ogLive + CACHEDEVICE=$(ogDiskToDev $(ogFindCache)) + OGLIVE=$(find $OGCAC/boot -name ogvmlinuz|head -1) + # Obtenemos parametros del kernel y sustituimos root + # La línea de opciones no puede contener la cadena initrd. + CMDLINE="$(cat /proc/cmdline|sed -e 's/^.*ogvmlinuz.efi //g' -e 's/^.*ogvmlinuz //g' -e 's|root=/dev/[a-z]* ||g' \ + -e 's/ogupdateinitrd=[a-z]* //g')" + CMDLINE="root=$CACHEDEVICE ${CMDLINE#*ogvmlinuz}" + + # Icono para la entrada de menú + OGICON=$(ls $OGLIB/refind/icons/so_opengnsys.png 2>/dev/null) + [ "$OGICON" == "" ] && OGICON="${EFIDIR}/refind/icons/os_unknown.png" + cp "$OGICON" "$OGCAC/.VolumeIcon.png" +fi + +# Configuramos rEFInd si es necesario +CFGFILE="${EFIDIR}/refind/refind.conf" +if [ "$CONFIG" == "TRUE" ]; then + echo -e "\n\n# Configuración OpenGnsys" >> $CFGFILE + # Excluimos dispositivos distintos de ESP y CACHE + DEVICES=$(blkid -s PARTUUID |awk -v D=$EFIDEVICE -v C=$CACHEDEVICE '$1!=D":" && $1!=C":" {gsub(/PARTUUID=/,"");gsub(/"/,""); aux = aux" "$2","} END {print aux}') + echo "dont_scan_volumes $DEVICES" >> $CFGFILE + # Excluimos en la ESP los directorios de los sistemas operativos + echo "dont_scan_dirs EFI/microsoft,EFI/ubuntu,EFI/grub" >> $CFGFILE + echo "use_graphics_for osx,linux,windows" >> $CFGFILE + echo "showtools reboot, shutdown" >> $CFGFILE + + # Configuramos ogLive + if [ "$OGLIVE" != "" ]; then + # Cambiamos nombre de kernel e initrd para que lo detecte refind + OGLIVEDIR="$(dirname $OGLIVE)" + cp "$OGLIVE" "${OGLIVE}.efi" + cp "$OGLIVEDIR/oginitrd.img" "$OGLIVEDIR/initrd.img" + + # Incluimos el directorio de ogLive. + echo "also_scan_dirs +,boot/$(basename $OGLIVEDIR)" >> $CFGFILE + # Fichero de configuración de refind para kernel de linux. + LNXCFGFILE="$OGLIVEDIR/refind_linux.conf" + echo "\"OpenGnsys Live\" \"$CMDLINE\"" > $LNXCFGFILE + + # Ponemos ogLive como la entrada por defecto + NUMENTRY=$(ls -d $EFIDIR/Part-??-??|wc -l) + echo "default_selection $((NUMENTRY+1))" >> $CFGFILE + fi +else + # Renombramos la configuración por defecto + mv $CFGFILE ${CFGFILE}.auto + + # Creamos nueva configuración + echo "# Configuración OpenGnsys" >> $CFGFILE + echo "timeout 20" > $CFGFILE + echo "showtools reboot, shutdown" >> $CFGFILE + echo -e "scanfor manual\n" >> $CFGFILE + # Configuración para sistemas restaurados con OpenGnsys + for DIR in $(ls -d /mnt/sda1/EFI/Part-*-* 2>/dev/null); do + echo "menuentry \"${DIR##*/}\" {" >> $CFGFILE + echo " loader /EFI/${DIR##*/}/Boot/ogloader.efi" >> $CFGFILE + [ -f $DIR/Boot/bootmgfw.efi ] && echo " icon /EFI/refind/icons/os_win8.png" >> $CFGFILE + [ -f $DIR/Boot/grubx64.efi ] && echo " icon /EFI/refind/icons/os_linux.png" >> $CFGFILE + echo "}" >> $CFGFILE + done + # Configuración ogLive si secureboot no está activado + if ! dmesg|grep secureboot.*enabled &>/dev/null; then + if [ "$OGLIVE" != "" ]; then + echo "menuentry \"OpenGnsys Live\" {" >> $CFGFILE + echo " volume CACHE" >> $CFGFILE + echo " ostype Linux" >> $CFGFILE + echo " loader /boot/$(basename ${OGLIVE%/*})/ogvmlinuz" >> $CFGFILE + echo " initrd /boot/$(basename ${OGLIVE%/*})/oginitrd.img" >> $CFGFILE + echo " options \"$CMDLINE\"" >> $CFGFILE + echo "}" >> $CFGFILE + + # Ponemos ogLive como la entrada por defecto + sed -i '1 i\default_selection "OpenGnsys Live"' $CFGFILE + fi + fi +fi +} + +#/** +# ogGrub4dosInstallMbr int_ndisk +#@brief Genera un nuevo Codigo de arranque en el MBR del disco indicado, compatible con los SO tipo Windows, Linux. +#@param int_ndisk nº de orden del disco +#@param int_ndisk nº de orden del particion +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Tipo de partición desconocido o no se puede montar. +#@exception OG_ERR_NOTBIOS Equipo no firmware BIOS legacy +#@exception OG_ERR_NOMSDOS Disco duro no particioniado en modo msdos +#@exception OG_ERR_NOTWRITE Particion no modificable. +#@version 1.1.1 - Adaptacion a OpenGnSys. +#@author Alberto García Padilla / Antonio J. Doblas Viso. Universidad de Malaga +#@date 2009-10-17 +#*/ ## + +function ogGrub4dosInstallMbr () +{ +# Variables locales. +local DISK PART DEVICE MOUNTDISK GRUBDISK BINBDIR + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_part " \ + "$FUNCNAME 1 1 " + return +fi +# Error si no se recibe 2 parámetros. +[ $# == 2 ] || return $(ogRaiseError $OG_ERR_FORMAT; echo $?) + +DISK="$1" +PART="$2" + +#Controlar existencia de disco y particion +DEVICE=$(ogDiskToDev $DISK) || ogRaiseError $OG_ERR_NOTFOUND || return $? +MOUNTDISK=$(ogMount $DISK $PART) || ogRaiseError $OG_ERR_PARTITION "$MSG_ERROR " || return $? +#Controlar acceso de escritura a la particion +ogIsReadonly $DISK $PART && return $(ogRaiseError $OG_ERR_NOTWRITE ": $DISK $PART" || echo $?) +#Controlar disco no uefi +ogIsEfiActive && return $(ogRaiseError $OG_ERR_NOTBIOS " : grub4dos solo soporta PC con bios legacy"; echo $?) +#Controlar particionado tipo msdos +ogCheckStringInGroup $(ogGetPartitionTableType $DISK) "MSDOS" || return $(ogRaiseError $OG_ERR_NOMSDOS ": grub2dos requiere particionado tipo MSDOS"; echo $?) +#Controlar la existencia del grub4dos con acceso a ntfs +BINDIR="${OGLIB}/grub4dos/grub4dos-0.4.6a" +[ -f ${BINDIR}/bootlace.com ] || ogRaiseError $OG_ERR_NOTFOUND ": ${BINDIR}/bootlace.com" || return $? + +#instalar el bootloader de grlrd en el MBR +${BINDIR}/bootlace64.com $DEVICE &>/dev/null +#copiar grld a la particion +cp ${BINDIR}/grldr $MOUNTDISK +#Instalar y configurar grub4dos +if [[ -f $MOUNTDISK/boot/grub/menu.lst ]]; then + rm $MOUNTDISK/boot/grub/menu.lst + rmdir /$MOUNTDISK/boot/grub +fi +if [[ ! -f $MOUNTDISK/boot/grub/menu.lst ]]; then + mkdir -p /$MOUNTDISK/boot/grub + touch /$MOUNTDISK/boot/grub/menu.lst + + GRUBDISK=$[$1-1] + +cat << EOT >/$MOUNTDISK/boot/grub/menu.lst +##NO-TOCAR-ESTA-LINEA MBR +timeout 0 +title MBR +root (hd$GRUBDISK,0) +chainloader (hd$GRUBDISK,0)+1 +boot +EOT + +fi +} diff --git a/client/engine/Cache.lib b/client/engine/Cache.lib new file mode 100755 index 0000000..cacba40 --- /dev/null +++ b/client/engine/Cache.lib @@ -0,0 +1,439 @@ +#!/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/') +} + diff --git a/client/engine/Disk.lib b/client/engine/Disk.lib new file mode 100755 index 0000000..f7def42 --- /dev/null +++ b/client/engine/Disk.lib @@ -0,0 +1,1715 @@ +#!/bin/bash +#/** +#@file Disk.lib +#@brief Librería o clase Disk +#@class Disk +#@brief Funciones para gestión de discos y particiones. +#@version 1.1.1 +#@warning License: GNU GPLv3+ +#*/ + + +# Función ficticia para lanzar parted con timeout, evitando cuelgues del programa. +function parted () +{ +timeout -k 5s -s KILL 3s $(which parted) "$@" +} + + +#/** +# 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. +#@version 0.9 - Primera versión para OpenGnSys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009/09/09 +#@version 0.9.1 - Corrección del redondeo del tamaño del disco. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010/03/09 +#@version 1.0.4 - Llamada a función específica para tablas GPT. +#@author Universidad de Huelva +#@date 2012/03/30 +#@version 1.1.1 - El inicio de la primera partición logica es el de la extendida más 4x512 +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2016/07/11 +#*/ ## +function ogCreatePartitions () +{ +# Variables locales. +local ND DISK PTTYPE PART SECTORS START SIZE TYPE CACHEPART IODISCO IOSIZE CACHESIZE +local EXTSTART EXTSIZE NVME_PREFIX tmpsfdisk +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk str_parttype:int_partsize ..." \ + "$FUNCNAME 1 NTFS:10000000 EXT3:5000000 LINUX-SWAP:1000000" + return +fi +# Error si no se reciben al menos 2 parámetros. +[ $# -ge 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Nº total de sectores, para evitar desbordamiento (evitar redondeo). +ND="$1" +DISK=$(ogDiskToDev "$ND") || return $? +PTTYPE=$(ogGetPartitionTableType $1) +PTTYPE=${PTTYPE:-"MSDOS"} # Por defecto para discos vacíos. +case "$PTTYPE" in + GPT) ogCreateGptPartitions "$@" + return $? ;; + MSDOS) ;; + *) ogRaiseError $OG_ERR_PARTITION "$PTTYPE" + return $? ;; +esac +SECTORS=$(ogGetLastSector $1) +# Se recalcula el nº de sectores del disco 1, si existe partición de caché. +CACHEPART=$(ogFindCache 2>/dev/null) +[ "$ND" = "${CACHEPART% *}" ] && CACHESIZE=$(ogGetCacheSize 2>/dev/null | awk '{print $0*2}') + +# Sector de inicio (la partición 1 empieza en el sector 63). +IODISCO=$(ogDiskToDev $1) +IOSIZE=$(fdisk -l $IODISCO | awk '/I\/O/ {print $4}') +if [ "$IOSIZE" == "4096" ]; then + START=4096 + SECTORS=$[SECTORS-8192] + [ -n "$CACHESIZE" ] && SECTORS=$[SECTORS-CACHESIZE+2048-(SECTORS-CACHESIZE)%2048-1] +else + START=63 + [ -n "$CACHESIZE" ] && SECTORS=$[SECTORS-CACHESIZE] +fi +PART=1 + +# Fichero temporal de entrada para "sfdisk" +tmpsfdisk=/tmp/sfdisk$$ +trap "rm -f $tmpsfdisk" 1 2 3 9 15 + +echo "unit: sectors" >$tmpsfdisk +echo >>$tmpsfdisk + +NVME_PREFIX="" +if [[ $DISK == *"nvme"* ]]; then + NVME_PREFIX="p" +fi + + +# Generar fichero de entrada para "sfdisk" con las particiones. +shift +while [ $# -gt 0 ]; do + # Conservar los datos de la partición de caché. + if [ "$ND $PART" == "$CACHEPART" -a -n "$CACHESIZE" ]; then + echo "$DISK$NVME_PREFIX$PART : start=$[SECTORS+1], size=$CACHESIZE, Id=ca" >>$tmpsfdisk + PART=$[PART+1] + fi + # Leer formato de cada parámetro - Tipo:Tamaño + TYPE="${1%%:*}" + SIZE="${1#*:}" + # Obtener identificador de tipo de partición válido. + ID=$(ogTypeToId "$TYPE" MSDOS) + [ "$TYPE" != "CACHE" -a -n "$ID" ] || ogRaiseError $OG_ERR_PARTITION "$TYPE" || return $? + # Comprobar tamaño numérico y convertir en sectores de 512 B. + [[ "$SIZE" == *([0-9]) ]] || ogRaiseError $OG_ERR_FORMAT "$SIZE" || return $? + SIZE=$[SIZE*2] + # Comprobar si la partición es extendida. + if [ $ID = 5 ]; then + [ $PART -le 4 ] || ogRaiseError $OG_ERR_FORMAT || return $? + # El inicio de la primera partición logica es el de la extendida más 4x512 + let EXTSTART=$START+2048 + let EXTSIZE=$SIZE-2048 + fi + # Incluir particiones lógicas dentro de la partición extendida. + if [ $PART = 5 ]; then + [ -n "$EXTSTART" ] || ogRaiseError $OG_ERR_FORMAT || return $? + START=$EXTSTART + SECTORS=$[EXTSTART+EXTSIZE] + fi + # Generar datos para la partición. + echo "$DISK$NVME_PREFIX$PART : start=$START, size=$SIZE, Id=$ID" >>$tmpsfdisk + # Error si se supera el nº total de sectores. + START=$[START+SIZE] + if [ "$IOSIZE" == "4096" -a $PART -gt 4 ]; then + START=$[START+2048] + fi + [ $START -le $SECTORS ] || ogRaiseError $OG_ERR_FORMAT "$[START/2] > $[SECTORS/2]" || return $? + PART=$[PART+1] + shift +done +# Si no se indican las 4 particiones primarias, definirlas como vacías, conservando la partición de caché. +while [ $PART -le 4 ]; do + if [ "$ND $PART" == "$CACHEPART" -a -n "$CACHESIZE" ]; then + echo "$DISK$NVME_PREFIX$PART : start=$[SECTORS+1], size=$CACHESIZE, Id=ca" >>$tmpsfdisk + else + echo "$DISK$NVME_PREFIX$PART : start=0, size=0, Id=0" >>$tmpsfdisk + fi + PART=$[PART+1] +done +# Si se define partición extendida sin lógicas, crear particion 5 vacía. +if [ $PART = 5 -a -n "$EXTSTART" ]; then + echo "${DISK}5 : start=$EXTSTART, size=$EXTSIZE, Id=0" >>$tmpsfdisk +fi + +# Desmontar los sistemas de archivos del disco antes de realizar las operaciones. +ogUnmountAll $ND 2>/dev/null +[ -n "$CACHESIZE" ] && ogUnmountCache 2>/dev/null + +# Si la tabla de particiones no es valida, volver a generarla. +ogCreatePartitionTable $ND +# Definir particiones y notificar al kernel. +sfdisk -f $DISK < $tmpsfdisk 2>/dev/null && partprobe $DISK +rm -f $tmpsfdisk +[ -n "$CACHESIZE" ] && ogMountCache 2>/dev/null || return 0 +} + + +#/** +# ogCreateGptPartitions int_ndisk str_parttype:int_partsize ... +#@brief Define el conjunto de particiones de un disco GPT +#@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 caché y no se modifica si existe. +#@note Requisitos: sfdisk, parted, partprobe, awk +#@todo Definir atributos (arranque, oculta) y tamaños en MB, GB, etc. +#@version 1.0.4 - Primera versión para OpenGnSys +#@author Universidad de Huelva +#@date 2012/03/30 +#*/ ## +function ogCreateGptPartitions () +{ +# Variables locales. +local ND DISK PART SECTORS ALIGN START SIZE TYPE CACHEPART CACHESIZE DELOPTIONS OPTIONS +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk str_parttype:int_partsize ..." \ + "$FUNCNAME 1 NTFS:10000000 EXT3:5000000 LINUX-SWAP:1000000" + return +fi +# Error si no se reciben menos de 2 parámetros. +[ $# -ge 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Nº total de sectores, para evitar desbordamiento (evitar redondeo). +ND="$1" +DISK=$(ogDiskToDev "$ND") || return $? +# Se calcula el ultimo sector del disco (total de sectores usables) +SECTORS=$(ogGetLastSector $1) +# Se recalcula el nº de sectores del disco si existe partición de caché. +CACHEPART=$(ogFindCache 2>/dev/null) +[ "$ND" = "${CACHEPART% *}" ] && CACHESIZE=$(ogGetCacheSize 2>/dev/null | awk '{print $0*2}') +[ -n "$CACHESIZE" ] && SECTORS=$[SECTORS-CACHESIZE] +# Si el disco es GPT empieza en el sector 2048 por defecto, pero podria cambiarse +ALIGN=$(sgdisk -D $DISK 2>/dev/null) +START=$ALIGN +PART=1 + +# Leer parámetros con definición de particionado. +shift + +while [ $# -gt 0 ]; do + # Si PART es la cache, nos la saltamos y seguimos con el siguiente numero para conservar los datos de la partición de caché. + if [ "$ND $PART" == "$CACHEPART" -a -n "$CACHESIZE" ]; then + PART=$[PART+1] + fi + # Leer formato de cada parámetro - Tipo:Tamaño + TYPE="${1%%:*}" + SIZE="${1#*:}" + # Error si la partición es extendida (no válida en discos GPT). + if [ "$TYPE" == "EXTENDED" ]; then + ogRaiseError $OG_ERR_PARTITION "EXTENDED" + return $? + fi + # Comprobar si existe la particion actual, capturamos su tamaño para ver si cambio o no + PARTSIZE=$(ogGetPartitionSize $ND $PART 2>/dev/null) + # En sgdisk no se pueden redimensionar las particiones, es necesario borrarlas y volver a crealas + [ $PARTSIZE ] && DELOPTIONS="$DELOPTIONS -d$PART" + # Creamos la particion + # Obtener identificador de tipo de partición válido. + ID=$(ogTypeToId "$TYPE" GPT) + [ "$TYPE" != "CACHE" -a -n "$ID" ] || ogRaiseError $OG_ERR_PARTITION "$TYPE" || return $? + # Comprobar tamaño numérico y convertir en sectores de 512 B. + [[ "$SIZE" == *([0-9]) ]] || ogRaiseError $OG_ERR_FORMAT "$SIZE" || return $? + SIZE=$[SIZE*2] + # SIZE debe ser múltiplo de ALIGN, si no gdisk lo mueve automáticamente. + DIV=$[$SIZE/$ALIGN] + SIZE=$[$DIV*$ALIGN] + # En el caso de que la partición sea EMPTY no se crea nada + if [ "$TYPE" != "EMPTY" ]; then + OPTIONS="$OPTIONS -n$PART:$START:+$SIZE -t$PART:$ID " + fi + START=$[START+SIZE] + # Error si se supera el nº total de sectores. + [ $START -le $SECTORS ] || ogRaiseError $OG_ERR_FORMAT "$[START/2] > $[SECTORS/2]" || return $? + PART=$[PART+1] + shift +done + +# Desmontar los sistemas de archivos del disco antes de realizar las operaciones. +ogUnmountAll $ND 2>/dev/null +[ -n "$CACHESIZE" ] && ogUnmountCache 2>/dev/null + +# Si la tabla de particiones no es valida, volver a generarla. +ogCreatePartitionTable $ND +# Definir particiones y notificar al kernel. +# Borramos primero las particiones y luego creamos las nuevas +sgdisk $DELOPTIONS $OPTIONS $DISK 2>/dev/null && partprobe $DISK +[ -n "$CACHESIZE" ] && ogMountCache 2>/dev/null || return 0 +} + + +#/** +# 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 +#@version 1.0.4 - Primera versión compatible con OpenGnSys. +#@author Universidad de Huelva +#@date 2012/03/06 +#@version 1.0.6a - Adaptar creación de nueva tabla MSDOS. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2016/01/29 +#*/ ## +function ogCreatePartitionTable () +{ +# Variables locales. +local DISK PTTYPE CREATE CREATEPTT + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME int_ndisk [str_partype]" \ + "$FUNCNAME 1 GPT" "$FUNCNAME 1" + return +fi +# Error si no se reciben 1 o 2 parámetros. +case $# in + 1) CREATEPTT="" ;; + 2) CREATEPTT="$2" ;; + *) ogRaiseError $OG_ERR_FORMAT + return $? ;; +esac + +# Capturamos el tipo de tabla de particiones actual +DISK=$(ogDiskToDev $1) || return $? +PTTYPE=$(ogGetPartitionTableType $1) +PTTYPE=${PTTYPE:-"MSDOS"} # Por defecto para discos vacíos. +CREATEPTT=${CREATEPTT:-"$PTTYPE"} + +# Si la tabla actual y la que se indica son iguales, se comprueba si hay que regenerarla. +if [ "$CREATEPTT" == "$PTTYPE" ]; then + case "$PTTYPE" in + GPT) [ ! $(sgdisk -p $DISK 2>&1 >/dev/null) ] || CREATE="GPT" ;; + MSDOS) [ $(parted -s $DISK print >/dev/null) ] || CREATE="MSDOS" ;; + esac +else + CREATE="$CREATEPTT" +fi +# Dependiendo del valor de CREATE, creamos la tabla de particiones en cada caso. +case "$CREATE" in + GPT) + # Si es necesario crear una tabla GPT pero la actual es MSDOS + if [ "$PTTYPE" == "MSDOS" ]; then + sgdisk -go $DISK + else + echo -e "2\nw\nY\n" | gdisk $DISK + fi + partprobe $DISK 2>/dev/null + ;; + MSDOS) + # Si es necesario crear una tabla MSDOS pero la actual es GPT + if [ "$PTTYPE" == "GPT" ]; then + sgdisk -Z $DISK + fi + # Crear y borrar una partición para que la tabla se genere bien. + echo -e "o\nn\np\n\n\n\nd\n\nw" | fdisk $DISK + partprobe $DISK 2>/dev/null + ;; +esac +} + + +#/** +# ogDeletePartitionTable ndisk +#@brief Borra la tabla de particiones del disco. +#@param int_ndisk nº de orden del disco +#@return la informacion propia del fdisk +#@version 0.1 - Integracion para OpenGnSys +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2008/10/27 +#@version 1.0.4 - Adaptado para su uso con discos GPT +#@author Universidad de Huelva +#@date 2012/03/13 +#*/ ## +function ogDeletePartitionTable () +{ +# Variables locales. +local DISK + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME int_ndisk" "$FUNCNAME 1" + return +fi +# Error si no se reciben 1 parámetros. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obteniendo Identificador linux del disco. +DISK=$(ogDiskToDev $1) || return $? +# Crear una tabla de particiones vacía. +case "$(ogGetPartitionTableType $1)" in + GPT) sgdisk -o $DISK ;; + MSDOS) echo -ne "o\nw" | fdisk $DISK ;; +esac +} + + +#/** +# 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. +#@version 0.1 - Integracion para Opengnsys - EAC: DiskEAC() en ATA.lib +#@author Antonio J. Doblas Viso, Universidad de Malaga +#@date 2008/10/27 +#@version 0.9 - Primera version para OpenGnSys +#@author Ramon Gomez, ETSII Universidad Sevilla +#@date 2009/07/20 +#@version 1.0.6 - Soporta parámetro con UIID o etiqueta. +#@author Ramon Gomez, ETSII Universidad Sevilla +#@date 2014/07/13 +#*/ ## +function ogDevToDisk () +{ +# Variables locales. +local CACHEFILE DEV PART NVME_PREFIX d n +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_device | LABEL=str_label | UUID=str_uuid" \ + "$FUNCNAME /dev/sda => 1" \ + "$FUNCNAME /dev/sda1 => 1 1" \ + "$FUNCNAME LABEL=CACHE => 1 4" + return +fi + +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener dispositivo a partir de camino, etiqueta o UUID. +DEV="$1" +case "$DEV" in + LABEL=*) DEV=$(blkid -L "${1#*=}") ;; + PARTLABEL=*) DEV=$(realpath "/dev/disk/by-partlabel/${1#*=}" 2>/dev/null) ;; + PARTUUID=*) DEV=$(realpath "/dev/disk/by-partuuid/${1#*=}" 2>/dev/null) ;; + UUID=*) DEV=$(blkid -U "${1#*=}") ;; +esac + +# Error si no es fichero de bloques o directorio (para LVM). +[ -b "$DEV" -o -d "$DEV" ] || ogRaiseError $OG_ERR_NOTFOUND "$1" || return $? + +# Buscar en fichero de caché de discos. +CACHEFILE=/var/cache/disks.cfg +PART=$(awk -F: -v d="$DEV" '{if ($2==d) {print $1}}' $CACHEFILE 2>/dev/null) +if [ -n "$PART" ]; then + echo "$PART" + return +fi +# Si no se encuentra, procesa todos los discos para devolver su nº de orden y de partición. +n=1 +for d in $(ogDiskToDev); do +NVME_PREFIX="" +if [[ $d == *"nvme"* ]]; then + NVME_PREFIX="p" +fi + + + [ -n "$(echo $DEV | grep $d)" ] && echo "$n ${DEV#$d$NVME_PREFIX}" && return + n=$[n+1] +done +ogRaiseError $OG_ERR_NOTFOUND "$1" +return $OG_ERR_NOTFOUND +} + + +#/** +# 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. +#@note Requisitos: awk, lvm +#@version 0.1 - Integracion para Opengnsys - EAC: Disk() en ATA.lib; HIDRA: DetectarDiscos.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.9 - Primera version para OpenGnSys +#@author Ramon Gomez, ETSII Universidad Sevilla +#@date 2009-07-20 +#@version 1.0.5 - Comprobación correcta de parámetros para soportar valores > 9. +#@author Ramon Gomez, ETSII Universidad Sevilla +#@date 2013-05-07 +#@version 1.0.6 - Soportar RAID hardware y Multipath. +#@author Ramon Gomez, ETSII Universidad Sevilla +#@date 2014-09-23 +#@version 1.1.0 - Usar caché de datos y soportar pool de volúmenes ZFS. +#@author Ramon Gomez, ETSII Universidad Sevilla +#@date 2016-05-27 +#*/ ## +function ogDiskToDev () +{ +# Variables locales +local CACHEFILE ALLDISKS MPATH VOLGROUPS ZFSVOLS DISK PART ZPOOL i + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk [int_npartition]" \ + "$FUNCNAME => /dev/sda /dev/sdb" \ + "$FUNCNAME 1 => /dev/sda" \ + "$FUNCNAME 1 1 => /dev/sda1" + return +fi + +# Borrar fichero de caché de configuración si hay cambios en las particiones. +CACHEFILE=/var/cache/disks.cfg +if ! diff -q <(cat /proc/partitions) /tmp/.partitions &>/dev/null; then + # Guardar copia de las particiones definidas para comprobar cambios. + cp -a /proc/partitions /tmp/.partitions + rm -f $CACHEFILE +fi + +# Si existe una correspondencia con disco/dispositivo en el caché; mostrarlo y salir. +PART=$(awk -F: -v d="$*" '{if ($1==d) {print $2}}' $CACHEFILE 2>/dev/null) +if [ -n "$PART" ]; then + echo "$PART" + return +fi + +# Continuar para detectar nuevos dispositivos. +# Listar dispositivos de discos. +ALLDISKS=$((lsblk -n -e 1,2 -x MAJ:MIN 2>/dev/null || lsblk -n -e 1,2) | \ + awk '$6~/^disk$/ {gsub(/!/,"/"); printf "/dev/%s ",$1}') +#ALLDISKS=$(lsblk -Jdp | jq -r '.blockdevices[] | select(.type=="disk").name') +# Listar volúmenes lógicos. +VOLGROUPS=$(vgs -a --noheadings 2>/dev/null | awk '{printf "/dev/%s ",$1}') +ALLDISKS="$ALLDISKS $VOLGROUPS" + +# Detectar caminos múltiples (ignorar mensaje si no está configurado Multipath). +if MPATH=$(multipath -l -v 1 2>/dev/null | awk '{printf "/dev/mapper/%s ",$1}'; exit ${PIPESTATUS[0]}); then + # Quitar de la lista los discos que forman parte de Multipath. + for i in $(multipath -ll | awk '$6=="ready" {printf "/dev/%s ",$3}'); do + ALLDISKS="${ALLDISKS//$i/}" + done + # Añadir caminos múltiples a los discos detectados. + ALLDISKS="$ALLDISKS $MPATH" +fi + +# Detectar volúmenes ZFS. +ZFSVOLS=$(blkid | awk -F: '/zfs/ {print $1}') +ALLDISKS="$ALLDISKS $ZFSVOLS" + +# Mostrar salidas segun el número de parametros. +case $# in + 0) # Muestra todos los discos, separados por espacios. + echo $ALLDISKS + ;; + 1) # Error si el parámetro no es un número positivo. + [[ "$1" =~ ^[1-9][0-9]*$ ]] || ogRaiseError $OG_ERR_FORMAT "$1" || return $? + DISK=$(echo "$ALLDISKS" | awk -v n=$1 '{print $n}') + # Error si el fichero no existe. + [ -e "$DISK" ] || ogRaiseError $OG_ERR_NOTFOUND "$1" || return $? + # Actualizar caché de configuración y mostrar dispositivo. + echo "$*:$DISK" >> $CACHEFILE + echo "$DISK" + ;; + 2) # Error si los 2 parámetros no son números positivos. + [[ "$1" =~ ^[1-9][0-9]*$ ]] && [[ "$2" =~ ^[1-9][0-9]*$ ]] || ogRaiseError $OG_ERR_FORMAT "$1 $2" || return $? + DISK=$(echo "$ALLDISKS" | awk -v n=$1 '{print $n}') + [ -e "$DISK" ] || ogRaiseError $OG_ERR_NOTFOUND "$1" || return $? + PART="$DISK$2" + # Comprobar si es partición. + if [ -b "$PART" ]; then + # Actualizar caché de configuración y mostrar dispositivo. + echo "$*:$PART" >> $CACHEFILE + echo "$PART" + else + # Comprobar si RAID o Multipath (tener en cuenta enlace simbólico). + PART="${DISK}p$2" + if [ "$(stat -L -c "%A" "$PART" 2>/dev/null | cut -c1)" == "b" ]; then + # Actualizar caché de configuración y mostrar dispositivo. + echo "$*:$PART" >> $CACHEFILE + echo "$PART" + else + PART="" + # Comprobar si volumen lógico. /* (comentario Doxygen) + if ogCheckStringInGroup "$DISK" "$VOLGROUPS"; then + PART=$(lvscan -a 2>/dev/null | \ + awk -F\' -v n=$2 "\$2~/^${DISK//\//\\/}\// {if (NR==n) print \$2}") + [ -e "$PART" ] || ogRaiseError $OG_ERR_NOTFOUND "$1 $2" || return $? + # (comentario Doxygen) */ + fi + # Comprobar si volumen ZFS que puede ser montado. + if ogCheckStringInGroup "$DISK" "$ZFSVOLS"; then + zpool import -f -R /mnt -N -a 2>/dev/null + ZPOOL=$(blkid -s LABEL -o value $DISK) + PART=$(zfs list -Hp -o name,canmount,mountpoint -r $ZPOOL | \ + awk -v n=$2 '$2=="on" && $3!="none" {c++; if (c==n) print $1}') + fi + # Salir si no se encuentra dispositivo. + [ -n "$PART" ] || ogRaiseError $OG_ERR_NOTFOUND "$1 $2" || return $? + # Devolver camino al dispositivo. + # Actualizar caché de configuración y mostrar dispositivo. + echo "$*:$PART" >> $CACHEFILE + echo "$PART" + fi + fi + ;; + *) # Formato erroneo. + ogRaiseError $OG_ERR_FORMAT + return $OG_ERR_FORMAT + ;; +esac +} + + +#/** +# 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 +#@version 0.9.2 - Primera version para OpenGnSys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010/09/15 +#@version 1.0.6 - Soportar LVM. +#@author Universidad de Huelva +#@date 2014/09/04 +#*/ ## +function ogGetDiskSize () +{ +# Variables locales. +local DISK SIZE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk" "$FUNCNAME 1 => 244198584" + return +fi +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener el tamaño del disco. +DISK="$(ogDiskToDev $1)" || return $? +SIZE=$(awk -v D=${DISK#/dev/} '{if ($4==D) {print $3}}' /proc/partitions) +# Si no, obtener tamaño del grupo de volúmenes. +[ -z "$SIZE" ] && SIZE=$(vgs --noheadings --units=B -o dev_size $DISK 2>/dev/null | \ + awk '{print $1/1024}') + +# Mostrar salida. +[ -n "$SIZE" ] && echo "$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 +#@version 1.1.1 - Primera version para OpenGnsys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2018-02-27 +#*/ ## +function ogGetDiskType () +{ +# Variables locales +local DEV MAJOR TYPE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_device" \ + "$FUNCNAME /dev/sdb => USB" + return +fi +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener el driver del dispositivo de bloques. +[ -b "$1" ] || ogRaiseError $OG_ERR_NOTFOUND "$1" || return $? +DEV=${1#/dev/} +MAJOR=$(awk -v D="$DEV" '{if ($4==D) print $1;}' /proc/partitions) +TYPE=$(awk -v D=$MAJOR '/Block/ {bl=1} {if ($1==D&&bl) print toupper($2)}' /proc/devices) +# Devolver mnemónico del driver de dispositivo. +case "$TYPE" in + SD) + TYPE="DISK" + udevadm info -q property $1 2>/dev/null | grep -q "^ID_BUS=usb" && TYPE="USB" + ;; + BLKEXT) + TYPE="NVM" + ;; + SR|IDE*) + TYPE="CDROM" # FIXME Comprobar discos IDE. + ;; + MD|CCISS*) + TYPE="RAID" + ;; + DEVICE-MAPPER) + TYPE="MAPPER" # FIXME Comprobar LVM y RAID. + ;; +esac +echo $TYPE +} + + +#/** +# ogGetEsp +#@brief Devuelve números de disco y partición para la partición EFI (ESP). +#*/ ## +function ogGetEsp () +{ +local PART d +for d in $(blkid -o device|sort); do + # Previene error para /dev/loop0 + PART="$(ogDevToDisk $d 2>/dev/null)" || 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 + LEN=$(echo $PART | awk '{ print length($0) }') + if [ $LEN -gt 1 ]; then + if [ "$(ogGetPartitionId $PART)" == "$(ogTypeToId EFI GPT)" ]; then + echo $PART + break + fi + fi +done +} + + +#/** +# 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 +#@version 1.0.4 - Primera versión compatible con OpenGnSys. +#@author Universidad de Huelva +#@date 2012-06-03 +#@version 1.0.6b - uso de sgdisk para todo tipo de particiones. Incidencia #762 +#@author Universidad de Málaga +#@date 2016-11-10 +#*/ ## +function ogGetLastSector () +{ +# Variables locales +local DISK PART LASTSECTOR + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk [int_npart]" \ + "$FUNCNAME 1 => 488392064" \ + "$FUNCNAME 1 1 => 102400062" + return +fi + +# Obtener último sector. +case $# in + 1) # Para un disco. + DISK=$(ogDiskToDev $1) || return $? + LASTSECTOR=$(LANG=C sgdisk -p $DISK | awk '/last usable sector/ {print($(NF))}') + ;; + 2) # Para una partición. + DISK=$(ogDiskToDev $1) || return $? + PART=$(ogDiskToDev $1 $2) || return $? + LASTSECTOR=$(LANG=C sgdisk -p $DISK | awk -v P="$2" '{if ($1==P) print $3}') + ;; + *) # Error si se reciben más parámetros. + ogRaiseError $OG_ERR_FORMAT + return $? ;; +esac +echo $LASTSECTOR +} + + +#/** +# ogGetPartitionActive int_ndisk +#@brief Muestra que particion de un disco esta marcada como de activa. +#@param int_ndisk nº de orden del disco +#@return int_npart Nº de partición activa +#@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, ...). +#@version 0.9 - Primera version compatible con OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009/09/17 +#*/ ## +function ogGetPartitionActive () +{ +# Variables locales +local DISK + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk" "$FUNCNAME 1 => 1" + return +fi +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Comprobar que el disco existe y listar su partición activa. +DISK="$(ogDiskToDev $1)" || return $? +LANG=C parted -sm $DISK print 2>/dev/null | awk -F: '$7~/boot/ {print $1}' +} + + +#/** +# 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 +#@version 0.9 - Primera versión compatible con OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-03-25 +#@version 1.0.2 - Detectar partición vacía. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-12-23 +#@version 1.0.6 - Soportar LVM. +#@author Universidad de Huelva +#@date 2014-09-04 +#@version 1.1.0 - Soportar pool de volúmenes ZFS. +#@author Ramon Gomez, ETSII Universidad Sevilla +#@date 2014-11-14 +#*/ ## +function ogGetPartitionId () +{ +# Variables locales. +local DISK ID + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition" \ + "$FUNCNAME 1 1 => 7" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Detectar y mostrar el id. de tipo de partición. +DISK=$(ogDiskToDev $1) || return $? +case "$(ogGetPartitionTableType $1)" in + GPT) ID=$(sgdisk -p $DISK 2>/dev/null | awk -v p="$2" '{if ($1==p) print $6;}') || ogRaiseError $OG_ERR_NOTFOUND "$1,$2" || return $? + [ "$ID" == "8300" -a "$1 $2" == "$(ogFindCache)" ] && ID=CA00 + ;; + MSDOS) ID=$(sfdisk --id $DISK $2 2>/dev/null) || ogRaiseError $OG_ERR_NOTFOUND "$1,$2" || return $? ;; + LVM) ID=10000 ;; + ZPOOL) ID=10010 ;; +esac +echo $ID +} + + +#/** +# 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 +#@version 0.1 - Integracion para Opengnsys - EAC: SizePartition () en ATA.lib +#@author Antonio J. Doblas Viso, Universidad de Malaga +#@date 2008/10/27 +#@version 0.9 - Primera version para OpenGnSys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009/07/24 +#@version 1.1.0 - Sustituir "sfdisk" por "partx". +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2016/05/04 +#*/ ## +function ogGetPartitionSize () +{ +# Variables locales. +local PART SIZE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition" \ + "$FUNCNAME 1 1 => 10000000" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Devolver tamaño de partición, del volumen lógico o del sistema de archivos (para ZFS). +PART="$(ogDiskToDev $1 $2)" || return $? +SIZE=$(partx -gbo SIZE $PART 2>/dev/null | awk '{print int($1/1024)}') +[ -z "$SIZE" ] && SIZE=$(lvs --noheadings -o lv_size --units k $PART | awk '{printf "%d",$0}') +[ -z "$SIZE" ] && SIZE=$(ogGetFsSize $1 $2) +echo ${SIZE:-0} +} + + +#/** +# 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 +#@version 0.1 - Integracion para Opengnsys - EAC: DetectNumberPartition () en ATA.lib +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date Date: 27/10/2008 +#@version 1.0 - Uso de sfdisk Primera version para OpenGnSys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-07-24 +#@version 1.0.4 - Uso de /proc/partitions para detectar el numero de particiones +#@author Universidad de Huelva +#@date 2012-03-28 +#@version 1.0.6 - Soportar LVM. +#@author Universidad de Huelva +#@date 2014-09-04 +#@version 1.1.0 - Soportar ZFS y sustituir "sfdisk" por "partx". +#@author Ramon Gomez, ETSII Universidad Sevilla +#@date 2016-04-28 +#*/ ## +function ogGetPartitionsNumber () +{ +# Variables locales. +local DISK +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk" \ + "$FUNCNAME 1 => 3" + return +fi +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Contar el nº de veces que aparece el disco en su lista de particiones. +DISK=$(ogDiskToDev $1) 2>/dev/null +case "$(ogGetPartitionTableType $1)" in + GPT|MSDOS) + partx -gso NR $DISK 2>/dev/null | awk -v p=0 '{p=$1} END {print p}' ;; + LVM) lvs --noheadings $DISK 2>/dev/null | wc -l ;; + ZPOOL) zpool list &>/dev/null || modprobe zfs + zpool import -f -R /mnt -N -a 2>/dev/null + zfs list -Hp -o name,canmount,mountpoint -r $(blkid -s LABEL -o value $DISK) | \ + awk '$2=="on" && $3!="none" {c++} + END {print c}' + ;; +esac +} + + +#/** +# 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 +#@version 1.0.4 - Primera versión para OpenGnSys +#@author Universidad de Huelva +#@date 2012/03/01 +#@version 1.0.6 - Soportar LVM. +#@author Universidad de Huelva +#@date 2014-09-04 +#@version 1.1.0 - Mejorar rendimiento y soportar ZFS. +#@author Ramon Gomez, ETSII Universidad Sevilla +#@date 2014-11-14 +#*/ ## +function ogGetPartitionTableType () +{ +# Variables locales. +local DISK TYPE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk" \ + "$FUNCNAME 1 => MSDOS" + return +fi +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Sustituye n de disco por su dispositivo. +DISK=$(ogDiskToDev $1) || return $? + +# Comprobar tabla de particiones. +if [ -b $DISK ]; then + TYPE=$(parted -sm $DISK print 2>/dev/null | awk -F: -v D=$DISK '{ if($1 == D) print toupper($6)}') + [ -z "$TYPE" ] && TYPE=$(parted -sm $DISK print 2>/dev/null | awk -F: -v D=$DISK '{ if($1 == D) print toupper($6)}') +fi +# Comprobar si es volumen lógico. +[ -d $DISK ] && vgs $DISK &>/dev/null && TYPE="LVM" +# Comprobar si es pool de ZFS. +[ -z "$TYPE" -o "$TYPE" == "UNKNOWN" ] && [ -n "$(blkid -s TYPE $DISK | grep zfs)" ] && TYPE="ZPOOL" + +# Mostrar salida. +[ -n "$TYPE" ] && echo "$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. +#@version 0.1 - Integracion para Opengnsys - EAC: TypeFS() en ATA.lib +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2008-10-27 +#@version 0.9 - Primera adaptacion para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-07-21 +#@version 1.0.3 - Código trasladado de antigua función ogGetFsType. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-12-01 +#@version 1.0.5 - Usar función ogIdToType para hacer la conversión id. a tipo. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2013-09-19 +#*/ ## +function ogGetPartitionType () +{ +# Variables locales. +local ID TYPE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition" \ + "$FUNCNAME 1 1 => NTFS" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Detectar id. de tipo de partición y codificar al mnemonico. +ID=$(ogGetPartitionId "$1" "$2") || return $? +TYPE=$(ogIdToType "$ID") +echo "$TYPE" +} + + +#/** +# 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. +#@version 1.0 - Versión en pruebas. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010/01/12 +#@version 1.1.1 - Se incluye tipo Windows para UEFI (ticket #802) +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2019/01/18 +#*/ ## +function ogHidePartition () +{ +# Variables locales. +local PART TYPE NEWTYPE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition" \ + "$FUNCNAME 1 1" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? +PART=$(ogDiskToDev "$1" "$2") || return $? + +# Obtener tipo de partición. +TYPE=$(ogGetPartitionType "$1" "$2") +case "$TYPE" in + NTFS) NEWTYPE="HNTFS" ;; + FAT32) NEWTYPE="HFAT32" ;; + FAT16) NEWTYPE="HFAT16" ;; + FAT12) NEWTYPE="HFAT12" ;; + WINDOWS)NEWTYPE="WIN-RESERV";; + *) ogRaiseError $OG_ERR_PARTITION "$TYPE" + return $? ;; +esac +# Cambiar tipo de partición. +ogSetPartitionType $1 $2 $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. +#@version 1.0.5 - Primera version para OpenGnSys +#@author Ramon Gomez, ETSII Universidad Sevilla +#@date 2013-02-07 +#*/ ## +function ogIdToType () +{ +# Variables locales +local ID TYPE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_idpart" \ + "$FUNCNAME 83 => LINUX" + return +fi +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener valor hexadecimal de 4 caracteres rellenado con 0 por delante. +ID=$(printf "%4s" "$1" | tr ' ' '0') +case "${ID,,}" in + 0000) TYPE="EMPTY" ;; + 0001) TYPE="FAT12" ;; + 0005|000f) TYPE="EXTENDED" ;; + 0006|000e) TYPE="FAT16" ;; + 0007) TYPE="NTFS" ;; + 000b|000c) TYPE="FAT32" ;; + 0011) TYPE="HFAT12" ;; + 0012) TYPE="COMPAQDIAG" ;; + 0016|001e) TYPE="HFAT16" ;; + 0017) TYPE="HNTFS" ;; + 001b|001c) TYPE="HFAT32" ;; + 0042) TYPE="WIN-DYNAMIC" ;; + 0082|8200) TYPE="LINUX-SWAP" ;; + 0083|8300) TYPE="LINUX" ;; + 008e|8E00) TYPE="LINUX-LVM" ;; + 00a5|a503) TYPE="FREEBSD" ;; + 00a6) TYPE="OPENBSD" ;; + 00a7) TYPE="CACHE" ;; # (compatibilidad con Brutalix) + 00af|af00) TYPE="HFS" ;; + 00be|be00) TYPE="SOLARIS-BOOT" ;; + 00bf|bf0[0145]) TYPE="SOLARIS" ;; + 00ca|ca00) TYPE="CACHE" ;; + 00da) TYPE="DATA" ;; + 00ee) TYPE="GPT" ;; + 00ef|ef00) TYPE="EFI" ;; + 00fb) TYPE="VMFS" ;; + 00fd|fd00) TYPE="LINUX-RAID" ;; + 0700) TYPE="WINDOWS" ;; + 0c01) TYPE="WIN-RESERV" ;; + 7f00) TYPE="CHROMEOS-KRN" ;; + 7f01) TYPE="CHROMEOS" ;; + 7f02) TYPE="CHROMEOS-RESERV" ;; + 8301) TYPE="LINUX-RESERV" ;; + a500) TYPE="FREEBSD-DISK" ;; + a501) TYPE="FREEBSD-BOOT" ;; + a502) TYPE="FREEBSD-SWAP" ;; + ab00) TYPE="HFS-BOOT" ;; + af01) TYPE="HFS-RAID" ;; + bf02) TYPE="SOLARIS-SWAP" ;; + bf03) TYPE="SOLARIS-DISK" ;; + ef01) TYPE="MBR" ;; + ef02) TYPE="BIOS-BOOT" ;; + 10000) TYPE="LVM-LV" ;; + 10010) TYPE="ZFS-VOL" ;; + *) TYPE="UNKNOWN" ;; +esac +echo "$TYPE" +} + + +# 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 "-". +#@version 1.1.0 - Primera versión para OpenGnsys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2016-04-08 +#*/ ## +function ogIsDiskLocked () +{ +# Variables locales +local DISK LOCKFILE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk" \ + "if $FUNCNAME 1; then ... ; fi" + return +fi +# Falso, en caso de error. +[ $# == 1 ] || return 1 +DISK="$(ogDiskToDev $1 2>/dev/null)" || return 1 + +# Comprobar existencia de fichero de bloqueo para el disco. +LOCKFILE="/var/lock/lock${DISK//\//-}" +test -f $LOCKFILE +} + + +#/** +# 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. +#@version 0.9 - Primera versión para OpenGnSys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009/07/24 +#*/ ## +function ogListPartitions () +{ +# Variables locales. +local DISK PART NPARTS TYPE SIZE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk" \ + "$FUNCNAME 1 => NTFS:10000000 EXT3:5000000 LINUX-SWAP:1000000" + return +fi +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT "$FORMAT" || return $? + +# Procesar la salida de \c parted . +DISK="$(ogDiskToDev $1)" || return $? +NPARTS=$(ogGetPartitionsNumber $1) +for (( PART = 1; PART <= NPARTS; PART++ )); do + TYPE=$(ogGetPartitionType $1 $PART 2>/dev/null); TYPE=${TYPE:-EMPTY} + SIZE=$(ogGetPartitionSize $1 $PART 2>/dev/null); SIZE=${SIZE:-0} + echo -n "$TYPE:$SIZE " +done +echo +} + + +#/** +# 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 +#*/ ## +function ogListPrimaryPartitions () +{ +# Variables locales. +local PTTYPE PARTS + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk" \ + "$FUNCNAME 1 => NTFS:10000000 EXT3:5000000 EXTENDED:1000000" + return +fi + +PTTYPE=$(ogGetPartitionTableType $1) || return $? +PARTS=$(ogListPartitions "$@") || return $? +case "$PTTYPE" in + GPT) echo $PARTS | sed 's/\( EMPTY:0\)*$//' ;; + MSDOS) echo $PARTS | cut -sf1-4 -d" " | sed 's/\( EMPTY:0\)*$//' ;; +esac +} + + +#/** +# 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 +#*/ ## +function ogListLogicalPartitions () +{ +# Variables locales. +local PTTYPE PARTS + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk" \ + "$FUNCNAME 1 => LINUX-SWAP:999998" + return +fi +PTTYPE=$(ogGetPartitionTableType $1) || return $? +[ "$PTTYPE" == "MSDOS" ] || ogRaiseError $OG_ERR_PARTITION "" || return $? +PARTS=$(ogListPartitions "$@") || return $? +echo $PARTS | cut -sf5- -d" " +} + + +#/** +# 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 "-". +#@version 1.1.0 - Primera versión para OpenGnsys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2016-04-07 +#*/ ## +function ogLockDisk () +{ +# Variables locales +local DISK LOCKFILE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk" \ + "$FUNCNAME 1" + return +fi +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener partición. +DISK="$(ogDiskToDev $1)" || return $? + +# Crear archivo de bloqueo exclusivo. +LOCKFILE="/var/lock/lock${DISK//\//-}" +touch $LOCKFILE +} + + +#/** +# 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 +#@version 0.1 - Integracion para Opengnsys - EAC: SetPartitionActive() en ATA.lib +#@author Antonio J. Doblas Viso, Universidad de Malaga +#@date 2008/10/27 +#@version 0.9 - Primera version compatible con OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009/09/17 +#*/ ## +function ogSetPartitionActive () +{ +# Variables locales +local DISK PART + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition" \ + "$FUNCNAME 1 1" + return +fi + +# Si el EFI esta activo me salgo. +ogIsEfiActive && ogEcho session log warning "EFI: $MSG_DONTUSE $FUNCNAME" && return + +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Comprobar que el disco existe y activar la partición indicada. +DISK="$(ogDiskToDev $1)" || return $? +PART="$(ogDiskToDev $1 $2)" || return $? +parted -s $DISK set $2 boot on 2>/dev/null +} + + +#/** +# 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 +#@version 0.1 - Integracion para Opengnsys - SetPartitionType() en ATA.lib +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2008/10/27 +#@version 1.0.4 - Soporte para discos GPT. +#@author Universidad de Huelva +#@date 2012/03/13 +#@version 1.0.5 - Utiliza el id. de tipo de partición (no el mnemónico) +#@author Universidad de Huelva +#@date 2012/05/14 +#*/ ## +function ogSetPartitionId () +{ +# Variables locales +local DISK PART PTTYPE ID + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition hex_partid" \ + "$FUNCNAME 1 1 7" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Sustituye nº de disco y nº partición por su dispositivo. +DISK=$(ogDiskToDev $1) || return $? +PART=$(ogDiskToDev $1 $2) || return $? +# Error si el id. de partición no es hexadecimal. +ID="${3^^}" +[[ "$ID" =~ ^[0-9A-F]+$ ]] || ogRaiseError $OG_ERR_OUTOFLIMIT "$3" || return $? + +# Elección del tipo de partición. +PTTYPE=$(ogGetPartitionTableType $1) +case "$PTTYPE" in + GPT) sgdisk -t$2:$ID $DISK 2>/dev/null ;; + MSDOS) sfdisk --id $DISK $2 $ID 2>/dev/null ;; + *) ogRaiseError $OG_ERR_OUTOFLIMIT "$1,$PTTYPE" + return $? ;; +esac + +# MSDOS) Correcto si fdisk sin error o con error pero realiza Syncing +if [ "${PIPESTATUS[1]}" == "0" -o $? -eq 0 ]; then + partprobe $DISK 2>/dev/null + return 0 +else + ogRaiseError $OG_ERR_PARTITION "$1,$2,$3" + return $? +fi +} + + +#/** +# 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. +#@version 0.9 - Primera versión para OpenGnSys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009/07/24 +#*/ ## +function ogSetPartitionSize () +{ +# Variables locales. +local DISK PART SIZE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition int_size" \ + "$FUNCNAME 1 1 10000000" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener el tamaño de la partición. +DISK="$(ogDiskToDev $1)" || return $? +PART="$(ogDiskToDev $1 $2)" || return $? +# Convertir tamaño en KB a sectores de 512 B. +SIZE=$[$3*2] || ogRaiseError $OG_ERR_FORMAT || return $? +# Redefinir el tamaño de la partición. +sfdisk -f -uS -N$2 $DISK <<< ",$SIZE" &>/dev/null || ogRaiseError $OG_ERR_PARTITION "$1,$2" || return $? +partprobe $DISK 2>/dev/null +} + + +#/** +# 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 +#@version 0.1 - Integracion para Opengnsys - SetPartitionType() en ATA.lib +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2008/10/27 +#@version 1.0.4 - Soporte para discos GPT. +#@author Universidad de Huelva +#@date 2012/03/13 +#@version 1.0.5 - Renombrada de ogSetPartitionId. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2013/03/07 +#*/ ## +function ogSetPartitionType () +{ +# Variables locales +local DISK PART PTTYPE ID + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition str_type" \ + "$FUNCNAME 1 1 NTFS" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Sustituye nº de disco por su dispositivo. +DISK=`ogDiskToDev $1` || return $? +PART=`ogDiskToDev $1 $2` || return $? + +# Elección del tipo de partición. +PTTYPE=$(ogGetPartitionTableType $1) +ID=$(ogTypeToId "$3" "$PTTYPE") +[ -n "$ID" ] || ogRaiseError $OG_ERR_FORMAT "$3,$PTTYPE" || return $? +ogSetPartitionId $1 $2 $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) +#@version 0.1 - Integracion para Opengnsys - EAC: TypeFS () en ATA.lib +#@author Antonio J. Doblas Viso, Universidad de Malaga +#@date 2008/10/27 +#@version 0.9 - Primera version para OpenGnSys +#@author Ramon Gomez, ETSII Universidad Sevilla +#@date 2009-12-14 +#@version 1.0.4 - Soportar discos GPT (sustituye a ogFsToId). +#@author Universidad de Huelva +#@date 2012/03/30 +#*/ ## +function ogTypeToId () +{ +# Variables locales +local PTTYPE ID="" + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME str_parttype [str_tabletype]" \ + "$FUNCNAME LINUX => 83" \ + "$FUNCNAME LINUX MSDOS => 83" + return +fi +# Error si no se reciben 1 o 2 parámetros. +[ $# -lt 1 -o $# -gt 2 ] && (ogRaiseError $OG_ERR_FORMAT; return $?) + +# Asociar id. de partición para su mnemónico. +PTTYPE=${2:-"MSDOS"} +case "$PTTYPE" in + GPT) # Se incluyen mnemónicos compatibles con tablas MSDOS. + case "$1" in + EMPTY) ID=0 ;; + WINDOWS|NTFS|EXFAT|FAT32|FAT16|FAT12|HNTFS|HFAT32|HFAT16|HFAT12) + ID=0700 ;; + WIN-RESERV) ID=0C01 ;; + CHROMEOS-KRN) ID=7F00 ;; + CHROMEOS) ID=7F01 ;; + CHROMEOS-RESERV) ID=7F02 ;; + LINUX-SWAP) ID=8200 ;; + LINUX|EXT[234]|REISERFS|REISER4|XFS|JFS) + ID=8300 ;; + LINUX-RESERV) ID=8301 ;; + LINUX-LVM) ID=8E00 ;; + FREEBSD-DISK) ID=A500 ;; + FREEBSD-BOOT) ID=A501 ;; + FREEBSD-SWAP) ID=A502 ;; + FREEBSD) ID=A503 ;; + HFS-BOOT) ID=AB00 ;; + HFS|HFS+) ID=AF00 ;; + HFSPLUS) ID=AF00 ;; + HFS-RAID) ID=AF01 ;; + SOLARIS-BOOT) ID=BE00 ;; + SOLARIS) ID=BF00 ;; + SOLARIS-SWAP) ID=BF02 ;; + SOLARIS-DISK) ID=BF03 ;; + CACHE) ID=CA00;; + EFI) ID=EF00 ;; + LINUX-RAID) ID=FD00 ;; + esac + ;; + MSDOS) + case "$1" in + EMPTY) ID=0 ;; + FAT12) ID=1 ;; + EXTENDED) ID=5 ;; + FAT16) ID=6 ;; + WINDOWS|NTFS|EXFAT) + ID=7 ;; + FAT32) ID=b ;; + HFAT12) ID=11 ;; + HFAT16) ID=16 ;; + HNTFS) ID=17 ;; + HFAT32) ID=1b ;; + LINUX-SWAP) ID=82 ;; + LINUX|EXT[234]|REISERFS|REISER4|XFS|JFS) + ID=83 ;; + LINUX-LVM) ID=8e ;; + FREEBSD) ID=a5 ;; + OPENBSD) ID=a6 ;; + HFS|HFS+) ID=af ;; + SOLARIS-BOOT) ID=be ;; + SOLARIS) ID=bf ;; + CACHE) ID=ca ;; + DATA) ID=da ;; + GPT) ID=ee ;; + EFI) ID=ef ;; + VMFS) ID=fb ;; + LINUX-RAID) ID=fd ;; + esac + ;; + LVM) + case "$1" in + LVM-LV) ID=10000 ;; + esac + ;; + ZVOL) + case "$1" in + ZFS-VOL) ID=10010 ;; + esac + ;; +esac +echo $ID +} + + +#/** +# 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. +#@version 1.0 - Versión en pruebas. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010/01/12 +#@version 1.1.1 - Se incluye tipo Windows Reserver para UEFI (ticket #802) +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2019/01/18 +#*/ ## +function ogUnhidePartition () +{ +# Variables locales. +local PART TYPE NEWTYPE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition" \ + "$FUNCNAME 1 1" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? +PART=$(ogDiskToDev "$1" "$2") || return $? + +# Obtener tipo de partición. +TYPE=$(ogGetPartitionType "$1" "$2") +case "$TYPE" in + HNTFS) NEWTYPE="NTFS" ;; + HFAT32) NEWTYPE="FAT32" ;; + HFAT16) NEWTYPE="FAT16" ;; + HFAT12) NEWTYPE="FAT12" ;; + WIN-RESERV) NEWTYPE="WINDOWS" ;; + *) ogRaiseError $OG_ERR_PARTITION "$TYPE" + return $? ;; +esac +# Cambiar tipo de partición. +ogSetPartitionType $1 $2 $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 "-". +#@version 1.1.0 - Primera versión para OpenGnsys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2016-04-08 +#*/ ## +function ogUnlockDisk () +{ +# Variables locales +local DISK LOCKFILE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk" \ + "$FUNCNAME 1" + return +fi +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener partición. +DISK="$(ogDiskToDev $1)" || return $? + +# Borrar archivo de bloqueo exclusivo. +LOCKFILE="/var/lock/lock${DISK//\//-}" +rm -f $LOCKFILE +} + + +#/** +# 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 +#@version 0.1 - Integracion para Opengnsys - EAC: UpdatePartitionTable() en ATA.lib +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 27/10/2008 +#*/ ## +function ogUpdatePartitionTable () +{ +local i +for i in `ogDiskToDev` +do + partprobe $i +done +} diff --git a/client/engine/File.lib b/client/engine/File.lib new file mode 100755 index 0000000..1be1a33 --- /dev/null +++ b/client/engine/File.lib @@ -0,0 +1,422 @@ +#!/bin/bash +#/** +#@file File.lib +#@brief Librería o clase File +#@class File +#@brief Funciones para gestión de archivos y directorios. +#@version 1.0.4 +#@warning License: GNU GPLv3+ +#*/ + + +#/** +# ogCalculateChecksum [ str_repo | int_ndisk int_npart ] path_filepath +#@brief Devuelve la suma de comprobación (checksum) de un fichero. +#@param path_filepath camino del fichero (independiente de mayúsculas) +#@param str_repo repositorio de ficheros +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@return hex_checksum Checksum del fichero +#@version 0.9.2 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010-07-24 +#@version 1.0.4 - Calcula solo el checksum del último MB del fichero. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2012-03-16 +#*/ ## +function ogCalculateChecksum () +{ +# Variables locales. +local FILE +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ str_repo | int_ndisk int_npartition ] path_filepath" \ + "$FUNCNAME REPO ubuntu.img ==> ef899299caf8b517ce36f1157a93d8bf" + return +fi + +# Comprobar que existe el fichero y devolver sus datos. +FILE=$(ogGetPath "$@") +[ -n "$FILE" ] || ogRaiseError $OG_ERR_NOTFOUND "$*" || return $? +tail -c1M "$FILE" | md5sum -b 2>&1 | cut -f1 -d" " +} + + +#/** +# ogCompareChecksumFiles [ str_repo | int_ndisk int_npart ] path_source [ str_repo | int_ndisk int_npart ] path_target +#@brief Metafunción que compara las sumas de comprobación almacenadas de 2 ficheros. +#@return bool_compare Valor de comparación. +#@warning No es necesario especificar la extensión ".sum". +#@version 0.9.2 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010-07-24 +#*/ ## +function ogCompareChecksumFiles () +{ +# Variables locales. +local ARGS SOURCE TARGET +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ str_repo | int_ndisk int_npartition ] path_filepath" \ + "if $FUNCNAME REPO ubuntu.img CACHE ubuntu.img; then ...; fi" + return +fi + +ARGS="$@" +case "$1" in + /*) # Camino completo. */ (Comentrio Doxygen) + SOURCE=$(ogGetPath "$1") + shift ;; + [1-9]*) # ndisco npartición. + SOURCE=$(ogGetPath "$1" "$2" "$3") + shift 3 ;; + *) # Otros: repo, cache, cdrom (no se permiten caminos relativos). + SOURCE=$(ogGetPath "$1" "$2") + shift 2 ;; +esac +TARGET=$(ogGetPath "$@") + +# Comparar los ficheros de checksum. +test "$(cat "$SOURCE.sum" 2>/dev/null)" == "$(cat "$TARGET.sum" 2>/dev/null)" +} + + +#/** +# ogCalculateFullChecksum [ str_repo | int_ndisk int_npart ] path_filepath +#@brief Devuelve la suma COMPLETA de comprobación (checksum) de un fichero. +#@param path_filepath camino del fichero (independiente de mayúsculas) +#@param str_repo repositorio de ficheros +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@return hex_checksum Checksum del fichero +#@version 1.0.5 - Primera versión para OpenGnSys. +#@author Antonio Doblas Viso, EVLT Universidad de Málaga +#@date 2014-07-09 +#*/ ## +function ogCalculateFullChecksum () +{ +# Variables locales. +local FILE +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ str_repo | int_ndisk int_npartition ] path_filepath" \ + "$FUNCNAME REPO ubuntu.img ==> ef899299caf8b517ce36f1157a93d8bf" + return +fi + +# Comprobar que existe el fichero y devolver sus datos. +FILE=$(ogGetPath "$@") +[ -n "$FILE" ] || ogRaiseError $OG_ERR_NOTFOUND "$*" || return $? +#ADV +md5sum "$FILE" -b 2>&1 | cut -f1 -d" " +# tail -c1M "$FILE" | md5sum -b 2>&1 | cut -f1 -d" " +} + + + + +#/** +# ogCopyFile [ str_repo | int_ndisk int_npart ] path_source [ str_repo | int_ndisk int_npart ] path_target +#@brief Metafunción para copiar un fichero de sistema OpenGnSys a un directorio. +#@see ogGetPath +#@return Progreso de la copia. +#@warning Deben existir tanto el fichero origen como el directorio destino. +#@version 0.9 - Pruebas con OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-10-20 +#@version 1.0.4 - Copiar usando rsync. +#@author Universidad de Huelva +#@date 2012-07-06 +#*/ ## +function ogCopyFile () +{ +# Variables locales. +local ARGS SOURCE TARGET +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ str_repo | int_ndisk int_npartition ] path_source [ str_repo | int_ndisk int_npartition ] path_target" \ + "$FUNCNAME REPO newfile.txt 1 2 /tmp/newfile.txt" + return +fi + +ARGS="$@" +case "$1" in + /*) # Camino completo. */ (Comentrio Doxygen) + SOURCE="$(ogGetPath "$1")" + shift ;; + [1-9]*) # ndisco npartición. + SOURCE="$(ogGetPath "$1" "$2" "$3")" + shift 3 ;; + *) # Otros: repo, cache, cdrom (no se permiten caminos relativos). + SOURCE="$(ogGetPath "$1" "$2")" + shift 2 ;; +esac +# Comprobar fichero origen y directorio destino. +[ -n "$SOURCE" ] || ogRaiseError $OG_ERR_NOTFOUND "${ARGS% $*}" || return $? +TARGET="$(ogGetPath "$@")" +[ -n "$TARGET" ] || ogRaiseError $OG_ERR_NOTFOUND "$*" || return $? +# Copiar fichero (para evitar problemas de comunicaciones las copias se hacen con rsync en vez de cp). +rsync --progress --inplace -avh "$SOURCE" "$TARGET" +} + + +#/** +# ogDeleteFile [ str_repo | int_ndisk int_npartition ] path_filepath +#@brief Metafunción que borra un fichero de un dispositivo. +#@see ogGetPath +#@version 0.9 - Pruebas con OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-29 +#*/ ## +function ogDeleteFile () +{ +# Variables locales. +local FILE +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ str_repo | int_ndisk int_npartition ] path_file" \ + "$FUNCNAME 1 2 /tmp/newfile.txt" + return +fi + +# Comprobar que existe el fichero y borrarlo. +FILE="$(ogGetPath "$@")" +[ -n "$FILE" ] || ogRaiseError $OG_ERR_NOTFOUND "$*" || return $? +rm -f "$FILE" || ogRaiseError $OG_ERR_NOTFOUND "$*" || return $? +} + + +#/** +# ogDeleteTree [ str_repo | int_ndisk int_npartition ] path_dirpath +#@brief Metafunción que borra todo un subárbol de directorios de un dispositivo. +#@see ogGetPath +#@version 0.9 - Pruebas con OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-29 +#*/ ## +function ogDeleteTree () +{ +# Variables locales. +local DIR +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ str_repo | int_ndisk int_npartition ] path_dir" \ + "$FUNCNAME 1 2 /tmp/newdir" + return +fi + +# Comprobar que existe el directorio y borrarlo con su contenido. +DIR="$(ogGetPath "$@")" +[ -n "$DIR" ] || ogRaiseError $OG_ERR_NOTFOUND "$*" || return $? +rm -fr "$DIR" || ogRaiseError $OG_ERR_NOTFOUND "$*" || return $? +} + + +#/** +# ogGetPath [ str_repo | int_ndisk int_npartition ] path_filepath +#@brief Inicia el proceso de arranque de un sistema de archivos. +#@param path_filepath camino del fichero (independiente de mayúsculas) +#@param str_repo repositorio de ficheros +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@return path_file - camino completo real del fichero. +#@note repo = { REPO, CACHE, CDROM } +#@note Requisitos: \c grep \c sed +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Fichero o dispositivo no encontrado. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar. +#@warning En caso de error, sólo devuelve el código y no da mensajes. +#@todo Terminar de definir parámetros para acceso a repositorios. +#@version 0.1 - Integracion para Opengnsys - HIDRA: CaminoWindows.sh; EAC: GetPath(), FormatSintaxSpacePath(), FormatSintaxBackSlashPath (), en FileSystem.lib +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@Date 2008/10/10 +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2008/10/27 +#@version 0.9 - Pruebas con OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-15 +#@version 1.1.1 - Correccion comentarios autodocumentacion doxygen . +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2018-07-05 +#*/ ## + +function ogGetPath () +{ +# Variables locales. +local MNTDIR FILE PREVFILE FILEPATH CURRENTDIR + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ str_repo | int_ndisk int_npartition ] path_filepath" \ + "$FUNCNAME \"/mnt/sda1/windows/system32\" ==> /mnt/sda1/WINDOWS/System32" \ + "$FUNCNAME REPO /etc/fstab ==> /opt/opengnsys/images/etc/fstab" \ + "$FUNCNAME 1 1 \"/windows/system32\" ==> /mnt/sda1/WINDOWS/System32" + return +fi + +# Procesar camino según el número de parámetros. +case $# in + 1) FILE="$1" ;; + 2) case "${1^^}" in + REPO) + FILE="$OGIMG/$2" ;; + CACHE) + MNTDIR="$(ogMountCache)" || return $? + FILE="$MNTDIR/$OGIMG/$2" ;; + CDROM) + MNTDIR="$(ogMountCdrom)" || return $? + FILE="$MNTDIR/$2" ;; + *) ogRaiseError $OG_ERR_FORMAT + return $? ;; + esac ;; + 3) MNTDIR="$(ogMount $1 $2)" || return $? + FILE="$MNTDIR/$3" ;; + *) ogRaiseError $OG_ERR_FORMAT + return $? ;; +esac + +# Eliminar caracteres \c / duplicados y finales. + +FILE="$(echo $FILE|sed -e 's/\(\/\)*\1/\//g; s/\/$//')" +# Comprobar si existe el fichero para reducir tiempos. +if [ -e "$FILE" ]; then + FILEPATH="$FILE" +else + # Buscar el nombre correcto en cada subdirectorio del camino. + FILEPATH="/" + + while [ "$FILE" != "$PREVFILE" ]; do + FILEPATH="$(ls -d "${FILEPATH%/}/${FILE%%/*}" 2>/dev/null || find "$FILEPATH" -maxdepth 1 -iname "${FILE%%/*}" -print 2>/dev/null)" #*/ (Comentario Doxygen) + PREVFILE="$FILE" + FILE="${FILE#*/}" + done + +fi +[ -n "$FILEPATH" ] && echo "$FILEPATH" +return 0 +} + + +#/** +# ogGetParentPath [ str_repo | int_ndisk int_npartition ] path_filepath +#@brief Metafunción que devuelve el camino del directorio padre. +#@see ogGetPath +#@version 0.9 - Pruebas con OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-29 +#*/ ## + +function ogGetParentPath () +{ +local PARENT +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ str_repo | int_ndisk int_npartition ] path_filepath" \ + "$FUNCNAME \"/mnt/sda1/windows/system32\" ==> /mnt/sda1/WINDOWS" \ + "$FUNCNAME REPO /etc/fstab ==> /opt/opengnsys/images/etc" \ + "$FUNCNAME 1 1 \"/windows/system32\" ==> /mnt/sda1/WINDOWS" + return +fi + +case $# in + 1) PARENT="$(dirname "$1")" ;; + 2) PARENT="$1 $(dirname "/$2")" ;; + 3) PARENT="$1 $2 $(dirname "/$3")" ;; + *) ogRaiseError $OG_ERR_FORMAT + return $? ;; +esac +ogGetPath $PARENT +} + + +#/** +# ogIsNewerFile [ str_repo | int_ndisk int_npart ] path_source [ str_repo | int_ndisk int_npart ] path_target +#@brief Metafunción que indica se un fichero es más nuevo que otro. +#@see ogGetPath +#@return Código de salida: 0 - nuevo, 1 - antiguo o error +#@warning Deben existir tanto el fichero origen como el destino. +#@version 0.9.2 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010-07-24 +#@version 1.0.1 - Devolver falso en caso de error. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-05-18 +#*/ ## +function ogIsNewerFile () +{ +# Variables locales. +local ARGS SOURCE TARGET +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ str_repo | int_ndisk int_npartition ] path_source [ str_repo | int_ndisk int_npartition ] path_target" \ + "if $FUNCNAME REPO ubuntu.img CACHE ubuntu.img; then ... fi" + return +fi + +ARGS="$@" +case "$1" in + /*) # Camino completo. */ (Comentrio Doxygen) + SOURCE="$(ogGetPath "$1")" + shift ;; + [1-9]*) # ndisco npartición. + SOURCE="$(ogGetPath "$1" "$2" "$3")" + shift 3 ;; + *) # Otros: repo, cache, cdrom (no se permiten caminos relativos). + SOURCE="$(ogGetPath "$1" "$2")" + shift 2 ;; +esac +# Comprobar que existen los ficheros origen y destino. +[ -n "$SOURCE" ] || ogRaiseError $OG_ERR_NOTFOUND "${ARGS% $*}" || return 1 +TARGET=$(ogGetPath "$@") +[ -n "$TARGET" ] || ogRaiseError $OG_ERR_NOTFOUND "$*" || return 1 +# Devolver si el primer fichero se ha modificado después que el segundo. +test "$SOURCE" -nt "$TARGET" +} + + +#/** +# ogMakeChecksumFile [ str_repo | int_ndisk int_npart ] path_filepath +#@brief Metafunción que guarda el valor de comprobación de un fichero. +#@see ogCalculateChecksum +#@warning Genera un fichero con extensión ".sum". +#@version 0.9.2 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010-07-24 +#*/ ## +function ogMakeChecksumFile () +{ +# Variables locales. +local FILE +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ str_repo | int_ndisk int_npartition ] path_filepath" \ + "$FUNCNAME REPO ubuntu.img" + return +fi + +# Comprobar que existe el fichero y guardar su checksum. +FILE="$(ogGetPath "$@")" +[ -n "$FILE" ] || ogRaiseError $OG_ERR_NOTFOUND "$*" || return $? +ogCalculateChecksum "$FILE" > "$FILE.sum" +} + + +#/** +# ogMakeDir [ str_repo | int_ndisk int_npartition ] path_dirpath +#@brief Metafunción que crea un subdirectorio vacío en un dispositivo. +#@see ogGetParentPath +#@version 0.1 - Integracion para Opengnsys - HIDRA: CrearDirectorio.sh, EAC: MkdirPath() en FileSystem.lib +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@Date 2008/10/10 +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2008/10/27 +#@version 0.9 - Pruebas con OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-29 +#*/ ## +function ogMakeDir () +{ +local PARENT DIR +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ str_repo | int_ndisk int_npartition ] path_dir" \ + "$FUNCNAME 1 2 /tmp/newdir" + return +fi + +PARENT="$(ogGetParentPath "$@")" || return $? +DIR="$(basename "${!#}")" +mkdir -p "$PARENT/$DIR" || ogRaiseError $OG_ERR_NOTFOUND "$*" || return $? +} + diff --git a/client/engine/FileSystem.lib b/client/engine/FileSystem.lib new file mode 100755 index 0000000..560250d --- /dev/null +++ b/client/engine/FileSystem.lib @@ -0,0 +1,1205 @@ +#!/bin/bash +#/** +#@file FileSystem.lib +#@brief Librería o clase FileSystem +#@class FileSystem +#@brief Funciones para gestión de sistemas de archivos. +#@version 1.1.0 +#@warning License: GNU GPLv3+ +#*/ + + +#/** +# 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. +#@version 0.9 - Primera adaptación para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-10-07 +#@version 1.0.2 - Ignorar códigos de salida de comprobación (no erróneos). +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-09-23 +#@version 1.0.4 - Soportar HFS/HFS+. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2012-05-21 +#@version 1.0.5 - Desmontar antes de comprobar, soportar Btrfs y ExFAT. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2012-09-05 +#@version 1.1.0 - Soportar F2FS. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2016-05-03 +#*/ ## +function ogCheckFs () +{ +# Variables locales. +local PART TYPE PROG PARAMS CODES ERRCODE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys" \ + "$FUNCNAME 1 1" + return +fi + +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Obtener partición. +PART="$(ogDiskToDev $1 $2)" || return $? + +TYPE=$(ogGetFsType $1 $2) +case "$TYPE" in + EXT[234]|CACHE) PROG="e2fsck"; PARAMS="-y"; CODES=(1 2) ;; + BTRFS) PROG="btrfsck"; CODES=(1) ;; + REISERFS) PROG="fsck.reiserfs"; PARAMS="<<<\"Yes\""; CODES=(1 2) ;; + 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" ;; + *) ogRaiseError $OG_ERR_PARTITION "$1, $2, $TYPE" + return $? ;; +esac +# Error si el sistema de archivos esta montado o bloqueado. +ogUnmount $1 $2 +if ogIsMounted $1 $2; then + ogRaiseError $OG_ERR_PARTITION "$1 $2" # Indicar nuevo error + return $? +fi +if ogIsLocked $1 $2; then + ogRaiseError $OG_ERR_LOCKED "$1 $2" + return $? +fi +# Comprobar en modo uso exclusivo. +ogLock $1 $2 +trap "ogUnlock $1 $2" 1 2 3 6 9 +eval $PROG $PARAMS $PART +ERRCODE=$? +case $ERRCODE in + 0|${CODES[*]}) + ERRCODE=0 ;; + 127) ogRaiseError $OG_ERR_NOTEXEC "$PROG" + ERRCODE=$OG_ERR_NOTEXEC ;; + *) ogRaiseError $OG_ERR_PARTITION "$1 $2" + ERRCODE=$OG_ERR_PARTITION ;; +esac +ogUnlock $1 $2 +return $ERRCODE +} + + +#/** +# 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* +#@version 0.1 - Integracion para Opengnsys - EAC: EnlargeFileSystem() en ATA.lib +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2008-10-27 +#@version 0.9 - Primera adaptacion para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-23 +#@version 1.0.5 - Soporte para BTRFS. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2012-06-28 +#*/ ## +function ogExtendFs () +{ +# Variables locales. +local PART TYPE PROG PARAMS ERRCODE DOMOUNT + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys" \ + "$FUNCNAME 1 1" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener partición. +PART="$(ogDiskToDev $1 $2)" || return $? + +# Redimensionar al tamano máximo según el tipo de partición. +TYPE=$(ogGetFsType $1 $2) +case "$TYPE" in + EXT[234]) PROG="resize2fs"; PARAMS="-f" ;; + BTRFS) PROG="btrfs"; PARAMS="filesystem resize max" + DOMOUNT=1 # Debe estar montado. + ;; + REISERFS|REISER4) + PROG="resize_reiserfs"; PARAMS="-f" ;; + F2FS) ;; # No se reduce (por el momento). + JFS) ;; # No se reduce (por el momento). + NILFS2) ;; # No se reduce (probar "nilfs-resize"). + XFS) ;; # No se reduce (por el momento). + NTFS) PROG="ntfsresize"; PARAMS="<<<\"y\" -f" ;; + EXFAT) ;; # No se reduce (por el momento). + FAT32|FAT16) ;; # No se reduce (probar "fatresize"). + HFS|HFSPLUS) ;; # No se reduce (por el momento). + UFS) ;; # No se reduce (por el momento). + *) ogRaiseError $OG_ERR_PARTITION "$1 $2 $TYPE" + return $? ;; +esac +# Salida normal si no se va a aplicar la operación. +[ -z "$PROG" ] && return +# Error si el sistema de archivos no se queda en el estado de montaje adecuado. +if [ "$DOMOUNT" ]; then + PART=$(ogMount $1 $2) || return $? # Indicar nuevo error +else + ogUnmount $1 $2 2>/dev/null + if ogIsMounted $1 $2; then + ogRaiseError $OG_ERR_PARTITION "$1 $2" # Indicar nuevo error + return $? + fi +fi +# Error si el sistema de archivos está bloqueado. +if ogIsLocked $1 $2; then + ogRaiseError $OG_ERR_LOCKED "$1 $2" + return $? +fi +# Redimensionar en modo uso exclusivo. +ogLock $1 $2 +trap "ogUnlock $1 $2" 1 2 3 6 9 +eval $PROG $PARAMS $PART &>/dev/null +ERRCODE=$? +case $ERRCODE in + 0) ;; + 127) ogRaiseError $OG_ERR_NOTEXEC "$PROG" + ERRCODE=$OG_ERR_NOTEXEC ;; + *) ogRaiseError $OG_ERR_PARTITION "$1 $2" + ERRCODE=$OG_ERR_PARTITION ;; +esac +ogUnlock $1 $2 +return $ERRCODE +} + + +#/** +# ogFormat int_ndisk int_nfilesys | CACHE +#@see ogFormatFs ogFormatCache +#*/ ## +function ogFormat () +{ +case "$*" in + CACHE|cache) ogFormatCache ;; + *) ogFormatFs "$@" ;; +esac +} + + +#/** +# 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. +#@version 0.9 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-10-08 +#@version 1.0.4 - Solucionado error cuando no se detecta tipo de sistema de ficheros pero si se indica. +#@author Universidad de Huelva +#@date 2012-04-11 +#@version 1.0.5 - Comprobar errores al inicio e independizar del tipo de tabla de particiones. +#@author Universidad de Huelva +#@date 2013-05-16 +#@version 1.1.0 - Soportar F2FS y NILFS. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2016-05-03 +#*/ ## +function ogFormatFs () +{ +# Variables locales +local PART ID TYPE LABEL PROG PARAMS LABELPARAM ERRCODE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys [str_label]" \ + "$FUNCNAME 1 1" \ + "$FUNCNAME 1 1 EXT4" \ + "$FUNCNAME 1 1 \"DATA\"" \ + "$FUNCNAME 1 1 EXT4 \"DATA\"" + return +fi +# Error si no se reciben entre 2 y 4 parámetros. +[ $# -ge 2 -a $# -le 4 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Obtener fichero de dispositivo. +PART="$(ogDiskToDev $1 $2)" || return $? +# Error si la partición está montada o bloqueada. +if ogIsMounted $1 $2; then + ogRaiseError $OG_ERR_DONTFORMAT "$MSG_MOUNT: $1 $2" + return $? +fi +if ogIsLocked $1 $2; then + ogRaiseError $OG_ERR_LOCKED "$1 $2" + return $? +fi +# Si no se indica el tipo de sisitema de archivos, intentar obtenerlo. +TYPE="${3:-$(ogGetFsType $1 $2)}" +# Error, si no especifica el tipo de sistema de archivos a formatear. +[ -n "$TYPE" ] || ogRaiseError $OG_ERR_FORMAT "$1 $2 ..." || return $? + +# Elegir tipo de formato. +case "$TYPE" in + 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 <<<\"y\"" ;; + XFS) PROG="mkfs.xfs"; PARAMS="-f" ;; + JFS) PROG="mkfs.jfs"; PARAMS="<<<\"y\"" ;; + 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" ;; + *) ogRaiseError $OG_ERR_PARTITION "$1 $2 $TYPE" + return $? ;; +esac + +# Etiquetas de particion. +if [ -z "$LABEL" ]; then + [ "$4" != "CACHE" ] || ogRaiseError $OG_ERR_FORMAT "$MSG_RESERVEDVALUE: CACHE" || return $? + [ -n "$4" ] && PARAMS="$PARAMS ${LABELPARAM:-"-L"} $4" +else + PARAMS="$PARAMS ${LABELPARAM:-"-L"} $LABEL" +fi + +# Formatear en modo uso exclusivo (desmontar siempre). +ogLock $1 $2 +trap "ogUnlock $1 $2" 1 2 3 6 9 +umount $PART 2>/dev/null +eval $PROG $PARAMS $PART 2>/dev/null +ERRCODE=$? +case $ERRCODE in + 0) ;; + 127) ogRaiseError $OG_ERR_NOTEXEC "$PROG" ;; + *) ogRaiseError $OG_ERR_PARTITION "$1 $2" ;; +esac +ogUnlock $1 $2 +return $ERRCODE +} + + +#/** +# 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. +#@version 0.1 - Integracion para Opengnsys - EAC: SizeFileSystem() en FileSystem.lib +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2008-10-27 +#@version 1.0.4 - Adaptación de las salidas. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2012-06-18 +#*/ ## +function ogGetFsSize () +{ +# Variables locales. +local MNTDIR UNIT VALUE FACTOR SIZE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition [str_unit]" \ + "$FUNCNAME 1 1 => 15624188" \ + "$FUNCNAME 1 1 KB => 15624188" + return +fi +# Error si no se reciben 2 o 3 parámetros. +[ $# == 2 ] || [ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Obtener unidad y factor de medida. +UNIT="$3" +UNIT=${UNIT:-"KB"} +case "$UNIT" in + [kK]B) + FACTOR=1 ;; + MB) FACTOR=1024 ;; + GB) FACTOR=$[1024*1024] ;; + TB) FACTOR=$[1024*1024*1024] ;; + *) ogRaiseError $OG_ERR_FORMAT "$3 != { KB, MB, GB, TB }" + return $? ;; +esac + +# Obtener el tamaño del sistema de archivo (si no está formateado; tamaño = 0). +MNTDIR="$(ogMount $1 $2 2>/dev/null)" +if [ -n "$MNTDIR" ]; then + VALUE=$(df -BK "$MNTDIR" | awk '{getline; print $2}') + SIZE=$(echo "$VALUE $FACTOR" | awk '{printf "%f\n", $1/$2}') +else + SIZE=0 +fi +# Devolver el tamaño (quitar decimales si son 0). +echo ${SIZE%.0*} +} + + +#/** +# 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. +#@version 0.1 - Integracion para Opengnsys - EAC: TypeFS() en ATA.lib +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2008-10-27 +#@version 0.9 - Primera adaptacion para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-07-21 +#@version 1.0.2 - Obtención de datos reales de sistemas de ficheros. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-12-02 +#@version 1.0.5 - Usar "blkid" para detectar tipo de sistema de archivo. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2014-06-10 +#@version 1.1.0 - Detectar volumen ZFS. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2014-11-14 +#*/ ## +function ogGetFsType () +{ +# Variables locales. +local PART TYPE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys" \ + "$FUNCNAME 1 1 => NTFS" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Detectar tipo de sistema de archivo (independientemente del tipo de partición). +PART=$(ogDiskToDev "$1" "$2") || return $? +if [[ "$PART" =~ ^/ ]]; then + TYPE=$(blkid -o export $PART | awk -F= '$1~/^TYPE/ { print toupper($2) }') +else + zfs mount $PART 2>/dev/null + TYPE=$(mount | awk "\$1==\"$PART\" { print toupper(\$5) }") +fi + +# Componer valores correctos. +case "$TYPE" in + EXT4) # Comprobar si es caché o Ext4. + if [ "$1 $2" == "$(ogFindCache)" ]; then + ogIsFormated $1 $2 2>/dev/null && TYPE="CACHE" + fi + ;; + VFAT) TYPE="$(blkid -po export $PART | awk -F= '$1~/^VERSION$/ { print toupper($2) }')" ;; + SWAP) TYPE="LINUX-SWAP" ;; + LVM*) TYPE="LINUX-LVM" ;; + *RAID*) TYPE="LINUX-RAID" ;; + ZFS_MEMBER) TYPE="ZVOL" ;; + *_MEMBER) TYPE="${TYPE/_MEMBER/}" ;; +esac + +[ -n "$TYPE" ] && echo "$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 +#@version 0.9 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-10-15 +#@version 1.0.6 - Usar comando findmnt. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2014-09-04 +#*/ ## +function ogGetMountPoint () +{ +# Variables locales +local PART +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys" \ + "$FUNCNAME 1 1 => /mnt/sda1" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Obtener partición. +PART="$(ogDiskToDev $1 $2)" || return $? + +# Devolver punto de montaje. +findmnt -n -o TARGET $PART +} + + +#/** +# 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: 0 - formateado, 1 - sin formato o error. +#@version 0.91 - Adaptación inicial para comprobar que existe caché. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010-03-18 +#@version 1.0.1 - Devolver falso en caso de error. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-05-18 +#@version 1.0.5 - Dejar de usar "parted". +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2012-09-04 +#@version 1.1.0 - Comprobar sin montar el sistema de ficheros. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2016-01-21 +#*/ ## +function ogIsFormated () +{ +# Variables locales +local PART +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys" \ + "if $FUNCNAME 1 1; then ... ; fi" + return +fi +# Falso, en caso de error. +[ $# == 2 ] || return 1 +PART="$(ogDiskToDev $1 $2 2>/dev/null)" || return 1 + +# Revisar tipo de sistema de ficheros. +if [[ "$PART" =~ ^/ ]]; then + # Sistemas de ficheros genéricos. + test -n "$(blkid -s TYPE $PART | egrep -vi "swap|_member")" +else + # ZFS. + test "$(zfs list -Hp -o canmount $PART 2>/dev/null)" = "on" +fi +} + + +#/** +# ogIsLocked int_ndisk int_npartition +#@see ogIsPartitionLocked +#*/ +function ogIsLocked () +{ +ogIsPartitionLocked "$@" +} + +#/** +# 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 "-". +#@version 0.9 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-03 +#@version 1.0.1 - Devolver falso en caso de error. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-05-18 +#@version 1.1.0 - Comprobar si el disco está también bloqueado. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2016-04-08 +#*/ ## +function ogIsPartitionLocked () +{ +# Variables locales +local DISK PART LOCKDISK LOCKPART + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition" \ + "if $FUNCNAME 1 1; then ... ; fi" + return +fi +# Falso, en caso de error. +[ $# == 2 ] || return 1 +PART="$(ogDiskToDev $1 $2 2>/dev/null)" || return 1 +DISK="$(ogDiskToDev $1)" + +# Comprobar existencia de fichero de bloqueo de la partición o de su disco. +LOCKDISK="/var/lock/lock${DISK//\//-}" +LOCKPART="/var/lock/lock${PART//\//-}" +test -f $LOCKDISK -o -f $LOCKPART +} + + +#/** +# 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. +#@version 0.9 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-10-15 +#@version 1.0.1 - Devolver falso en caso de error. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-05-18 +#*/ ## +function ogIsMounted () +{ +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys" \ + "if $FUNCNAME 1 1; then ... ; fi" + return +fi +# Falso, en caso de error. +[ $# == 2 ] || return 1 + +test -n "$(ogGetMountPoint $1 $2)" +} + + +#/** +# 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 +#*/ ## + +function ogIsReadonly () +{ +# Variables locales +local PART + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_filesys" \ + "if $FUNCNAME 1 1; then ... ; fi" + return +fi +# Falso, en caso de error. +[ $# == 2 ] || return 1 +PART="$(ogDiskToDev $1 $2 2>/dev/null)" || return 1 + +test -n "$(findmnt -n -o OPTIONS $PART | awk 'BEGIN {RS=","} /^ro$/ {print}')" +} + + +#/** +# 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. +#@version 1.0.5 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2013-10-09 +#*/ ## +function ogIsWritable () +{ +# Variables locales +local PART + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_filesys" \ + "if $FUNCNAME 1 1; then ... ; fi" + return +fi +# Falso, en caso de error. +[ $# == 2 ] || return 1 +PART="$(ogDiskToDev $1 $2 2>/dev/null)" || return 1 + +test -n "$(findmnt -n -o OPTIONS $PART | awk 'BEGIN {RS=","} /^rw$/ {print}')" +} + + +#/** +# ogLock int_ndisk int_npartition +#@see ogLockPartition +#*/ +function ogLock () +{ +ogLockPartition "$@" +} + +#/** +# 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 "-". +#@version 0.9 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-03 +#*/ ## +function ogLockPartition () +{ +# Variables locales +local PART LOCKFILE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition" \ + "$FUNCNAME 1 1" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener partición. +PART="$(ogDiskToDev $1 $2)" || return $? + +# Crear archivo de bloqueo exclusivo. +LOCKFILE="/var/lock/lock${PART//\//-}" +touch $LOCKFILE +} + + +#/** +# ogMount int_ndisk int_nfilesys +#@see ogMountFs ogMountCache ogMountCdrom +#*/ ## +function ogMount () +{ +case "$*" in + CACHE|cache) + ogMountCache ;; + CDROM|cdrom) + ogMountCdrom ;; + *) ogMountFs "$@" ;; +esac +} + + +#/** +# 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 +#*/ ## +function ogMountFirstFs () +{ +# Variables locales +local PART NPARTS MNTDIR + +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener nº de particiones del disco. +NPARTS=$(ogGetPartitionsNumber "$1") || return $? +for (( PART = 1; PART <= NPARTS; PART++ )); do + MNTDIR=$(ogMount $1 $PART 2>/dev/null) + if [ -n "$MNTDIR" ]; then + echo "$MNTDIR" + return 0 + fi +done +ogRaiseError $OG_ERR_NOTFOUND "$1" +return $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. +#@version 0.1 - Integracion para Opengnsys - EAC: MountPartition() en FileSystem.lib +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2008-10-27 +#@version 0.9 - Primera version para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-28 +#@version 1.0.5 - Independiente del tipo de sistema de ficheros. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2012-09-04 +#@version 1.1.0 - Montar sistema de archivos ZFS y NTFS hibernado. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2016-09-19 +#*/ ## +function ogMountFs () +{ +# Variables locales +local PART MNTDIR + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys" \ + "$FUNCNAME 1 1 => /mnt/sda1" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener partición. +PART="$(ogDiskToDev "$1" "$2")" || return $? + +# Comprobar si el sistema de archivos ya está montada. +MNTDIR="$(ogGetMountPoint $1 $2)" +# Si no, montarlo en un directorio de sistema. +if [ -z "$MNTDIR" ]; then + # Error si la particion esta bloqueada. + if ogIsLocked $1 $2; then + ogRaiseError $OG_ERR_LOCKED "$MSG_PARTITION, $1 $2" + return $? + fi + # El camino de un dispositivo normal comienza por el carácter "/". + if [[ "$PART" =~ ^/ ]]; then + # Crear punto de montaje o enlace simbólico para caché local. + MNTDIR=${PART/dev/mnt} + DEBUG="no" + if [ "$(ogFindCache)" == "$1 $2" -a -n "$OGCAC" ]; then + mkdir -p $OGCAC + ln -fs $OGCAC $MNTDIR + else + mkdir -p $MNTDIR + fi + unset DEBUG + # Montar sistema de archivos. + mount $PART $MNTDIR &>/dev/null || \ + mount $PART $MNTDIR -o force,remove_hiberfile &>/dev/null + case $? in + 0) # Correcto. + ;; + 14) # Intentar limpiar hibernación NTFS y montar. + ntfsfix -d $PART &>/dev/null && mount $PART $MNTDIR &>/dev/null || \ + ogRaiseError $OG_ERR_PARTITION "$1, $2" || return $? + ;; + *) # Probar montaje de solo lectura. + mount $PART $MNTDIR -o ro &>/dev/null || \ + ogRaiseError $OG_ERR_PARTITION "$1, $2" || return $? + ;; + esac + # Aviso de montaje de solo lectura. + if ogIsReadonly $1 $2; then + ogEcho warning "$FUNCNAME: $MSG_MOUNTREADONLY: \"$1, $2\"" + fi + else + # Montar sistema de archivos ZFS (un ZPOOL no comienza por "/"). + zfs mount $PART 2>/dev/null + fi +fi +echo "$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 +#*/ ## +function ogMountCdrom () +{ +local DEV MNTDIR +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" "$FUNCNAME" + return +fi +# Error si se reciben parámetros. +[ $# == 0 ] || ogRaiseError $OG_ERR_FORMAT || return $? +DEV="/dev/cdrom" # Por defecto +MNTDIR=$(mount | awk -v D=$DEV '{if ($1==D) {print $3}}') +if [ -z "$MNTDIR" ]; then + MNTDIR=${DEV/dev/mnt} + mkdir -p $MNTDIR + mount -t iso9660 $DEV $MNTDIR || ogRaiseError $OG_ERR_PARTITION "cdrom" || return $? +fi +echo $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* +#@version 0.1 - Integracion para Opengnsys - EAC: ReduceFileSystem() en ATA.lib +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2008-10-27 +#@version 0.9 - Primera version para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-23 +#@version 0.9.2 - Añadir un 10% al tamaño mínimo requerido. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010-09-27 +#@version 1.0 - Deteccion automatica del tamaño minimo adecuado +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2011-02-24 +#@version 1.0.6 - Integrar código de antigua función "ogReduceFsCheck". +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2014-10-28 +#@version 1.1.1b - Detectar metadispositivos. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2020-02-24 +#*/ ## +function ogReduceFs () +{ +# Variables locales +local PART BLKS SIZE MAXSIZE EXTRASIZE=0 RETVAL + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys" \ + "$FUNCNAME 1 1" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener partición. +PART="$(readlink -f "$(ogDiskToDev $1 $2)")" || return $? + +# Redimensionar según el tipo de particion. +case "$(ogGetFsType $1 $2)" in + EXT[234]) + ogUnmount $1 $2 &>/dev/null + resize2fs -fpM $PART &>/dev/null || ogRaiseError $OG_ERR_PARTITION "$1,$2" || return $? + ;; + + BTRFS) + MNTDIR=$(ogMount $1 $2) + # Calcular tamaño ocupado + 10%, redondeado + 1 (incluyendo letra de unidad). + SIZE=$(btrfs filesystem show $MNTDIR | awk -v P=$PART '{ d=""; "readlink -f "$8" 2>/dev/null"|getline d; if(d==P) printf("%d%s", $6*1.1+1, substr($6,match($6,/[A-Z]/),1)) }') + btrfs filesystem resize ${SIZE} $MNTDIR &>/dev/null + ;; + REISERFS|REISER4) + # Calcular tamaño ocupado + 10%. + MNTDIR=$(ogMount $1 $2) + SIZE=$[ $(df -k $MNTDIR | awk '{getline;print $3}') * 110 / 100 ] + ogUnmount $1 $2 2>/dev/null + resize_reiserfs -s${SIZE}K $PART <<<"y" + ;; + + F2FS) ;; # No se reduce (por el momento). + JFS) ;; # No se reduce (por el momento). + NILFS2) ;; # No se reduce (probar "nilfs-resize"). + XFS) ;; # No se reduce (por el momento). + + NTFS) + # Calcular tamaño ocupado + 10%. + ogUnmount $1 $2 &>/dev/null + read -e MAXSIZE SIZE <<<$(ntfsresize -fi $PART | \ + awk '/device size/ {d=$4} + /resize at/ {r=int($5*1.1/1024+1)*1024} + END { print d,r}') + # Error si no puede obtenerse el tamaño máximo del volumen. + [ -n "$MAXSIZE" -a -n "$SIZE" ] || ogRaiseError $OG_ERR_PARTITION "$1,$2" || return $? + # Simular la redimensión y comprobar si es necesario ampliarala. + RETVAL=1 + while [ $RETVAL != 0 -a $[ SIZE+=EXTRASIZE ] -lt $MAXSIZE ]; do + # Obtener espacio de relocalización y devolver código de salida + # (ntfsresize devuelve 0 si no necesita relocalizar). + EXTRASIZE=$(ntfsresize -fns $SIZE $PART 2>/dev/null | \ + awk '/Needed relocations/ {print int($4*1.1/1024+1)*1024}' + exit ${PIPESTATUS[0]}) + RETVAL=$? + done + # Redimensionar solo si hace falta. + if [ $SIZE -lt $MAXSIZE ]; then + ntfsresize -fs $SIZE $PART <<<"y" >/dev/null || ogRaiseError $OG_ERR_PARTITION "$1,$2" || return $? + fi + ;; + + EXFAT) ;; # No se reduce (por el momento). + FAT32|FAT16) # Se deja comentado por no haber un método seguro para extender el SF. + # Calcular tamaño ocupado + 10%. + #ogUnmount $1 $2 &>/dev/null + #SIZE=$(fatresize --info $PART | awk -F: '/Min size/ {printf("%d", $2*1.1)}') + #[ "$SIZE" ] && fatresize --size $SIZE $PART &>/dev/null + ;; + HFS|HFSPLUS) ;; # No se reduce (por el momento). + UFS) ;; # No se reduce (por el momento). + + *) ogRaiseError $OG_ERR_PARTITION "$1,$2" + return $? ;; +esac + +# Devuelve tamaño del sistema de ficheros. +ogGetFsSize $1 $2 +} + + +#/** +# ogUnlock int_ndisk int_npartition +#@see ogUnlockPartition +#*/ ## +function ogUnlock () +{ +ogUnlockPartition "$@" +} + +#/** +# 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 "-". +#@version 0.9 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-03 +#*/ ## +function ogUnlockPartition () +{ +# Variables locales +local PART LOCKFILE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition" \ + "$FUNCNAME 1 1" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener partición. +PART="$(ogDiskToDev $1 $2)" || return $? + +# Borrar archivo de bloqueo exclusivo. +LOCKFILE="/var/lock/lock${PART//\//-}" +rm -f $LOCKFILE +} + + +#/** +# ogUnmount int_ndisk int_npartition +#@see ogUnmountFs +#*/ ## +function ogUnmount () +{ +ogUnmountFs "$@" +} + +#/** +# 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. +#@version 0.1 - Integracion para Opengnsys - EAC: UmountPartition() en FileSystem.lib +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2008-10-27 +#@version 0.9 - Primera version para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-28 +#*/ ## +function ogUnmountFs () +{ +# Variables locales +local PART MNTDIR + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition" "$FUNCNAME 1 1" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener partición y punto de montaje. +PART="$(ogDiskToDev $1 $2)" || return $? +MNTDIR="$(ogGetMountPoint $1 $2)" + +# Si está montada, desmontarla. +if [ -n "$MNTDIR" ]; then + # Error si la particion está bloqueada. + if ogIsLocked $1 $2; then + ogRaiseError $OG_ERR_LOCKED "$MSG_PARTITION $1, $2" + return $? + fi + # Desmontar y borrar punto de montaje. + umount $PART 2>/dev/null || ogEcho warning "$FUNCNAME: $MSG_DONTUNMOUNT: \"$1, $2\"" + rmdir $MNTDIR 2>/dev/null || rm -f $MNTDIR 2>/dev/null +else + ogEcho warning "$MSG_DONTMOUNT: \"$1,$2\"" +fi +} + + +#/** +# 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. +#@version 0.9 - Versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-10-07 +#*/ ## +function ogUnmountAll () +{ +# Variables locales +local DISK PART +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk" "FUNCNAME 1" + return +fi +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener partición y punto de montaje. +DISK="$(ogDiskToDev $1)" || return $? +for ((PART=1; PART<=$(ogGetPartitionsNumber $1); PART++)); do + case "$(ogGetFsType $1 $PART)" in + CACHE) ;; + *) ogUnmount $1 $PART 2>/dev/null ;; + esac +done +} + +#/** +# 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. +#@version 1.1.0 - Versión para OpenGnsys. +#@author Carmelo Cabezuelo, ASIC Universidad Politécnica de Valencia +#@date 2016-04-20 +#*/ ## +function ogUnsetDirtyBit () +{ +# Variables locales +local PART +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk" "FUNCNAME 1" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener partición y punto de montaje. +case "$(ogGetFsType $1 $2)" in + NTFS) + ogUnmount $1 $2 2>/dev/null + PART="$(ogDiskToDev $1 $2)" || return $? + ntfsfix -d $PART ;; + *) ;; +esac +} + + +#/** +# 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. +#@version +#@author +#@date +#*/ ## + +function ogGetFreeSize () +{ +local particion unit factor valor +if [ $# = 0 ] +then + echo "sintaxis: ogGetFreeSize int_disco int_partition str_SizeOutput [ kB MB GB -default GB]-]" red + echo "devuelve int_size : int_data : int_free" red +return +fi +if [ $# -ge 2 ] +then + particion=`ogMount $1 $2 ` #1>/dev/null 2>&1 + if [ -z $3 ] + then + unit=kB # s B kB MB GB TB % + else + unit=$3 + fi + case $unit in + kB) + factor="1.024"; + #valor=`df | grep $particion | awk -F" " '{size=$2*1.024; used=$3*1.024; free=$4*1.024; printf "%d:%d:%d", size,used,free}'` + valor=`df | grep $particion | awk -F" " '{size=$2*1.024; used=$3*1.024; free=$4*1.024; printf "%d", free}'` + ;; + MB) + factor="1.024/1000"; + valor=`df | grep $particion | awk -F" " '{size=$2*1.024/1000; used=$3*1.024/1000; free=$4*1.024/1000; printf "%d:%d:%d", size,used,free}'` + ;; + GB) + factor="1.024/1000000"; + valor=`df | grep $particion | awk -F" " '{size=$2*1.024/1000000; used=$3*1.024/1000000; free=$4*1.024/1000000; printf "%f:%f:%f", size,used,free}'` + ;; + esac + #echo $valor + #NumberRound $valor + #valor=`NumberRound $valor`; + echo $valor +fi +} + diff --git a/client/engine/Image.lib b/client/engine/Image.lib new file mode 100755 index 0000000..cf9eb83 --- /dev/null +++ b/client/engine/Image.lib @@ -0,0 +1,1209 @@ +#!/bin/bash +#/** +#@file Image.lib +#@brief Librería o clase Image +#@class Image +#@brief Funciones para creación, restauración y clonación de imágenes de sistemas. +#@version 1.1.0 +#@warning License: GNU GPLv3+ +#*/ + + +#/** +# ogCreateImageSyntax path_device path_filename [str_tool] [str_compressionlevel] +#@brief Genera una cadena de texto con la instrucción para crear un fichero imagen +#@param path_device dispositivo Linux del sistema de archivos +#@param path_fileneme path absoluto del fichero imagen +#@param [opcional] str_tool herrmaienta de clonacion [partimage, partclone, ntfsclone] +#@param [opcional] str_compressionlevel nivel de compresion. [0 -none-, 1-lzop-, 2-gzip] +#@return str_command - cadena con el comando que se debe ejecutar. +#@warning Salida nula si se producen errores. +#@TODO introducir las herramientas fsarchiver, dd +#@version 1.0 - Primeras pruebas +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date 2010/02/08 +#@version 1.0.5 - Incrustar códico de antigua función ogPartcloneSyntax +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2012/09/14 +#*/ ## +function ogCreateImageSyntax() +{ +local FS TOOL LEVEL DEV IMGFILE BUFFER PARAM1 PARAM2 PARAM3 + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_device path_imagefile [str_tool] [str_compressionlevel]" \ + "$FUNCNAME /dev/sda1 /opt/opengnsys/images/prueba.img partclone lzop" \ + "$FUNCNAME /dev/sda1 /opt/opengnsys/images/prueba.img" + return +fi +# Error si no se reciben entre 2 y 4 parámetros. +[ $# -ge 2 -a $# -le 4 ] || ogRaiseError $OG_ERR_FORMAT "$*" || return $? + +# Asignación de parámetros. +DEV="$1" +IMGFILE="$2" +case "$#" in + 2) # Sintaxis por defecto OG DEV IMGFILE + TOOL="partclone" + LEVEL="gzip" + ;; + 4) # Sintaxis condicionada. + TOOL="${3,,}" + LEVEL="${4,,}" + ;; +esac + +case "$TOOL" in + ntfsclone) + PARAM1="ntfsclone --force --save-image -O - $DEV" + ;; + partimage|default) + PARAM1="partimage -M -f3 -o -d -B gui=no -c -z0 --volume=0 save $DEV stdout" + ;; + partclone) + FS="$(ogGetFsType $(ogDevToDisk $DEV 2>/dev/null) 2>/dev/null)" + case "$FS" in + EXT[234]) PARAM1="partclone.extfs" ;; + BTRFS) PARAM1="partclone.btrfs" ;; + REISERFS) PARAM1="partclone.reiserfs" ;; + REISER4) PARAM1="partclone.reiser4" ;; + JFS) PARAM1="partclone.jfs" ;; + XFS) PARAM1="partclone.xfs" ;; + F2FS) PARAM1="partclone.f2fs" ;; + NILFS2) PARAM1="partclone.nilfs2" ;; + NTFS) PARAM1="partclone.ntfs" ;; + EXFAT) PARAM1="partclone.exfat" ;; + FAT16|FAT32) PARAM1="partclone.fat" ;; + HFS|HFSPLUS) PARAM1="partclone.hfsp" ;; + UFS) PARAM1="partclone.ufs" ;; + VMFS) PARAM1="partclone.vmfs" ;; + *) PARAM1="partclone.imager" ;; + esac + # Por compatibilidad, si no existe el ejecutable usar por defecto "parclone.dd". + which $PARAM1 &>/dev/null || PARAM1="partclone.dd" + PARAM1="$PARAM1 -d0 -F -c -s $DEV" + # Algunas versiones de partclone.dd no tienen opción "-c". + [ -z "$(eval ${PARAM1%% *} --help 2>&1 | grep -- -c)" ] && PARAM1="${PARAM1/ -c / }" + ;; +esac +# Comprobar que existe mbuffer. +which mbuffer &>/dev/null && PARAM2="| mbuffer -q -m 40M " || PARAM2=" " + +# Nivel de compresion. +case "$LEVEL" in + 0|none) PARAM3=" > " ;; + 1|lzop) PARAM3=" | lzop > " ;; + 2|gzip) PARAM3=" | gzip -c > " ;; + 3|bzip) PARAM3=" | bzip -c > " ;; +esac + +# Sintaxis final. +[ -n "$PARAM1" ] && echo "$PARAM1 $PARAM2 $PARAM3 $IMGFILE" +} + + +#/** +# ogRestoreImageSyntax path_filename path_device [str_tools] [str_compressionlevel] +#@brief Genera una cadena de texto con la instrucción para crear un fichero imagen +#@param path_device dispositivo Linux del sistema de archivos +#@param path_fileneme path absoluto del fichero imagen +#@param [opcional] str_tools herrmaienta de clonacion [partimage, partclone, ntfsclone] +#@param [opcional] str_compressionlevel nivel de compresion. [0 -none-, 1-lzop-, 2-gzip] +#@return cadena con el comando que se debe ejecutar. +#@exception OG_ERR_FORMAT formato incorrecto. +#@warning En pruebas iniciales +#@TODO introducir las herramientas fsarchiver, dd +#@TODO introducir el nivel de compresion gzip +#@version 1.0 - Primeras pruebas +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date 2010/02/08 +#*/ ## +function ogRestoreImageSyntax () +{ +local TOOL COMPRESSOR LEVEL PART IMGFILE FILEHEAD INFOIMG + + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME filename partition [tool] [levelcompresor]" \ + "$FUNCNAME /opt/opengnsys/images/prueba.img /dev/sda1 [partclone] [lzop]" + return +fi + +# Error si no se reciben entre 2 y 4 parámetros. +[ $# -ge 2 -a $# -le 4 ] || ogRaiseError $OG_ERR_FORMAT "$*" || return $? + +# controlamos que el parametro 1 (imagen) es tipo file. +[ -f $1 ] || ogRaiseError $OG_ERR_NOTFOUND "$1" || return $? + +# Si 2 parametros (file-origen-, device-destino-) = ogGetImageFull($1) +if [ "$#" -eq 2 ]; then + IMGFILE=$1 + PART=$2 + INFOIMG=$(ogGetImageInfo $IMGFILE) || ogRaiseError $OG_ERR_NOTFOUND "No Image $1" || return $? + TOOL=`echo $INFOIMG | cut -f1 -d:` + COMPRESSOR=`echo $INFOIMG | cut -f2 -d:` + ogRestoreImageSyntax $IMGFILE $PART $TOOL $COMPRESSOR +fi + + +# Si cuatro parametros genera sintaxis +if [ "$#" -eq 4 ]; then + IMGFILE=$1 + PART=$2 + # comprobamos parametro herramienta compresion. + TOOL=$(echo $3 | tr [A-Z] [a-z]) + #ogCheckProgram $TOOL + #comprobar parámetro compresor. + LEVEL=$(echo $4 | tr [A-Z] [a-z]) + #ogCheckProgram $LEVEL + + case "$LEVEL" in + "0"|"none") + COMPRESSOR=" " + ;; + "1"|"lzop" | "LZOP") + COMPRESSOR=" lzop -dc " + ;; + "2"|"gzip" | "GZIP") + COMPRESSOR=" gzip -dc " + ;; + "3"|"bzip" | "BZIP" ) + COMPRESSOR=" bzip -dc " + ;; + *) + ogRaiseError $OG_ERR_NOTFOUND "Compressor no valid $TOOL" || return $? + ;; + esac + #comprobar mbuffer + which mbuffer > /dev/null && MBUFFER="| mbuffer -q -m 40M " || MBUFFER=" " + + case "${TOOL,,}" in + ntfsclone) + TOOL="| ntfsclone --restore-image --overwrite $PART -" + ;; + partimage) + TOOL="| partimage -f3 -B gui=no restore $PART stdin" + ;; + partclone*) + # -C para que no compruebe tamaños + TOOL="| partclone.restore -d0 -C -I -o $PART" + ;; + dd) + TOOL="| pv | dd conv=sync,noerror bs=1M of=$PART" + ;; + *) + ogRaiseError $OG_ERR_NOTFOUND "Tools imaging no valid $TOOL" || return $? + ;; + esac + + echo "$COMPRESSOR $IMGFILE $MBUFFER $TOOL" +fi + +} + + + + +#/** +# ogCreateDiskImage int_ndisk str_repo path_image [str_tools] [str_compressionlevel] +#@brief Crea una imagen (copia de seguridad) de un disco completo. +#@param int_ndisk nº de orden del disco +#@param str_repo repositorio de imágenes (remoto o caché local) +#@param path_image camino de la imagen (sin extensión) +#@return (nada, por determinar) +#@note repo = { REPO, CACHE } +#@note Esta primera versión crea imágenes con dd comprimidas con gzip. +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. +#@exception OG_ERR_LOCKED particion bloqueada por otra operación. +#@exception OG_ERR_IMAGE error al crear la imagen del sistema. +#@warning En pruebas iniciales +#@todo Gestión de bloqueos de disco +#@todo Comprobar si debe desmontarse la caché local +#@todo Comprobar que no se crea la imagen en el propio disco +#@version 1.1.0 - Primera versión para OpenGnsys con herramientas prefijadas. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@Date 2016/04/08 +#*/ ## +function ogCreateDiskImage () +{ +# Variables locales +local DISK PROGRAM IMGDIR IMGFILE IMGTYPE ERRCODE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk str_repo path_image" \ + "$FUNCNAME 1 REPO /disk1" + return +fi +# Error si no se reciben entre 3 y 5 parámetros. +[ $# -ge 3 -a $# -le 5 ] || ogRaiseError $OG_ERR_FORMAT "$*" || return $? + +# Comprobar que no está bloqueada ni la partición, ni la imagen. +DISK="$(ogDiskToDev $1)" || return $? +if ogIsDiskLocked $1; then + ogRaiseError $OG_ERR_LOCKED "$MSG_LOCKED $1" + return $? +fi +IMGTYPE="dsk" # Extensión genérica de imágenes de disco. +IMGDIR=$(ogGetParentPath "$2" "$3") +[ -n "$IMGDIR" ] || ogRaiseError $OG_ERR_NOTFOUND "$2 $(dirname $3)" || return $? +IMGFILE="$IMGDIR/$(basename "$3").$IMGTYPE" +if ogIsImageLocked "$IMGFILE"; then + ogRaiseError $OG_ERR_LOCKED "$MSG_IMAGE $3, $4" + return $? +fi + +# No guardar imagen en el propio disco (disco no incluido en el camino del repositorio). +if [[ $(ogGetPath "$2" /) =~ ^$DISK ]]; then + ogRaiseError $OG_ERR_IMAGE "$2 = $DISK" + return $? +fi + +# Generar la instruccion a ejecutar antes de aplicar los bloqueos. +PROGRAM=$(ogCreateImageSyntax $DISK $IMGFILE) +# Desmontar todos los sistemas de archivos del disco, bloquear disco e imagen. +ogUnmountAll $1 2>/dev/null +ogLockDisk $1 || return $? +ogLockImage "$2" "$3.$IMGTYPE" || return $? + +# Crear Imagen. +trap "ogUnlockDisk $1; ogUnlockImage "$3" "$4.$IMGTYPE"; rm -f $IMGFILE" 1 2 3 6 9 +eval $PROGRAM + +# Controlar salida de error, crear fichero de información y desbloquear partición. +ERRCODE=$? +if [ $ERRCODE == 0 ]; then + echo "$(ogGetImageInfo $IMGFILE):$(ogGetHostname)" > $IMGFILE.info +else + ogRaiseError $OG_ERR_IMAGE "$1 $2 $IMGFILE" + rm -f "$IMGFILE" +fi +# Desbloquear disco e imagen. +ogUnlockDisk $1 +ogUnlockImage "$2" "$3.$IMGTYPE" +return $ERRCODE +} + + +#/** +# ogCreateImage int_ndisk int_npartition str_repo path_image [str_tools] [str_compressionlevel] +#@brief Crea una imagen a partir de una partición. +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@param str_repo repositorio de imágenes (remoto o caché local) +#@param path_image camino de la imagen (sin extensión) +#@param [opcional] str_tools herrmaienta de clonacion [partimage, partclone, ntfsclone] +#@param [opcional] str_compressionlevel nivel de compresion. [0 -none-, 1-lzop-, 2-gzip] +#@return (nada, por determinar) +#@note repo = { REPO, CACHE } +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. +#@exception OG_ERR_PARTITION partición no accesible o no soportada. +#@exception OG_ERR_LOCKED particion bloqueada por otra operación. +#@exception OG_ERR_IMAGE error al crear la imagen del sistema. +#@todo Comprobaciones, control de errores, definir parámetros, etc. +#@version 0.1 - Integracion para Opengnsys - HIDRA:CrearImagen{EXT3, NTFS}.sh; EAC: CreateImageFromPartition () en Deploy.lib +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@Date 2008/05/13 +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2008/10/27 +#@version 0.9 - Versión en pruebas para OpenGnSys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009/10/07 +#@version 1.0 - Llama a función ogCreateImageSyntax para generar la llamada al comando. +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date 2010/02/08 +#*/ ## +function ogCreateImage () +{ +# Variables locales +local PART PROGRAM IMGDIR IMGFILE IMGTYPE ERRCODE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npart str_repo path_image" \ + "$FUNCNAME 1 1 REPO /aula1/win7" + return +fi +# Error si no se reciben entre 4 y 6 parámetros. +[ $# -ge 4 -a $# -le 6 ] || ogRaiseError $OG_ERR_FORMAT "$*" || return $? + +# Comprobar que no está bloqueada ni la partición, ni la imagen. +PART="$(ogDiskToDev $1 $2)" || return $? +if ogIsLocked $1 $2; then + ogRaiseError $OG_ERR_LOCKED "$MSG_LOCKED $1, $2" + return $? +fi + +IMGTYPE="img" # Extensión genérica de imágenes. +IMGDIR=$(ogGetParentPath "$3" "$4") +[ -n "$IMGDIR" ] || ogRaiseError $OG_ERR_NOTFOUND "$3 $(dirname $4)" || return $? + +IMGFILE="$IMGDIR/$(basename "$4").$IMGTYPE" +if ogIsImageLocked "$IMGFILE"; then + ogRaiseError $OG_ERR_LOCKED "$MSG_IMAGE $3, $4" + return $? +fi +# Generar la instruccion a ejecutar antes de aplicar los bloqueos. +PROGRAM=$(ogCreateImageSyntax $PART $IMGFILE $5 $6) +# Desmontar partición, bloquear partición e imagen. +ogUnmount $1 $2 2>/dev/null +ogLock $1 $2 || return $? +ogLockImage "$3" "$4.$IMGTYPE" || return $? + +# Crear Imagen. +trap "ogUnlock $1 $2; ogUnlockImage "$3" "$4.$IMGTYPE"; rm -f $IMGFILE" 1 2 3 6 9 +eval $PROGRAM + +# Controlar salida de error, crear fichero de información y desbloquear partición. +ERRCODE=$? +if [ $ERRCODE == 0 ]; then + echo "$(ogGetImageInfo $IMGFILE):$(ogGetHostname)" > $IMGFILE.info +else + ogRaiseError $OG_ERR_IMAGE "$1 $2 $IMGFILE" + rm -f "$IMGFILE" +fi +# Desbloquear partición e imagen. +ogUnlock $1 $2 +ogUnlockImage "$3" "$4.$IMGTYPE" +return $ERRCODE +} + + +#/** +# ogCreateMbrImage int_ndisk str_repo path_image +#@brief Crea una imagen a partir del sector de arranque de un disco. +#@param int_ndisk nº de orden del disco +#@param str_repo repositorio de imágenes (remoto o caché local) +#@param path_image camino de la imagen (sin extensión) +#@return (nada, por determinar) +#@note repo = { REPO, CACHE } +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. +#@exception OG_ERR_IMAGE error al crear la imagen del sistema. +#@version 0.9 - Versión en pruebas para OpenGNSys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010/01/12 +#@version 1.0 - Adaptación a OpenGnSys 1.0 +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011/03/10 +#*/ ## +function ogCreateMbrImage () +{ +# Variables locales +local DISK IMGDIR IMGFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk str_repo path_image" \ + "$FUNCNAME 1 REPO /aula1/mbr" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +DISK=$(ogDiskToDev "$1") || return $? +IMGDIR=$(ogGetParentPath "$2" "$3") +[ -n "$IMGDIR" ] || ogRaiseError $OG_ERR_NOTFOUND "$2 $(dirname $3)" || return $? +IMGFILE="$IMGDIR/$(basename "$3").mbr" + +# Crear imagen del MBR. +dd if="$DISK" of="$IMGFILE" bs=512 count=1 || ogRaiseError $OG_ERR_IMAGE "$1 $IMGFILE" || return $? +} + + +#/** +# ogCreateBootLoaderImage int_ndisk str_repo path_image +#@brief Crea una imagen del boot loader a partir del sector de arranque de un disco. +#@param int_ndisk nº de orden del disco +#@param str_repo repositorio de imágenes (remoto o caché local) +#@param path_image camino de la imagen (sin extensión) +#@return (nada, por determinar) +#@note repo = { REPO, CACHE } +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. +#@exception OG_ERR_IMAGE error al crear la imagen del sistema. +#@version 1.0 - Adaptacion de ogCreateMbrImage para guardar solo el Boot Loader +#@author Juan Carlos Xifre, SICUZ Universidad de Zaragoza +#@date 2011/03/21 +#*/ ## +function ogCreateBootLoaderImage () +{ +# Variables locales +local DISK IMGDIR IMGFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk str_repo path_image" \ + "$FUNCNAME 1 REPO /aula1/mbr" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +DISK=$(ogDiskToDev "$1") || return $? +IMGDIR=$(ogGetParentPath "$2" "$3") +[ -n "$IMGDIR" ] || ogRaiseError $OG_ERR_NOTFOUND "$2 $(dirname $3)" || return $? +IMGFILE="$IMGDIR/$(basename "$3").mbr" + +# Crear imagen del Boot Loader dentro del MBR. +dd if="$DISK" of="$IMGFILE" bs=446 count=1 || ogRaiseError $OG_ERR_IMAGE "$1 $IMGFILE" || return $? +} + +#/** +# ogGetSizeParameters int_num_disk int_num_part str_repo [monolit|sync|diff] +#@brief Devuelve el tamaño de los datos de un sistema de ficheros, el espacio necesario para la imagen y si cabe en el repositorio elegido. +#@param int_disk numero de disco +#@param int_part numero de particion +#@param str_repo repositorio de imágenes { REPO, CACHE } +#@param str_imageName Nombre de la imagen +#@param str_imageType Tipo de imagen: monolit (por defecto), sync o diff. (parametro opcional) +#@return SIZEDATA SIZEREQUIRED SIZEFREE ISENOUGHSPACE +#@note si str_imageType= diff necesario /tmp/ogimg.info, que es creado por ogCreateInfoImage. +#@note para el tamaño de la imagen no sigue enlaces simbólicos. +#@exception OG_ERR_FORMAT formato incorrecto. +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2014/10/24 +#@version 1.1.0 - En la salida se incluye el espacio disponible en el repositorio (ticket #771) +#@author Irina Gomez - ETSII Universidad de Sevilla +#@date 2017-03-28 +#@version 1.1.0 - Si la imagen ya existe en el REPO se suma su tamaño al espacio libre +#@author Irina Gomez - ETSII Universidad de Sevilla +#@date 2017-11-08 +#*/ ## +function ogGetSizeParameters () +{ +local REPO MNTDIR SIZEDATA KERNELVERSION SIZEREQUIRED FACTORGZIP FACTORLZOP FACTORSYNC SIZEFREE +local IMGTYPE IMGDIR IMGFILE IMGEXT IMGSIZE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME num_disk num_part str_repo path_imgname [monolit|sync|diff]" \ + "if $FUNCNAME 1 2 REPO Windows10 sync ; then ...; fi" \ + "if $FUNCNAME 1 6 Ubuntu16 CACHE ; then ...; fi" + return +fi +# Error si no se reciben 1 o 2 parámetros. +[ $# -lt 4 ] && return $(ogRaiseError session $OG_ERR_FORMAT "$MSG_FORMAT: $PROG ndisco nparticion REPO|CACHE imgname [monolit|sync|diff]" ; echo $?) + +# Recogemos parametros +REPO=${3^^} +IMGTYPE="_${5^^}_" + +MNTDIR=$(ogMount $1 $2) +if [ "$MNTDIR" == "" ]; then + ogRaiseError $OG_ERR_PARTITION "$1 $2" + return $? +fi + +# Datos contenidos en la particion o en la lista de archivos de contiene la diferencial. +if [ "$IMGTYPE" == "_DIFF_" ]; then + [ -r /tmp/ogimg.info ] || return $(ogRaiseError session $OG_ERR_NOTFOUND "/tmp/ogimg.info"; echo $?) + cd $MNTDIR + SIZEDATA=$(grep -v "\/$" /tmp/ogimg.info | tr '\n' '\0'| du -x -c --files0-from=- 2>/dev/null|tail -n1 |cut -f1) + cd / +else + SIZEDATA=$(df -k | grep $MNTDIR\$ | awk '{print $3}') +fi + +#Aplicar factor de compresion +if [ "$IMGTYPE" == "_SYNC_" -o "$IMGTYPE" == "_DIFF_" ]; then + + # Sistema de fichero de la imagen según kernel, menor que 3.7 EXT4. comparamos revision + KERNELVERSION=$(uname -r| awk '{printf("%d",$1);sub(/[0-9]*\./,"",$1);printf(".%02d",$1)}') + [ $KERNELVERSION \< 3.07 ] && IMGFS="EXT4" || IMGFS=${IMGFS:-"BTRFS"} + FACTORSYNC=${FACTORSYNC:-"130"} + # Si IMGFS="BTRFS" la compresion es mayor. + [ $IMGFS == "BTRFS" ] && let FACTORSYNC=$FACTORSYNC-20 + + let SIZEREQUIRED=$SIZEDATA*$FACTORSYNC/100 + # El tamaño mínimo del sistema de ficheros btrfs es 250M, ponemos 300 + [ $SIZEREQUIRED -lt 300000 ] && SIZEREQUIRED=300000 + +else + FACTORGZIP=55/100 + FACTORLZOP=65/100 + let SIZEREQUIRED=$SIZEDATA*$FACTORLZOP +fi + +#Comprobar espacio libre en el contenedor. +[ "$REPO" == "CACHE" ] && SIZEFREE=$(ogGetFreeSize `ogFindCache`) +[ "$REPO" == "REPO" ] && SIZEFREE=$(df -k | grep $OGIMG | awk '{print $4}') + +# Comprobamos si existe una imagen con el mismo nombre en $REPO +# En sincronizadas restamos tamaño de la imagen y en monoloticas de la .ant +case "${IMGTYPE}" in + _DIFF_) IMGEXT="img.diff" + ;; + _SYNC_) IMGEXT="img" + ;; + *) IMGEXT="img.ant" + ;; +esac + +IMGDIR=$(ogGetParentPath "$REPO" "/$4") +IMGFILE=$(ogGetPath "$IMGDIR/$(basename "/$4").$IMGEXT") +if [ -z "$IMGFILE" ]; then + IMGSIZE=0 +else + IMGSIZE=$(ls -s "$IMGFILE" | cut -f1 -d" ") +fi + +let SIZEFREE=$SIZEFREE+$IMGSIZE + +[ "$SIZEREQUIRED" -lt "$SIZEFREE" ] && ISENOUGHSPACE=TRUE || ISENOUGHSPACE=FALSE + +echo $SIZEDATA $SIZEREQUIRED $SIZEFREE $ISENOUGHSPACE + +} + +#/** +# ogIsImageLocked [str_repo] path_image +#@brief Comprueba si una imagen está bloqueada para uso exclusivo. +#@param str_repo repositorio de imágenes (opcional) +#@param path_image camino de la imagen (sin extensión) +#@return Código de salida: 0 - bloqueado, 1 - sin bloquear o error. +#@note repo = { REPO, CACHE } +#@exception OG_ERR_FORMAT formato incorrecto. +#@version 1.0 - Adaptación a OpenGnSys 1.0 +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011/03/10 +#@version 1.0.1 - Devolver falso en caso de error. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-05-18 +#*/ ## +function ogIsImageLocked () +{ +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [str_repo] path_image" \ + "if $FUNCNAME /opt/opengnsys/images/aula1/win7.img; then ...; fi" \ + "if $FUNCNAME REPO /aula1/win7.img; then ...; fi" + return +fi +# Error si no se reciben 1 o 2 parámetros. +[ $# -lt 1 -o $# -gt 2 ] && return 1 + +# Comprobar si existe el fichero de bloqueo. +test -n "$(ogGetPath $@.lock)" +} + + +#/** +# ogLockImage [str_repo] path_image +#@brief Bloquea una imagen para uso exclusivo. +#@param str_repo repositorio de imágenes (opcional) +#@param path_image camino de la imagen (sin extensión) +#@return Nada. +#@note Se genera un fichero con extensión .lock +#@note repo = { REPO, CACHE } +#@exception OG_ERR_FORMAT formato incorrecto. +#@version 1.0 - Adaptación a OpenGnSys 1.0 +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011/03/10 +#*/ ## +function ogLockImage () +{ +# Variables locales +local IMGDIR + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [str_repo] path_image" \ + "$FUNCNAME /opt/opengnsys/images/aula1/win7.img" \ + "$FUNCNAME REPO /aula1/win7.img" + return +fi +# Error si no se reciben 1 o 2 parámetros. +[ $# == 1 -o $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Comprobar que existe directorio de imagen +IMGDIR=$(ogGetParentPath $@) || return $? +# Crear fichero de bloqueo. +touch $IMGDIR/$(basename "${!#}").lock 2>/dev/null || ogRaiseError $OG_ERR_NOTWRITE "$*" || return $? +} + + +#/** +# ogRestoreDiskImage str_repo path_image int_npartition +#@brief Restaura (recupera) una imagen de un disco completo. +#@param str_repo repositorio de imágenes o caché local +#@param path_image camino de la imagen +#@param int_ndisk nº de orden del disco +#@return (por determinar) +#@warning Primera versión en pruebas +#@todo Gestionar bloqueos de disco +#@todo Comprobar que no se intenta restaurar de la caché sobre el mismo disco +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero de imagen o partición no detectados. +#@exception OG_ERR_LOCKED partición bloqueada por otra operación. +#@exception OG_ERR_IMAGE error al restaurar la imagen del sistema. +#@exception OG_ERR_IMGSIZEPARTITION Tamaño de la particion es menor al tamaño de la imagen. +#@version 1.1.0 - Primera versión para OpenGnsys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@Date 2016/04/08 +#*/ ## +function ogRestoreDiskImage () +{ +# Variables locales +local DISK DISKSIZE IMGFILE IMGTYPE IMGSIZE PROGRAM ERRCODE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME str_repo path_image int_ndisk" \ + "$FUNCNAME REPO /aula1/win7 1" + return +fi +# Error si no se reciben 4 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Procesar parámetros. +DISK="$(ogDiskToDev $3)" || return $(ogRaiseError $OG_ERR_NOTFOUND " $3 $4"; echo $?) +IMGTYPE="dsk" +IMGFILE=$(ogGetPath "$1" "$2.$IMGTYPE") +[ -r "$IMGFILE" ] || return $(ogRaiseError $OG_ERR_NOTFOUND " $3 $4"; echo $?) + +# comprobamos consistencia de la imagen +ogGetImageInfo $IMGFILE >/dev/null || return $(ogRaiseError $OG_ERR_IMAGE " $1 $2"; echo $?) + +#/* (Comienzo comentario Doxygen) +# Error si la imagen no cabe en la particion. +#IMGSIZE=$(ogGetImageSize "$1" "$2") || return $(ogRaiseError $OG_ERR_IMAGE " $1 $2"; echo $?) +#DISKSIZE=$(ogGetDiskSize $3) +#if [ $IMGSIZE -gt $DISKSIZE ]; then +# ogRaiseError $OG_ERR_IMGSIZEPARTITION "$DISKSIZE < $IMGSIZE" +# return $? +#fi +#*/ (Fin comentario Doxygen) + +# Comprobar el bloqueo de la imagen y de la partición. +if ogIsImageLocked "$IMGFILE"; then + ogRaiseError $OG_ERR_LOCKED "$MSG_IMAGE $1, $2.$IMGTYPE" + return $? +fi +if ogIsDiskLocked $3; then + ogRaiseError $OG_ERR_LOCKED "$MSG_DISK $3" + return $? +fi +# Solicitamos la generación de la instruccion a ejecutar +PROGRAM=$(ogRestoreImageSyntax $IMGFILE $DISK) + +# Bloquear el disco +ogLockDisk $3 || return $? +trap "ogUnlockDisk $3" 1 2 3 6 9 + +# Ejecutar restauración según el tipo de imagen. +eval $PROGRAM + +ERRCODE=$? +if [ $ERRCODE != 0 ]; then + ogRaiseError $OG_ERR_IMAGE "$IMGFILE, $3, $4" +fi +ogUnlockDisk $3 $4 +return $ERRCODE +} + + +#/** +# ogRestoreImage str_repo path_image int_ndisk int_npartition +#@brief Restaura una imagen de sistema de archivos en una partición. +#@param str_repo repositorio de imágenes o caché local +#@param path_image camino de la imagen +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@return (por determinar) +#@exception OG_ERR_FORMAT 1 formato incorrecto. +#@exception OG_ERR_NOTFOUND 2 fichero de imagen o partición no detectados. +#@exception OG_ERR_PARTITION 3 # Error en partición de disco. +#@exception OG_ERR_LOCKED 4 partición bloqueada por otra operación. +#@exception OG_ERR_IMAGE 5 error al restaurar la imagen del sistema. +#@exception OG_ERR_IMGSIZEPARTITION 30 Tamaño de la particion es menor al tamaño de la imagen. +#@todo Comprobar incongruencias partición-imagen, control de errores, definir parámetros, caché/repositorio, etc. +#@version 0.1 - Integracion para Opengnsys - HIDRA:RestaurarImagen{EXT3, NTFS}.sh; EAC: RestorePartitionFromImage() en Deploy.lib +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@Date 2008/05/13 +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 2008/10/27 +#@version 0.9 - Primera version muy en pruebas para OpenGnSys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009/09/10 +#@version 1.0 - generacion sintaxis de restauracion +#@author Antonio J. Doblas Viso, Universidad de Malaga +#@date 2011/02/01 +#@version 1.0.1 - Control errores, tamaño particion, fichero-imagen +#@author Antonio J. Doblas Viso, Universidad de Malaga +#@date 2011/05/11 +#*/ ## +function ogRestoreImage () +{ +# Variables locales +local PART PARTSIZE IMGFILE IMGTYPE IMGSIZE FSTYPE PROGRAM ERRCODE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME str_repo path_image int_ndisk int_npart" \ + "$FUNCNAME REPO /aula1/win7 1 1" + return +fi +# Error si no se reciben 4 parámetros. +[ $# == 4 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Procesar parámetros. +PART="$(ogDiskToDev $3 $4)" || return $(ogRaiseError $OG_ERR_NOTFOUND " $3 $4"; echo $?) +#IMGTYPE=$(ogGetImageType "$1" "$2") +IMGTYPE=img +IMGFILE=$(ogGetPath "$1" "$2.$IMGTYPE") +[ -r "$IMGFILE" ] || return $(ogRaiseError $OG_ERR_NOTFOUND " $3 $4"; echo $?) +# comprobamos consistencia de la imagen +ogGetImageInfo $IMGFILE >/dev/null || return $(ogRaiseError $OG_ERR_IMAGE " $1 $2"; echo $?) + +# Error si la imagen no cabe en la particion. +IMGSIZE=$(ogGetImageSize "$1" "$2") || return $(ogRaiseError $OG_ERR_IMAGE " $1 $2"; echo $?) +#TODO: +#Si la particion no esta formateado o tiene problemas formateamos +ogMount $3 $4 || ogFormat $3 $4 +PARTSIZE=$(ogGetPartitionSize $3 $4) +if [ $IMGSIZE -gt $PARTSIZE ]; then + ogRaiseError $OG_ERR_IMGSIZEPARTITION " $PARTSIZE < $IMGSIZE" + return $? +fi +# Comprobar el bloqueo de la imagen y de la partición. +if ogIsImageLocked "$IMGFILE"; then + ogRaiseError $OG_ERR_LOCKED "$MSG_IMAGE $1, $2.$IMGTYPE" + return $? +fi +if ogIsLocked $3 $4; then + ogRaiseError $OG_ERR_LOCKED "$MSG_PARTITION $3, $4" + return $? +fi + +# Solicitamos la generación de la instruccion a ejecutar +# Atención: no se comprueba el tipo de sistema de archivos. +# Atención: no se comprueba incongruencia entre partición e imagen. +PROGRAM=`ogRestoreImageSyntax $IMGFILE $PART` + +# Desmontar y bloquear partición. +ogUnmount $3 $4 2>/dev/null || return $(ogRaiseError $OG_ERR_PARTITION " $3 $4"; echo $?) +ogLock $3 $4 || return $? +trap "ogUnlock $3 $4" 1 2 3 6 9 + +# Ejecutar restauración según el tipo de imagen. +eval $PROGRAM + +ERRCODE=$? +if [ $ERRCODE != 0 ]; then + ogRaiseError $OG_ERR_IMAGE "$IMGFILE, $3, $4" +fi +ogUnlock $3 $4 +return $ERRCODE +} + + +#/** +# ogRestoreMbrImage str_repo path_image int_ndisk +#@brief Restaura la imagen del sector de arranque de un disco. +#@param str_repo repositorio de imágenes o caché local +#@param path_image camino de la imagen +#@param int_ndisk nº de orden del disco +#@return (por determinar) +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero de imagen o partición no detectados. +#@exception OG_ERR_IMAGE error al restaurar la imagen del sistema. +#@version 0.9 - Primera versión en pruebas. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010/01/12 +#@version 1.0 - Adaptación a OpenGnSys 1.0 +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011/03/10 +#*/ ## +function ogRestoreMbrImage () +{ +# Variables locales +local DISK IMGFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME str_repo path_image int_ndisk" \ + "$FUNCNAME REPO /aula1/mbr 1" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Procesar parámetros. +DISK=$(ogDiskToDev "$3") || return $? +IMGFILE=$(ogGetPath "$1" "$2.mbr") +[ -r "$IMGFILE" ] || ogRaiseError $OG_ERR_NOTFOUND "$IMGFILE" || return $? + +# Restaurar imagen del MBR. +dd if="$IMGFILE" of="$DISK" bs=512 count=1 || ogRaiseError $OG_ERR_IMAGE "$1 $IMGFILE" || return $? +} + + +#/** +# ogRestoreBootLoaderImage str_repo path_image int_ndisk +#@brief Restaura la imagen del boot loader del sector de arranque de un disco. +#@param str_repo repositorio de imágenes o caché local +#@param path_image camino de la imagen +#@param int_ndisk nº de orden del disco +#@return (por determinar) +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero de imagen o partición no detectados. +#@exception OG_ERR_IMAGE error al restaurar la imagen del sistema. +#@version 1.0 - Adaptacion de ogRestoreMbrImage para restaurar solo el Boot Loader +#@author Juan Carlos Xifre, SICUZ Universidad de Zaragoza +#@date 2011/03/21 +#*/ ## +function ogRestoreBootLoaderImage () +{ +# Variables locales +local DISK IMGFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME str_repo path_image int_ndisk" \ + "$FUNCNAME REPO /aula1/mbr 1" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Procesar parámetros. +DISK=$(ogDiskToDev "$3") || return $? +IMGFILE=$(ogGetPath "$1" "$2.mbr") +[ -r "$IMGFILE" ] || ogRaiseError $OG_ERR_NOTFOUND "$IMGFILE" || return $? + +# Restaurar imagen del MBR. +dd if="$IMGFILE" of="$DISK" bs=446 count=1 || ogRaiseError $OG_ERR_IMAGE "$1 $IMGFILE" || return $? +} + +#/** +# ogUnlockImage [str_repo] path_image +#@brief Desbloquea una imagen con uso exclusivo. +#@param str_repo repositorio de imágenes (opcional) +#@param path_image camino de la imagen (sin extensión) +#@return Nada. +#@note repo = { REPO, CACHE } +#@note Se elimina el fichero de bloqueo con extensión .lock +#@exception OG_ERR_FORMAT formato incorrecto. +#@version 1.0 - Adaptación a OpenGnSys 1.0 +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011/03/10 +#*/ ## +function ogUnlockImage () +{ +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [str_repo] path_image" \ + "$FUNCNAME /opt/opengnsys/images/aula1/win7.img" \ + "$FUNCNAME REPO /aula1/win7.img" + return +fi +# Error si no se reciben 1 o 2 parámetros. +[ $# == 1 -o $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Borrar fichero de bloqueo para la imagen. +rm -f $(ogGetPath $@.lock) +} + + +#/** +# ogGetImageInfo filename +#@brief muestra información sobre la imagen monolitica. +#@param 1 filename path absoluto del fichero imagen +#@return cadena compuesta por clonacion:compresor:sistemaarchivos:tamañoKB +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero no encontrado. +#@exception OG_ERR_IMAGE "Image format is not valid $IMGFILE" +#@warning En pruebas iniciales +#@TODO Definir sintaxis de salida (herramienta y compresor en minuscula) +#@TODO Arreglar loop para ntfsclone +#@TODO insertar parametros entrada tipo OG +#@version 1.0 - Primeras pruebas +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date 2010/02/08 +#*/ ## + +function ogGetImageInfo () +{ +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_filename" \ + "$FUNCNAME /opt/opengnsys/images/prueba.img ==> PARTCLONE:LZOP:NTFS:5642158" + return +fi + +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +#comprobando que el parametro uno es un file. +[ -f $1 ] || ogRaiseError $OG_ERR_NOTFOUND "$1" || return $? + +local TOOLS COMPRESSOR IMGFILE FILEHEAD FS FSPLUS SIZE SIZEFACTOR PARTIMAGEINFO PARTCLONEINFO NTFSCLONEINFO IMGDETECT +IMGDETECT="FALSE" + +IMGFILE=$1 +FILEHEAD=/tmp/`basename $IMGFILE`.infohead +COMPRESSOR=`file $IMGFILE | awk '{print $2}'` +ogCheckStringInGroup "$COMPRESSOR" "gzip lzop" || ogRaiseError $OG_ERR_IMAGE "Image format is not valid $IMGFILE" || return $? +$($COMPRESSOR -dc $IMGFILE 2>/dev/null | head -n 40 > $FILEHEAD) || ogRaiseError $OG_ERR_IMAGE "Image format is not valid $IMGFILE" || return $? + +## buscando Primera opción. +if [ "$IMGDETECT" == "FALSE" ] +then + PARTCLONEINFO=$(LC_ALL=C partclone.info $FILEHEAD 2>&1) + if `echo $PARTCLONEINFO | grep size > /dev/null` + then + TOOLS=PARTCLONE + FS=$(echo $PARTCLONEINFO | awk '{gsub(/\: /,"\n"); print toupper($8);}') + if [[ "$FS" == "HFS" || "$FS" == "HFSPLUS" || "$FS" == "FAT32" ]]; then + FSPLUS=$(echo $PARTCLONEINFO | awk '{gsub(/\: /,"\n"); print toupper($9);}') + echo $PARTCLONEINFO | grep GB > /dev/null && SIZEFACTOR=1000000 || SIZEFACTOR=1024 + if [ "$FSPLUS" == "PLUS" ]; then + FS=$FS$FSPLUS + SIZE=$(echo $PARTCLONEINFO | awk -v FACTOR=$SIZEFACTOR '{printf "%d\n", $17*FACTOR;}') + else + SIZE=$(echo $PARTCLONEINFO | awk -v FACTOR=$SIZEFACTOR '{printf "%d\n", $16*FACTOR;}') + fi + else + echo $PARTCLONEINFO | grep GB > /dev/null && SIZEFACTOR=1000000 || SIZEFACTOR=1024 + SIZE=$(echo $PARTCLONEINFO | awk -v FACTOR=$SIZEFACTOR '{gsub(/\: /,"\n"); printf "%d\n", $11*FACTOR;}') + fi + IMGDETECT="TRUE" + fi +fi +#buscando segunda opcion. +if [ "$IMGDETECT" == "FALSE" -a ! -f /dev/loop2 ] +then + cat $FILEHEAD | grep -w ntfsclone-image > /dev/null && NTFSCLONEINFO=$(cat $FILEHEAD | ntfsclone --restore --overwrite /dev/loop2 - 2>&1) + if `echo $NTFSCLONEINFO | grep ntfsclone > /dev/null` + then + TOOLS=NTFSCLONE + SIZE=$(echo $NTFSCLONEINFO | awk '{gsub(/\(|\)|\./,""); printf "%d\n",$17/1000;}') + FS=NTFS + IMGDETECT="TRUE" + fi +fi +## buscando Tercer opción. +if [ "$IMGDETECT" == "FALSE" ] +then + PARTIMAGEINFO=$(partimage -B gui=no imginfo "$FILEHEAD" 2>&1) + if `echo $PARTIMAGEINFO | grep Partition > /dev/null` + then + TOOLS=PARTIMAGE + FS=$(echo $PARTIMAGEINFO | awk '{gsub(/ /,"\n"); print $17;}' | awk '{sub(/\.\.+/," "); print toupper($2)}') + SIZE=$( echo $PARTIMAGEINFO | awk '{gsub(/ /,"\n"); print $36;}' | awk '{sub(/\.\.+/," "); printf "%d\n",$2*1024*1024;}') + IMGDETECT="TRUE" + fi + if file $FILEHEAD 2> /dev/null | grep -q "boot sector"; then + TOOLS="partclone.dd" + FS= + SIZE= + IMGDETECT="TRUE" + fi +fi +#comprobamos valores #Chequeamos los valores devueltos. +if [ -z "$TOOLS" -o -z "$COMPRESSOR" -o "$IMGDETECT" == "FALSE" ] +then + ogRaiseError $OG_ERR_IMAGE "Image format is not valid $IMGFILE" || return $? +else + COMPRESSOR=$(echo $COMPRESSOR | tr [a-z] [A-Z]) + echo $TOOLS:$COMPRESSOR:$FS:$SIZE +fi +} + +#/** +# ogGetImageProgram str_REPO str_imagen +#@brief muestra información sobre la imagen monolitica. +#@see ogGetImageInfo +#@param 1 REPO o CACHE contenedor de la imagen +#@param 2 filename nombre de la imagen sin extension +#@return nombre del programa usado para generar la imagen +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero no encontrado. +#@note ogGetImageProgram REPO imagenA -> partclone +#@version 1.0 - Primeras pruebas +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date 2010/02/08 +#*/ ## + +function ogGetImageProgram () +{ +local IMGFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME str_repo path_image" \ + "$FUNCNAME REPO prueba ==> PARTCLONE" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? +IMGFILE=$(ogGetPath "$1" "$2.img") +[ -r "$IMGFILE" ] || ogRaiseError $OG_ERR_NOTFOUND "$IMGFILE" || return $? +ogGetImageInfo $IMGFILE | awk -F: '{print $1}' +} + +#/** +# ogGetImageCompressor str_REPO str_imagen +#@brief muestra información sobre la imagen monolitica. +#@see ogGetImageInfo +#@param 1 REPO o CACHE contenedor de la imagen +#@param 2 filename nombre de la imagen sin extension +#@return tipo de compresión usada al generar la imagen +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero no encontrado. +#@note ogGetImageCompressor REPO imagenA -> lzop +#@version 1.0 - Primeras pruebas +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date 2010/02/08 +#*/ ## +function ogGetImageCompressor () +{ +local IMGFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME str_repo path_image" \ + "$FUNCNAME REPO prueba ==> LZOP" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? +IMGFILE=$(ogGetPath "$1" "$2.img") +[ -r "$IMGFILE" ] || ogRaiseError $OG_ERR_NOTFOUND "$IMGFILE" || return $? +ogGetImageInfo $IMGFILE | awk -F: '{print $2}' +} + + +#/** +# ogGetImageType str_REPO str_imagen +#@brief muestra información sobre el sistema de archivos de imagen monolitica. +#@see ogGetImageInfo +#@param 1 REPO o CACHE contenedor de la imagen +#@param 2 filename nombre de la imagen sin extension +#@return tipo de compresión usada al generar la imagen +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero no encontrado. +#@note ogGetImageType REPO imagenA -> NTFS +#@version 1.0 - Primeras pruebas +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date 2010/02/08 +#*/ ## +function ogGetImageType () +{ +local IMGFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME str_repo path_image" \ + "$FUNCNAME REPO prueba ==> NTFS" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? +IMGFILE=$(ogGetPath "$1" "$2.img") +[ -r "$IMGFILE" ] || ogRaiseError $OG_ERR_NOTFOUND "$IMGFILE" || return $? +ogGetImageInfo $IMGFILE | awk -F: '{print $3}' +} + + +#/** +# ogGetImageSize str_REPO str_imagen +#@brief muestra información sobre el tamaño (KB) del sistema de archivos de imagen monolitica. +#@see ogGetImageInfo +#@param 1 REPO o CACHE contenedor de la imagen +#@param 2 filename nombre de la imagen sin extension +#@return tipo de compresión usada al generar la imagen +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero no encontrado. +#@note ogGetImagesize REPO imagenA -> 56432234 > Kb +#@version 1.0 - Primeras pruebas +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date 2010/02/08 +#*/ ## +function ogGetImageSize () +{ +# Variables locales +local IMGFILE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME str repo path_image" \ + "$FUNCNAME REPO prueba ==> 5642158" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Error si el fichero de imagen no es accesible. +IMGFILE=$(ogGetPath "$1" "$2.img") +[ -r "$IMGFILE" ] || ogRaiseError $OG_ERR_NOTFOUND "$IMGFILE" || return $? + +# Devuelve el tamaño de la imagen en KB. +ogGetImageInfo $IMGFILE | awk -F: '{print $4}' +} + + +#/** +# ogCreateGptImage int_ndisk str_repo path_image +#@brief Crea una imagen de la tabla de particiones GPT de un disco. +#@param int_ndisk nº de orden del disco +#@param str_repo repositorio de imágenes (remoto o caché local) +#@param path_image camino de la imagen (sin extensión) +#@return (nada, por determinar) +#@note repo = { REPO, CACHE } +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. +#@exception OG_ERR_IMAGE error al crear la imagen del sistema. +#@version 1.1 - Adaptación a OpenGnSys 1.1 +#@author Juan Carlos Garcia. Universidad de Zaragoza +#@date 2017/03/29 +#*/ ## +function ogCreateGptImage () +{ +# Variables locales +local DISK IMGDIR IMGFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk path_dir str_image" \ + "$FUNCNAME 1 REPO /aula1/gpt" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +DISK=$(ogDiskToDev "$1") || return $? +IMGDIR=$(ogGetParentPath "$2" "$3") +[ -n "$IMGDIR" ] || ogRaiseError $OG_ERR_NOTFOUND "$2 $(dirname $3)" || return $? +IMGFILE="$IMGDIR/$(basename "$3").gpt" + +# Crear imagen de la tabla GPT. +sgdisk -b="$IMGFILE" "$DISK" || ogRaiseError $OG_ERR_IMAGE "$1 $IMGFILE" || return $? +} + +#/** +# ogRestoreGptImage str_repo path_image int_ndisk +#@brief Restaura la imagen de la tabla de particiones GPT de un disco. +#@param str_repo repositorio de imágenes o caché local +#@param path_image camino de la imagen +#@param int_ndisk nº de orden del disco +#@return (por determinar) +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero de imagen o partición no detectados. +#@exception OG_ERR_IMAGE error al restaurar la imagen del sistema. +#@version 1.1 - Adaptación a OpenGnSys 1.1 +#@author Juan Carlos Garcia, Universidad de Zaragoza +#@date 2017/03/29 +#*/ ## +function ogRestoreGptImage () +{ +# Variables locales +local DISK IMGFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_dir str_image int_ndisk" \ + "$FUNCNAME REPO /aula1/gpt 1" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Procesar parámetros. +DISK=$(ogDiskToDev "$3") || return $? +IMGFILE=$(ogGetPath "$1" "$2.gpt") +[ -r "$IMGFILE" ] || ogRaiseError $OG_ERR_NOTFOUND "$IMGFILE" || return $? + +# Restaurar tabla GPT del disco. +sgdisk -l="$IMGFILE" "$DISK" || ogRaiseError $OG_ERR_IMAGE "$1 $IMGFILE" || return $? +} + diff --git a/client/engine/Inventory.lib b/client/engine/Inventory.lib new file mode 100755 index 0000000..a905f1b --- /dev/null +++ b/client/engine/Inventory.lib @@ -0,0 +1,528 @@ +#!/bin/bash +#/** +#@file Inventory.lib +#@brief Librería o clase Inventory +#@class Inventory +#@brief Funciones para recogida de datos de inventario de hardware y software de los clientes. +#@version 1.1.0 +#@warning License: GNU GPLv3+ +#*/ + + +#/** +# ogGetArch +#@brief Devuelve el tipo de arquitectura del cliente. +#@return str_arch - Arquitectura (i386 para 32 bits, x86_64 para 64 bits). +#@version 0.9.2 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010-07-17 +#*/ +function ogGetArch () +{ +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" "$FUNCNAME => x86_64" + return +fi + +[ -d /lib64 ] && echo "x86_64" || echo "i386" +} + + +#/** +# ogGetOsType int_ndisk int_npartition +#@brief Devuelve el tipo del sistema operativo instalado. +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@return OSType - Tipo de sistema operativo. +#@see ogGetOsVersion +#*/ ## +function ogGetOsType () +{ +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition" \ + "$FUNCNAME 1 2 => Linux" + return +fi +ogGetOsVersion "$@" | cut -sf1 -d: +} + + +#/** +# ogGetOsUuid int_ndisk int_nfilesys +#@brief Devuelve el UUID del sistema operativo instalado en un sistema de archivos. +#@param int_ndisk nº de orden del disco +#@param int_nfilesys nº de orden de la partición +#@return str_uuid - UUID del sistema operativo. +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Disco o partición no corresponden con un dispositiv +#@version 1.1.0 - Primera versión para OpenGnsys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2015-09-09 +#*/ ## +function ogGetOsUuid () +{ +# Variables locales. +local MNTDIR +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys" \ + "$FUNCNAME 1 2 => 540e47c6-8e78-4178-aa46-042e4803fb16" + return +fi +# Error si no se reciben 2 parametros. +[ $# = 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Montar la particion, si no lo estaba previamente. +MNTDIR=$(ogMount $1 $2) || return $? + +# Obtener UUID según el tipo de sistema operativo. +case "$(ogGetOsType $1 $2)" in + Linux) + # Leer el UUID del sistema de ficheros raíz o el fichero de identificador. + findmnt -no UUID $MNTDIR 2>/dev/null || cat $MNTDIR/etc/machine-id 2>/dev/null + ;; + Windows) + # Leer identificador en clave de registro. + ogGetRegistryValue $MNTDIR SOFTWARE '\Microsoft\Cryptography\MachineGuid' 2>/dev/null + ;; +esac +} + + +#/** +# ogGetSerialNumber +#@brief Obtiene el nº de serie del cliente. +#@version 1.1.0 - Primeras versión con OpenGnsys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2015-06-08 +#*/ ## +function ogGetSerialNumber () +{ +# Variables locales. +local SERIALNO +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" "$FUNCNAME => 123456" + return +fi + +# Obtener nº de serie (ignorar los no especificados). +SERIALNO=$(dmidecode -s system-serial-number | egrep -vi "(^[ 0]+$|not specified|to be filled|invalid entry|default string)") +# Quitar espacios y truncar cadena si >25 caracteres. +SERIALNO="${SERIALNO// /}" +[ ${#SERIALNO} -gt 25 ] && SERIALNO="${SERIALNO:0:22}..." +[ -n "$SERIALNO" ] && echo "$SERIALNO" +return 0 +} + + +#/** +# ogIsEfiActive +#@brief Comprueba si el sistema tiene activo el arranque EFI. +#*/ ## +function ogIsEfiActive () +{ +test -d /sys/firmware/efi +} + + +#/** +# ogListHardwareInfo +#@brief Lista el inventario de hardware de la máquina cliente. +#@return TipoDispositivo:Modelo (por determinar) +#@warning Se ignoran los parámetros de entrada. +#@note TipoDispositivo = { bio, boa, bus, cha, cdr, cpu, dis, fir, mem, mod, mul, net, sto, usb, vga } +#@note Requisitos: dmidecode, lshw, awk +#@version 0.1 - Primeras pruebas con OpenGnSys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-07-28 +#@version 1.1.0 - Incluir nuevos componentes al inventario. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2014-04-23 +#*/ ## +function ogListHardwareInfo () +{ +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" + return +fi + +# Recopilación de dispositivos procesando la salida de \c lshw +ogEcho info "$MSG_HARDWAREINVENTORY}" +echo "cha=$(dmidecode -s chassis-type)" | grep -v "Other" +[ -e /sys/firmware/efi ] && echo "boo=UEFI" || echo "boo=BIOS" +lshw | awk 'BEGIN {type="mod";} + /product:/ {sub(/ *product: */,""); prod=$0;} + /vendor:/ {sub(/ *vendor: */,""); vend=$0;} + /version:/ {sub(/ *version: */,"v.");vers=$0;} + /size:/ {size=$2;} + /clock:/ {clock=$2;} + /slot:/ {sub(/ *slot: */,""); slot=$0;} + /\*-/ {if (type=="mem"){ + if (size!=""){ + numbank++; + print type"="vend,prod,size,clock" ("slot")";} + }else{ + if (type=="totalmem"){ + if (size!=""){ + totalmemory="mem="size;} + }else{ + if (type!="" && prod!=""){ + if (prod=="v."vers) + vers=""; + print type"="vend,prod,size,vers;} } + } + type=prod=vend=vers=size=clock=slot="";} + $1~/-core/ {type="boa";} + $1~/-firmware/ {type="bio";} + $1~/-cpu/ {type="cpu";} + $1~/-bank/ {type="mem";} + $1~/-memory/ {type="totalmem";} + $1~/-ide/ {type="ide";} + $1~/-storage/ {type="sto";} + $1~/-disk/ {type="dis";} + $1~/-cdrom/ {type="cdr";} + $1~/-display/ {type="vga";} + $1~/-network/ {type="net";} + $1~/-multimedia/ {type="mul";} + $1~/-usb/ {type="usb";} + $1~/-firewire/ {type="fir";} + $1~/-serial/ {type="bus";} + END {if (type!="" && prod!="") + print type"="vend,prod,size,vers; + if (length(numbank)==0 && length(totalmemory)>=4) + print totalmemory; } + ' +# */ (comentario para Doxygen) +} + + +#/** +# ogListSoftware int_ndisk int_npartition +#@brief Lista el inventario de software instalado en un sistema operativo. +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@return programa versión ... +#@warning Se ignoran los parámetros de entrada. +#@note Requisitos: ... +#@todo Detectar software en Linux +#@version 0.1 - Primeras pruebas con OpenGnSys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-23 +#@version 1.0.5 - Aproximación para inventario de software de Mac OS. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2013-10-08 +#@version 1.0.6 - Proceso depende del tipo de SO y soporte para FreeBSD. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2014-11-13 +#@version 1.1.0 - Se muestra el sistema operativo en la primera línea de la salida +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2016-04-26 +#*/ ## +function ogListSoftware () +{ +# Variables locales. +local APPS HIVE k KEYS KEYS32 MNTDIR PKGDIR PROG VERS TMPFILE TYPE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME 1 1" + return +fi +# Error si no se reciben 2 parametros. +[ $# = 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Obtener tipo de sistema de archivos y montarlo. +MNTDIR=$(ogMount $1 $2) || return $? +TYPE=$(ogGetOsType $1 $2) || return $? + +# Ficheros temporales. +APPS=$(mktemp /tmp/apps.XXXXX) +TMPFILE=$(mktemp /tmp/tmp.XXXXX) +trap "rm -f $APPS $TMPFILE" 1 2 3 9 15 + +case "$TYPE" in + Linux) # Software de GNU/Linux. + # Procesar paquetes dpkg. + PKGDIR="${MNTDIR}/var/lib/dpkg" + if [ -r $PKGDIR ]; then + # Proceso de fichero en sistemas de 64 bits. + awk '/Package:/ {if (pack!="") print pack,vers; + sub(/-dev$/,"",$2); + pack=$2} + /Version:/ {sub(/^.*:/,"",$2); sub(/-.*$/,"",$2); + vers=$2} + /Status:/ {if ($2!="install") pack=vers=""} + END {if (pack!="") print pack,vers} + ' $PKGDIR/status > $APPS + fi + # Procesar paquetes RPM. + PKGDIR="${MNTDIR}/var/lib/rpm" + if [ -r $PKGDIR ]; then + # Listar si está instalado el paquete "rpm" en el cliente. + if which rpm &>/dev/null; then + rm -f ${PKGDIR}/__db.* + rpm --dbpath $PKGDIR -qa --qf "%{NAME} %{VERSION}\n" 2>/dev/null | \ + awk '$1!~/-devel$/ {sub(/-.*$/,"",$2); print $0}' > $APPS + rm -f ${PKGDIR}/__db.* + else + # Obtener el nombre de cada paquete en la BD de RPM. + python <<<" +import re; +import bsddb; +db=bsddb.hashopen('$PKGDIR/Name','r'); +for k in db.keys(): + print re.sub('-devel$','',k);" > $APPS + fi + fi + # Procesar paquetes pacman. + PKGDIR="${MNTDIR}/var/lib/pacman/local" + if [ -r $PKGDIR ]; then + ls $PKGDIR | awk -F- '/-/ {print gensub(/-/, " ", NF-2);}' > $APPS + fi + # Procesar aplicaciones Snappy. + PKGDIR="${MNTDIR}/snap" + find $PKGDIR/*/current/meta -name snap.yaml -exec \ + awk '/name:/ {pack=$2} + /version:/ {vers=$2} + END {if (pack!="") print pack,"(snap)",vers}' {} 2>/dev/null \; >> $APPS + # Procesar aplicaciones Flatpak. + PKGDIR="${MNTDIR}/var/lib/flatpak" + ls -1 $PKGDIR/app/*/current/active/deploy 2> /dev/null | python -c " +import sys +for f in sys.stdin: + p = open(f.strip()).read().split('\0') + try: + if(p[0] != 'flathub'): + raise ValueError + print('{} (flatpak) {}'.format(p[p.index('appdata-name') + 4], p[p.index('appdata-version') + 1])) + except ValueError: + pass +" >> $APPS + ;; + Windows) # Software de Windows. + # Comprobar tipo de proceso del registro de Windows. + if which hivexregedit &>/dev/null; then + # Nuevo proceso más rápido basado en "hivexregedit". + HIVE=$(ogGetHivePath $MNTDIR software 2>/dev/null) + if [ -n "$HIVE" ]; then + # Claves de registro para programas instalados. + hivexregedit --unsafe-printable-strings --export "$HIVE" '\Microsoft\Windows\CurrentVersion\Uninstall' > $TMPFILE 2>/dev/null + hivexregedit --unsafe-printable-strings --export "$HIVE" '\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall' >> $TMPFILE 2>/dev/null + # Mostrar los valores "DisplayName" y "DisplayVersion" para cada clave. + awk -F\" '$1~/^\[/ {n=""} + $2~/DisplayName/ {n=$4} + $2~/DisplayVersion/ {print n,$4} + ' $TMPFILE > $APPS + fi + else + # Compatibilidad con clientes ogLive antiguos. + # Claves de registro para programas instalados: formato "{clave}". + KEYS=$(ogListRegistryKeys $MNTDIR software '\Microsoft\Windows\CurrentVersion\Uninstall') + KEYS32=$(ogListRegistryKeys $MNTDIR software '\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall') + # Mostrar los valores "DisplayName" y "DisplayVersion" para cada clave. + for k in $KEYS; do + PROG=$(ogGetRegistryValue $MNTDIR software "\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$k\\DisplayName") + if [ -n "$PROG" ]; then + VERS=$(ogGetRegistryValue $MNTDIR software "\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$k\\DisplayVersion") + echo "$PROG $VERS" + fi + done > $APPS + for k in $KEYS32; do + PROG=$(ogGetRegistryValue $MNTDIR software "\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$k\\DisplayName") + if [ -n "$PROG" ]; then + VERS=$(ogGetRegistryValue $MNTDIR software "\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$k\\DisplayVersion") + echo "$PROG $VERS" + fi + done >> $APPS + fi + ;; + MacOS) # Software de Mac OS. + # Listar directorios de aplicaciones e intentar obtener la versión del fichero .plist (tanto original como descomprimido). + find "${MNTDIR}/Applications" -type d -name "*.app" -prune -print | \ + while read k; do + FILE="$k/Contents/version.plist" + [ -s "$FILE" ] || FILE="$k/Contents/version.plist.uncompress" + [ -s "$FILE" ] && VERSION=$(awk -F"[<>]" '/ShortVersionString/ {getline;v=$3} + END {print v}' "$FILE") + echo "$(basename "$k" .app) $VERSION" + done > $APPS + ;; + BSD) # Software de FreeBSD. + sqlite3 $MNTDIR/var/db/pkg/local.sqlite <<<"SELECT name FROM pkg_search;" 2>/dev/null | \ + sed 's/\(.*\)-\(.*\)/\1 \2/g' > $APPS + ;; + *) ogRaiseError $OG_ERR_NOTOS "$1, $2 ${TYPE+($TYPE)}" + return $? ;; +esac + +# Mostrar sistema Operativo y aplicaciones. +ogGetOsVersion $1 $2 | awk -F: '{print $2}' +sort $APPS | uniq | iconv -ct utf-8 +rm -f $APPS $TMPFILE +} + +#/** +# ogGetOsVersion int_ndisk int_nfilesys +#@brief Devuelve la versión del sistema operativo instalado en un sistema de archivos. +#@param int_ndisk nº de orden del disco +#@param int_nfilesys nº de orden de la partición +#@return OSType:OSVersion - tipo y versión del sistema operativo. +#@note OSType = { Android, BSD, GrubLoader, Hurd, Linux, MacOS, Solaris, Windows, WinLoader } +#@note Requisitos: awk, head, chroot +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Disco o partición no corresponden con un dispositiv +#@exception OG_ERR_PARTITION Fallo al montar el sistema de archivos. +#@version 0.9 - Primera versión para OpenGnSys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-15 +#@version 1.0.4 - Incluir tipos BSD, MacOS y Solaris. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2012-06-29 +#@version 1.0.5 - Incluir tipos GrubLoader, Hurd y WinLoader, leer por defecto fichero /etc/os-release. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2013-10-07 +#@version 1.0.6 - Detectar GrubLoader al final y sistemas basados en EFI. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2014-08-27 +#*/ ## +function ogGetOsVersion () +{ +# Variables locales. +local MNTDIR TYPE DISTRIB VERSION IS64BIT FILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys" \ + "$FUNCNAME 1 2 => Linux:Ubuntu precise (12.04 LTS) 64 bits" + return +fi +# Error si no se reciben 2 parametros. +[ $# = 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Montar la particion, si no lo estaba previamente. +MNTDIR=$(ogMount $1 $2) || return $? + +# Buscar tipo de sistema operativo. +# Para GNU/Linux: leer descripción. +TYPE="Linux" +FILE="$MNTDIR/etc/os-release" +[ -r $FILE ] && VERSION="$(awk -F= '$1~/PRETTY_NAME/ {gsub(/\"/,"",$2); print $2}' $FILE)" +# Si no se puede obtener, buscar en ficheros del sistema. +if [ -z "$VERSION" ]; then + FILE="$MNTDIR/etc/lsb-release" + [ -r $FILE ] && VERSION="$(awk -F= '$1~/DESCRIPTION/ {gsub(/\"/,"",$2); print $2}' $FILE)" + for DISTRIB in redhat SuSE mandrake gentoo; do + FILE="$MNTDIR/etc/${DISTRIB}-release" + [ -r $FILE ] && VERSION="$(head -1 $FILE)" + done + FILE="$MNTDIR/etc/arch-release" + [ -r $FILE ] && VERSION="Arch Linux" + FILE="$MNTDIR/etc/slackware-version" + [ -r $FILE ] && VERSION="Slackware $(cat $FILE)" +fi +# Si no se encuentra, intentar ejecutar "lsb_release". +[ -z "$VERSION" ] && VERSION=$(chroot $MNTDIR lsb_release -d 2>/dev/null | awk -F":\t" '{print $2}') +# Comprobar Linux de 64 bits. +[ -n "$VERSION" ] && [ -e $MNTDIR/lib64 ] && IS64BIT="$MSG_64BIT" +# Para Android, leer fichero de propiedades. +if [ -z "$VERSION" ]; then + TYPE="Android" + FILE="$MNTDIR/android*/system/build.prop" + [ -r $FILE ] && VERSION="Android $(awk -F= '$1~/(product.brand|build.version.release)/ {print $2}' $FILE | tr '\n' ' ')" + [ -e $MNTDIR/lib64 ] && IS64BIT="$MSG_64BIT" +fi +# Para GNU/Hurd, comprobar fichero de inicio (basado en os-prober). +if [ -z "$VERSION" ]; then + TYPE="Hurd" + FILE="$MNTDIR/hurd/init" + [ -r $FILE ] && VERSION="GNU/Hurd" +fi +# Para Windows: leer la version del registro. +if [ -z "$VERSION" ]; then + TYPE="Windows" + FILE="$(ogGetHivePath $MNTDIR SOFTWARE)" + if [ -n "$FILE" ]; then + # Nuevo método más rápido para acceder al registro de Windows.. + VERSION=$(echo $(hivexsh << EOT 2>/dev/null +load $FILE +cd \Microsoft\Windows NT\CurrentVersion +lsval ProductName +lsval ReleaseId +EOT + )) + [ -n "$(reglookup -H -p "Microsoft/Windows/CurrentVersion/ProgramW6432Dir" "$FILE" 2>/dev/null)" ] && IS64BIT="$MSG_64BIT" + if [ -z "$VERSION" ]; then + # Compatibilidad con métrodo antiguo y más lento de acceder al registro. + VERSION=$(ogGetRegistryValue $MNTDIR software '\Microsoft\Windows NT\CurrentVersion\ProductName' 2>/dev/null) + [ -n "$(ogGetRegistryValue $MNTDIR software '\Microsoft\Windows\CurrentVersion\ProgramW6432Dir' 2>/dev/null)" ] && IS64BIT="$MSG_64BIT" + fi + fi +fi +# Para cargador Windows: buscar versión en fichero BCD (basado en os-prober). +if [ -z "$VERSION" ]; then + TYPE="WinLoader" + FILE="$(ogGetPath $MNTDIR/boot/bcd)" + [ -z "$FILE" ] && FILE="$(ogGetPath $MNTDIR/EFI/Microsoft/boot/bcd)" + if [ -n "$FILE" ]; then + for DISTRIB in "Windows Recovery" "Windows Boot"; do + if grep -aqs "$(echo "$DISTRIB" | sed 's/./&./g')" $FILE; then + VERSION="$DISTRIB loader" + fi + done + fi +fi +# Para macOS: detectar kernel y completar con fichero plist de información del sistema. +if [ -z "$VERSION" ]; then + TYPE="MacOS" + # Kernel de Mac OS (no debe ser fichero de texto). + FILE="$MNTDIR/mach_kernel" + if [ -z "$(file -b $FILE | grep 'text')" ]; then + # Obtener tipo de kernel. + [ -n "$(file -b $FILE | grep 'Mach-O')" ] && VERSION="macOS" + [ -n "$(file -b $FILE | grep 'Mach-O 64-bit')" ] && IS64BIT="$MSG_64BIT" + # Datos de configuración de versión de Mac OS. + FILE="$MNTDIR/System/Library/CoreServices/SystemVersion.plist" + [ -r $FILE ] && VERSION=$(awk -F"[<>]" ' + /ProductName/ {getline;s=$3} + /ProductVersion/ {getline;v=$3} + END {print s,v}' $FILE) + # Datos de recuperación de macOS. + FILE="$MNTDIR/com.apple.recovery.boot" + [ -r $FILE -a -n "$VERSION" ] && VERSION="$VERSION recovery" + fi +fi +# Para FreeBSD: obtener datos del Kernel. +### TODO Revisar solución. +if [ -z "$VERSION" ]; then + TYPE="BSD" + FILE="$MNTDIR/boot/kernel/kernel" + if [ -r $FILE ]; then + VERSION="$(strings $FILE|awk '/@.*RELEASE/ {sub(/@\(#\)/,""); print $1,$2}')" + [ -n "$(file -b $FILE | grep 'x86-64')" ] && IS64BIT="$MSG_64BIT" + fi +fi +# Para Solaris: leer el fichero de versión. +### TODO Revisar solución. +if [ -z "$VERSION" ]; then + TYPE="Solaris" + FILE="$MNTDIR/etc/release" + [ -r $FILE ] && VERSION="$(head -1 $FILE)" +fi +# Para cargador GRUB, comprobar fichero de configuración. +if [ -z "$VERSION" ]; then + TYPE="GrubLoader" + for FILE in $MNTDIR/{,boot/}grub/menu.lst; do + [ -r $FILE ] && VERSION="GRUB Loader" + done +#/* (comentario Doxygen) + for FILE in $MNTDIR/{,boot/}{grub{,2},EFI/*}/grub.cfg; do + [ -r $FILE ] && VERSION="GRUB2 Loader" + done +fi +#*/ (Comentario Doxygen) +# Mostrar resultado y salir sin errores. +[ -n "$VERSION" ] && echo "$TYPE:$VERSION $IS64BIT" +return 0 +} diff --git a/client/engine/Net.lib b/client/engine/Net.lib new file mode 100755 index 0000000..d8f272b --- /dev/null +++ b/client/engine/Net.lib @@ -0,0 +1,345 @@ +#!/bin/bash +#/** +#@file Net.lib +#@brief Librería o clase Net +#@class Net +#@brief Funciones básicas de red. +#@version 1.0.6 +#@warning License: GNU GPLv3+ +#*/ + + +#/** +# ogChangeRepo IPREPO [ OgUnit ] +#@brief Cambia el repositorio para el recurso remoto images. +#@param 1 Ip Repositorio +#@param 2 Abreviatura Unidad Organizativa +#@return Cambio recurso remoto en OGIMG. +#@version 1.1 - Primera versión para OpenGnSys. +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2015-06-16 +#*/ +function ogChangeRepo () +{ +local SRCIMG NEWREPO REPO OGUNIT + +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME IPREPO [ OgUnit ]" \ + "$FUNCNAME 10.1.120.3" \ + "$FUNCNAME 10.1.120.3 cdc" + return +fi + + +if [ $# -lt 1 ]; then + ogRaiseError $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME IPREPO [ OgUnit ]" + return $? +fi + + +# Opciones de montaje: lectura o escritura +mount |grep "ogimages.*rw," &>/dev/null && RW=",rw" || RW=",ro" + +# Si REPO tomamos el repositorio y la unidad organizativa actual +REPO=$(ogGetRepoIp) +OGUNIT="$(df | awk -F " " '/ogimages/ {sub("//.*/ogimages","",$1); sub("/","",$1); print $1}')" + +# Parametros de entrada. Si $1 = "REPO" dejo el repositorio actual +[ "${1^^}" == "REPO" ] && NEWREPO="$REPO" || NEWREPO="${1}" + +# Si $1 y $2 son el repositorio y la OU actual me salgo +[ "$NEWREPO" == "$REPO" ] && [ "$2" == "$OGUNIT" ] && return 0 + +source /scripts/functions +source /scripts/ogfunctions +umount $OGIMG +[ "$2" == "" ] && SRCIMG="ogimages" || SRCIMG="ogimages/$2" +eval $(grep "OPTIONS=" /scripts/ogfunctions) + +ogEcho session log "$MSG_HELP_ogChangeRepo $NEWREPO ${2%/}" +ogConnect $NEWREPO $ogprotocol $SRCIMG $OGIMG $RW + +# Si da error volvemos a montar el inicial +if [ $? -ne 0 ]; then + ogConnect $REPO $ogprotocol $SRCIMG $OGIMG $RW + ogRaiseError session $OG_ERR_REPO "$NEWREPO" + return $? +fi + +} + + +#/** +# ogGetGroupDir [ str_repo ] +#@brief Devuelve el camino del directorio para el grupo del cliente. +#@param str_repo repositorio de imágenes (opcional) +#@return path_dir - Camino al directorio del grupo. +#@note repo = { REPO, CACHE } REPO por defecto +#@exception OG_ERR_FORMAT formato incorrecto. +#@version 1.0.2 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-10-03 +#*/ +function ogGetGroupDir () +{ +local REPO DIR GROUP +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME str_repo" \ + "$FUNCNAME REPO ==> /opt/opengnsys/images/groups/Grupo1" + return +fi +# Error si se recibe más de 1 parámetro. +case $# in + 0) REPO="REPO" ;; + 1) REPO="$1" ;; + *) ogRaiseError $OG_ERR_FORMAT "$*" + return $? ;; +esac + +GROUP="$(ogGetGroupName)" +if [ -n "$GROUP" ]; then + DIR=$(ogGetPath "$REPO" "/groups/$GROUP" 2>/dev/null) + [ -d "$DIR" ] && echo "$DIR" +fi +# Para que no haya error al fallar la condición anterior +return 0 +} + + +#/** +# ogGetGroupName +#@brief Devuelve el nombre del grupo al que pertenece el cliente. +#@return str_group - Nombre de grupo. +#@version 1.0.2 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-10-03 +#*/ +function ogGetGroupName () +{ +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" "$FUNCNAME => Grupo1" + return +fi +[ -n "$group" ] && echo "$group" +} + + +#/** +# ogGetHostname +#@brief Muestra el nombre del cliente. +#@return str_host - nombre de máquina +#@version 0.10 - Integración en OpenGnSys 0.10 +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010-02-11 +#*/ ## +function ogGetHostname () +{ +local HOST +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" "$FUNCNAME => pc1" + return +fi +# Tomar nombre de la variable HOSTNAME +HOST="$HOSTNAME" +# Si no, tomar del DHCP, opción host-name /* (comentario para Doxygen) +[ -z "$HOST" ] && HOST=$(awk -F\" '/option host-name/ {gsub(/;/,""); host=$2} + END {print host} + ' /var/lib/dhcp3/dhclient.leases) +# Si no, leer el parámetro del kernel hostname (comentario para Doxygen) */ +[ -z "$HOST" ] && HOST=$(awk 'BEGIN {RS=""; FS="="} + $1~/hostname/ {print $2}' /proc/cmdline) +[ "$HOSTNAME" != "$HOST" ] && export HOSTNAME="$HOST" +[ -n "$HOST" ] && echo $HOST +} + + +#/** +# ogGetIpAddress +#@brief Muestra la dirección IP del sistema +#@return str_ip - Dirección IP +#@version 0.10 - Integración en OpenGnSys 0.10 +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010-02-11 +#@version 1.0 - Integración OpenGnSys 0.10 Opengnsys 0.10-testing +#@note Usa las variables utilizadas por el initrd "/etc/net-ethX.conf +#@author Antonio J. Doblas Viso. Universidad de Malaga. +#@date 2011-02-24 +#@version 1.0.2 - Soporte para varias tarjetas de red +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-06-17 +#*/ ## +function ogGetIpAddress () +{ +local IP +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" "$FUNCNAME => 192.168.0.10" + return +fi +if [ -n "$IPV4ADDR" ]; then + IP="$IPV4ADDR" +else + # Obtener direcciones IP. + if [ -n "$DEVICE" ]; then + IP=$(ip -o address show up dev "$DEVICE" 2>/dev/null | awk '{if ($3~/inet$/) {printf ("%s ", $4)}}') + else + IP=$(ip -o address show up | awk '$2!~/lo/ {if ($3~/inet$/) {printf ("%s ", $4)}}') + fi +fi +# Mostrar solo la primera. +echo "${IP%%/*}" # (comentario para Doxygen) */ +} + + +#/** +# ogGetMacAddress +#@brief Muestra la dirección Ethernet del cliente. +#@return str_ether - Dirección Ethernet +#@version 0.10 - Integración en OpenGnSys 0.10 +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2010-02-11 +#@version 1.0.2 - Soporte para varias tarjetas de red +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-06-17 +#*/ ## +function ogGetMacAddress () +{ +local MAC +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" "$FUNCNAME => 00:11:22:33:44:55" + return +fi +# Obtener direcciones Ethernet. +if [ -n "$DEVICE" ]; then + MAC=$(ip -o link show up dev "$DEVICE" 2>/dev/null | awk '{sub (/.*\\/, ""); if ($1~/ether/) printf ("%s ", toupper($2));}') +else + MAC=$(ip -o link show up | awk '$2!~/lo/ {sub (/.*\\/, ""); if ($1~/ether/) printf ("%s ", toupper($2));}') +fi +# Mostrar sólo la primera. +echo ${MAC%% *} +} + + +#/** +# ogGetNetInterface +#@brief Muestra la interfaz de red del sistema +#@return str_interface - interfaz de red +#@version 1.0 - Integración OpenGnSys 0.10 Opengnsys 0.10-testing +#@note Usa las variables utilizadas por el initrd "/etc/net-ethX.conf +#@author Antonio J. Doblas Viso. Universidad de Malaga. +#@date 2011-02-24 +#*/ ## +function ogGetNetInterface () +{ +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" "$FUNCNAME => eth0" + return +fi +[ -n "$DEVICE" ] && echo "$DEVICE" +} + + +#/** +# ogGetRepoIp +#@brief Muestra la dirección IP del repositorio de datos. +#@return str_ip - Dirección IP +#@version 0.10 - Integración en OpenGnSys 0.10 +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-01-13 +#@version 1.0 - Integración OpenGnSys 0.10 Opengnsys 0.10-testing +#@note Comprobacion segun protocolo de conexion al Repo +#@author Antonio J. Doblas Viso. Universidad de Malaga. +#@date 2011-02-24 +#@version 1.0.6 - Obtener datos del punto de montaje, evitando fallo si $ogprotocol está vacía. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2014-08-27 +#*/ ## +function ogGetRepoIp () +{ +# Variables locales. +local SOURCE FSTYPE + +# Mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" "$FUNCNAME => 192.168.0.2" + return +fi + +# Obtener direcciones IP, según el tipo de montaje. +eval $(findmnt -P -o SOURCE,FSTYPE $OGIMG) +case "$FSTYPE" in + nfs) echo "$SOURCE" | cut -f1 -d: ;; + cifs) echo "$SOURCE" | cut -f3 -d/ ;; +esac +} + + +#/** +# ogGetServerIp +#@brief Muestra la dirección IP del Servidor de OpenGnSys. +#@return str_ip - Dirección IP +#@version 0.10 - Integración en OpenGnSys 0.10 +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-01-13 +#@version 1.0 - Integración OpenGnSys 0.10 Opengnsys 0.10-testing +#@note Comprobacion segun protocolo de conexion al Repo +#@author Antonio J. Doblas Viso. Universidad de Malaga. +#@date 2011-02-24 +#@version 1.0.6 - Obtener datos del punto de montaje, evitando fallo si $ogprotocol está vacía. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2014-08-27 +#*/ ## +function ogGetServerIp () +{ +# Variables locales. +local SOURCE FSTYPE + +# Mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" "$FUNCNAME => 192.168.0.2" + return +fi + +# Obtener direcciones IP, según el tipo de montaje. +eval $(findmnt -P -o SOURCE,FSTYPE $OPENGNSYS) +case "$FSTYPE" in + nfs) echo "$SOURCE" | cut -f1 -d: ;; + cifs) echo "$SOURCE" | cut -f3 -d/ ;; +esac +} + + +#/** +# ogMakeGroupDir [ str_repo ] +#@brief Crea el directorio para el grupo del cliente. +#@param str_repo repositorio de imágenes (opcional) +#@return (nada) +#@note repo = { REPO, CACHE } REPO por defecto +#@exception OG_ERR_FORMAT formato incorrecto. +#@version 1.0.5 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2013-09-26 +#*/ +function ogMakeGroupDir () +{ +local REPO DIR GROUP +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME str_repo" \ + "$FUNCNAME" "$FUNCNAME REPO" + return +fi +# Error si se recibe más de 1 parámetro. +case $# in + 0) REPO="REPO" ;; + 1) REPO="$1" ;; + *) ogRaiseError $OG_ERR_FORMAT "$*" + return $? ;; +esac +# Comprobar tipo de repositorio. +DIR=$(ogGetPath "$REPO" / 2>/dev/null) +[ -n "$DIR" ] || ogRaiseError $OG_ERR_FORMAT "$1" +GROUP="$(ogGetGroupName)" +if [ -n "$GROUP" ]; then + mkdir -p "$DIR/groups/$GROUP" 2>/dev/null +fi +} + diff --git a/client/engine/PostConf.lib b/client/engine/PostConf.lib new file mode 100755 index 0000000..eb5f6eb --- /dev/null +++ b/client/engine/PostConf.lib @@ -0,0 +1,543 @@ +#!/bin/bash +#/** +#@file PostConf.lib +#@brief Librería o clase PostConf +#@class PostConf +#@brief Funciones para la postconfiguración de sistemas operativos. +#@version 1.1.0 +#@warning License: GNU GPLv3+ +#*/ + + +#/** +# ogCleanOs int_ndisk int_nfilesys +#@brief Elimina los archivos que no son necesarios en el sistema operativo. +#@param int_ndisk nº de orden del disco +#@param int_nfilesys nº de orden del sistema de archivos +#@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 Antes incluido en la funcion ogReduceFs +#@author Irina Gomez. Universidad de Sevilla. +#@return (nada) +#@date 2014-10-27 +#*/ ## +function ogCleanOs () +{ + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_nfilesys" \ + "$FUNCNAME 1 1" + return +fi + +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_ndisk int_nfilesys" || return $? + +case "$(ogGetOsType $1 $2)" in + Linux) + # Borramos los ficheros de dispositivos y los temporales. + ogCleanLinuxDevices $1 $2 + rm -rf $(ogMount $1 $2)/tmp/* #*/ Comentario Doxygen + ;; + Windows) + # Borrar ficheros de hibernación y paginación de Windows. + [ -n "$(ogGetPath $1 $2 pagefile.sys)" ] && ogDeleteFile $1 $2 pagefile.sys + [ -n "$(ogGetPath $1 $2 hiberfil.sys)" ] && ogDeleteFile $1 $2 hiberfil.sys + [ -n "$(ogGetPath $1 $2 swapfile.sys)" ] && ogDeleteFile $1 $2 swapfile.sys + ;; +esac + +} + + + +#/** +# ogInstallMiniSetup int_ndisk int_npartition str_filename [str_admuser str_admpassword bool_autologin [str_autouser str_autopassword] ] +#@brief Metafunción para instalar archivo que se ejecutará en el arranque de Windows. +#@see ogInstallFirstBoot ogInstallRunonce +#*/ ## +function ogInstallMiniSetup () +{ +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$MSG_SEE ogInstallFirstBoot ogInstallRunonce" + return +fi +case $# in + 3) # Ejecución en el primer arranque de la máquina. + ogInstallFirstBoot "$@" ;; + 6|8) # Ejecución en el "runonce". + ogInstallRunonce "$@" ;; + *) ogRaiseError $OG_ERR_FORMAT + return $? ;; +esac +} + + +#/** +# ogInstallFirstBoot int_ndisk int_npartition str_filename +#@brief Crea unas claves del registro y el archivo cmd que se ejecutara en el primer arranque estando la maquina en un estado bloqueado +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@param str_filename nombre del archivo .cmd a ejecutar en el arranque +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@note El archivo estará en system32 y será visible por el sistema. +#@version 1.0.2 - Nueva función +#@author Jonathan Alonso Martinez - Universidad Autonoma de Barcelona +#@date 2011-06-29 +#@version 1.0.4 - Heredada de antigua función ogInstallMiniSetup. +#@author Jonathan Alonso Martinez - Universidad Autonoma de Barcelona +#@date 2012-04-16 +#*/ ## +function ogInstallFirstBoot () +{ +local MNTDIR DIR CMDDIR CMDFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition str_filename" \ + "$FUNCNAME 1 1 filename.cmd" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || return $(ogRaiseError $OG_ERR_FORMAT; echo $?) +# Comprobar que existe el directorio del fichero de comandos. +MNTDIR=$(ogMount "$1" "$2") || return $? +for i in winnt windows; do + DIR=$(ogGetPath $MNTDIR/$i/system32) + [ -n "$DIR" ] && CMDDIR=$DIR +done +[ -n "$CMDDIR" ] || ogRaiseError $OG_ERR_NOTFOUND "$MNTDIR/windows/system32" || return $? +CMDFILE="$CMDDIR/$3" + +# Creamos el archivo cmd y creamos un par de comandos para que una vez acabe la +# postconfiguracion resetee el mini setup, sino lo haria en cada arranque. +cat > "$CMDFILE" << EOF +REG ADD HKLM\System\Setup /v SystemSetupInProgress /t REG_DWORD /d 0 /f +REG ADD HKLM\System\Setup /v CmdLine /t REG_SZ /d "" /f +EOF + +# Crear los valores de registro necesarios para que se haga la ejecución del .cmd al aranque. +ogSetRegistryValue "$MNTDIR" SYSTEM "\Setup\SystemSetupInProgress" 1 +ogSetRegistryValue "$MNTDIR" SYSTEM "\Setup\SetupType" 4 +#ogDeleteRegistryValue "$MNTDIR" SYSTEM "\Setup\CmdLine" +ogAddRegistryValue "$MNTDIR" SYSTEM "\Setup\CmdLine" +ogSetRegistryValue "$MNTDIR" SYSTEM "\Setup\CmdLine" "cmd.exe /c $(basename $CMDFILE)" +} + + +#/** +# ogInstallRunonce int_ndisk int_npartition str_filename str_adm_user str_adm_password bool_autologin [str_auto_user str_auto_password] +#@brief Crea el archivo cmd que se ejecutara en el runonce de un usuario administrador +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@param str_filename nombre del archivo .cmd a ejecutar en el arranque (estara en system32 y sera visible por el sistema) +#@param str_adm_user usuario administrador para hacer autologin y ejecutar el runonce +#@param str_adm_password password del usuario administrador +#@param bool_autologin si despues de la postconfiguracion queremos que la maquina haga autologin (0 o 1) +#@param str_auto_user Usuario con el que queremos que haga autologin despues de la postconfiguracion +#@param str_auto_password Password del usuario que hara autologin +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@version 1.0.2 - Nueva función +#@author Jonathan Alonso Martinez - Universidad Autonoma de Barcelona +#@date 2011-06-29 +#@version 1.0.4 - Heredado de antigua función ogInstallMiniSetup +#@author Jonathan Alonso Martinez - Universidad Autonoma de Barcelona +#@date 2012-04-16 +#@version 1.1.0 - Resuelve problemas a partir de Windows 10 +#@author Carmelo Cabezuelo Aguilar - Universidad Politécnica de Valencia +#@date 2018-02-20 +#*/ ## +function ogInstallRunonce () +{ +local MOUNTPOINT DIR CMDDIR CMDFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition str_filename str_adm_user str_adm_password bool_autologin [str_auto_user str_auto_password]" \ + "$FUNCNAME 1 1 filename.cmd administrator passadmin 1 userauto passuserauto" \ + "$FUNCNAME 1 1 filename.cmd administrator passadmin 0" + return +fi +# Error si no se reciben 6 u 8 parámetros. +[ $# == 6 -o $# == 8 ] || return $(ogRaiseError $OG_ERR_FORMAT; echo $?) +# Punto de montaje. +MOUNTPOINT="$(ogGetPath "$1" "$2" /)" +# Comprobar que existe el directorio del fichero de comandos. +for i in winnt windows; do + DIR=$(ogGetPath $MOUNTPOINT/$i/system32) + [ -n "$DIR" ] && CMDDIR=$DIR +done +[ -n "$CMDDIR" ] || ogRaiseError $OG_ERR_NOTFOUND "$MOUNTPOINT/Windows/System32" || return $? +CMDFILE="$CMDDIR/$3" + +if [ $6 == 0 ]; then + # Si no queremos hacer autologin despues de la postconfiguracion lo indicamos en las claves de registro + cat > "$CMDFILE" << EOF +DEL C:\ogboot.* +REG ADD "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v AutoAdminLogon /t REG_SZ /d 0 /f +REG ADD "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v DefaultUserName /t REG_SZ /d "" /f +REG DELETE "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v DefaultPassword /f +EOF +else + # Si queremos hacer autologin despues de la postconfiguracion introducimos la informacion en las claves de registro + cat > "$CMDFILE" << EOF +DEL C:\ogboot.* +REG ADD "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v AutoAdminLogon /t REG_SZ /d 1 /f +REG ADD "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v DefaultUserName /t REG_SZ /d "$7" /f +REG ADD "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v DefaultPassword /t REG_SZ /d "$8" /f +EOF +fi +#Creamos las claves de registro necesarias para que meter el cmd en el runonce del usuario y este haga autologin +ogAddRegistryValue $MOUNTPOINT software '\Microsoft\Windows\CurrentVersion\RunOnce\PostConfiguracion' 2>/dev/null +ogSetRegistryValue $MOUNTPOINT software '\Microsoft\Windows\CurrentVersion\RunOnce\PostConfiguracion' "C:\windows\system32\\$3" 2>/dev/null +ogAddRegistryValue $MOUNTPOINT software '\Microsoft\Windows NT\CurrentVersion\Winlogon\AutoAdminLogon' 2>/dev/null +ogSetRegistryValue $MOUNTPOINT software '\Microsoft\Windows NT\CurrentVersion\Winlogon\AutoAdminLogon' 1 2>/dev/null +ogAddRegistryValue $MOUNTPOINT software '\Microsoft\Windows NT\CurrentVersion\Winlogon\DefaultUserName' 2>/dev/null +ogSetRegistryValue $MOUNTPOINT software '\Microsoft\Windows NT\CurrentVersion\Winlogon\DefaultUserName' "$4" 2>/dev/null +ogAddRegistryValue $MOUNTPOINT software '\Microsoft\Windows NT\CurrentVersion\Winlogon\DefaultDomainName' 2>/dev/null +ogSetRegistryValue $MOUNTPOINT software '\Microsoft\Windows NT\CurrentVersion\Winlogon\DefaultDomainName' ".\\" 2>/dev/null +ogAddRegistryValue $MOUNTPOINT software '\Microsoft\Windows NT\CurrentVersion\Winlogon\DefaultPassword' 2>/dev/null +ogSetRegistryValue $MOUNTPOINT software '\Microsoft\Windows NT\CurrentVersion\Winlogon\DefaultPassword' "$5" 2>/dev/null +ogDeleteRegistryValue $MOUNTPOINT software '\Microsoft\Windows NT\CurrentVersion\Winlogon\ForceAutoLockOnLogon' 2>/dev/null +ogDeleteRegistryValue $MOUNTPOINT software '\Microsoft\Windows NT\CurrentVersion\Winlogon\AutoLogonCount' 2>/dev/null +} + +#/** +# ogAddCmd int_ndisk int_npartition str_filename str_commands +#@brief Añade comandos al cmd creado por ogInstalMiniSetup +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@param str_filename nombre del fichero cmd (siempre se guardara en windows\system32\para que sea visible por el sistema +#@param str_commands comando o comandos que se añadiran al fichero +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar. +#@version 1.0.2 - Nueva función +#@author Jonathan Alonso Martinez - Universidad Autonoma de Barcelona +#@date 2011-06-29 +#@version 1.0.4 - Cambios en los parametros de entrada de la funcion +#@author Jonathan Alonso Martinez - Universidad Autonoma de Barcelona +#@date 2012-04-16 +#*/ ## +function ogAddCmd () +{ +local MOUNTPOINT CMDFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npartition str_filename str_commands" \ + "$FUNCNAME 1 1 filename.cmd command" + return +fi +# Error si no se reciben 4 parámetros. +[ $# == 4 ] || return $(ogRaiseError $OG_ERR_FORMAT; echo $?) +# Punto de montaje +MOUNTPOINT="$(ogMount "$1" "$2")" || return $? +# Crear fichero de comandos, si no existe. +CMDFILE="$(ogGetPath "$MOUNTPOINT/windows/system32")/$3" +[ -n "$CMDFILE" ] || ogInstallMiniSetup "$1" "$2" "$3" +[ -n "$CMDFILE" ] || ogRaiseError $OG_ERR_NOTFOUND "$MOUNTPOINT/windows/system32/$3" || return $? + +# Concatenamos el comando en el fichero de comandos +cat >> "$CMDFILE" << EOF +$4 +EOF +} + + +#/** +# ogDomainScript int_ndisk int_npartition str_domain str_user str_password +#@brief Crea un script .vbs para unir al dominio una maquina windows y el comando adequado en el archivo cmd creado por ogInstallMiniSetup +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@param str_filename nombre del fichero cmd donde deberemos introducir el comando de ejecutar el script vbs +#@param str_domain dominio donde se quiere conectar +#@param str_user usuario con privilegios para unir al dominio +#@param str_password password del usuario con privilegios +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@version 1.0.2 - Nueva función +#@author Jonathan Alonso Martinez - Universidad Autonoma de Barcelona +#@date 2011-06-29 +#@version 1.0.4 - Cambios en los parametros de entrada de la funcion +#@author Jonathan Alonso Martinez - Universidad Autonoma de Barcelona +#@date 2012-04-16 +#*/ ## +function ogDomainScript () +{ +local CMDDIR +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" \ + "$FUNCNAME int_ndisk int_npartition str_filename str_domain str_user str_password" \ + "$FUNCNAME 1 1 filename.cmd domain user password_user" + return +fi +# Error si no se reciben 6 parámetros. +[ $# == 6 ] || return $(ogRaiseError $OG_ERR_FORMAT; echo $?) +# Punto de montaje +MOUNTPOINT="$(ogMount "$1" "$2")" || return $? +# Comprobar que existe el directorio de comandos. +CMDDIR=$(ogGetPath "$MOUNTPOINT/windows/system32") +[ -n "$CMDDIR" ] || ogRaiseError $OG_ERR_NOTFOUND "$1/windows/system32" || return $? + +# Añadimos en el cmd que se ejecutara al arranque, el comando de ejecutar el script que añade al dominio. +ogAddCmd $1 $2 "$3" "CSCRIPT joindomain.vbs" +# Eliminamos el script porque tiene el usuario de administrador de dominio en claro +ogAddCmd $1 $2 "$3" "DEL /Q C:\Windows\System32\joindomain.vbs" +# Metemos unas claves de registro para que el dominio salga seleccionado por defecto +ogAddCmd $1 $2 "$3" "REG ADD \"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\" /v DefaultDomainName /t REG_SZ /d \"$4\" /f" + +# Creamos el archivo joindomain.vbs que nos introduce la maquina en el dominio +cat > "$CMDDIR/joindomain.vbs" << EOF +Const JOIN_DOMAIN = 1 +Const ACCT_CREATE = 2 +Const ACCT_DELETE = 4 +Const WIN9X_UPGRADE = 16 +Const DOMAIN_JOIN_IF_JOINED = 32 +Const JOIN_UNSECURE = 64 +Const MACHINE_PASSWORD_PASSED = 128 +Const DEFERRED_SPN_SET = 256 +Const INSTALL_INVOCATION = 262144 + +strDomain = "$4" +strUser = "$5" +strPassword = "$6" + +Set objNetwork = CreateObject("WScript.Network") +strComputer = objNetwork.ComputerName + +Set objComputer = GetObject("winmgmts:{impersonationLevel=Impersonate}!\\\" & _ + strComputer & "\root\cimv2:Win32_ComputerSystem.Name='" & strComputer & "'") + +ReturnValue = objComputer.JoinDomainOrWorkGroup(strDomain, strPassword, _ + strDomain & "\" & strUser, NULL, JOIN_DOMAIN + ACCT_CREATE) +EOF + +#*/ " (comentario Doxygen) + + +} + + +### PRUEBAS. + +#/** +# ogConfigureOgagent int_ndisk int_filesys +#@brief Modifica el fichero de configuración del nuevo agente OGAent para sistemas operativos. +#@param int_ndisk nº de orden del disco +#@param int_filesys nº de orden del sistema de archivos +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Fichero o dispositivo no encontrado. +#@exception OG_ERR_LOCKED Sistema de archivos bloqueado. +#@version 1.1.0 - Primera adaptación para OpenGnsys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2016-07-15 +#*/ ## +function ogConfigureOgagent () +{ +# Variables locales. +local MNTDIR AGENTDIR CFGFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_filesys" \ + "$FUNCNAME 1 1" + return +fi + +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Obtener sistema de archvios. +MNTDIR=$(ogMount $1 $2) || return $? + +# Comprobar si existe el fichero de configuración de OGAgent. +for AGENTDIR in usr/share/OGAgent "Program Files/OGAgent" "Program Files (x86)/OGAgent" Applications/OGAgent.app; do + CFGFILE=$(ogGetPath "$MNTDIR/$AGENTDIR/cfg/ogagent.cfg") + [ -n "$CFGFILE" ] && break +done +[ -n "$CFGFILE" ] || ogRaiseError $OG_ERR_NOTFOUND "ogagent.cfg" || return $? +# Parchear dirección del servidor OpenGnsys en el fichero de configuración de OGAgent. +sed -i "0,/remote=/ s,remote=.*,remote=https://$(ogGetServerIp)/opengnsys/rest/," "$CFGFILE" +} + + +#/** +# ogInstallLaunchDaemon int_ndisk int_nfilesys str_filename +#@brief Instala archivo que se ejecutará en el arranque de macOS. +#@param int_ndisk nº de orden del disco +#@param int_nfilesys nº de orden del sistema de archivos +#@param str_filename nombre del script +#return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Fichero o directorio no encontrado. +#@npte Crea ficheros de configuración /Library/LaunchDaemon/es.opengnsys.Script.plist. +#@version 1.0.6 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2014-10-06 +#*/ ## +function ogInstallLaunchDaemon () +{ +# Variables locales. +local LAUNCHDIR SCRIPTDIR +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_filesys str_scriptname" \ + "$FUNCNAME 1 2 postconf" + return +fi + +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Comprobar directorios. +LAUNCHDIR=$(ogGetPath $1 $2 /Library/LaunchDaemons) +[ -n "$LAUNCHDIR" ] || ogRaiseError $OG_ERR_NOTFOUND "$1 $2 /Library/LaunchDaemons" || return $? +SCRIPTDIR=$(ogGetPath $1 $2 /usr/share) +[ -n "$SCRIPTDIR" ] || ogRaiseError $OG_ERR_NOTFOUND "$1 $2 /usr/share" || return $? + +# Crear fichero de configuración del servicio de arranque. +cat << EOT $LAUNCHDIR/es.opengnsys.$3.plist + + + + Label + es.opengnsys.$3 + ProgramArguments + + $SCRIPTDIR/$3.sh + + RunAtLoad + + StandardOutPath + /var/log/$3.log + StandardErrorPath + /var/log/$3.err + + + +EOT + +# Crear un fichero de script vacío. +rm -f $SCRIPTDIR/$3.sh +touch $SCRIPTDIR/$3.sh +chmod +x $SCRIPTDIR/$3.sh +} + + +### PRUEBAS. + +#/** +# ogAddToLaunchDaemon int_ndisk int_nfilesys str_filename str_commands +#@brief Añade comandos al script creado por ogInstalLaunchDaemon. +#@param int_ndisk nº de orden del disco +#@param int_nfilesys nº de orden del sistema de archivos +#@param str_filename nombre del script (siempre se guardará en /usr/share para que sea visible por el sistema +#@param str_commands comando o comandos que se añadiran al fichero +#return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Fichero o directorio no encontrado. +#@version 1.0.6 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2014-10-06 +#*/ ## +function ogAddToLaunchDaemon () +{ +# Variables locales. +local SCRIPTFILE +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_filesys str_scriptname" \ + "$FUNCNAME 1 2 postconf \"diskutil enableJournal disk0s2\"" + return +fi + +# Error si no se reciben 4 parámetros. +[ $# == 4 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Comprobar que existe el fichero de comandos. +SCRIPTFILE=$(ogGetPath $1 $2 "/usr/share/$3.sh") +[ -n "$SCRIPTFILE" ] || ogRaiseError $OG_ERR_NOTFOUND "$1 $2 /usr/share/$3" || return $? + +# Concatenamos el comando en el fichero de comandos +cat >> "$SCRIPTFILE" << EOT +$4 +EOT +} + + +#/** +# ogUninstallLinuxClient int_ndisk int_filesys +#@brief Desinstala el cliente OpenGnSys para sistemas operativos GNU/Linux. +#@param int_ndisk nº de orden del disco +#@param int_filesys nº de orden del sistema de archivos +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Paritición o sistema de archivos incorrectos. +#@exception OG_ERR_LOCKED Sistema de archivos bloqueado. +#@version 1.1.0 - Primera adaptación para OpenGnsys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2016-08-22 +#*/ ## +function ogUninstallLinuxClient () +{ +# Variables locales. +local MNTDIR +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_filesys" \ + "$FUNCNAME 1 1" + return +fi + +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Obtener sistema de archvios. +MNTDIR=$(ogMount $1 $2) || return $? + +# Borrar ficheros y quitar llamada al cliente durante el proceso de inicio. +rm -f $MNTDIR/{usr/sbin,sbin,usr/local/sbin}/ogAdmLnxClient +rm -f $MNTDIR/{etc,usr/local/etc}/ogAdmLnxClient.cfg +sed -i -e '/ogAdmLnxClient/ d' $MNTDIR/{etc,usr/local/etc}/{rc.local,rc.d/rc.local} 2>/dev/null +} + + +#/** +# ogUninstallWindowsClient int_ndisk int_filesys str_filename +#@brief Desinstala el cliente OpenGnSys para sistemas operativos Windows. +#@param int_ndisk nº de orden del disco +#@param int_npartition nº de orden de la partición +#@param str_filename nombre del fichero cmd donde deberemos introducir el comando de ejecutar el script vbs +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Paritición o sistema de archivos incorrectos. +#@exception OG_ERR_LOCKED Sistema de archivos bloqueado. +#@version 1.1.0 - Primera adaptación para OpenGnsys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2016-08-22 +#*/ ## + +function ogUninstallWindowsClient () +{ +# Variables locales. +local MNTDIR +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_filesys str_filename" \ + "$FUNCNAME 1 1 filename.cmd" + return +fi + +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Obtener sistema de archvios. +MNTDIR=$(ogMount "$1" "$2") || return $? + +# Crear órdenes para desinstalar servicio y borrar ejecutable del cliente. +if [ -n "$(ogGetPath $MNTDIR/windows/ogAdmWinClient.exe)" -o -n "$(ogGetPath $MNTDIR/winnt/ogAdmWinClient.exe)" ]; then + ogAddCmd $1 $2 "$3" 'ogAdmWinClient -remove' + ogAddCmd $1 $2 "$3" 'DEL C:\Windows\ogAdmWinClient.exe' + ogAddCmd $1 $2 "$3" 'DEL C:\Winnt\ogAdmWinClient.exe' +fi +} + diff --git a/client/engine/PostConfEAC.lib b/client/engine/PostConfEAC.lib new file mode 100755 index 0000000..d34864d --- /dev/null +++ b/client/engine/PostConfEAC.lib @@ -0,0 +1,699 @@ +#!/bin/bash + +# ogLoadHiveWindows int_ndisk int_partiton +#@brief Localiza los hive del registro de windows (de sistema y usuarios) +#@param int_ndisk nº de orden del disco +#@param int_partition nº de particion +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar. +#@version 0.9 - Adaptación a OpenGNSys. +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date 2009-09-24 +#*/ ## + + +function ogLoadHiveWindows () { +# Variables locales. +local PART DISK + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_partition" \ + "$FUNCNAME 1 1 " + return +fi + +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || return $(ogRaiseError $OG_ERR_FORMAT; echo $?) + +DISK=$1; PART=$2; + +#Comprobaciones redundantes: borrar" +#ogDiskToDev $DISK $PART || return $(ogRaiseError $OG_ERR_PARTITION "particion de windows no detectada"; echo $?) +#ogGetOsType $DISK $PART | grep "Windows" || return $(ogRaiseError $OG_ERR_NOTOS "no es windows"; echo $?) +#VERSION=$(ogGetOsVersion $DISK $PART) +#Fin Comprobaciones redundantes: borrar" + + +# primera fase, carga de los hive del sistema +if ogGetPath $DISK $PART WINDOWS +then + SYSTEMROOT="Windows" +elif ogGetPath $DISK $PART WINNT +then + SYSTEMROOT="winnt" +else + return $(ogRaiseError $OG_ERR_NOTOS "version windows no detectada"; echo $?) +fi + +hiveSAM=$(ogGetPath $DISK $PART /${SYSTEMROOT}/system32/config/SAM) +[ -n "$hiveSAM" ] && export hiveSAM || return $(ogRaiseError $OG_ERR_NOTOS " hive SAM no detectada"; echo $?) +hiveSYSTEM=$(ogGetPath $DISK $PART /${SYSTEMROOT}/system32/config/system) +[ -n "$hiveSYSTEM" ] && export hiveSYSTEM || return $(ogRaiseError $OG_ERR_NOTOS "hive SYSTEM no detectada"; echo $?) +hiveSOFTWARE=$(ogGetPath $DISK $PART /${SYSTEMROOT}/system32/config/software) +[ -n "$hiveSOFTWARE" ] && export hiveSOFTWARE || return $(ogRaiseError $OG_ERR_NOTOS "hive SOFTWARE no detectada"; echo $?) +export TEMPhive=/tmp/tmpregistry + +# segunda fase, carga de los hive de usuarios windows. +declare -i COUNT +COUNT=3 +#TODO WINDOWS XP WINDOWS7 +BASEHOMEDIR=$(ogGetPath $DISK $PART /"Documents and Settings") +TMPUSERFILE="/tmp/WuserRegAndDAT.tmp" +find "$BASEHOMEDIR/" -type f -name NTUSER.DAT > $TMPUSERFILE +LISTUSERS=$(drbl-chntpw -l $hiveSAM | grep RID | awk -F"<" '{print $2}' | awk -F">" '{print $1}') +#echo "$BASEHOMEDIR" $LISTUSERS +for user in $LISTUSERS +do + # Comprobamos que el usuario registrado tiene .DAT + if HOMEDIR=$(cat $TMPUSERFILE | grep -w $user) + then + #echo "$user exportamos los usuarios de windows como variables, y como valor hiveUSERX; donde la X es 3 4 5 6 ... X" + export `echo $user=hiveUSER$COUNT` + #echo "$user exportamos la variable hiveUSERX con el valor del home de la variable-usuario_windows" + ##export `echo hiveUSER$COUNT`="$(echo $HOMEDIR | sed -e 's/ /\\ /'g | sed -e 's/\\/\\\\/g')" + export `echo hiveUSER$COUNT`="$(echo $HOMEDIR)" + #echo " estas variables \$USUARIO -> Identificador del HIVE ; \${!USUARIO} -> path del HIVE " + COUNT=${COUNT}+1 + fi + +done +COUNT=0 +} + + +# ogUpdateHiveWindows +#@brief Actualiza los hive de windows. +#@param int_ndisk +#@param int_partition +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar. +#@version 0.9 - Adaptación a OpenGNSys. +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date 2009-09-24 +#*/ ## + + +function ogUpdateHiveWindows (){ +# Variables locales. +local PART DISK FILE + +#TODO detectar llamada a ogLoadHiveWindows + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME " \ + "$FUNCNAME " + return +fi + +echo drbl-chntpw -f $TEMPhive $hiveSAM $hiveSYSTEM $hiveSOFTWARE \"${hiveUSER3}\" \"${hiveUSER4}\" \"${hiveUSER5}\" \"${hiveUSER6}\" \"${hiveUSER7}\" \"${hiveUSER8}\" \"${hiveUSER9}\" > /tmp/run.sh +cat /tmp/run.sh; sh /tmp/run.sh; rm -fr $TEMPhive; rm /tmp/run.sh + +unset hiveSAM hiveSYSTEM hiveSOFTWARE TEMPhive hiveUSER3 hiveUSER4 hiveUSER5 hiveUSER6 hiveUSER7 hiveUSER8 hiveUSER9 + + +} + + + +function ogHiveNTRunMachine () { +#echo sintaxis: PathScripts idScripts +#echo ejemplo: c:\\\\WINDOSWS\\\\crearusuarios.bat scripts1 +#echo IMPORTANTE: el path debe llevar dos barras \\, pero como se deben 'escapar' debes poner cuatro \\\\ +#echo "identifica 0=$hiveSAM 1=$hiveSystem 2=$hiveSoftware 3=$HiveUser3" + +local PART DISK FILE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME PathScripts|command keyName " \ + "$FUNCNAME c:\\\\Windows\\\\crearusuarios.cmd scripts_crearUsuarios "\ + "$FUNCNAME "cmd /c del c:\ogboot.*" ogcleanboot "\ + "$FUNCNAME Requiere la previa ejecución de ogLoadHive int_disk int_part"\ + "$FUNCNAME Despues requiere el ogUpdateHive" + return +fi + + +# Error si no se reciben al menos 1 parámetros. +[ $# == 2 ] || return $(ogRaiseError $OG_ERR_FORMAT; echo $?) + + +cat >> $TEMPhive << EOF +h 2 +cd \Microsoft\Windows\CurrentVersion\Run +nv 1 $2 +ed $2 +$1 +EOF +#ogGetRegistryValue /mnt/sda1 software '\Microsoft\Windows\CurrentVersion\Run\og3' +} + +function ogNTPolUserOn () { + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME id_hive_user " \ + "$FUNCNAME NombreUsuario"\ + "$FUNCNAME " + return +fi + +# TODO: error si no se ha llamado previamente a ogLoadHiveWindows +[ -n $hiveSAM ] || return $(ogRaiseError $OG_ERR_FORMAT "se debe utilizar primero la utilidad ogLoadHiveWindows"; echo $?) + +# TODO: error si el usuario no tiene cuenta en windows. +drbl-chntpw -l $hiveSAM | grep RID | grep -w $1 || return $(ogRaiseError $OG_ERR_FORMAT "el usuario $1 no tiene cuenta en este windows: Compruebe mayusculas o minusculas"; echo $?) + +# TODO: error si no el usario no no tiene HIVE asociado. +[ -n "${!1}" ] || return $(ogRaiseError $OG_ERR_FORMAT "el usuario no tiene hive creado"; echo $?) + + +HIVEID=$(echo ${!1} | tr -d "hiveUSER") + + +#echo "IMPORTANTE: la variable HiveUser3=/mnt/windows/Document/\ and/\ Seeting\alumnmos\NTUSER.dat" +echo $HIVEID +#cp /var/EAC/admin/utilswin/Fondo.BMP ${particion}/WINDOWS/ + +cat >> $TEMPhive << EOF +h $HIVEID +cd \Control Panel\Desktop +ed Wallpaper +C:\\WINDOWS\\fondo.bmp + +cd \Software\Microsoft\Windows\CurrentVersion\Policies +nk Explorer +cd Explorer + +nv 4 NoDesktop +ed NoDesktop +1 + +nv 4 NoSimpleStartMenu +ed NoSimpleStartMenu +1 +nv 4 NoWindowsUpdate +ed NoWindowsUpdate +1 + +nv 4 NoSMConfigurePrograms +ed NoSMConfigurePrograms +1 + +nv 4 NoChangeStartMenu +ed NoChangeStartMenu +1 + +nv 4 Intellimenus +ed Intellimenus +1 + +nv 4 NoRun +ed NoRun +1 + +nv 4 NoRecentDocsHistory +ed NoRecentDocsHistory +1 +EOF +} + + + + + +########################################################## +########################################################## +#####librerias de PostConfiguracion v0.1para Advanced Deploy enViorenment########### +# Liberado bajo licencia GPL ################ +############# 2008 Antonio Jes�s Doblas Viso adv@uma.es ########################## +########### Universidad de Malaga (Spain)############################ +########################################################## + + + + + +function NTChangeName () { +if [ $# = 0 ] +then +echo sintaxis: NTChangeNAME str_$var +echo ejemplos: NTCHangeName adi${IPcuatro}-xp +fi +cat >> $temporal << EOF +h 1 +ed ControlSet001\Control\ComputerName\ComputerName\ComputerName +$1 +ed ControlSet001\Services\Tcpip\Parameters\Hostname +$1 +ed ControlSet001\Services\Tcpip\Parameters\NV Hostname +$1 +h 2 +cd \Microsoft\Windows NT\CurrentVersion\Winlogon +ed DefaultDomainName +$1 +EOF +} + + + +function NTSetGroupName () { +if [ $# = 0 ] +then +echo sintaxis: NTSetGroupName str_$var +echo ejemplos: NTSetGroupName adi +fi +cat >> $temporal << EOF +h 2 +ed \Microsoft\Windows NT\CurrentVersion\Winlogon\DefaultDomainName +$1 +EOF +} + + +function NTSetOwner (){ +if [ $# = 0 ] +then +echo sintaxis: NtSetOwner str_propietario str_organizacion +echo ejemplos: NTSetOwner eu\ politecnica universidad\ de\ malaga +fi +cat >> $temporal << EOF +h 2 +ed \Microsoft\Windows NT\CurrentVersion\RegisteredOwner +$1 +ed \Microsoft\Windows NT\CurrentVersion\RegisteredOrganization +$2 +EOF +} + + +function NTAutoLogon (){ +if [ $# = 0 ] +then +echo sintaxis: Int_Activar Int_nves str_usuario str_passwd str_equipo +echo ejemplos: 1 2 administrador 3451 $equipo +echo IMPORTANTE: cuando AutoLogonCount llegue a 0, activa el AutoAdminLogon a 0. Pero no borra los valores de DefaultPassword +return 2 +fi +#echo la pass es $4 +export temporal=/tmp/tmpregistry +cat >> $temporal << EOF +hive 2 +cd \Microsoft\Windows NT\CurrentVersion\Winlogon +nv 1 AutoAdminLogon +ed AutoAdminLogon +$1 +nv 1 AutoLogonCount +ed AutoLogonCount +$2 +nv 1 DefaultUserName +ed DefaultUserName +$3 +nv 1 DefaultDomainName +ed DefaultDomainName +$5 +EOF +if [ "$4" == none ] +then +echo "debe aparecer done" $4 +cat >> $temporal << EOF +dv DefaultPassword + + +EOF +else +cat >> $temporal << EOF +nv 1 DefaultPassword +ed DefaultPassword +$4 +EOF +fi +} + +function NTStatusRatonTeclado (){ +if [ $# = 0 ] +then +echo sintaxis: Int-StatusRaton Int-StatusTeclado +echo ejemplos: int=1 activo int=4 desactivado +return 2 +fi +cat >> $temporal << EOF +hive 1 +cd \ControlSet001\Services\Mouclass +ed Start +$1 +cd \ControlSet001\Services\Kbdclass +ed Start +$2 +EOF +} + +function NTRunOnceMachine () { +if [ $# = 0 ] +then +echo sintaxis: PathScripts idScripts +echo "ejemplo: c:\\\\WINDOSWS\\\\crearusuarios.bat scripts1" +echo "IMPORTANTE: el path debe llevar dos barras \\, pero como se deben 'escapar' debes poner cuatro \\\\" +return 2 +fi +export temporal=/tmp/tmpregistry +cat >> $temporal << EOF +h 2 +cd \Microsoft\Windows\CurrentVersion\RunOnce +nv 1 $2 +ed $2 +$1 +EOF +} + +function NTRunMachine () { +if [ $# = 0 ] +then +echo sintaxis: PathScripts idScripts +echo ejemplo: c:\\\\WINDOSWS\\\\crearusuarios.bat scripts1 +echo IMPORTANTE: el path debe llevar dos barras \\, pero como se deben 'escapar' debes poner cuatro \\\\ +return 2 +fi +export temporal=/tmp/tmpregistry +cat >> $temporal << EOF +h 2 +cd \Microsoft\Windows\CurrentVersion\Run +nv 1 $2 +ed $2 +$1 +EOF +} + +function NTRunUser () { +if [ $# = 0 ] +then +echo sintaxis: str_PathWINScripts str_idScripts Int_hive||\$usuario +echo ejemplo: c:\\\\WINDOSWS\\\\crearusuarios.bat scripts1 3 +echo IMPORTANTE: el pathWIN debe llevar dos barras \\, pero como se deben 'escapar' debes poner cuatro \\\\ +echo IMPORTANTE: el pathLinux si lleva espacios debe escaparse con una barra \\ +echo IMPORTANTE Int_hive: 3 para el primer usuario, 4 para el segundo usuario +echo requiere export un HiveUser3=/mnt/windows/Document\ and\ Seeting\alumnmos\NTUSER.dat +return 2 +fi +cat >> $temporal << EOF +h $3 +cd \Software\Microsoft\Windows\CurrentVersion\Run +nv 1 $2 +ed $2 +$1 +EOF +} + + + +function NTPolUserOn () { +if [ $# = 0 ] +then +Msg "requiere LoadRegistryUser str_user1 str_user2..." orange +echo "sintaxis: Int_hive" +echo "ejemplo: NTPolUserOn 3" +echo "IMPORTANTE: la variable HiveUser3=/mnt/windows/Document/\ and/\ Seeting\alumnmos\NTUSER.dat" +return 2 +fi +cp /var/EAC/admin/utilswin/Fondo.BMP ${particion}/WINDOWS/ +cat >> $temporal << EOF +h $1 +cd \Control Panel\Desktop +ed Wallpaper +C:\\WINDOWS\\fondo.bmp + +cd \Software\Microsoft\Windows\CurrentVersion\Policies +nk Explorer +cd Explorer + +nv 4 NoDesktop +ed NoDesktop +1 + +nv 4 NoSimpleStartMenu +ed NoSimpleStartMenu +1 +nv 4 NoWindowsUpdate +ed NoWindowsUpdate +1 + +nv 4 NoSMConfigurePrograms +ed NoSMConfigurePrograms +1 + +nv 4 NoChangeStartMenu +ed NoChangeStartMenu +1 + +nv 4 Intellimenus +ed Intellimenus +1 + +nv 4 NoRun +ed NoRun +1 + +nv 4 NoRecentDocsHistory +ed NoRecentDocsHistory +1 +EOF +} + +function NTPolUserOFF () { +if [ $# = 0 ] +then +Msg "requiere LoadRegistryUser str_user1 str_user2..." orange +echo "sintaxis: Int_hive" +echo "ejemplo: NTPolUserOFF 3" +echo "IMPORTANTE: la variable HiveUser3=/mnt/windows/Document/\ and/\ Seeting\alumnmos\NTUSER.dat" +return 2 +fi +cat >> $temporal << EOF +h $1 +cd \Control Panel\Desktop +ed Wallpaper +C:\\WINDOWS\\web\\wallpaper\\Felicidad.bmp + +cd \Software\Microsoft\Windows\CurrentVersion\ +rdel Policies +nk Policies +1 +EOF +} + + +function ogSetWindowsChkdisk() { +if [ $# = 0 ] +then +echo sintaxis: true|TRUE|0 false|false|1 +echo ejemplos: int=0 desactivado int=1 activado +return 2 +fi +case $1 in + 0|true|TRUE) + valor="autocheck autochk *";; + 1|false|FALSE) + valor="none";; + *) + return 0 ;; +esac + +cat >> $TEMPhive << EOF +hive 1 +cd \ControlSet001\Control\Session Manager +ed BootExecute +$valor +--n +EOF +} + + + +### FASE DE PRUEBAS NO FUNCIONA +function NTStartRecovery () { +if [ $# = 0 ] +then + echo sintaxis: Int-Status + echo ejemplos: int=0 desactivado int=1 activado + return 2 +fi + +[ $1 = 0 ] && valor="none" +[ $1 = 1 ] && valor="00000000" + + +cat >> $TEMPhive << EOF +hive 2 +#cd \Policies\Microsoft\Windows\WinRE +#ed DisableSetup +cd \Policies\Microsoft\Windows +nk WinRE +nv 4 DisableSetup +ed DisableSetup +$valor +--n +EOF + + +#Activado +#[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WinRE] +#"DisableSetup"=- + +# Desactivado +#[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WinRE] +#"DisableSetup"=dword:00000000 + + +} + + +function ogSchrootLinux () { + +# Variables locales. +local PART DISK DIRCONF SCHROOTDEVICE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_partition" \ + "$FUNCNAME 1 1 " + return +fi + +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || return $(ogRaiseError $OG_ERR_FORMAT; echo $?) + +DISK=$1; PART=$2; DIRCONF="/etc/schroot" + + +VERSION=$(ogGetOsVersion $DISK $PART) +echo $VERSION | grep "Linux" || return $(ogRaiseError $OG_ERR_NOTOS "no es linux"; echo $?) + +ogUnmount $DISK $PART || return $(ogRaiseError $OG_ERR_NOTOS "no es linux"; echo $?) + +SCHROOTDEVICE=$(ogDiskToDev $DISK $PART) + + +rm ${DIRCONF}/mount-defaults +rm ${DIRCONF}/schroot.conf + +cat >> ${DIRCONF}/mount-defaults << EOF +# +proc /proc proc defaults 0 0 +/dev /dev none rw,bind 0 0 +/dev/pts /dev/pts none rw,bind 0 0 +/dev/shm /dev/shm none rw,bind 0 0 +EOF + + +cat >> ${DIRCONF}/schroot.conf << EOF +[linux] +description=$VERSION +type=block-device +device=$SCHROOTDEVICE +EOF + + + + +schroot -c linux + +schroot -end-sessiona --all-sessions +} + + +#/** @function ogDiskToRelativeDev: @brief Traduce los ID de discos o particiones EAC a ID Linux relativos, es decir 1 1 => sda1 +#@param Admite 1 parametro: $1 int_numdisk +#@param Admite 2 parametro: $1 int_numdisk $2 int_partition +#@return Para 1 parametros traduce Discos Duros: Devuelve la ruta relativa linux del disco duro indicado con nomenclatura EAC.........ejemplo: IdPartition 1 => sda +#@return Para 2 parametros traduce Particiones: Devuelve la ruta relativa linux de la particion indicado con nomenclatura EAC........... ejemplo: IdPartition 2 1 => sdb1 +#@warning No definidas +#@attention +#@note Notas sin especificar +#@version 0.1 - Integracion para Opengnsys - EAC: IdPartition en ATA.lib +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 27/10/2008 +#*/ +function ogDiskToRelativeDev () { +if [ $# = 0 ] +then + Msg "Info: Traduce el identificador del dispositivo EAC a dispositivo linux \n" info + Msg "Sintaxis1: IdPartition int_disk -----------------Ejemplo1: IdPartition 1 -> sda " example + Msg "Sintaxis2: IdPartition int_disk int_partition --Ejemplo2: IdPartition 1 2 -> sda2 " example + +return +fi +#PART="$(Disk|cut -f$1 -d' ')$2" # se comenta esta linea porque doxygen no reconoce la funcion disk y no crea los enlaces y referencias correctas. +PART=$(ogDiskToDev|cut -f$1 -d' ')$2 +echo $PART | cut -f3 -d \/ +} + + +#/** @function ogDeletePartitionsLabels: @brief Elimina la informacion que tiene el kernel del cliente og sobre los labels de los sistemas de archivos +#@param No requiere +#@return Nada +#@warning +#@attention Requisitos: comando interno linux rm +#@note +#@version 0.1 - Integracion para Opengnsys - EAC: DeletePartitionTable() en ATA.lib +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date 27/10/2008 +#*/ +function ogDeletePartitionsLabels () { +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME " \ + "$FUNCNAME " + return +fi + +rm /dev/disk/by-label/* # */ COMENTARIO OBLIGATORIO PARA DOXYGEN +} + + +#/** @function ogInfoCache: @brief muestra la informacion de la CACHE. +#@param sin parametros +#@return texto que se almacena en $IP.-InfoCache. punto_montaje, tama?oTotal, TamanioOcupado, TaminioLibre, imagenes dentro de la cahce +#@warning Salidas de errores no determinada +#@warning printf no soportado por busybox +#@attention +#@version 0.1 Date: 27/10/2008 Author Antonio J. Doblas Viso. Universidad de Malaga +#*/ +function ogInfoCache () +{ +local info infoFilesystem infoSize infoUsed infoUsedPorcet infoMountedOn content +if ogMountCache +then + info=`df -h | grep $OGCAC` + infoFilesystem=`echo $info | cut -f1 -d" "` + infoSize=`echo $info | cut -f2 -d" "` + infoUsed=`echo $info | cut -f3 -d" "` + infoAvail=`echo $info | cut -f4 -d" "` + infoUsedPorcet=`echo $info | cut -f5 -d" "` + infoMountedOn=`echo $info | cut -f2 -d" "` + if `ls ${OGCAC}$OGIMG > /dev/null 2>&1` + then + cd ${OGCAC}${OPENGNSYS} + #content=`find images/ -type f -printf "%h/ %f %s \n"` busybox no soporta printf + content=`find images/ -type f` + cd / + echo $info + echo -ne $content + echo " " + #echo "$info" > ${OGLOG}/${IP}-InfoCache + #echo "$content" >> {$OGLOG}/${IP}-InfoCache + else + echo $info + #echo "$info" > {$OGLOG}/${IP}-InfoCache + fi + ogUnmountCache +else + echo " " + #echo " " > {$OGLOG}/${IP}-InfoCache + +fi +} + diff --git a/client/engine/Protocol.lib b/client/engine/Protocol.lib new file mode 100755 index 0000000..f54edc4 --- /dev/null +++ b/client/engine/Protocol.lib @@ -0,0 +1,1216 @@ +#!/bin/bash +#/** +#@file Protocol.lib +#@brief Librería o clase Protocol +#@class Protocol +#@brief Funciones para transmisión de datos +#@version 1.0.5 +#@warning License: GNU GPLv3+ +#*/ + + +##################### FUNCIONES UNICAST ################ + +#/** +# ogUcastSyntax +#@brief Función para generar la instrucción de transferencia de datos unicast +#@param 1 Tipo de operación [ SENDPARTITION RECEIVERPARTITION SENDFILE RECEIVERFILE ] +#@param 2 Sesion Unicast +#@param 3 Dispositivo (opción PARTITION) o fichero(opción FILE) que será enviado. +#@param 4 Tools de clonación (opcion PARTITION) +#@param 5 Tools de compresion (opcion PARTITION) +#@return instrucción para ser ejecutada. +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_UCASTSYNTAXT formato de la sesion unicast incorrecta. +#@note Requisitos: mbuffer +#@todo: controlar que mbuffer esta disponible para los clientes. +#@version 1.0 - +#@author Antonio Doblas Viso, Universidad de Málaga +#@date 2011/03/09 +#*/ ## + +function ogUcastSyntax () +{ + +local PARM SESSION SESSIONPARM MODE PORTBASE PERROR ADDRESS +local TOOL LEVEL DEVICE MBUFFER SYNTAXSERVER SYNTAXCLIENT + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" -o "$2" == "help" ]; then + ogHelp "$FUNCNAME SENDPARTITION str_sessionSERVER str_device str_tools str_level" \ + "$FUNCNAME RECEIVERPARTITION str_sessionCLIENT str_device str_tools str_level "\ + "$FUNCNAME SENDFILE str_sessionSERVER str_file "\ + "$FUNCNAME RECEIVERFILE str_sessionCLIENT str_file " \ + "sessionServer syntax: portbase:ipCLIENT-1:ipCLIENT-2:ipCLIENT-N " \ + "sessionServer example: 8000:172.17.36.11:172.17.36.12" \ + "sessionClient syntax: portbase:ipMASTER " \ + "sessionClient example: 8000:172.17.36.249 " + return +fi +PERROR=0 + + + + +# Error si no se reciben $PARM parámetros. +echo "$1" | grep "PARTITION" > /dev/null && PARM=5 || PARM=3 +[ "$#" -eq "$PARM" ] || ogRaiseError $OG_ERR_FORMAT "sin parametros"|| return $? + + +# 1er param check +ogCheckStringInGroup "$1" "SENDPARTITION sendpartition RECEIVERPARTITION receiverpartition SENDFILE sendfile RECEIVERFILE receiverfile" || ogRaiseError $OG_ERR_FORMAT "1st param: $1" || PERROR=1 #return $? + +# 2º param check +echo "$1" | grep "SEND" > /dev/null && MODE=server || MODE=client + +######### No controlamos el numero de elementos de la session unicast porque en el master es variable en numero +#TODO: diferenciamos los paramatros especificos de la sessión unicast +#SI: controlamos todos los parametros de la sessión unicast. +#[ $MODE == "client" ] && SESSIONPARM=2 || SESSIONPARM=6 +OIFS=$IFS; IFS=':' ; SESSION=($2); IFS=$OIFS + + +#[[ ${#SESSION[*]} == $SESSIONPARM ]] || ogRaiseError $OG_ERR_FORMAT "parametros session multicast no completa" || PERROR=2# return $? + + +#controlamos el PORTBASE de la sesion. Comun.- +PORTBASE=${SESSION[0]} +ogCheckStringInGroup ${SESSION[0]} "8000 8001 8002 8003 8004 8005" || ogRaiseError $OG_ERR_FORMAT "McastSession portbase ${SESSION[0]}" || PERROR=3 #return $? + +if [ $MODE == "server" ] +then + SIZEARRAY=${#SESSION[@]} + for (( i = 1 ; i < $SIZEARRAY ; i++ )) + do + ADDRESS="$ADDRESS -O ${SESSION[$i]}:$PORTBASE" + #echo " -O ${SESSION[$i]}:$PORTBASE" + done + +else + ADDRESS=${SESSION[1]}:${PORTBASE} +fi + +#3er param check - que puede ser un dispositvo o un fichero. +#[ -n "$(ogGetPath "$3")" ] || ogRaiseError $OG_ERR_NOTFOUND " device or file $3" || PERROR=9 #return $? +DEVICE=$3 + +#4 y 5 param check . solo si es sobre particiones. +if [ "$PARM" == "5" ] +then + # 4 param check + ogCheckStringInGroup "$4" "partclone PARTCLONE partimage PARTIMAGE ntfsclone NTFSCLONE" || ogRaiseError $OG_ERR_NOTFOUND " herramienta $4 no soportada" || PERROR=10 #return $? + TOOL=$4 + ogCheckStringInGroup "$5" "lzop gzip LZOP GZIP 0 1" || ogRaiseError $OG_ERR_NOTFOUND " compresor $5 no valido" || PERROR=11 #return $? + LEVEL=$5 +fi + +[ "$PERROR" == "0" ] || ogRaiseError $OG_ERR_UCASTSYNTAXT " $PERROR" || return $? + +# Generamos la instrucción base de unicast -Envio,Recepcion- +SYNTAXSERVER="mbuffer $ADDRESS" +SYNTAXCLIENT="mbuffer -I $ADDRESS " + + +case "$1" in +SENDPARTITION) + PROG1=`ogCreateImageSyntax $DEVICE " " $TOOL $LEVEL | awk -F"|" '{print $1 "|" $3}' | tr -d ">"` + echo "$PROG1 | $SYNTAXSERVER" + ;; + RECEIVERPARTITION) + COMPRESSOR=`ogRestoreImageSyntax " " $DEVICE $TOOL $LEVEL | awk -F\| '{print $1}'` + TOOLS=`ogRestoreImageSyntax " " $DEVICE $TOOL $LEVEL | awk -F\| '{print $NF}'` + echo "$SYNTAXCLIENT | $COMPRESSOR | $TOOLS " + ;; + SENDFILE) + echo "$SYNTAXSERVER -i $3" + ;; + RECEIVERFILE) + echo "$SYNTAXCLIENT -i $3" + ;; + *) + ;; +esac +} + + +#/** +# ogUcastSendPartition +#@brief Función para enviar el contenido de una partición a multiples particiones remotas usando UNICAST. +#@param 1 disk +#@param 2 partition +#@param 3 sesionUcast +#@param 4 tool image +#@param 5 tool compresor +#@return +#@exception $OG_ERR_FORMAT +#@exception $OG_ERR_UCASTSENDPARTITION +#@note +#@todo: ogIsLocked siempre devuelve 1 +#@version 1.0 - +#@author Antonio Doblas Viso, Universidad de Málaga +#@date 2011/03/09 +#*/ ## + +function ogUcastSendPartition () +{ + +# Variables locales +local PART COMMAND RETVAL + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npart SessionUNICAST-SERVER tools compresor" \ + "$FUNCNAME 1 1 8000:172.17.36.11:172.17.36.12 partclone lzop" + return +fi +# Error si no se reciben 5 parámetros. +[ "$#" == 5 ] || ogRaiseError $OG_ERR_FORMAT || return $? +#chequeamos la particion. +PART=$(ogDiskToDev "$1" "$2") || return $? + +#ogIsLocked $1 $2 || ogRaiseError $OG_ERR_LOCKED "$1,$2" || return $? +ogUnmount $1 $2 + +#generamos la instrucción a ejecutar. +COMMAND=`ogUcastSyntax SENDPARTITION "$3" $PART $4 $5` +RETVAL=$? + +if [ "$RETVAL" -gt "0" ] +then + return $RETVAL +else + echo $COMMAND + eval $COMMAND || ogRaiseError $OG_ERR_UCASTSENDPARTITION " "; return $? +fi + +} + + +#/** +# ogUcastReceiverPartition +#@brief Función para recibir directamente en la partición el contenido de un fichero imagen remoto enviado por UNICAST. +#@param 1 disk +#@param 2 partition +#@param 3 session unicast +#@return +#@exception OG_ERR_FORMAT +#@exception OG_ERR_UCASTRECEIVERPARTITION +#@note +#@todo: +#@version 1.0 - Integración para OpenGNSys. +#@author Antonio Doblas Viso, Universidad de Málaga +#@date 2011/03/09 +#*/ ## +function ogUcastReceiverPartition () +{ +# Variables locales +local PART COMMAND RETVAL + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npart SessionMulticastCLIENT tools compresor" \ + "$FUNCNAME 1 1 8000:ipMASTER partclone lzop" + return +fi +# Error si no se reciben 5 parámetros. +[ "$#" == 5 ] || ogRaiseError $OG_ERR_FORMAT || return $? +#chequeamos la particion. +PART=$(ogDiskToDev "$1" "$2") || return $? + +#ogIsLocked $1 $2 || ogRaiseError $OG_ERR_LOCKED "$1,$2" || return $? +ogUnmount $1 $2 + +#generamos la instrucción a ejecutar. +COMMAND=`ogUcastSyntax RECEIVERPARTITION "$3" $PART $4 $5` +RETVAL=$? + +if [ "$RETVAL" -gt "0" ] +then + return $RETVAL +else + echo $COMMAND + eval $COMMAND || ogRaiseError $OG_ERR_UCASTRECEIVERPARTITION " "; return $? +fi +} + + + +#/** +# ogUcastSendFile [ str_repo | int_ndisk int_npart ] /Relative_path_file sessionMulticast +#@brief Envía un fichero por unicast ORIGEN(fichero) DESTINO(sessionmulticast) +#@param (2 parámetros) $1 path_aboluto_fichero $2 sesionMcast +#@param (3 parámetros) $1 Contenedor REPO|CACHE $2 path_absoluto_fichero $3 sesionMulticast +#@param (4 parámetros) $1 disk $2 particion $3 path_absoluto_fichero $4 sesionMulticast +#@return +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception $OG_ERR_NOTFOUND +#@exception OG_ERR_UCASTSENDFILE +#@note Requisitos: +#@version 1.0 - Definición de Protocol.lib +#@author Antonio Doblas Viso, Universidad de Málaga +#@date 2010/05/09 +#*/ ## +# + +function ogUcastSendFile () +{ +# Variables locales. +local ARG ARGS SOURCE TARGET COMMAND DEVICE RETVAL LOGFILE + + +#ARGS usado para controlar ubicación de la sesion multicast +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME [str_REPOSITORY] [int_ndisk int_npart] /Relative_path_file sesionMcast(puerto:ip:ip:ip)" \ + "$FUNCNAME 1 1 /aula1/winxp.img 8000:172.17.36.11:172.17.36.12" \ + "$FUNCNAME REPO /aula1/ubuntu.iso sesionUcast" \ + "$FUNCNAME CACHE /aula1/winxp.img sesionUcast" \ + "$FUNCNAME /opt/opengnsys/images/aula1/hd500.vmx sesionUcast" + return +fi + +ARGS="$@" +case "$1" in + /*) # Camino completo. */ (Comentrio Doxygen) + SOURCE=$(ogGetPath "$1") + ARG=2 + DEVICE="$1" + ;; + [1-9]*) # ndisco npartición. + SOURCE=$(ogGetPath "$1" "$2" "$3") + ARG=4 + DEVICE="$1 $2 $3" + ;; + *) # Otros: repo, cache, cdrom (no se permiten caminos relativos). + SOURCE=$(ogGetPath "$1" "$2") + ARG=3 + DEVICE="$1 $2 " + ;; +esac + + +# Error si no se reciben los argumentos ARG necesarios según la opcion. +[ $# == "$ARG" ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Comprobar fichero origen +[ -n "$(ogGetPath $SOURCE)" ] || ogRaiseError $OG_ERR_NOTFOUND " device or file $DEVICE not found" || return $? + + +SESSION=${!ARG} + +#generamos la instrucción a ejecutar. +COMMAND=`ogUcastSyntax "SENDFILE" "$SESSION" "$SOURCE"` +RETVAL=$? + +if [ "$RETVAL" -gt "0" ] +then + return $RETVAL +else + echo $COMMAND + eval $COMMAND || ogRaiseError $OG_ERR_UCASTSENDFILE " "; return $? +fi + +} + + + +#/** +# ogMcastSyntax +#@brief Función para generar la instrucción de ejucción la transferencia de datos multicast +#@param 1 Tipo de operación [ SENDPARTITION RECEIVERPARTITION SENDFILE RECEIVERFILE ] +#@param 2 Sesión Mulicast +#@param 3 Dispositivo (opción PARTITION) o fichero(opción FILE) que será enviado. +#@param 4 Tools de clonación (opcion PARTITION) +#@param 5 Tools de compresion (opcion PARTITION) +#@return instrucción para ser ejecutada. +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTEXEC +#@exception OG_ERR_MCASTSYNTAXT +#@note Requisitos: upd-cast 2009 o superior +#@todo localvar check versionudp +#@version 1.0 - +#@author Antonio Doblas Viso, Universidad de Málaga +#@date 2010/05/09 +#@version 2.0 - cambios en udp-receiver para permitir multicast entre subredes +#@author Juan Carlos Garcia, Universidad de Zaragoza +#@date 2015/11/17 +#@version 1.1 - Control de errores en transferencia multicast (ticket #781) +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2017/04/20 +#@version 1.1.0.a - Parametros de clientes como sesision de multicast (ticket #851) +#@author Antonio J. Doblas Viso +#@date 2018/09/22 +#*/ ## +# + +function ogMcastSyntax () +{ + +local ISUDPCAST RECEIVERTIMEOUT STARTTIMEOUT PARM SESSION SESSIONPARM MODE PORTBASE PERROR +local METHOD ADDRESS BITRATE NCLIENTS MAXTIME CERROR +local TOOL LEVEL DEVICE MBUFFER SYNTAXSERVER SYNTAXCLIENT + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" -o "$2" == "help" ]; then + ogHelp "$FUNCNAME SENDPARTITION str_sessionSERVER str_device str_tools str_level" \ + "$FUNCNAME RECEIVERPARTITION str_sessionCLIENT str_device str_tools str_level "\ + "$FUNCNAME SENDFILE str_sessionSERVER str_file "\ + "$FUNCNAME RECEIVERFILE str_sessionCLIENT str_file " \ + "sessionServer syntax: portbase:method:mcastaddress:speed:nclients:ntimeWaitingUntilNclients " \ + "sessionServer example: 9000:full-duplex|half-duplex|broadcast:239.194.17.36:80M:50:60 " \ + "sessionClient syntax: portbase " \ + "sessionClient example: 9000 "\ + "sessionClient syntax: portbase:serverIP:TimeOut_session:TimeOut_transmision" \ + "sessionClient example: 9000:172.17.88.161:40:120" + return +fi +PERROR=0 + +#si no tenemos updcast o su version superior 2009 udpcast error. +ISUDPCAST=$(udp-receiver --help 2>&1) +echo $ISUDPCAST | grep "not found" > /dev/null && (ogRaiseError $OG_ERR_NOTEXEC "upd-cast no existe " || return $?) + +############ BEGIN NUMBERS PARAMETERS CHECK AND SESSION OPTIONS IF CLIENT OR SERVER ############## +# Definimos los parametros de la funcion segun la opcion de envio/recepcion. +echo "$1" | grep "PARTITION" > /dev/null && PARM=5 || PARM=3 +[ "$#" -eq "$PARM" ] || ogRaiseError $OG_ERR_FORMAT "sin parametros"|| return $? +# 1er param check: opcion de envio/recepcion +ogCheckStringInGroup "$1" "SENDPARTITION sendpartition RECEIVERPARTITION receiverpartition SENDFILE sendfile RECEIVERFILE receiverfile" || ogRaiseError $OG_ERR_FORMAT "1st param: $1" || PERROR=1 #return $? +# 1º param check : opcion de cliente/servidor +echo "$1" | grep "SEND" > /dev/null && MODE=server || MODE=client + +# 2º param check: sesion multicast cliente/servidor. comprobamos el numero de parametros segun el tipo de sesion cliente o servidor. +#Definimos los parametros de la sesion multicast. La sesion de cliente seran 3, aunque uno es el obligado y dos opcionales. puerto:server:autostart +[ $MODE == "client" ] && SESSIONPARM=1 || SESSIONPARM=6 +#Controlamos el numero de paratros incluidos en la sesion usada como paraetro $2 +OIFS=$IFS; IFS=':' ; SESSION=($2); IFS=$OIFS +#Controlamos la sesion multicast del server +if [ $MODE == "server" ] +then + [[ ${#SESSION[*]} == $SESSIONPARM ]] || ogRaiseError $OG_ERR_FORMAT "parametros session de servidor multicast no completa" || PERROR=2# return $? +fi +#controlamos la sesion de cliente. +if [ $MODE == "client" ] +then + [[ ${#SESSION[*]} -ge $SESSIONPARM ]] || ogRaiseError $OG_ERR_FORMAT "parametros session de cliente multicast no completa" || PERROR=2# return $? +fi +############ END NUMBERS PARAMETERS CHECK ############## + +##### BEGIN SERVER SESSION ##### +# 2º param check: controlamos el primer componente comun de las sesiones de servidor y cliente: PORTBASE +PORTBASE=${SESSION[0]} +ogCheckStringInGroup ${SESSION[0]} "$(seq 9000 2 9098)" || ogRaiseError $OG_ERR_FORMAT "McastSession portbase ${SESSION[0]}" || PERROR=3 #return $? +# 2º param check: Controlamos el resto de componenentes de la sesion del servidor. +if [ $MODE == "server" ] +then + ogCheckStringInGroup ${SESSION[1]} "full-duplex FULL-DUPLEX half-duplex HALF-DUPLEX broadcast BROADCAST" || ogRaiseError $OG_ERR_FORMAT "McastSession method ${SESSION[1]}" || PERROR=4 #return $? + METHOD=${SESSION[1]} + ogCheckIpAddress ${SESSION[2]} || ogRaiseError $OG_ERR_FORMAT "McastSession address ${SESSION[2]}" || PERROR=5 #return $? + ADDRESS=${SESSION[2]} + ogCheckStringInReg ${SESSION[3]} "^[0-9]{1,3}\M$" || ogRaiseError $OG_ERR_FORMAT "McastSession bitrate ${SESSION[3]}" || PERROR=6 # return $? + BITRATE=${SESSION[3]} + ogCheckStringInReg ${SESSION[4]} "^[0-9]{1,10}$" || ogRaiseError $OG_ERR_FORMAT "McastSession nclients ${SESSION[4]}" || PERROR=7 # return $? + NCLIENTS=${SESSION[4]} + ogCheckStringInReg ${SESSION[5]} "^[0-9]{1,10}$" || ogRaiseError $OG_ERR_FORMAT "McastSession maxtime ${SESSION[5]}" || PERROR=8 # return $? + MAXTIME=${SESSION[5]} +fi + +#3er param check - que puede ser un dispositvo o un fichero. +# [ -n "$(ogGetPath $3)" ] || ogRaiseError $OG_ERR_NOTFOUND " device or file $3" || PERROR=9 #return $? +DEVICE=$3 + +#4 y 5 param check . solo si es sobre particiones. +if [ "$PARM" == "5" ] +then + # 4 param check + ogCheckStringInGroup "$4" "partclone PARTCLONE partimage PARTIMAGE ntfsclone NTFSCLONE" || ogRaiseError $OG_ERR_NOTFOUND " herramienta $4 no soportada" || PERROR=10 #return $? + TOOL=$4 + ogCheckStringInGroup "$5" "lzop LZOP gzip GZIP 0 1" || ogRaiseError $OG_ERR_NOTFOUND " compresor $5 no valido" || PERROR=11 #return $? + LEVEL=$5 +fi +# Controlamos si ha habido errores en la comprobacion de la sesion de servidor. +if [ "$PERROR" != "0" ]; then + ogRaiseError $OG_ERR_MCASTSYNTAXT " $PERROR"; return $? +fi +# Asignamos mas valores no configurables a la sesioe servidor. +CERROR="8x8/128" +# opcion del usuo de tuberia intermedia en memoria mbuffer. +which mbuffer > /dev/null && MBUFFER=" --pipe 'mbuffer -q -m 20M' " + +# Generamos la instruccion base del servidor de multicast -Envio- +SYNTAXSERVER="udp-sender $MBUFFER --nokbd --portbase $PORTBASE --$METHOD --mcast-data-address $ADDRESS --fec $CERROR --max-bitrate $BITRATE --ttl 16 --min-clients $NCLIENTS --max-wait $MAXTIME --autostart $MAXTIME --log /tmp/mcast.log" +########################################################################## +#### END SERVER SESSION ############## + + +##### BEGIN CLIENT SESSION ##### +#La primera opcion PORTBASE, ya esta controlado. Porque es comun al server y al cliente. +#La segunda opcion de la sesion para el cliente:: SERVERADDRES +if ogCheckIpAddress ${SESSION[1]} 2>/dev/null +then + SERVERADDRESS=" --mcast-rdv-address ${SESSION[1]}" +else + # Deteccion automatica de la subred del cliente para anadir la IP del repositorio a la orden udp-receiver en el caso de encontrarse en distinta subred del repo + REPOIP="$(ogGetRepoIp)" + CLIENTIP=$(ip -o address show up | awk '$2!~/lo/ {if ($3~/inet$/) {printf ("%s ", $4)}}') + MASCARA=`echo $CLIENTIP | cut -f2 -d/` + CLIENTIP=`echo $CLIENTIP | cut -f1 -d/` + RIPBT="" + IPBT="" + for (( i = 1 ; i < 5 ; i++ )) + do + RIP=`echo $REPOIP | cut -f$i -d.` + RIP=`echo "$[$RIP + 256]"` + RIPB="" + while [ $RIP -gt 0 ] + do + let COCIENTE=$RIP/2 + let RESTO=$RIP%2 + RIPB=$RESTO$RIPB + RIP=$COCIENTE + done + RIPB=`echo "$RIPB" | cut -c2-` + RIPBT=$RIPBT$RIPB + IP=`echo $CLIENTIP | cut -f$i -d.` + IP=`echo "$[$IP + 256]"` + IPB="" + while [ $IP -gt 0 ] + do + let COCIENTE=$IP/2 + let RESTO=$IP%2 + IPB=$RESTO$IPB + IP=$COCIENTE + done + IPB=`echo "$IPB" | cut -c2-` + IPBT=$IPBT$IPB + done + REPOSUBRED=`echo $RIPBT | cut -c1-$MASCARA` + CLIENTSUBRED=`echo $IPBT | cut -c1-$MASCARA` + if [ $REPOSUBRED == $CLIENTSUBRED ]; then + SERVERADDRESS=" " + else + SERVERADDRESS=" --mcast-rdv-address $REPOIP" + fi +fi +#La tercera opcion de la sesion para el cliente: ${SESSION[2]} ERRORSESSION - TIMEOUT ERROR IF NO FOUNT SESSEION MULTICAST +if ogCheckStringInReg ${SESSION[2]} "^[0-9]{1,10}$" &>/dev/null +then + case ${SESSION[2]} in + 0) + STARTTIMEOUT=" " + ;; + *) + STARTTIMEOUT=" --start-timeout ${SESSION[2]}" + ;; + esac +else + #asignamos valor definido en el engine.cfg + STARTTIMEOUT=" --start-timeout $MCASTERRORSESSION" +fi +#Verificamos que la opcion start-time out esta soportada por la version del cliente +echo $ISUDPCAST | grep start-timeout > /dev/null || STARTTIMEOUT=" " + +#La cuarta opcion de la sesion para el cliente: ${SESSION[2]} ERROR TRANSFER - TIMEOUT EEOR IF NOT RECEIVER DATA FROM SERVER +if ogCheckStringInReg ${SESSION[3]} "^[0-9]{1,10}$" &>/dev/null +then + case ${SESSION[3]} in + 0) + RECEIVERTIMEOUT=" " + ;; + *) + RECEIVERTIMEOUT=" --receive-timeout ${SESSION[3]}" + ;; + esac +else + #asignamos valor definido en el engine.cfg + RECEIVERTIMEOUT=" --receive-timeout $MCASTWAIT" +fi +#Verificamos que la opcion receive-timeou esta soportada por la version del cliente +echo $ISUDPCAST | grep receive-timeout > /dev/null || RECEIVERTIMEOUT=" " + +#Componenemos la sesion multicast del cliente +SYNTAXCLIENT="udp-receiver $MBUFFER --portbase $PORTBASE $SERVERADDRESS $STARTTIMEOUT $RECEIVERTIMEOUT --log /tmp/mcast.log" +########################################################################## +#### END CLIENT SESSION ############## + + ######## BEGIN MAIN PROGAM ##### +case "$1" in + SENDPARTITION) + PROG1=`ogCreateImageSyntax $DEVICE " " $TOOL $LEVEL | awk -F"|" '{print $1 "|" $3}' | tr -d ">"` + echo "$PROG1 | $SYNTAXSERVER" + ;; + RECEIVERPARTITION) + COMPRESSOR=`ogRestoreImageSyntax " " $DEVICE $TOOL $LEVEL | awk -F\| '{print $1}'` + TOOLS=`ogRestoreImageSyntax " " $DEVICE $TOOL $LEVEL | awk -F\| '{print $NF}'` + echo "$SYNTAXCLIENT | $COMPRESSOR | $TOOLS " + ;; + SENDFILE) + echo "$SYNTAXSERVER --file $3" + ;; + RECEIVERFILE) + echo "$SYNTAXCLIENT --file $3" + ;; + *) + ;; +esac +######## END MAIN PROGAM ##### +} + + + +#/** +# ogMcastSendFile [ str_repo | int_ndisk int_npart ] /Relative_path_file sessionMulticast +#@brief Envía un fichero por multicast ORIGEN(fichero) DESTINO(sessionmulticast) +#@param (2 parámetros) $1 path_aboluto_fichero $2 sesionMcast +#@param (3 parámetros) $1 Contenedor REPO|CACHE $2 path_absoluto_fichero $3 sesionMulticast +#@param (4 parámetros) $1 disk $2 particion $3 path_absoluto_fichero $4 sesionMulticast +#@return +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception $OG_ERR_NOTFOUND +#@exception OG_ERR_MCASTSENDFILE +#@note Requisitos: +#@version 1.0 - Definición de Protocol.lib +#@author Antonio Doblas Viso, Universidad de Málaga +#@date 2010/05/09 +#*/ ## +# + +function ogMcastSendFile () +{ +# Variables locales. +local ARGS ARG SOURCE TARGET COMMAND DEVICE RETVAL LOGFILE + +#LOGFILE="/tmp/mcast.log" + +#ARGS usado para controlar ubicación de la sesion multicast +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME [str_REPOSITORY] [int_ndisk int_npart] /Relative_path_file sesionMcast" \ + "$FUNCNAME 1 1 /aula1/winxp.img sesionMcast" \ + "$FUNCNAME REPO /aula1/ubuntu.iso sesionMcast" \ + "$FUNCNAME CACHE /aula1/winxp.img sesionMcast" \ + "$FUNCNAME /opt/opengnsys/images/aula1/hd500.vmx sesionMcast" + return +fi + +ARGS="$@" +case "$1" in + /*) # Camino completo. */ (Comentrio Doxygen) + SOURCE=$(ogGetPath "$1") + ARG=2 + DEVICE="$1" + ;; + [1-9]*) # ndisco npartición. + SOURCE=$(ogGetPath "$1" "$2" "$3") + ARG=4 + DEVICE="$1 $2 $3" + ;; + *) # Otros: repo, cache, cdrom (no se permiten caminos relativos). + SOURCE=$(ogGetPath "$1" "$2") + ARG=3 + DEVICE="$1 $2 " + ;; +esac + + +# Error si no se reciben los argumentos ARG necesarios según la opcion. +[ $# == "$ARG" ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Comprobar fichero origen +[ -n "$(ogGetPath $SOURCE)" ] || ogRaiseError $OG_ERR_NOTFOUND " device or file $DEVICE not found" || return $? + +# eliminamos ficheros antiguos de log +#rm $LOGFILE + +SESSION=${!ARG} + + +#generamos la instrucción a ejecutar. +COMMAND=`ogMcastSyntax "SENDFILE" "$SESSION" "$SOURCE"` +RETVAL=$? + +if [ "$RETVAL" -gt "0" ] +then + return $RETVAL +else + echo $COMMAND + eval $COMMAND || ogRaiseError $OG_ERR_MCASTSENDFILE " "; return $? + #[ -s "$LOGFILE" ] || return 21 +fi + +} + + + +#/** +# ogMcastReceiverFile sesion Multicast [ str_repo | int_ndisk int_npart ] /Relative_path_file +#@brief Recibe un fichero multicast ORIGEN(sesionmulticast) DESTINO(fichero) +#@param (2 parámetros) $1 sesionMcastCLIENT $2 path_aboluto_fichero_destino +#@param (3 parámetros) $1 sesionMcastCLIENT $2 Contenedor REPO|CACHE $3 path_absoluto_fichero_destino +#@param (4 parámetros) $1 sesionMcastCLIENT $2 disk $3 particion $4 path_absoluto_fichero_destino +#@return +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception $OG_ERR_MCASTRECEIVERFILE +#@note Requisitos: +#@version 1.0 - Definición de Protocol.lib +#@author Antonio Doblas Viso, Universidad de Málaga +#@date 2010/05/09 +#*/ ## +# + +function ogMcastReceiverFile () +{ + +# Variables locales. +local ARGS ARG TARGETDIR TARGETFILE + + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ str_portMcast] [ [Relative_path_file] | [str_REPOSITORY path_file] | [int_ndisk int_npart path_file ] ]" \ + "$FUNCNAME 9000 /PS1_PH1.img" \ + "$FUNCNAME 9000 CACHE /aula1/PS2_PH4.img" \ + "$FUNCNAME 9000 1 1 /isos/linux.iso" + return +fi + +ARGS="$@" +case "$2" in + /*) # Camino completo. */ (Comentrio Doxygen) + TARGETDIR=$(ogGetParentPath "$2") + ARG=2 + ;; + [1-9]*) # ndisco npartición. + TARGETDIR=$(ogGetParentPath "$2" "$3" "$4") + ARG=4 + ;; + *) # Otros: repo, cache, cdrom (no se permiten caminos relativos). + TARGETDIR=$(ogGetParentPath "$2" "$3") + ARG=3 + ;; +esac + +# Error si no se reciben los argumentos ARG necesarios según la opcion. +[ $# == "$ARG" ] || ogRaiseError $OG_ERR_FORMAT "Parametros no admitidos"|| return $? + +#obtenemos el nombre del fichero a descargar. +TARGETFILE=`basename ${!ARG}` + +#generamos la instrucción a ejecutar. +COMMAND=`ogMcastSyntax RECEIVERFILE "$1" $TARGETDIR/$TARGETFILE ` +RETVAL=$? + +if [ "$RETVAL" -gt "0" ] +then + return $RETVAL +else + echo $COMMAND + eval $COMMAND || ogRaiseError $OG_ERR_MCASTRECEIVERFILE "$TARGETFILE"; return $? + #[ -s "$LOGFILE" ] || return 21 +fi +} + + +#/** +# ogMcastSendPartition +#@brief Función para enviar el contenido de una partición a multiples particiones remotas. +#@param 1 disk +#@param 2 partition +#@param 3 session multicast +#@param 4 tool clone +#@param 5 tool compressor +#@return +#@exception OG_ERR_FORMAT +#@exception OG_ERR_MCASTSENDPARTITION +#@note +#@todo: ogIsLocked siempre devuelve 1. crear ticket +#@version 1.0 - Definición de Protocol.lib +#@author Antonio Doblas Viso, Universidad de Málaga +#@date 2010/05/09 +#*/ ## + +function ogMcastSendPartition () +{ + +# Variables locales +local PART COMMAND RETVAL + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npart SessionMulticastSERVER tools compresor" \ + "$FUNCNAME 1 1 9000:full-duplex:239.194.37.31:50M:20:2 partclone lzop" + return +fi +# Error si no se reciben 5 parámetros. +[ "$#" == 5 ] || ogRaiseError $OG_ERR_FORMAT || return $? +#chequeamos la particion. +PART=$(ogDiskToDev "$1" "$2") || return $? + +#ogIsLocked $1 $2 || ogRaiseError $OG_ERR_LOCKED "$1,$2" || return $? +ogUnmount $1 $2 + +#generamos la instrucción a ejecutar. +COMMAND=`ogMcastSyntax SENDPARTITION "$3" $PART $4 $5` +RETVAL=$? + +if [ "$RETVAL" -gt "0" ] +then + return $RETVAL +else + echo $COMMAND + eval $COMMAND || ogRaiseError $OG_ERR_MCASTSENDPARTITION " "; return $? +fi + + +} + +#/** +# ogMcastReceiverPartition +#@brief Función para recibir directamente en la partición el contenido de un fichero imagen remoto enviado por multicast. +#@param 1 disk +#@param 2 partition +#@param 3 session multicast +#@param 4 tool clone +#@param 5 tool compressor +#@return +#@exception $OG_ERR_FORMAT +#@note +#@todo: +#@version 1.0 - Definición de Protocol.lib +#@author Antonio Doblas Viso, Universidad de Málaga +#@date 2010/05/09 +#*/ ## +function ogMcastReceiverPartition () +{ +# Variables locales +local PART COMMAND RETVAL + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_npart SessionMulticastCLIENT tools compresor" \ + "$FUNCNAME 1 1 9000 partclone lzop" + return +fi +# Error si no se reciben 5 parámetros. +[ "$#" == 5 ] || ogRaiseError $OG_ERR_FORMAT || return $? +#chequeamos la particion. +PART=$(ogDiskToDev "$1" "$2") || return $? + +#ogIsLocked $1 $2 || ogRaiseError $OG_ERR_LOCKED "$1,$2" || return $? +ogUnmount $1 $2 + +#generamos la instrucción a ejecutar. +COMMAND=`ogMcastSyntax RECEIVERPARTITION "$3" $PART $4 $5` +RETVAL=$? + +if [ "$RETVAL" -gt "0" ] +then + return $RETVAL +else + echo $COMMAND + eval $COMMAND || ogRaiseError $OG_ERR_MCASTSENDPARTITION " "; return $? +fi + +} + + +#/** +# ogMcastRequest +#@brief Función temporal para solicitar al ogRepoAux el envio de un fichero por multicast +#@param 1 Fichero a enviar ubicado en el REPO. puede ser ruta absoluta o relatica a /opt/opengnsys/images +#@param 2 PROTOOPT opciones protocolo multicast +#@return +#@exception +#@note +#@todo: +#@version 1.0.5 +#@author Antonio Doblas Viso, Universidad de Málaga +#@date 2012/05/29 +#@version 1.1 - Control de errores en transferencia multicast (ticket #781) +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2017/04/20 +#@version 1.1 - Unidades organizativas con directorio de imágenes separado. (ticket #678) +#@author Irina Gomez, ETSII Universidad de Sevilla +#@date 2017/06/23 +#*/ ## +function ogMcastRequest () +{ +# Variables locales +local FILE PROTOOPT PORT PORTAUX REPOIP REPOPORTAUX REPEAT OGUNIT + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_filename str_mcastoptions" + return +fi +# Error si no se reciben 2 parámetros. +[ "$#" == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +OGUNIT="$(df|awk '/ogimages/ {print $1}'|cut -d/ -f5)/" +FILE="$OGUNIT$1" +PROTOOPT="$2" + +#TODO: CONTROL PARAMETROS + +PORT=$(echo $2 | cut -f1 -d":") +let PORTAUX=$PORT+1 +REPOIP=$(ogGetRepoIp) +REPOPORTAUX=2009 +REPEAT=0 +until nmap -n -sU -p $PORTAUX $REPOIP | grep open +do + let REPEAT=$REPEAT+1 + [ "$REPEAT" -lt 6 ] || ogRaiseError session log $OG_ERR_PROTOCOLJOINMASTER "MULTICAST \"$FILE\" \"$PROTOOPT\" $FILELIST" || return $? + echo "$MSG_SCRIPTS_TASK_START : hose $REPOIP $REPOPORTAUX --out sh -c "echo -ne START_MULTICAST $FILE $2"" + #update-cache: + hose $REPOIP $REPOPORTAUX --out sh -c "echo -ne START_MULTICAST "$FILE" "$PROTOOPT"" + #multicas-direct: hose $REPOIP 2009 --out sh -c "echo -ne START_MULTICAST /$IMAGE.img $OPTPROTOCOLO" + sleep 10 +done +} + + +########################################## +############## funciones torrent +#/** +# ogTorrentStart [ str_repo | int_ndisk int_npart ] Relative_path_file.torrent | SessionProtocol +#@brief Función iniciar P2P - requiere un tracker para todos los modos, y un seeder para los modos peer y leecher y los ficheros .torrent. +#@param str_pathDirectory str_Relative_path_file +#@param int_disk int_partition str_Relative_path_file +#@param str_REPOSITORY(CACHE - LOCAL) str_Relative_path_file +#@param (2 parámetros) $1 path_aboluto_fichero_torrent $2 Parametros_Session_Torrent +#@param (3 parámetros) $1 Contenedor CACHE $2 path_absoluto_fichero_Torrent $3 Parametros_Session_Torrent +#@param (4 parámetros) $1 disk $2 particion $3 path_absoluto_fichero_Torrent 4$ Parametros_Session_Torrent +#@return +#@note protocoloTORRENT=mode:time mode=seeder -> Dejar el equipo seedeando hasta que transcurra el tiempo indicado o un kill desde consola, mode=peer -> seedear mientras descarga mode=leecher -> NO seedear mientras descarga time tiempo que una vez descargada la imagen queremos dejar al cliente como seeder. +#@todo: +#@version 0.1 - Integración para OpenGNSys. +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date +#@version 0.2 - Chequeo del tamaño de imagen descargado. +#@author Irina . Univesidad de Sevilla. +#@date +#@version 0.3 - Control de los modos de operación, y estado de descarga. +#@author Antonio J. Doblas Viso. Univesidad de Málaga. +#@date +#@version 0.4 - Enviadando señal (2) a ctorrent permiendo la comunicación final con tracker +#@author Antonio J. Doblas Viso. Univesidad de Málaga. +#@date +#*/ ## +function ogTorrentStart () +{ + +# Variables locales. +local ARGS ARG TARGETDIR TARGETFILE SESSION ERROR +ERROR=0 + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME $FUNCNAME [ str_repo] [ [Relative_path_fileTORRENT] | [str_REPOSITORY path_fileTORRENT] | [int_ndisk int_npart path_fileTORRENT ] ] SessionTorrent" \ + "$FUNCNAME CACHE /PS1_PH1.img.torrent seeder:10000" \ + "$FUNCNAME /opt/opengnsys/cache/linux.iso peer:60" \ + "$FUNCNAME 1 1 /linux.iso.torrent leecher:60" + return +fi + +case "$1" in + /*) # Camino completo. */ (Comentrio Doxygen) + SOURCE=$(ogGetPath "$1") + ARG=2 + ;; + [1-9]*) # ndisco npartición. + SOURCE=$(ogGetPath "$1" "$2" "$3") + ARG=4 + ;; + *) # Otros: Solo cache (no se permiten caminos relativos). + SOURCE=$(ogGetPath "$1" "$2" 2>/dev/null) + ARG=3 + ;; +esac + +# Error si no se reciben los argumentos ARG necesarios según la opcion. +[ $# == "$ARG" ] || ogRaiseError $OG_ERR_FORMAT "Parametros no admitidos"|| return $? + +#controlar source, que no se haga al repo. +if [ $ARG == "3" ] +then + ogCheckStringInGroup "$1" "CACHE cache" || ogRaiseError $OG_ERR_FORMAT "La descarga torrent solo se hace desde local, copia el torrent a la cache y realiza la operación desde esa ubicación" || return $? +fi +if [ $ARG == "2" ] +then + if `ogCheckStringInReg "$1" "^/opt/opengnsys/images"` + then + ogRaiseError $OG_ERR_FORMAT "La descarga torrent solo se hace desde local, copia el torrent a la cache y realiza la operación desde esa ubicación" + return $? + fi +fi + +#controlar el source, para que sea un torrent. +ctorrent -x ${SOURCE} &> /dev/null; [ $? -eq 0 ] || ogRaiseError $OG_ERR_NOTFOUND "${ARGS% $*}" || return $? + +TARGET=`echo $SOURCE | awk -F.torrent '{print $1}'` +DIRSOURCE=`ogGetParentPath $SOURCE` +cd $DIRSOURCE + + + +SESSION=${!ARG} +OIFS=$IFS; IFS=':' ; SESSION=($SESSION); IFS=$OIFS +[[ ${#SESSION[*]} == 2 ]] || ogRaiseError $OG_ERR_FORMAT "parametros session Torrent no completa: modo:tiempo" || ERROR=1# return $? +#controlamos el modo de operación del cliente- +ogCheckStringInGroup ${SESSION[0]} "seeder SEEDER peer PEER leecher LEECHER" || ogRaiseError $OG_ERR_FORMAT "valor modo Torrent no valido ${SESSION[0]}" || ERROR=1 #return $? +MODE=${SESSION[0]} +#contolamos el tiempo para el seeder o una vez descargada la imagen como peer o leecher. +ogCheckStringInReg ${SESSION[1]} "^[0-9]{1,10}$" || ogRaiseError $OG_ERR_FORMAT "valor tiempo no valido ${SESSION[1]}" || ERROR=1 # return $? +TIME=${SESSION[1]} +# si ha habido error en el control de parametros error. +[ "$ERROR" == "1" ] && return 1 + + +#SYNTAXSEEDER="echo MODE seeder ctorrent ; (sleep \$TIME && kill -9 \`pidof ctorrent\`) & ; ctorrent \${SOURCE}" + +# si No fichero .bf, y Si fichero destino imagen ya descargada y su chequeo fue comprobado en su descarga inicial. +if [ ! -f ${SOURCE}.bf -a -f ${TARGET} ] +then + echo "imagen ya descargada" + case "$MODE" in + seeder|SEEDER) + echo "MODE seeder ctorrent" #### ${SOURCE} -X 'sleep $TIME; kill -9 \$(pidof ctorrent)' -C 100" + (sleep $TIME && kill -2 `pidof ctorrent`) & + ctorrent -f ${SOURCE} + esac + return 0 +fi + +#Si no existe bf ni fichero destino descarga inicial. +if [ ! -f ${SOURCE}.bf -a ! -f ${TARGET} ] +then + OPTION=DOWNLOAD + echo "descarga inicial" +fi + +# Si fichero bf descarga anterior no completada -. +if [ -f ${SOURCE}.bf -a -f ${TARGET} ] +then + echo Continuar con Descargar inicial no terminada. + OPTION=DOWNLOAD +fi + +if [ "$OPTION" == "DOWNLOAD" ] +then + case "$MODE" in + peer|PEER) + echo "Donwloading Torrent as peer" ### echo "ctorrent -X 'sleep $TIME; kill -9 \$(pidof ctorrent)' -C 100 $SOURCE -s $TARGET -b ${SOURCE}" + # Creamos el fichero de resumen por defecto + touch ${SOURCE}.bf + # ctorrent controla otro fichero -b ${SOURCE}.bfog + ctorrent -f -c -X "sleep $TIME; kill -2 \$(pidof ctorrent)" -C 100 ${SOURCE} -s ${TARGET} -b ${SOURCE}.bfog + ;; + leecher|LEECHER) + echo "Donwloading Torrent as leecher" # echo "ctorrent ${SOURCE} -X 'sleep 30; kill -9 \$(pidof ctorrent)' -C 100 -U 0" + ctorrent ${SOURCE} -X "sleep 30; kill -2 \$(pidof ctorrent)" -C 100 -U 0 + ;; + seeder|SEEDER) + echo "MODE seeder ctorrent" #### ${SOURCE} -X 'sleep $TIME; kill -9 \$(pidof ctorrent)' -C 100" + # Creamos el fichero de resumen por defecto + touch ${SOURCE}.bf + # ctorrent controla otro fichero -b ${SOURCE}.bfog + ctorrent -f -c -X "sleep $TIME; kill -2 \$(pidof ctorrent)" -C 100 ${SOURCE} -s ${TARGET} -b ${SOURCE}.bfog + ;; + esac +fi +cd /tmp +} + +#/** +# ogCreateTorrent [ str_repo | int_ndisk int_npart ] Relative_path_file +#@brief Función para crear el fichero torrent. +#@param str_pathDirectory str_Relative_path_file +#@param int_disk int_partition str_Relative_path_file +#@param str_REPOSITORY(CACHE - LOCAL) str_Relative_path_file +#@return +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Disco o particion no corresponden con un dispositivo. +#@exception OG_ERR_PARTITION Tipo de partición desconocido o no se puede montar. +#@exception OG_ERR_NOTOS La partición no tiene instalado un sistema operativo. +#@note +#@version 0.1 - Integración para OpenGNSys. +#@author Antonio J. Doblas Viso. Universidad de Málaga +#@date +#@version 0.2 - Integración para btlaunch. +#@author Irina . Univesidad de Sevilla. +#@date +#*/ ## + +function ogCreateTorrent () +{ +# Variables locales. +local ARGS ARG SOURCE EXT IPTORRENT + + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [str_REPOSITORY] [int_ndisk int_npart] Relative_path_file IpBttrack" \ "$FUNCNAME 1 1 /aula1/winxp 10.1.15.23" \ + "$FUNCNAME REPO /aula1/winxp 10.1.15.45" + + return +fi + +# Error si se quiere crear el fichero en cache y no existe +[ "$1" != "CACHE" ] || `ogFindCache >/dev/null` || ogRaiseError $OG_ERR_NOTFOUND "CACHE"|| return $? + +case "$1" in + /*) # Camino completo. */ (Comentrio Doxygen) + SOURCE=$(ogGetPath "$1.img") + ARG=2 + ;; + [1-9]*) # ndisco npartición. + SOURCE=$(ogGetPath "$1" "$2" "$3.img") + ARG=4 + ;; + *) # Otros: repo, cache, cdrom (no se permiten caminos relativos). + EXT=$(ogGetImageType "$1" "$2") + SOURCE=$(ogGetPath "$1" "$2.$EXT") + ARG=3 + ;; +esac + +# Error si no se reciben los argumentos ARG necesarios según la opcion. +[ $# -eq "$ARG" ] || ogRaiseError $OG_ERR_FORMAT || return $? + + +# Error si no existe la imagen +[ $SOURCE ] || ogRaiseError $OG_ERR_NOTFOUND || return $? + +[ -r $SOURCE.torrent ] && mv "$SOURCE.torrent" "$SOURCE.torrent.ant" && echo "Esperamos que se refresque el servidor" && sleep 20 + +IPTORRENT="${!#}" +# Si ponemos el path completo cuando creamos el fichero torrent da error +cd `dirname $SOURCE` +echo ctorrent -t `basename $SOURCE` -u http://$IPTORRENT:6969/announce -s $SOURCE.torrent +ctorrent -t `basename $SOURCE` -u http://$IPTORRENT:6969/announce -s $SOURCE.torrent + +} + + +#/** +# ogUpdateCacheIsNecesary [ str_repo ] Relative_path_file_OGIMG_with_/ +#@brief Comprueba que el fichero que se desea almacenar en la cache del cliente, no esta. +#@param 1 str_REPO +#@param 2 str_Relative_path_file_OGIMG_with_/ +#@param 3 md5 to check: use full to check download image torrent +#@return 0 (true) cache sin imagen, SI es necesario actualizar el fichero. +#@return 1 (false) imagen en la cache, NO es necesario actualizar el fichero +#@return >1 (false) error de sintaxis (TODO) +#@note +#@todo: Proceso en el caso de que el fichero tenga el mismo nombre, pero su contenido sea distinto. +#@todo: Se dejan mensajes mientras se confirma su funcionamiento. +#@version 0.1 - Integracion para OpenGNSys. +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date +#@version 1.6 - gestiona ficheros hash full.sum (TORRENT) y .sum (MULTICAST) +#@author Antonio J. Doblas Viso. Universidad de Malaga +#@date +#*/ ## +function ogUpdateCacheIsNecesary () +{ +#echo "admite full check con 3param TORRENT" +# Variables locales. +local ERROR SOURCE CACHE FILESOURCE MD5SOURCE FILETARGET MD5TARGET +ERROR=0 + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME str_repo relative_path_image [protocol|FULL]" \ + "$FUNCNAME REPO /PS1_PH1.img UNICAST" \ + "$FUNCNAME REPO /ogclient.sqfs FULL" + + return +fi + +#Control de la cache +ogFindCache &>/dev/null || return $(ogRaiseError $OG_ERR_NOTCACHE; echo $?) + +#Control de parametros: ahora admite tres. +[ $# -ge 2 ] || return $(ogRaiseError $OG_ERR_FORMAT "$MSG_FORMAT: $PROG str_repo relative_path_image [protocol|FULL]"; echo $?) + +ogCheckStringInGroup "$1" "REPO repo" || return $(ogRaiseError $OG_ERR_NOTFOUND " $1 $2"; echo $?) +FILESOURCE=`ogGetPath $1 $2` +[ -n "$FILESOURCE" ] || return $(ogRaiseError $OG_ERR_NOTFOUND " $1 $2"; echo $?) + +#echo "paso 1. si no existe la imagen, confirmar que es necesario actualizar la cache." +FILETARGET=`ogGetPath CACHE $2` +if [ -z "$FILETARGET" ] +then + # borramos el fichero bf del torrent, en el caso de que se hubiese quedado de algun proceso fallido + [ -n "$(ogGetPath CACHE "/$2.torrent.bf")" ] && ogDeleteFile CACHE "/$2.torrent.bf" &> /dev/null + [ -n "$(ogGetPath CACHE "/$2.sum")" ] && ogDeleteFile CACHE "/$2.sum" &> /dev/null + [ -n "$(ogGetPath CACHE "/$2.full.sum")" ] && ogDeleteFile CACHE "/$2.full.sum" &> /dev/null + echo "TRUE(0), es necesario actualizar. Paso 1, la cache no contiene esa imagen " + return 0 +fi + +#echo "Paso 2. Comprobamos que la imagen no estuviese en un proceso previo torrent" +if [ -n "$(ogGetPath "$FILETARGET.torrent.bf")" ]; then + #TODO: comprobar los md5 del fichero .torrent para asegurarnos que la imagen a descarga es la misma. + echo "TRUE(0), es necesario actualizar. Paso 2, la imagen esta en un estado de descarga torrent interrumpido" + return 0 +fi + +## En este punto la imagen en el repo y en la cache se llaman igual, +#echo "paso 4. Obtener los md5 del fichero imagen en la cacha segun PROTOCOLO $3" +case "${3^^}" in + FULL|TORRENT) + #Buscamos MD5 en el REPO SOURCE + if [ -f $FILESOURCE.full.sum ] + then + MD5SOURCE=$(cat $FILESOURCE.full.sum) + else + MD5SOURCE=$(ogCalculateFullChecksum $FILESOURCE) + fi + # Generamos el MD5 (full) en la CACHE + [ ! -f $FILETARGET.full.sum ] && ogCalculateFullChecksum $FILETARGET > $FILETARGET.full.sum + MD5TARGET=$(cat $FILETARGET.full.sum) + # Generamos el MD5 (little) en la CACHE para posteriores usos del protocolo MULTICAST + [ ! -f $FILETARGET.sum ] && ogCalculateChecksum $FILETARGET > $FILETARGET.sum + ;; + *) + #Buscamos MD5 en el REPO SOURCE + if [ -f $FILESOURCE.sum ] + then + MD5SOURCE=$(cat $FILESOURCE.sum) + else + MD5SOURCE=$(ogCalculateChecksum $FILESOURCE) + fi + # Generamos el MD5 (little) en la CACHE + [ ! -f $FILETARGET.sum ] && ogCalculateChecksum $FILETARGET > $FILETARGET.sum + MD5TARGET=$(cat $FILETARGET.sum) + #Generamos o copiamos MD5 (full) en la CACHE para posteriores usos con Torrent + # Si no existe el full.sum y si existe el .sum es porque el upateCACHE multicast o unicast ha sido correcto. + if [ ! -f $FILETARGET.full.sum -a $FILETARGET.sum ] + then + if [ -f $FILESOURCE.full.sum ] + then + #Existe el .full.sum en REPO realizamos COPIA + cp $FILESOURCE.full.sum $FILETARGET.full.sum + else + #No existe .full.sum no en REPO LO GENERAMOS en la cache: situacion dificil que ocurra + ogCalculateFullChecksum $FILETARGET > $FILETARGET.full.sum + fi + fi + +esac + +#echo "Paso 5. comparar los md5" +if [ "$MD5SOURCE" == "$MD5TARGET" ] +then + echo "FALSE (1), No es neceario actualizar. Paso5.A la imagen esta en cache" + return 1 +else + echo "imagen en cache distinta, borramos la imagen anterior" + rm -f $FILETARGET $FILETARGET.sum $FILETARGET.torrent $FILETARGET.full.sum + echo "TRUE (0), Si es necesario actualizar." + return 0 +fi +} + diff --git a/client/engine/README.es.txt b/client/engine/README.es.txt new file mode 100644 index 0000000..531900a --- /dev/null +++ b/client/engine/README.es.txt @@ -0,0 +1,38 @@ + +OpenGnsys Client Cloning Engine README +======================================= + +En este directorio se incluirán las funciones del motor de +clonación de OpenGnsys y la documentación asociada. + +Este directorio estará localizado en el directorio del servidor +/opt/opengnsys/client/lib/engine/bin + +Las funciones serán accesibles por el cliente en el directorio +/opt/opengnsys/lib/engine/bin + +OpenGnsys Client Cloning Engine se distribuye en un conjunto de +librerías que incluyen funciones BASH que deben ser exportadas +al entorno del cliente. + +Librerías: + +- Boot.lib funciones de arranque y posconfiguración de + sistemas operativos. +- Cache.lib funciones de gestión de la caché local del cliente. +- Disk.lib funciones de control de dispositivos de disco. +- File.lib funciones de manipulación de ficheros. +- FileSystem.lib funciones de gestión de sistemas de ficheros. +- Image.lib funciones de administración de imágenes de + sistemas operativos. +- Inventory.lib funciones de control de inventario e informes. +- Net.lib funciones básicas de control de acceso a la red. +- Postconf.lib funciones de post-configuración de sistemas + operativos. +- Protocol.lib funciones de implementación de protocolos de + comunicaciones. +- Registry.lib funciones de gestión del registro de Windows. +- Rsync.lib funciones de sincronización de ficheros. +- String.lib funciones de control de cadena. +- System.lib funciones básicas del sistema. + diff --git a/client/engine/Registry.lib b/client/engine/Registry.lib new file mode 100755 index 0000000..60f7124 --- /dev/null +++ b/client/engine/Registry.lib @@ -0,0 +1,455 @@ +#!/bin/bash +#/** +#@file Registry.lib +#@brief Librería o clase Registry +#@class Boot +#@brief Funciones para gestión del registro de Windows. +#@version 1.1.0 +#@warning License: GNU GPLv3+ +#*/ + + +# Función ficticia para lanzar chntpw con timeout de 5 s., evitando cuelgues del programa. +function chntpw () +{ +local CHNTPW +CHNTPW=$(which drbl-chntpw) +CHNTPW=${CHNTPW:-$(which chntpw)} +timeout --foreground 5s $CHNTPW -e "$@" +} + + +#/** +# ogAddRegistryKey path_mountpoint str_hive str_keyname +#@brief Añade una nueva clave al registro de Windows. +#@param path_mountpoint directorio donde está montado el sistema Windows +#@param str_hive sección del registro +#@param str_keyname nombre de la clave +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Fichero de registro no encontrado. +#@note hive = { default, sam, security, software, system, components } +#@warning Requisitos: chntpw +#@warning El sistema de archivos de Windows debe estar montada previamente. +#@version 1.0.1 - Nueva función +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-05-25 +#*/ ## +function ogAddRegistryKey () +{ +# Variables locales. +local FILE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_mountpoint str_hive str_key" \ + "$FUNCNAME /mnt/sda1 SOFTWARE '\Microsoft\NewKey'" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Camino del fichero de registro. +FILE=$(ogGetHivePath "$1" "$2") || return $? + +# Añadir nueva clave. +chntpw "$FILE" << EOT &> /dev/null +cd ${3%\\*} +nk ${3##*\\} +q +y +EOT +} + +#/** +# ogAddRegistryValue path_mountpoint str_hive str_valuename [str_valuetype] +#@brief Añade un nuevo valor al registro de Windows, indicando su tipo de datos. +#@param path_mountpoint directorio donde está montado el sistema Windows +#@param str_hive sección del registro +#@param str_valuename nombre del valor +#@param str_valuetype tipo de datos del valor (opcional) +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Fichero de registro no encontrado. +#@note hive = { DEFAULT, SAM, SECURITY, SOFTWARE, SYSTEM, COMPONENTS } +#@note valuetype = { STRING, BINARY, DWORD }, por defecto: STRING +#@warning Requisitos: chntpw +#@warning El sistema de archivos de Windows debe estar montada previamente. +#@version 1.0.1 - Nueva función +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-05-25 +#*/ ## +function ogAddRegistryValue () +{ +# Variables locales. +local FILE TYPE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_mountpoint str_hive str_valuename [str_valuetype]" \ + "$FUNCNAME /mnt/sda1 SOFTWARE '\Microsoft\NewKey\Value1'" \ + "$FUNCNAME /mnt/sda1 SOFTWARE '\Microsoft\NewKey\Value1' DWORD" + return +fi +# Error si no se reciben 3 o 4 parámetros. +[ $# == 3 -o $# == 4 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Camino del fichero de registro. +FILE=$(ogGetHivePath "$1" "$2") || return $? +case "${4^^}" in + STRING|"") TYPE=1 ;; + BINARY) TYPE=3 ;; + DWORD) TYPE=4 ;; + *) ogRaiseError $OG_ERR_OUTOFLIMIT "$4" + return $? ;; +esac + +# Devolver el dato del valor de registro. +# /* (comentario Doxygen) +chntpw "$FILE" << EOT &> /dev/null +cd ${3%\\*} +nv $TYPE ${3##*\\} +q +y +EOT +# (comentario Doxygen) */ +} + + +#/** +# ogDeleteRegistryKey path_mountpoint str_hive str_keyname +#@brief Elimina una clave del registro de Windows con todo su contenido. +#@param path_mountpoint directorio donde está montado el sistema Windows +#@param str_hive sección del registro +#@param str_keyname nombre de la clave +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Fichero de registro no encontrado. +#@note hive = { default, sam, security, software, system, components } +#@warning Requisitos: chntpw +#@warning El sistema de archivos de Windows debe estar montada previamente. +#@warning La clave debe estar vacía para poder ser borrada. +#@version 1.0.1 - Nueva función +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-05-25 +#*/ ## +function ogDeleteRegistryKey () +{ +# Variables locales. +local FILE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_mountpoint str_hive str_key" \ + "$FUNCNAME /mnt/sda1 SOFTWARE '\Microsoft\NewKey'" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Camino del fichero de registro. +FILE=$(ogGetHivePath "$1" "$2") || return $? + +# Añadir nueva clave. +chntpw "$FILE" << EOT &> /dev/null +cd ${3%\\*} +dk ${3##*\\} +q +y +EOT +} + + +#/** +# ogDeleteRegistryValue path_mountpoint str_hive str_valuename +#@brief Elimina un valor del registro de Windows. +#@param path_mountpoint directorio donde está montado el sistema Windows +#@param str_hive sección del registro +#@param str_valuename nombre del valor +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Fichero de registro no encontrado. +#@note hive = { default, sam, security, software, system, components } +#@warning Requisitos: chntpw +#@warning El sistema de archivos de Windows debe estar montada previamente. +#@version 1.0.1 - Nueva función +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-05-25 +#*/ ## +function ogDeleteRegistryValue () +{ +# Variables locales. +local FILE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_mountpoint str_hive str_valuename" \ + "$FUNCNAME /mnt/sda1 SOFTWARE '\Microsoft\NewKey\Value1'" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Camino del fichero de registro. +FILE=$(ogGetHivePath "$1" "$2") || return $? + +# Devolver el dato del valor de registro. +# /* (comentario Doxygen) +chntpw "$FILE" << EOT &> /dev/null +cd ${3%\\*} +dv ${3##*\\} +q +y +EOT +# (comentario Doxygen) */ +} + + +#/** +# ogGetHivePath path_mountpoint [str_hive|str_user] +#@brief Función básica que devuelve el camino del fichero con una sección del registro. +#@param path_mountpoint directorio donde está montado el sistema Windows +#@param str_hive sección del registro +#@return str_path - camino del fichero de registro +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Fichero de registro no encontrado. +#@note hive = { DEFAULT, SAM, SECURITY, SOFTWARE, SYSTEM, COMPONENTS, NombreDeUsuario } +#@warning El sistema de archivos de Windows debe estar montada previamente. +#@version 1.0.1 - Nueva función +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-05-18 +#@version 1.1.0 - Soportar registro de un usuario local. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2015-10-14 +#*/ ## +function ogGetHivePath () +{ +# Variables locales. +local FILE HIVE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_mountpoint [str_hive|str_user]" \ + "$FUNCNAME /mnt/sda1 SOFTWARE => /mnt/sda1/WINDOWS/System32/config/SOFTWARE" \ + "$FUNCNAME /mnt/sda1 user1 => /mnt/sda1/Users/user1/NTUSER.DAT" + return +fi +# Error si no se reciben 2 parámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Camino del fichero de registro de usuario o de sistema (de menor a mayor prioridad). +FILE="$(ogGetPath "/$1/Windows/System32/config/$2")" +[ -z "$FILE" ] && FILE="$(ogGetPath "/$1/Users/$2/NTUSER.DAT")" +[ -z "$FILE" ] && FILE="$(ogGetPath "/$1/winnt/system32/config/$2")" +[ -z "$FILE" ] && FILE="$(ogGetPath "/$1/Documents and Settings/$2/NTUSER.DAT")" +[ -f "$FILE" ] && echo "$FILE" || ogRaiseError $OG_ERR_NOTFOUND "$1 $2" || return $? +} + + +#/** +# ogGetRegistryValue path_mountpoint str_hive str_valuename +#@brief Devuelve el dato de un valor del registro de Windows. +#@param path_mountpoint directorio donde está montado el sistema Windows +#@param str_hive sección del registro +#@param str_valuename nombre del valor +#@return str_valuedata - datos del valor. +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Fichero de registro no encontrado. +#@note hive = { default, sam, security, software, system, components } +#@warning Requisitos: chntpw, awk +#@warning El sistema de archivos de Windows debe estar montado previamente. +#@version 0.9 - Adaptación para OpenGNSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-11 +#@version 1.1.0 - Soportar tipos BINARY (parejas hexadecimales separadas por espacio). +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2015-09-28 +#*/ ## +function ogGetRegistryValue () +{ +# Variables locales. +local FILE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_mountpoint str_hive str_valuename" \ + "$FUNCNAME /mnt/sda1 SOFTWARE '\Microsoft\NewKey\Value1' ==> 1" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Camino del fichero de registro. +FILE=$(ogGetHivePath "$1" "$2") || return $? + +# Devolver el dato del valor de registro. +# /* (comentario Doxygen) +chntpw "$FILE" << EOT 2> /dev/null | awk '/> Value/ {if (index($0, "REG_BINARY") > 0) + {data=""} + else + {getline; data=$0;} } + /^:[0-9A-F]+ / {data=data""substr($0, 9, 48);} + END {print data;}' +cd ${3%\\*} +cat ${3##*\\} +q +EOT +# (comentario Doxygen) */ +} + + +#/** +# ogListRegistryKeys path_mountpoint str_hive str_key +#@brief Lista los nombres de subclaves de una determinada clave del registro de Windows. +#@param path_mountpoint directorio donde está montado el sistema Windows +#@param str_hive sección del registro +#@param str_key clave de registro +#@return str_subkey ... - lista de subclaves +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Fichero de registro no encontrado. +#@note hive = { default, sam, security, software, system, components } +#@warning Requisitos: chntpw, awk +#@warning El sistema de archivos de Windows debe estar montado previamente. +#@version 0.9 - Adaptación para OpenGNSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-23 +#*/ ## +function ogListRegistryKeys () +{ +# Variables locales. +local FILE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_mountpoint str_hive str_key" \ + "$FUNCNAME /mnt/sda1 SOFTWARE '\Microsoft\Windows\CurrentVersion'" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Camino del fichero de registro. +FILE=$(ogGetHivePath "$1" "$2") || return $? + +# Devolver la lista de claves de registro. +chntpw "$FILE" << EOT 2> /dev/null | awk 'BEGIN {FS="[<>]"} $1~/^ $/ {print $2}' +ls $3 +q +EOT +} + + +#/** +# ogListRegistryValues path_mountpoint str_hive str_key +#@brief Lista los nombres de valores de una determinada clave del registro de Windows. +#@param path_mountpoint directorio donde está montado el sistema Windows +#@param str_hive sección del registro +#@param str_key clave de registro +#@return str_value ... - lista de valores +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Fichero de registro no encontrado. +#@note hive = { default, sam, security, software, system, components } +#@warning Requisitos: chntpw, awk +#@warning El sistema de archivos de Windows debe estar montado previamente. +#@version 1.0.1 - Nueva función. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-05-26 +#*/ ## +function ogListRegistryValues () +{ +# Variables locales. +local FILE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_mountpoint str_hive str_key" \ + "$FUNCNAME /mnt/sda1 SOFTWARE '\Microsoft\Windows\CurrentVersion'" + return +fi +# Error si no se reciben 3 parámetros. +[ $# == 3 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Camino del fichero de registro. +FILE=$(ogGetHivePath "$1" "$2") || return $? + +# Devolver la lista de claves de registro. +chntpw "$FILE" << EOT 2> /dev/null | awk 'BEGIN {FS="[<>]"} $1~/REG_/ {print $2}' +ls $3 +q +EOT +} + + +#/** +# ogSetRegistryValue path_mountpoint str_hive str_valuename str_valuedata +#@brief Establece el dato asociado a un valor del registro de Windows. +#@param path_mountpoint directorio donde está montado el sistema Windows +#@param str_hive sección del registro +#@param str_valuename nombre del valor de registro +#@param str_valuedata dato del valor de registro +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND Fichero de registro no encontrado. +#@note hive = { default, sam, security, software, system, components } +#@warning Requisitos: chntpw +#@warning El sistema de archivos de Windows debe estar montado previamente. +#@version 0.9 - Adaptación para OpenGNSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-09-24 +#@version 1.1.0 - Soportar tipos BINARY (parejas hexadecimales separadas por espacio). +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2015-09-28 +#*/ ## +function ogSetRegistryValue () +{ +# Variables locales. +local FILE i n tmpfile + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME path_mountpoint str_hive str_valuename str_data" \ + "$FUNCNAME /mnt/sda1 SOFTWARE '\Key\SubKey\StringValue' \"Abcde Fghij\"" \ + "$FUNCNAME /mnt/sda1 SOFTWARE '\Key\SubKey\DwordValue' 1" \ + "$FUNCNAME /mnt/sda1 SOFTWARE '\Key\SubKey\BinaryValue' \"04 08 0C 10\"" + return +fi +# Error si no se reciben 4 parámetros. +[ $# == 4 ] || ogRaiseError $OG_ERR_FORMAT || return $? +# Camino del fichero de registro. +FILE=$(ogGetHivePath "$1" "$2") || return $? + +# Fichero temporal para componer la entrada al comando "chntpw". +tmpfile=/tmp/chntpw$$ +trap "rm -f $tmpfile" 1 2 3 9 15 + +# Comprobar tipo de datos del valor del registro. +cat << EOT >$tmpfile +ls ${3%\\*} +q +EOT +if [ -n "$(chntpw "$FILE" < $tmpfile 2> /dev/null | grep "BINARY.*<${3##*\\}>")" ]; then + # Procesar tipo binario (incluir nº de bytes y líneas de 16 parejas hexadecimales). + [[ "$4 " =~ ^([0-9A-F]{2} )*$ ]] || ogRaiseError $OG_ERR_FORMAT "\"$4\"" || return $? + let n=${#4}+1 + cat << EOT >$tmpfile +cd ${3%\\*} +ed ${3##*\\} +$[n/3] +EOT + # Formato de líneas hexadecimales: :OFFSET XX YY ZZ ... (hasta 16 parejas). + for (( i=0; i> $tmpfile + done + echo -e "s\nq\ny" >> $tmpfile +else + # Cambiar el dato del valor de registro para cadenas y bytes. + cat << EOT >$tmpfile +cd ${3%\\*} +ed ${3##*\\} +$4 +q +y +EOT + +fi + +# Aplicar cambios. +chntpw "$FILE" < $tmpfile &> /dev/null +rm -f $tmpfile +} + + diff --git a/client/engine/Rsync.lib b/client/engine/Rsync.lib new file mode 100755 index 0000000..2478956 --- /dev/null +++ b/client/engine/Rsync.lib @@ -0,0 +1,900 @@ +#!/bin/bash + +#/** +# rsync +#@brief Función para utilizar la versión de rsync situada en $OPENGNSYS/bin en vez de la del sistema operativo. +#@param los mismos que el comando rsync del sistema operativo. +#@warning Solo en clientes ogLive de 32 bits. +#@return instrucción para ser ejecutada. +#*/ +function rsync () +{ +local RSYNC +[ "$(arch)" == "i686" -a -x $OPENGNSYS/bin/rsync ] && RSYNC=$OPENGNSYS/bin/rsync +RSYNC=${RSYNC:-$(which rsync)} + +$RSYNC "$@" +} + + +#/** +# ogCreateFileImage [ REPO | CACHE ] image_name extension size +#@brief Crear el archivo +#@param 1 Repositorio [ REPO | CACHE ] +#@param 2 Nombre Imagen +#@param 3 Tipo imagen [ img |diff ] +#@param 4 Tamaño de la imagen +#@return instrucción para ser ejecutada. +#*/ + +function ogCreateFileImage () { +local SIZEREQUIRED IMGDIR IMGFILE DIRMOUNT LOOPDEVICE IMGSIZE IMGEXT KERNELVERSION + +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" \ + "$FUNCNAME [ REPO|CACHE ] image_name extension size(K)" \ + "$FUNCNAME REPO Ubuntu12 img 300000" \ + "$FUNCNAME CACHE Windows7 diff 20000000" + return +fi + + +if [ $# -lt 4 ]; then + ogRaiseError $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME [ REPO|CACHE ] image_name extension size(k)" + return $? +fi + +SIZEREQUIRED=$4 +[ $SIZEREQUIRED -lt 300000 ] && SIZEREQUIRED=300000 +KERNELVERSION=$(uname -r| awk '{printf("%d",$1);sub(/[0-9]*\./,"",$1);printf(".%02d",$1)}') + +if [ "$1" == "CACHE" -o "$1" == "cache" ]; then + IMGDIR="$(ogGetParentPath "$1" "/$2")" + [ "$3" == "img" ] && IMGEXT="img" || IMGEXT="img.diff" + IMGFILE="${IMGDIR}/$(basename "/$2").$IMGEXT" + ## Si no existe, crear subdirectorio de la imagen. + if [ $? != 0 ]; then + ogEcho log session " $MSG_HELP_ogMakeDir \"$1 $(dirname "$2")." + ogMakeDir "$1" "$(dirname "/$2")" || return $(ogRaiseError $OG_ERR_NOTWRITE "$3 /$4"; echo $?) + IMGDIR="$(ogGetParentPath "$1" "/$2")" || return $(ogRaiseError $OG_ERR_NOTWRITE "$3 /$4"; echo $?) + fi + DIRMOUNT="/tmp/$(ogGetMountImageDir "$2" "$3")" + mkdir -p "$DIRMOUNT" + LOOPDEVICE=$(losetup -f) + # Si existe el fichero de la imagen se hace copia de seguridad, si no existe se crea. + if [ -f "$IMGFILE" ]; then + # Si la imagen esta montada la desmonto + if [ -r "$DIRMOUNT/ogimg.info" ]; then + umount "$DIRMOUNT" + [ $? -ne 0 ] && return $(ogRaiseError $OG_ERR_DONTUNMOUNT_IMAGE "$1 $2.$IMGEXT"; echo $?) + fi + + if [ "$BACKUP" == "true" -o "$BACKUP" == "TRUE" ]; then + # Copia seguridad + ogEcho log session " $MSG_SCRIPTS_FILE_RENAME \"$IMGFILE\" -> \"$IMGFILE.ant\"." + cp -f "$IMGFILE" "$IMGFILE.ant" + mv -f "$IMGFILE.torrent" "$IMGFILE.torrent.ant" 2>/dev/null + rm -f "$IMGFILE.sum" + fi + + IMGSIZE=$(ls -l --block-size=1024 "$IMGFILE" | awk '{print $5}') + if [ $IMGSIZE -lt $SIZEREQUIRED ];then + ogEcho log session " $MSG_SYNC_RESIZE" + echo " truncate --size=>$SIZEREQUIRED k $IMGFILE" + truncate --size=">$SIZEREQUIRED"k "$IMGFILE" &> $OGLOGCOMMAND + # FS de la imagen segun el contenido del archivo .img + if file "$IMGFILE" |grep -i -e " ext4 filesystem " 2>&1 > /dev/null ; then + losetup $LOOPDEVICE "$IMGFILE" + echo " resize2fs -f $LOOPDEVICE" + resize2fs -f $LOOPDEVICE &> $OGLOGCOMMAND + + else + echo " ogMountImage $1 "$2" $3" + ogMountImage $1 "$2" $3 + echo " btrfs filesystem resize max $DIRMOUNT" + btrfs filesystem resize max "$DIRMOUNT" &> $OGLOGCOMMAND + fi + fi + else + touch "$IMGFILE" + echo " truncate --size=>$SIZEREQUIRED k $IMGFILE" + truncate --size=">$SIZEREQUIRED"k "$IMGFILE" &> $OGLOGCOMMAND + #Formateamos imagen + losetup $LOOPDEVICE $IMGFILE + # FS de la imagen segun la configuracion y la version del kernel: < 3.7 ext4, si >= btrfs + [ $KERNELVERSION \< 3.07 ] && IMGFS="EXT4" || IMGFS=${IMGFS:-"BTRFS"} + + if [ "$IMGFS" == "EXT4" ]; then + echo " mkfs.ext4 -i 4096 -b 4096 -L "${2##*\/}" $LOOPDEVICE" + mkfs.ext4 -i 4096 -b 4096 -L "${2##*\/}" $LOOPDEVICE 2>&1 |tee -a $OGLOGCOMMAND + else + echo " mkfs.btrfs -L ${2##*\/} $LOOPDEVICE " + mkfs.btrfs -L "${2##*\/}" $LOOPDEVICE 2>&1 | tee -a $OGLOGCOMMAND + fi + fi + # Monto la imagen + ogMountImage $1 "$2" $3 &>/dev/null + [ $? -eq 0 ] || return $( ogRaiseError $OG_ERR_IMAGE "$3 $4"; echo $?) + echo "mounted"> $IMGFILE.lock + + # Si existe dispositivo de loop lo borro. + [ $LOOPDEVICE ] && losetup -d $LOOPDEVICE 2>&1 &>/dev/null + +else + [ -z $REPOIP ] && REPOIP=$(ogGetRepoIp) + echo " hose $REPOIP 2009 --out sh -c \"echo -ne CREATE_IMAGE $2 $3 $SIZEREQUIRED \"" + hose $REPOIP 2009 --out sh -c "echo -ne CREATE_IMAGE \"$2\" $3 $SIZEREQUIRED" +fi + +} + + +#/** +# ogCreateInfoImage +#@brief Crear listados con la informacion de la imagen, los situa en /tmp. +#@param 1 num_disk +#@param 2 num_part +#@param 3 Repositorio [ REPO | CACHE ] (opcional en las completas) +#@param 4 Nombre Imagen Basica (opcional en las completas) +#@param 5 Tipo imagen [ img | diff ] +#@version 1.0.6 rsync opcion W (whole) para que sea más rápido +#*/ +function ogCreateInfoImage () { +local IMGTYPE IMGDIRAUX DIRMOUNT DESTRSYNC PASSWORD USERRSYNC ORIG FSTYPE PART DIREMPTY IMGLIST IMGINFO IMGACL KERNELVERSION +# Ayuda o menos de 5 parametros y la imagen no es basica +if [ "$*" == "help" -o $# -lt 5 -a "$3" != "img" ]; then + ogHelp "$FUNCNAME" \ + "$FUNCNAME num_disk num_part [ REPO|CACHE ] [ base_image_name ] extension " \ + "base image -> $FUNCNAME 1 2 img" \ + "diff image -> $FUNCNAME 1 1 CACHE Windows7 diff " + return +fi + +if [ $# -lt 3 ]; then + ogRaiseError $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME num_disk num_part [ REPO|CACHE ] [ base_image_name] extension " + return $? +fi + +# Comprobar errores. +PART=$(ogDiskToDev "$1" "$2") || return $? +ORIG=$(ogMount $1 $2) || return $? + +if [ $3 == "img" ]; then + IMGTYPE="img" +else + # Comprobamos que las extension sea valida + ogCheckStringInGroup $5 "img diff" || return $( ogRaiseError $OG_ERR_FORMAT "$MSG_SYNC_EXTENSION"; echo $?) + IMGTYPE=$5 + if [ "$IMGTYPE" == "diff" ]; then + # Imagen completa con la que comparo la particion. + IMGDIRAUX="$(ogGetMountImageDir "$4" "img")" + if [ "$3" == "CACHE" -o "$3" == "cache" ]; then + DIRMOUNT="/tmp/$IMGDIRAUX" + DESTRSYNC="$DIRMOUNT" + else + [ -z $REPOIP ] && REPOIP=$(ogGetRepoIp) + DIRMOUNT="$OGIMG/$IMGDIRAUX" + USERRSYNC="opengnsys" + PASSWORD="--password-file=/scripts/passrsync" + DESTRSYNC="$USERRSYNC@$REPOIP::ogimages/$IMGDIRAUX" + fi + fi +fi + + +FSTYPE=$(ogGetFsType $1 $2) + +# Creamos la lista del contenido y lo situamos en la particion a copiar. +DIREMPTY="/tmp/empty$$" +IMGLIST="/tmp/ogimg.list" +IMGINFO="/tmp/ogimg.info" +IMGACL="/tmp/ogimg.acl" + +# Borramos archivos antiguos. +rm -f /tmp/ogimg.* 2>/dev/null +rm -f $ORIG/ogimg.* 2>/dev/null + +# En las diferenciales no sabemos el tamaño -> ponemos una constante. +SIZEDATA=${SIZEDATA:-"SIZEDATA"} + +# Incluimos información de la imagen. Segun el kernel sera ext4 o btrfs. +KERNELVERSION=$(uname -r| awk '{printf("%d",$1);sub(/[0-9]*\./,"",$1);printf(".%02d",$1)}') +[ $KERNELVERSION \< 3.07 ] && IMGFS="EXT4" || IMGFS=${IMGFS:-"BTRFS"} +echo "#$IMGFS:NO:$FSTYPE:$SIZEDATA" > $IMGINFO + +if [ "$IMGTYPE" == "img" ]; then + # Imagen Basica + echo " rsync -aHAXWvn --delete $ORIG/ $DIREMPTY >> $IMGINFO" + rsync -aHAXWvn --delete $ORIG/ $DIREMPTY>> $IMGINFO + sed -i -e s/"^sent.*.bytes\/sec"//g -e s/^total.*.speedup.*.$//g -e s/"sending.*.list"//g $IMGINFO + sed -i '/^\.\//d' $IMGINFO + +else + # Imagen Diferencial + echo " rsync -aHAXWvn --delete $ORIG/ $DESTRSYNC a $IMGLIST" + rsync -aHAXWvn $PASSWORD --delete "$ORIG/" "$DESTRSYNC" >> $IMGLIST + sed -i -e s/"^sent.*.bytes\/sec"//g -e s/^total.*.speedup.*.$//g -e s/"sending.*.list"//g $IMGLIST + sed -i '/^\.\//d' $IMGLIST + + # Creamos informacion de la imagen + grep -e '\->' -e '\=>' $IMGLIST > /tmp/ogimg.ln + grep -e ^deleting $IMGLIST | sed s/^deleting\ //g | grep -v ^ogimg > /tmp/ogimg.rm + #grep -v -e '\->' -e '\=>' -e ^deleting $IMGLIST >> $IMGINFO + grep -v -e '\->' -e '\=>' -e ^deleting -e ^created $IMGLIST >> $IMGINFO + + rm -f $IMGLIST + + # Comprobamos que los ficheros de diferencias no esten vacios o salimos con error. + if [ $(grep -v -e "^$" -e "^#" $IMGINFO /tmp/ogimg.ln /tmp/ogimg.rm |wc -l) -eq 0 ]; then + ogRaiseError $OG_ERR_NOTDIFFERENT "$1 $2 $3 $4 $5" + return $? + fi + +fi + +# Guardamos el contenido de las acl (Solo win) Necesario particion desmontada (esta asi) +ogUnmount $1 $2 +if [ $FSTYPE == "NTFS" ]; then + echo " ntfs-3g.secaudit -b $PART /" + ntfs-3g.secaudit -b $PART / > $IMGACL +fi + +} + + +#/** +# ogAclFilter +#@brief Del fichero de acl de la partición extraemos las acl de los ficheros de la diferencial (falla: no se usa) +#@param No. +#@return (nada) +#*/ +function ogAclFilter () { +local IMGACL IMGINFO FILES ACLTMP + +# Ayuda +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" + return +fi + +IMGACL="/tmp/ogimg.acl" +IMGINFO="/tmp/ogimg.info" +FILES="/tmp/files$$" +ACLTMP="/tmp/acl$$.tmp" +ACLFILES="/tmp/aclfiles$$" + +# comprobamos que existan los archivos de origen. Si no salimos sin error. +[ -f $IMGACL -a -f $IMGINFO ] || return 0 + +echo "" > $ACLTMP +grep -n -e "File" -e "Directory" $IMGACL > $ACLFILES + +# Al listado de ficheros le quitamos las líneas sobrantes: comentarios y lineas vacias. +sed -e s/"^#.*$"//g -e '/^$/d' $IMGINFO > $FILES + + +# Recorremos el listado y extraemos la acl correspondiente al fichero o directorio. +while read LINE; do + read END INI <<< "$(grep -A 1 "$LINE" $ACLFILES | awk -F : '!(NR%2){print $1" "p}{p=$1}' )" + let NUM=$END-$INI-1 + # Si algún archivo no se encuentra, el error lo mandamos a /dev/null + sed -n -e $INI,+"$NUM"p $IMGACL 2>/dev/null >> $ACLTMP + echo "aclfilter: $LINE" >> $OGLOGCOMMAND +done < $FILES + +cp $ACLTMP $IMGACL +rm -f $FILES $ACLTMP $ACLFILES +} + + +#/** +# ogRestoreInfoImage +#@brief Crear o modificar enlaces y restaurar las ACL. La informacion esta ya copiada a la particion. +#@param 1 num_disk +#@param 2 num_part +#*/ +function ogRestoreInfoImage () { +local DEST PART IMGACL IMGLN OPTLN LINEA DESTLN ORIGLN TYPELN + +# Ayuda o menos de 5 parametros y la imagen no es basica +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" \ + "$FUNCNAME num_disk num_part" \ + "base image -> $FUNCNAME 1 2 " \ + "diff image -> $FUNCNAME 1 1 " + return +fi + +if [ $# -lt 2 ]; then + ogRaiseError $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME num_disk num_part " + return $? +fi + +# Control de errores. +PART=$(ogDiskToDev "$1" "$2") || return $? +DEST=$(ogMount $1 $2) || return $? + +IMGACL="ogimg.acl" +IMGLN="ogimg.ln" +IMGINFO="ogimg.info" + +# Copiamos informacion de la imagen a /tmp (para basicas) +[ -r $DEST/$IMGINFO ] && cp $DEST/ogimg.* /tmp + +#Creamos o modificamos los enlaces. +# La imagen diferencial tiene ogimg.ln +# para la completa lo generamos con los enlaces que contengan /mnt/ +[ -r "/tmp/$IMGLN" ] || grep -e "->" -e "=>" "/tmp/$IMGINFO"|grep "/mnt/" > "/tmp/$IMGLN" +if [ $(wc -l "/tmp/$IMGLN"|cut -f1 -d" ") -ne 0 ]; then + while read LINEA + do + ORIGLN="${LINEA#*> }" + # Si origen hace referencia a la particion lo modificamos + echo $ORIGLN|grep "/mnt/"> /dev/null && ORIGLN="$DEST/${ORIGLN#/mnt/*/}" + # rsync marca - los enlaces simbolicos y = enlaces "duros" + LINEA="${LINEA%>*}" + TYPELN="${LINEA##* }" + DESTLN="${LINEA% *}" + + if [ "$TYPELN" == "-" ] + then + OPTLN='-s' + else + OPTLN='' + fi + cd "$DEST/$(dirname "$DESTLN")" + rm -f "$(basename "$DESTLN")" + ln $OPTLN "$ORIGLN" "$(basename "$DESTLN")" + echo -n "." + done < "/tmp/$IMGLN" 2>/dev/null + echo "" + +fi +cd / +} + + +#/** +# ogRestoreAclImage +#@brief Restaurar las ACL. La informacion esta ya copiada al directorio /tmp +#@param 1 num_disk +#@param 2 num_part +#*/ +function ogRestoreAclImage () { +local PART IMGACL + +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" \ + "$FUNCNAME num_disk num_part" \ + "$FUNCNAME 1 1" + return +fi + +PART=$(ogDiskToDev "$1" "$2") || return $? +IMGACL="ogimg.acl" + +# Restauramos acl +if [ "$(ogGetFsType $1 $2)" == "NTFS" -a -f "/tmp/$IMGACL" ] ; then + cd / + ogUnmount "$1" "$2" + echo "ntfs-3g.secaudit -se $PART /tmp/$IMGACL" + ntfs-3g.secaudit -se $PART /tmp/$IMGACL + # Para evitar que de falso error + echo "" +fi +} + + +#/** +# ogSyncCreate +#@brief sincroniza los datos de la partición a la imagen para crearla. La imagen esta montada en un directorio. +#@param 1 num_disk +#@param 2 num_part +#@param 3 Repositorio [ REPO | CACHE ] +#@param 4 Nombre Imagen +#@param 5 Tipo imagen [ img | diff ] +#*/ +function ogSyncCreate () { +local ORIG DIRAUX DIRMOUNT DESTRSYNC USERRSYNC PASSWORD OPTRSYNC RETVAL + +# Limpiamos los archivo de log +echo "" >$OGLOGCOMMAND; + +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" \ + "$FUNCNAME num_disk num_part [ REPO|CACHE ] image_name extension " \ + "$FUNCNAME 1 2 REPO Ubuntu12 img" \ + "$FUNCNAME 1 1 CACHE Windows7 diff " + return +fi + + +if [ $# -lt 4 ]; then + ogRaiseError $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME num_disk num_part [ REPO|CACHE ] image_name extension " + return $? +fi + +ORIG=$(ogMount $1 $2) || return $? + +DIRMOUNT="$(ogGetMountImageDir "$4" $5)" +# Si la imagen es diferencial la lista de ficheros a transferir esta dentro de la imagen. +if [ "$5" == "diff" ]; then + FILESFROM=" --files-from=/tmp/ogimg.info" + # Borramos los directorios + sed -i '/\/$/d' /tmp/ogimg.info +else + FILESFROM="" +fi + +if [ "$3" == "CACHE" -o "$3" == "cache" ]; then + DESTRSYNC="/tmp/$DIRMOUNT" +else + [ -z $REPOIP ] && REPOIP=$(ogGetRepoIp) + PASSWORD="--password-file=/scripts/passrsync" + [ "$ogrsyncz" == "true" ] && OPTRSYNC="z " + [ "$ogrsyncw" == "true" ] && OPTRSYNC="W$OPTRSYNC" + USERRSYNC="opengnsys" + DESTRSYNC="$USERRSYNC@$REPOIP::ogimages/$DIRMOUNT" +fi +# Sincronizamos los datos de la partición a la imagen +echo " rsync -aHAX$OPTRSYNC --progress --inplace --delete $FILESFROM $ORIG/ $DESTRSYNC" +rsync -aHAX$OPTRSYNC $PASSWORD --progress --inplace --delete $FILESFROM "$ORIG/" "$DESTRSYNC" 2>$OGLOGCOMMAND | egrep "^deleting|^sent|^sending|^total|%" |tee -a $OGLOGCOMMAND +RETVAL=${PIPESTATUS[0]} +echo " rsync -aHAX$OPTRSYNC --inplace /tmp/ogimg* $DESTRSYNC" +rsync -aHAX$OPTRSYNC $PASSWORD --inplace /tmp/ogimg* "$DESTRSYNC" + +return $RETVAL +} + + +#/** +# ogSyncRestore +#@brief sincroniza los datos de la imagen a la partición para restaurarla. +#@param 1 Repositorio [ REPO | CACHE ] +#@param 2 Nombre Imagen +#@param 3 Tipo imagen [ img | diff ] +#@param 4 num_disk +#@param 5 num_part +#*/ +function ogSyncRestore () { +local DIRMOUNT ORIG DESTRSYNC PASSWORD OPTRSYNC USERRSYNC IMGINFO FILESFROM + +# Limpiamos los archivo de log +echo "" >$OGLOGCOMMAND; + +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" \ + "$FUNCNAME [ REPO|CACHE ] image_name extension num_disk num_part " \ + "$FUNCNAME REPO Ubuntu12 img 1 2" \ + "$FUNCNAME CACHE Windows7 diff 1 1" + return +fi + + +if [ $# -lt 5 ]; then + ogRaiseError $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME [ REPO|CACHE ] image_name extension num_disk num_part " + return $? +fi + + +DIRMOUNT="$(ogGetMountImageDir "$2" "$3")" +DESTRSYNC=$(ogGetMountPoint $4 $5) + +# Borramos ficheros de informacion de restauraciones antiguas +rm -rf $DESTRSYNC/ogimg.* +rm -rf /tmp/ogimg.* + +# Origen y destino de la sincronizacion y en REPO opciones rsync +if [ "$1" == "CACHE" -o "$1" == "cache" ]; then + ORIG="/tmp/$DIRMOUNT" +else + [ -z $REPOIP ] && REPOIP=$(ogGetRepoIp) + PASSWORD="--password-file=/scripts/passrsync" + [ "$ogrsyncz" == "true" ] && OPTRSYNC="z " + [ "$ogrsyncw" == "true" ] && OPTRSYNC="W$OPTRSYNC" + USERRSYNC="opengnsys" + ORIG="$USERRSYNC@$REPOIP::ogimages/$DIRMOUNT" +fi + +# Opciones rsync en cache y repo +# Para la imagen basica, opcion de borrar archivos de la particion que no existen en la imagen +[ "$3" == "img" ] && [ "$ogrsyncdel" != "false" ] && OPTRSYNC="$OPTRSYNC --delete" + +# Nos traemos listado ficheros y bajamos la imagen + +ogEcho log session " $MSG_SYNC_RESTORE" + +# Si la imagen es diferencial nos traemos los archivos de informacion de la imagen. +if [ "$3" == "diff" ]; then + # Lista de archivos a copiar: + IMGINFO="ogimg.info" + FILESFROM=" --files-from=/tmp/$IMGINFO" + + echo " rsync -aHAX$OPTRSYNC --progress $ORIG/ogimg* /tmp" + rsync -aHAX$OPTRSYNC $PASSWORD --progress "$ORIG"/ogimg* /tmp + # Borramos linea de información de la imagen, sino busca un fichero con ese nombre + sed -i '/^\#/d' /tmp/$IMGINFO + + cd $DESTRSYNC + # Diferencial: Borramos archivos sobrantes. + ogEcho log session " $MSG_SYNC_DELETE" + sed -e s/^/\"/g -e s/$/\"/g "/tmp/ogimg.rm" 2>/dev/null | xargs rm -rf + +fi + +echo " rsync -aHAX$OPTRSYNC --progress $FILESFROM $ORIG/ $DESTRSYNC" +rsync -aHAX$OPTRSYNC $PASSWORD --progress $FILESFROM "$ORIG/" "$DESTRSYNC" 2>$OGLOGCOMMAND | egrep "^deleting|^sent|^sending|^total|%" |tee -a $OGLOGCOMMAND +RETVAL=${PIPESTATUS[0]} +cd / +#*/ " Comentario Doxygen +} + + +#/** +# ogMountImage +#@brief Monta la imagen para sincronizar. +#@param 1 Repositorio [ REPO | CACHE ] +#@param 2 Nombre Imagen +#@param 3 Tipo imagen [ img |diff ] +#@return punto de montaje +#*/ +function ogMountImage () { +local IMGEXT IMGFILE DIRMOUNT KERNELVERSION + +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" \ + "$FUNCNAME [ REPO|CACHE ] image_name [ extension ]" \ + "$FUNCNAME REPO Ubuntu12" \ + "$FUNCNAME CACHE Windows7 diff" + return +fi + + +if [ $# -lt 2 ]; then + ogRaiseError $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME [ REPO|CACHE ] image_name [ extension ]" + return $? +fi + +[ "$3" == "" -o "$3" == "img" ] && IMGEXT="img" || IMGEXT="img.diff" + +DIRMOUNT="$(ogGetMountImageDir "$2" ${IMGEXT#*\.})" + +if [ "$1" == "REPO" -o "$1" == "repo" ]; then + [ -z $REPOIP ] && REPOIP=$(ogGetRepoIp) + hose $REPOIP 2009 --out sh -c "echo -ne MOUNT_IMAGE \"$2\" ${IMGEXT#*\.}" + echo "$OGIMG/$DIRMOUNT" +else + # Si está montado nada que hacer. + df | grep "$DIRMOUNT$" 2>&1 >/dev/null && echo "/tmp/$DIRMOUNT" && return 0 + + IMGFILE="$(ogGetPath "$1" /"$2.$IMGEXT")" \ + || return $(ogRaiseError $OG_ERR_NOTFOUND "$1 $2.$IMGEXT"; echo $?) + mkdir -p "/tmp/$DIRMOUNT" + + + # FS de la imagen segun el contenido del archivo .img + if file "$IMGFILE" |grep -i -e " ext4 filesystem " 2>&1 > /dev/null ; then + mount -t ext4 -o loop "$IMGFILE" "/tmp/$DIRMOUNT" 1>/dev/null + else + mount -o compress=lzo "$IMGFILE" "/tmp/$DIRMOUNT" 1>/dev/null + fi + + # Comprobamos que se ha montado bien + [ $? -eq 0 ] || return $(ogRaiseError $OG_ERR_DONTMOUNT_IMAGE "$1 $2 $3"; echo $?) + echo "/tmp/$DIRMOUNT" +fi + +} + + +#/** +# ogUnmountImage [ REPO | CACHE ] Image_name [ extension ] +#@brief Desmonta la imagen para sincronizar. +#@param 1 Repositorio [ REPO | CACHE ] +#@param 2 Nombre Imagen +#@param 3 Tipo imagen [ img |diff ] +#*/ +function ogUnmountImage () { +local IMGTYPE DIRMOUNT + +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" \ + "$FUNCNAME [ REPO|CACHE ] image_name [ extension ]" \ + "$FUNCNAME REPO Ubuntu12" \ + "$FUNCNAME CACHE Windows7 diff" + return +fi + +if [ $# -lt 2 ]; then + ogRaiseError $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME [ REPO|CACHE ] image_name [ extension ]" + return $? +fi + +[ "$3" == "" ] && IMGTYPE="img" || IMGTYPE="$3" + +if [ "$1" == "CACHE" -o "$1" == "cache" ]; then + DIRMOUNT="/tmp/$(ogGetMountImageDir "$2" $IMGTYPE)" + umount "$DIRMOUNT" + rmdir "$DIRMOUNT" + [ -f $IMGFILE.lock ] && sed -i s/"mounted"//g $IMGFILE.lock +else + [ -z $REPOIP ] && REPOIP=$(ogGetRepoIp) + echo " hose $REPOIP 2009 --out sh -c echo -ne UMOUNT_IMAGE \"$2\" $IMGTYPE" + hose $REPOIP 2009 --out sh -c "echo -ne UMOUNT_IMAGE \"$2\" $IMGTYPE" +fi +} + + +#/** +# ogGetMountImageDir +#@brief Devuelve el directorio de montaje de la imagen. +#@param 1 Nombre Imagen +#@param 2 Tipo imagen [ img |diff ] +#*/ +function ogGetMountImageDir () { +local DIRMOUNT +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" \ + "$FUNCNAME image_name [ extension ]" \ + "$FUNCNAME Ubuntu12" \ + "$FUNCNAME Windows7 diff" + return +fi + + +if [ $# -lt 1 ]; then + ogRaiseError $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME image_name [ extension ]" + return $? +fi + + +DIRMOUNT="mount/$1" +[ "$2" == "diff" ] && DIRMOUNT="$DIRMOUNT.diff" +echo "$DIRMOUNT" + +} + + +#/** +# ogWaitSyncImage image_name extension stado imagen_size +#@brief Se espera un tiempo a que se monte la imagen en el servidor. +#@brief Cuando se esta creando la imagen hay que dar el tamaño, para que espere el tiempo de creación. +#@param 1 Respositorio [ REPO | CACHE ] +#@param 2 Nombre Imagen +#@param 3 Tipo imagen [ img | diff ] +#@param 4 Estado [ mounted | reduced ] +#@param 5 Tamaño imagen (opcional) +#*/ +function ogWaitSyncImage () { +local SIZE TIME DIRMOUNT TIMEOUT TIMEAUX LOCKFILE IMGDIR IMGEXT STATE + +TIME=$SECONDS + +# Ayuda o menos de 5 parametros y la imagen no es basica +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" \ + "$FUNCNAME [ REPO | CACHE ] image_name extension state [ image_size ] " \ + "$FUNCNAME REPO Ubuntu12 img 30000000" \ + "$FUNCNAME CACHE Windows7 diff " + return +fi + +if [ $# -lt 4 ]; then + ogRaiseError $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME [ REPO | CACHE ] image_name extension state [ image_size ] " + return $? +fi + +SIZE=${5:-"300000"} +STATE="$4" +ogCheckStringInGroup "$STATE" "mounted reduced" || \ + return $(ogRaiseError command $OG_ERR_FORMAT "STATE = [ mounted | reduced ]" ) + +IMGDIR="$(ogGetParentPath "$1" "/$2")" +[ "$3" == "img" ] && IMGEXT="img" || IMGEXT="img.diff" +LOCKFILE="${IMGDIR}/$(basename "/$2").$IMGEXT.lock" + +if [ "$1" == "CACHE" -o "$1" == "cache" ]; then + DIRMOUNT="/tmp/$(ogGetMountImageDir "$2" $3)" +else + DIRMOUNT="$OGIMG/$(ogGetMountImageDir "$2" $3)" +fi + +echo -n -e " $MSG_SYNC_SLEEP: $DIRMOUNT\n #" | tee -a $OGLOGSESSION $OGLOGFILE + +# Comprobamos: mounted -> que exista $DIRMOUNT/ogimg.info o que el fichero de lock contenga mounted +# reduced -> que el fichero de lock contenga reduced. + +# time-out segun el tamaño de la imagen. por defecto: 100000k -> 3s +let TIMEOUT=$SIZE/$CREATESPEED +[ $TIMEOUT -lt 60 ] && TIMEOUT=60 +until $(grep -i $STATE $LOCKFILE &>/dev/null) ; do + [ $STATE = "mounted" -a -f "$DIRMOUNT/ogimg.info" ] && ogEcho log session "" && return 0 + TIMEAUX=$[SECONDS-TIME] + [ "$TIMEAUX" -lt "$TIMEOUT" ] || return $(ogRaiseError $OG_ERR_DONTMOUNT_IMAGE "$3 $4 $IMGEXT: time_out."; echo $?) + echo -n "#" | tee -a $OGLOGSESSION $OGLOGFILE + sleep 5 +done +echo "" | tee -a $OGLOGSESSION $OGLOGFILE + +} + + +#/** +# ogReduceImage +#@brief Reduce el archivo de la imagen a tamaño datos + 500M +#@param 1 Repositorio [ REPO | CACHE ] +#@param 2 Nombre Imagen +#@param 3 Tipo Imagen [ img |diff ] +#@return +#@exception OG_ERR_FORMAT # 1 formato incorrecto. +#@exception OG_ERR_NOTFOUND # 2 Fichero o dispositivo no encontrado. +#*/ +function ogReduceImage () { +local IMGEXT DIRMOUNT AVAILABLE USED IMGDIR IMGFILE ENDSIZE LOOPDEVICE +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" \ + "$FUNCNAME [ REPO|CACHE ] image_name [ extension ]" \ + "$FUNCNAME REPO Ubuntu12" \ + "$FUNCNAME CACHE Windows7 diff" + return +fi + +if [ $# -lt 2 ]; then + ogRaiseError $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME [ REPO|CACHE ] image_name [ extension ]" + return $? +fi + + +[ "$3" == "" -o "$3" == "img" ] && IMGEXT="img" || IMGEXT="img.diff" +IMGDIR="$(ogGetParentPath "$1" "/$2")" +IMGFILE="${IMGDIR}/$(basename "/$2").$IMGEXT" + + +if [ "$1" == "CACHE" -o "$1" == "cache" ]; then + # Para imagenes EXT4 reduzco, para BTRFS solo desmonto. + if file "$IMGFILE" | grep -i " ext4 filesystem " 2>&1 > /dev/null; then + # Al montar se comprueba la existencia de la imagen + DIRMOUNT="$(ogMountImage $1 "$2" ${IMGEXT#*\.})" + AVAILABLE=$(df -k|grep "$DIRMOUNT$"|awk '{print $4}') + # Si el espacio libre menor que 500Mb nos salimos + if [ $AVAILABLE -lt 200000 ]; then + ogUnmountImage $1 "$2" ${IMGEXT#*\.} + echo "reduced" > "$IMGFILE.lock" + return 0 + fi + + # Calculamos la diferencia entre el tamaño interno y externo + EXTSIZE=$(ls -l --block-size=1024 "$IMGFILE" | cut -f5 -d" ") + INTSIZE=$(df -k|grep "$DIRMOUNT"|awk '{print $2}') + let EDGESIZE=$EXTSIZE-$INTSIZE + ogUnmountImage $1 "$2" ${IMGEXT#*\.} + LOOPDEVICE=$(losetup -f) + losetup $LOOPDEVICE "$IMGFILE" + + # Redimensiono sistema de ficheros + echo " resize2fs -fpM $LOOPDEVICE" + resize2fs -fpM $LOOPDEVICE |tee -a $OGLOGCOMMAND + ogMountImage $1 "$2" ${IMGEXT#*\.} >/dev/null + + # Calculamos el tamaño final del archivo + INTSIZE=$(df -k|grep "$DIRMOUNT"|awk '{print $2}') + let EXTSIZE=$INTSIZE+$EDGESIZE + umount "$DIRMOUNT" + + # Si existe dispositivo de loop lo borro. + [ $LOOPDEVICE ] && losetup -d $LOOPDEVICE + + # Corto el archivo al tamaño del sistema de ficheros. + echo " truncate --size=\"$EXTSIZE\"k $IMGFILE " + truncate --size="$EXTSIZE"k "$IMGFILE" + else + # Desmonto la imagen + umount "$DIRMOUNT" + fi + + echo "reduced" > "$IMGFILE.lock" + rmdir "$DIRMOUNT" + +else + [ -z $REPOIP ] && REPOIP=$(ogGetRepoIp) + echo " hose $REPOIP 2009 --out sh -c echo -ne REDUCE_IMAGE \"$2\" ${IMGEXT#*\.}" + hose $REPOIP 2009 --out sh -c "echo -ne REDUCE_IMAGE \"$2\" ${IMGEXT#*\.}" +fi + +} + + + +#/** +# ogIsSyncImage +#@brief Comprueba si la imagen es sincronizable +#@param 1 Repositorio [ REPO | CACHE ] +#@param 2 Nombre Imagen +#@param 3 Tipo Imagen [ img |diff ] +#@return +#@exception OG_ERR_FORMAT # 1 formato incorrecto. +#@exception OG_ERR_NOTFOUND # 2 Fichero o dispositivo no encontrado. +#*/ +function ogIsSyncImage () { +local IMGEXT IMGDIR IMGFILE + +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" \ + "$FUNCNAME [ REPO|CACHE ] image_name [ extension ]" \ + "$FUNCNAME REPO Ubuntu12" \ + "$FUNCNAME CACHE Windows7 diff" + return +fi + +if [ $# -lt 2 ]; then + ogRaiseError $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME [ REPO|CACHE ] image_name [ extension ]" + return $? +fi + +[ "$3" == "" -o "$3" == "img" ] && IMGEXT="img" || IMGEXT="img.diff" +IMGDIR="$(ogGetParentPath "$1" "/$2")" +IMGFILE="${IMGDIR}"/$(basename "/$2").$IMGEXT + +file "$IMGFILE" | grep -i -e " BTRFS Filesystem " -e " ext4 filesystem " >/dev/null +[ $? -eq 0 ] && return 0 || return $OG_ERR_DONTSYNC_IMAGE + +} + + +#/** +# ogCheckSyncImage +#@brief Muestra el contenido de la imagen para comprobarla. +#@param 1 Repositorio [ REPO | CACHE ] +#@param 2 Nombre Imagen +#@param 3 Tipo Imagen [ img |diff ] +#@return +#@exception OG_ERR_FORMAT # 1 formato incorrecto. +#@exception OG_ERR_NOTFOUND # 2 Fichero o dispositivo no encontrado. +#*/ +function ogCheckSyncImage () { +local IMGEXT IMGDIR IMGFILE DIRMOUNT ISMOUNT RETVAL KERNELVERSION + +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" \ + "$FUNCNAME [ REPO|CACHE ] image_name [ extension ]" \ + "$FUNCNAME REPO Ubuntu12" \ + "$FUNCNAME CACHE Windows7 diff" + return +fi + +if [ $# -lt 2 ]; then + ogRaiseError $OG_ERR_FORMAT "$MSG_FORMAT: $FUNCNAME [ REPO|CACHE ] image_name [ extension ]" + return $? +fi + +[ "$3" == "" -o "$3" == "img" ] && IMGEXT="img" || IMGEXT="img.diff" +IMGDIR="$(ogGetParentPath "$1" "/$2")" +IMGFILE="${IMGDIR}/$(basename "/$2").$IMGEXT" + +ogIsSyncImage $1 "$2" "${IMGEXT#*\.}" || return $(ogRaiseError $OG_ERR_DONTSYNC_IMAGE "$3 $4"; echo $?) + +# Comprobamos que no esté montada (daria falso error) +if [ "$1" == "CACHE" -o "$1" == "cache" ]; then + $(df | grep "/tmp/mount/$2${IMGEXT#img}$" &>/dev/null) && ISMOUNT=TRUE +else + [ -f "$OGIMG/mount/$2${IMGEXT#img}/ogimg.info" ] && ISMOUNT=TRUE +fi +[ "$ISMOUNT" == TRUE ] && ogEcho log session warning "$MSG_SYNC_NOCHECK" && return 0 + +DIRMOUNT="/tmp/ogCheckImage$$" +mkdir "$DIRMOUNT" +# FS de la imagen segun el contenido del archivo .img +if file "$IMGFILE" |grep -i -e " ext4 filesystem " 2>&1 > /dev/null ; then + mount -t ext4 -o loop "$IMGFILE" "$DIRMOUNT" 2>&1 | tee -a $OGLOGCOMMAND + RETVAL=${PIPESTATUS[0]} +else + mount -o compress=lzo "$IMGFILE" "$DIRMOUNT" 2>&1 | tee -a $OGLOGCOMMAND + RETVAL=${PIPESTATUS[0]} +fi +ls -C "$DIRMOUNT" | tee -a $OGLOGCOMMAND +umount "$DIRMOUNT" + +rmdir "$DIRMOUNT" +return $RETVAL +} + diff --git a/client/engine/String.lib b/client/engine/String.lib new file mode 100755 index 0000000..5774fe7 --- /dev/null +++ b/client/engine/String.lib @@ -0,0 +1,122 @@ +#!/bin/bash + +#/** +# ogCheckStringInGroup +#@brief Función para determinar si el elemento pertenece a un conjunto +#@param 1 elemento a comprobar +#@param 2 grupo de elementos para comprobar tipo "valor1 valor2 valor3" +#@return 0 si pertenece al grupo +#@return 1 si NO pertenece al grupo +#@exception OG_ERR_FORMAT formato incorrecto. +#@note +#@todo +#@version 0.91 - Definición de +#@author Antonio Doblas Viso, Universidad de Málaga +#@date 2010/05/09 +#*/ ## + +function ogCheckStringInGroup () +{ +local i +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME str_elemento str_grupo" \ + "$FUNCNAME full-duplex \"full-duplex half-duplex broadcast\" " + return +fi + +# Error si no se recibe 2 parámetro. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + + +for i in `echo $2` +do + if [ "$1" == "$i" ] + then + return 0 + fi +done + +return 1 +} + +#/** +# ogCheckStringInReg +#@brief Función para determinar si el elemento contiene una "expresión regular" +#@param 1 elemento a comprobar +#@param 2 expresión regular" +#@return 0 si coincide con la expresión +#@return 1 si NO coincide con la expresión +#@exception OG_ERR_FORMAT formato incorrecto. +#@note +#@todo +#@version 0.91 - Definición de +#@author Antonio Doblas Viso, Universidad de Málaga +#@date 2010/05/09 +#*/ ## + +function ogCheckStringInReg() +{ + +local REG + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME str_elemento str_expresión_regular" \ + "$FUNCNAME 50M \"^[0-9]{1,2}\M$\" " + return +fi + +# Error si no se recibe 2 parámetro. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +REG=$2 +[[ $1 =~ $REG ]] && return 0 || return 1 +} + + + +#/** +# ogCheckIpAddress +#@brief Función para determinar si una cadena es una dirección ipv4 válida +#@param 1 string de la ip a comprobar +#@return 0 si es una dirección válida +#@return 1 si NO es una dirección válida +#@exception OG_ERR_FORMAT formato incorrecto. +#@note +#@todo +#@version 0.91 - Definición de +#@author Antonio Doblas Viso, Universidad de Málaga +#@date 2010/05/09 +#*/ ## + +function ogCheckIpAddress() +{ +local REG IP arrIP + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME str_IpAddressToCheck" \ + "$FUNCNAME 192.18.35.3" + return +fi + +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + + +IP=$1 +REG="^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$" +if [[ "$IP" =~ $REG ]] +then + OIFS=$IFS; + IFS='.' ; + arrIP=($IP) + IFS=$OIFS + if [[ ${arrIP[0]} -le 255 && ${arrIP[1]} -le 255 && ${arrIP[2]} -le 255 && ${arrIP[3]} -le 255 ]] + then + return 0 + fi +fi +return 1 +} diff --git a/client/engine/System.lib b/client/engine/System.lib new file mode 100755 index 0000000..261cacf --- /dev/null +++ b/client/engine/System.lib @@ -0,0 +1,339 @@ +#!/bin/bash +#/** +#@file System.lib +#@brief Librería o clase System +#@class System +#@brief Funciones básicas del sistema. +#@version 1.1.0 +#@warning License: GNU GPLv3+ +#*/ + + +#/** +# ogEcho [str_logtype ...] [str_loglevel] "str_message" ... +#@brief Muestra mensajes en consola y lo registra en fichero de incidencias. +#@param str_logtype tipo de registro de incidencias. +#@param str_loglevel nivel de registro de incidencias. +#@param str_message mensaje (puede recibir más de 1 parámetro. +#@return Mensaje mostrado. +#@warning Si no se indica nivel de registro, solo muestra mensaje en pantalla. +#@warning Si DEBUG="no", no se registran mensajes de error. +#@note logfile = { log, command, session }; usa "log" si se indica nivel de registro. +#@note loglevel = { help, info, warning, error } +#@note El nivel de ayuda \c (help) no se registra en el fichero de incidencias. +#@version 0.9 - Primera versión para OpenGnSys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-07-23 +#@version 1.0.5 - Elegir fichero de log. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2014-03-17 +#@version 1.1.0 - Posibilidad de no registrar mensajes en ficheros. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2015-11-10 +#*/ +function ogEcho () { + +# Variables locales +local CONT=1 LOGS LOGLEVEL DATETIME + +# Selección de ficheros de rgistro de incidencias. +while [ $CONT ]; do + case "${1,,}" in + log) LOGS="$LOGS $OGLOGFILE"; shift ;; + command) LOGS="$LOGS $OGLOGCOMMAND"; shift ;; + session) LOGS="$LOGS $OGLOGSESSION"; shift ;; + *) CONT= ;; + esac +done + +# Selección del nivel de registro (opcional). +case "${1,,}" in + help) shift ;; + info) LOGLEVEL="$1"; shift ;; + warning) LOGLEVEL="$1"; shift ;; + error) LOGLEVEL="$1"; shift ;; + *) ;; +esac + +if [ -n "$LOGLEVEL" ]; then + DATETIME=$(date +"%F %T") + # Registrar mensajes en fichero de log si la depuración no está desactivada. + [ "${DEBUG,,}" != "no" ] && LOGS="$OGLOGFILE $LOGS" + logger -s -t "OpenGnsys $LOGLEVEL" "$DATETIME $*" 2>&1 | tee -a $LOGS +else + echo "$*" | tee -a $LOGS +fi +} + + +#/** +# ogExecAndLog str_logfile ... str_command ... +#@brief Ejecuta un comando y guarda su salida en fichero de registro. +#@param str_logfile fichero de registro (pueden ser varios). +#@param str_command comando y comandos a ejecutar. +#@return Salida de ejecución del comando. +#@note str_logfile = { LOG, SESSION, COMMAND } +#@version 1.0.6 - Primera versión para OpenGnSys +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2013-07-02 +#*/ +function ogExecAndLog () { + +# Variables locales +local ISCOMMAND ISLOG ISSESSION COMMAND CONTINUE=1 FILES REDIREC + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME str_logfile ... str_command ..." \ + "$FUNCNAME COMMAND ls -al /" + return +fi + +# Procesar parámetros. +while [ $CONTINUE ]; do + case "${1,,}" in + command) ISCOMMAND=1; shift ;; + log) ISLOG=1; shift ;; + session) ISSESSION=1; shift ;; + *) COMMAND="$@" + CONTINUE= ;; + esac +done +# Error si no se recibe un comando que ejecutar. +[ -n "$COMMAND" ] || ogRaiseError $OG_ERR_FORMAT || return $? + +# Componer lista de ficheros de registro. +if [ $ISCOMMAND ]; then + FILES="$OGLOGCOMMAND" + > $FILES + REDIREC="2>&1" +fi +[ $ISLOG ] && FILES="$FILES $OGLOGFILE" +[ $ISSESSION ] && FILES="$FILES $OGLOGSESSION" + +# Ejecutar comando. +eval $COMMAND $REDIREC | tee -a $FILES +# Salida de error del comando ejecutado. +return ${PIPESTATUS[0]} +} + + +#/** +# ogGetCaller +#@brief Devuelve nombre del programa o script ejecutor (padre). +#@param No. +#@return str_name - Nombre del programa ejecutor. +#@version 0.10 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-01-17 +#*/ +function ogGetCaller () { + +# Obtener el nombre del programa o del script que ha llamado al proceso actual. +basename "$(COLUMNS=200 ps hp $PPID -o args | \ + awk '{if ($1~/bash/ && $2!="") { print $2; } + else { sub(/^-/,"",$1); print $1; } }')" +} + + +#/** +# ogHelp ["str_function" ["str_format" ["str_example" ... ]]] +#@brief Muestra mensaje de ayuda para una función determinda. +#@param str_function Nombre de la función. +#@param str_format Formato de ejecución de la función. +#@param str_example Ejemplo de ejecución de la función. +#@return str_help - Salida de ayuda. +#@note Si no se indican parámetros, la función se toma de la variable \c $FUNCNAME +#@note La descripción de la función se toma de la variable compuesta por \c MSG_FUNC_$función incluida en el fichero de idiomas. +#@note Pueden especificarse varios mensajes con ejemplos. +#@version 0.9 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-07-27 +#*/ +function ogHelp () { + +# Variables locales. +local FUNC MSG + +# Mostrar función, descripción y formato. +FUNC="${1:-${FUNCNAME[${#FUNCNAME[*]}-1]}}" +MSG="MSG_HELP_$FUNC" +ogEcho help "$MSG_FUNCTION $FUNC: ${!MSG}" +[ -n "$2" ] && ogEcho help " $MSG_FORMAT: $2" +# Mostrar ejemplos (si existen). +shift 2 +while [ $# -gt 0 ]; do + ogEcho help " $MSG_EXAMPLE: $1" + shift +done +} + + +#/** +# ogRaiseError [str_logtype ...] int_errcode ["str_errmessage" ...] +#@brief Devuelve el mensaje y el código de error correspondiente. +#@param str_logtype tipo de registro de incidencias. +#@param int_errcode código de error. +#@param str_errmessage mensajes complementarios de error. +#@return str_message - Mensaje de error, incluyendo las funciones relacionadas. +#@warning No definidas +#@note Mensajes internacionales del fichero de idiomas. +#@version 0.9 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2009-07-21 +#@version 1.0.5 - Muestra en el mensaje todas las funciones relacionadas (separadas por <-). +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2014-03-17 +#*/ +function ogRaiseError () { + +# Variables locales +local CONT=1 LOGS MSG CODE FUNCS + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [str_logfile ...] int_errorcode str_errormessage" + return +fi + +# Selección de rgistros de incidencias. +while [ $CONT ]; do + case "${1,,}" in + log|command|session) LOGS="$LOGS $1"; shift ;; + *) CONT= ;; + esac +done + +# Obtener código y mensaje de error. +CODE="$1" +case "$CODE" in + $OG_ERR_FORMAT) MSG="$MSG_ERR_FORMAT \"$2\"" ;; + $OG_ERR_NOTFOUND) MSG="$MSG_ERR_NOTFOUND \"$2\"" ;; + $OG_ERR_OUTOFLIMIT) MSG="$MSG_ERR_OUTOFLIMIT \"$2\"" ;; + $OG_ERR_PARTITION) MSG="$MSG_ERR_PARTITION \"$2\"" ;; + $OG_ERR_LOCKED) MSG="$MSG_ERR_LOCKED \"$2\"" ;; + $OG_ERR_CACHE) MSG="$MSG_ERR_CACHE \"$2\"" ;; + $OG_ERR_NOGPT) MSG="$MSG_ERR_NOGPT \"$2\"" ;; + $OG_ERR_REPO) MSG="$MSG_ERR_REPO \"$2\"" ;; + $OG_ERR_FILESYS) MSG="$MSG_ERR_FILESYS \"$2\"" ;; + $OG_ERR_IMAGE) MSG="$MSG_ERR_IMAGE \"$2\"" ;; + $OG_ERR_NOTOS) MSG="$MSG_ERR_NOTOS \"$2\"" ;; + $OG_ERR_NOTEXEC) MSG="$MSG_ERR_NOTEXEC \"$2\"" ;; + $OG_ERR_NOTWRITE) MSG="$MSG_ERR_NOTWRITE \"$2\"" ;; + $OG_ERR_NOTCACHE) MSG="$MSG_ERR_NOTCACHE \"$2\"" ;; + $OG_ERR_CACHESIZE) MSG="$MSG_ERR_CACHESIZE \"$2\"" ;; + $OG_ERR_REDUCEFS) MSG="$MSG_ERR_REDUCEFS \"$2\"" ;; + $OG_ERR_EXTENDFS) MSG="$MSG_ERR_EXTENDFS \"$2\"" ;; + $OG_ERR_IMGSIZEPARTITION) MSG="$MSG_ERR_IMGSIZEPARTITION \"$2\"" ;; + $OG_ERR_UPDATECACHE) MSG="$MSG_ERR_UPDATECACHE \"$2\"" ;; + $OG_ERR_DONTFORMAT) MSG="$MSG_ERR_DONTFORMAT \"$2\"" ;; + $OG_ERR_IMAGEFILE) MSG="$MSG_ERR_IMAGEFILE \"$2\"" ;; + $OG_ERR_UCASTSYNTAXT) MSG="$MSG_ERR_UCASTSYNTAXT \"$2\"" ;; + $OG_ERR_UCASTSENDPARTITION) MSG="$MSG_ERR_UCASTSENDPARTITION \"$2\"" ;; + $OG_ERR_UCASTSENDFILE) MSG="$MSG_ERR_UCASTSENDFILE \"$2\"" ;; + $OG_ERR_UCASTRECEIVERPARTITION) MSG="$MSG_ERR_UCASTRECEIVERPARTITION \"$2\"" ;; + $OG_ERR_UCASTRECEIVERFILE) MSG="$MSG_ERR_UCASTRECEIVERFILE \"$2\"" ;; + $OG_ERR_MCASTSYNTAXT) MSG="$MSG_ERR_MCASTSYNTAXT \"$2\"" ;; + $OG_ERR_MCASTSENDFILE) MSG="$MSG_ERR_MCASTSENDFILE \"$2\"" ;; + $OG_ERR_MCASTRECEIVERFILE) MSG="$MSG_ERR_MCASTRECEIVERFILE \"$2\"" ;; + $OG_ERR_MCASTSENDPARTITION) MSG="$MSG_ERR_MCASTSENDPARTITION \"$2\"" ;; + $OG_ERR_MCASTRECEIVERPARTITION) MSG="$MSG_ERR_MCASTRECEIVERPARTITION \"$2\"" ;; + $OG_ERR_PROTOCOLJOINMASTER) MSG="$MSG_ERR_PROTOCOLJOINMASTER \"$2\"" ;; + $OG_ERR_DONTMOUNT_IMAGE) MSG="$MSG_ERR_DONTMOUNT_IMAGE \"$2\"" ;; + $OG_ERR_DONTUNMOUNT_IMAGE) MSG="$MSG_ERR_DONTUNMOUNT_IMAGE \"$2\"" ;; + $OG_ERR_DONTSYNC_IMAGE) MSG="$MSG_ERR_DONTSYNC_IMAGE \"$2\"" ;; + $OG_ERR_NOTDIFFERENT) MSG="$MSG_ERR_NOTDIFFERENT \"$2\"" ;; + $OG_ERR_SYNCHRONIZING) MSG="$MSG_ERR_SYNCHRONIZING \"$2\"" ;; + $OG_ERR_NOTUEFI) MSG="$MSG_ERR_NOTUEFI \"$2\"" ;; + $OG_ERR_NOMSDOS) MSG="$MSG_ERR_NOMSDOS \"$2\"" ;; + $OG_ERR_NOTBIOS) MSG="$MSG_ERR_NOTBIOS \"$2\"" ;; + *) MSG="$MSG_ERR_GENERIC"; CODE=$OG_ERR_GENERIC ;; +esac + +# Obtener lista de funciones afectadas, incluyendo el script que las llama. +FUNCS="${FUNCNAME[@]:1}" +FUNCS="${FUNCS/main/$(basename $0 2>/dev/null)}" + +# Mostrar mensaje de error si es función depurable y salir con el código indicado. +if [ $CODE == $OG_ERR_FORMAT ] || ogCheckStringInGroup "$FUNCS" "$NODEBUGFUNCTIONS" || ! ogCheckStringInGroup "${FUNCS%% *}" "$NODEBUGFUNCTIONS"; then + ogEcho $LOGS error "${FUNCS// /<-}: $MSG" >&2 +fi +return $CODE +} + + +#/** +# ogIsRepoLocked +#@brief Comprueba si el repositorio está siendo usado (tiene ficheros abiertos). +#@param No. +#@return Código de salida: 0 - bloqueado, 1 - sin bloquear o error. +#@version 0.10 - Primera versión para OpenGnSys. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-01-17 +#@version 1.0.1 - Devolver falso en caso de error. +#@author Ramon Gomez, ETSII Universidad de Sevilla +#@date 2011-05-18 +#*/ +function ogIsRepoLocked () +{ +# Variables locales. +local f FILES + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" "if $FUNCNAME; then ...; fi" + return +fi + +# No hacer nada, si no está definido el punto de montaje del repositorio. +[ -z "$OGIMG" ] && return 1 + +# Comprobar si alguno de los ficheros abiertos por los procesos activos está en el +# punto de montaje del repositorio de imágenes. +FILES=$(for f in /proc/[0-9]*/fd/*; do readlink -f "$f"; done | grep "^$OGIMG") # */ (comentario Doxygen) +test -n "$FILES" +} + + +function ogCheckProgram () +{ +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME \"str_program ...\"" \ + "$FUNCNAME \"partimage partclone mbuffer\"" + return +fi + +# Error si no se recibe 1 parámetro. +[ $# == 1 ] || ogRaiseError $OG_ERR_FORMAT || return $? + +local PERROR PLOG i +PERROR=0 +PLOG=" " +for i in `echo $1` +do + if [ ! `which $i` ] + then + PERROR=1 + PLOG="$PLOG $i" + fi +done +if [ "$PERROR" == "1" ] +then + ogRaiseError $OG_ERR_NOTEXEC "$PLOG" || return $? +else + return 0 +fi +} + + + +#### PRUEBA +function ogIsVirtualMachine() { +case "$(dmidecode -s system-product-name)" in + KVM|VirtualBox) + return 1 ;; + *) return 0 ;; +esac +} + diff --git a/client/engine/ToolsGNU.c b/client/engine/ToolsGNU.c new file mode 100644 index 0000000..5198bf0 --- /dev/null +++ b/client/engine/ToolsGNU.c @@ -0,0 +1,147 @@ +#!/bin/bash +#/** +# * @mainpage Proyecto OpenGnSys +# * +# * Documentación de la API de funciones del motor de clonación de OpenGnSys. +# * +# * +# * @file ToolsGNU.c +# * @brief Librería o clase Tools GNU used by OpenGNSys +# * @class Tools +# * @brief Herramientas gnu utilizadas por opengnsys. +# * @version 0.9 +# * @warning License: GNU GPLv3+ +# */ + +function install () +{ + [ $# = 0 ] && echo pasar url del tar.gz && return + cd /tmp + wget -O download.tgz $1 + mkdir download || directorio no creado + tar xzvf download.tgz -C download + for i in `ls download` + do + cd download/$i + [ -e "configure" ] && ./configure + make && make install + cd - && rm -fr download* + done +} + +function mbuffer () +{ + if [ "$1" = install ] + then + install "http://www.maier-komor.de/software/mbuffer/mbuffer-20110119.tgz" + else + return + fi +} + +function ms-sys () +{ + if [ "$1" = install ] + then + install "http://prdownloads.sourceforge.net/ms-sys/ms-sys-2.2.1.tar.gz?download" + #install "http://downloads.sourceforge.net/project/ms-sys/ms-sys%20development/2.1.5/ms-sys-2.1.5.tar.gz" + else + return + fi +} + +function ctorrent () +{ + if [ "$1" = install ] + then + install "http://sourceforge.net/projects/dtorrent/files/dtorrent/3.3.2/ctorrent-dnh3.3.2.tar.gz/download" + else + return + fi +} + +function udpcast () +{ + if [ "$1" = install ] + then + install "http://udpcast.linux.lu/download/udpcast-20100130.tar.gz" + else + return + fi +} + +function ntfs-3g () +{ +if [ "$1" = install ] + then + install "http://tuxera.com/opensource/ntfs-3g-2011.1.15.tgz" + else + return +fi + +} + +function partitionsaving () +{ + +} + +function awk () +{ +} + +function chntpw () +{ +} + +function ctorrent () +{ +} + +function fdisk () +{ +} + +function fsck () +{ +} + +function kexec () +{ +} + +function lshw () +{ +} + +function mkfs () +{ +} + +function mount () +{ +} + + + +function parted () +{ +} + +function partimage () +{ +} + +function partprobe () +{ +} + +function sfdisk () +{ +} + +function umount () +{ +} + + diff --git a/client/engine/UEFI.lib b/client/engine/UEFI.lib new file mode 100755 index 0000000..22a7f0a --- /dev/null +++ b/client/engine/UEFI.lib @@ -0,0 +1,679 @@ +#!/bin/bash +# Libreria provisional para uso de UEFI +# Las funciones se incluirán las librerías ya existentes + +#/** +# ogNvramActiveEntry +#@brief Activa entrada de la NVRAM identificada por la etiqueta o el orden +#@param Num_order_entry | Label_entry Número de orden o la etiqueta de la entrada a borrar. +#@return (nada) +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTUEFI UEFI no activa. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. +#*/ ## +function ogNvramActiveEntry () { +local NUMENTRY + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ Num_order_entry | Label_entry ] " \ + "$FUNCNAME 2" \ + "$FUNCNAME \"Windows Boot Manager\"" + return +fi + +# Error si no se recibe 1 parámetro. +[ $# -eq 1 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME [ Num_order_entry | Label_entry ]" || return $? + +# Si no es equipo UEFI salir con error +ogIsEfiActive || ogRaiseError $OG_ERR_NOTUEFI || return $? + +# Distingo si es número de orden o etiqueta +if [[ $1 =~ ^([0-9a-fA-F]+)$ ]]; then + NUMENTRY=$( efibootmgr |awk -v NUM="$(printf %04x 0x$1|tr '[:lower:]' '[:upper:]')" '{ if($1~NUM) print substr($1,5,4)}') +else + NUMENTRY=$(efibootmgr |awk -v LABEL="$1" '{ if(substr($0, index($0,$2))==LABEL) print substr($1,5,4)}') +fi + +[ "$NUMENTRY" == "" ] && return $(ogRaiseError $OG_ERR_NOTFOUND "NVRAM entry '$1'") + +efibootmgr -a -b $NUMENTRY &>/dev/null +} + +#/** +# ogNvramAddEntry +#@brief Crea nueva entrada en el gestor de arranque (NVRAM), opcionalmente la incluye al final del orden de arranque. +#@param Str_Label_entry Número de disco o etiqueta de la entrada a crear. +#@param Str_BootLoader Número de partición o cargador de arranque. +#@param Bool_Incluir_Arranque Incluir en el orden de arranque (por defecto FALSE) (opcional) +#@return (nada) +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTUEFI UEFI no activa. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. +#*/ ## +function ogNvramAddEntry () { +local EFIDISK EFIPART BOOTLABEL BOOTLOADER ADDORDER + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME Str_label_entry Str_boot_loader [ Bool_add_bootorder ]" \ + "$FUNCNAME 1 2 TRUE" \ + "$FUNCNAME grub /EFI/grub/grubx64.efi TRUE" \ + "$FUNCNAME Windows /EFI/Microsoft/Boot/bootmgfw.efi" + return +fi + +# Error si no se recibe 1 parámetro. +[ $# -ge 2 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME Str_label_entry Str_boot_locader" || return $? + +# Si no es equipo UEFI salir con error +ogIsEfiActive || ogRaiseError $OG_ERR_NOTUEFI || return $? + +read -e EFIDISK EFIPART <<<"$(ogGetEsp)" +[ -n "$EFIPART" ] || ogRaiseError $OG_ERR_NOTFOUND "ESP" || return $? + +# Recogemos parámetros +# Distinguimos si es disco/partición o etiqueta/cargador +if [[ "$1$2" =~ ^([0-9]+)$ ]]; then + BOOTLABEL=$(printf "Part-%02d-%02d" $1 $2) + BOOTLOADER="/EFI/$BOOTLABEL/Boot/ogloader.efi" +else + BOOTLABEL="$1" + BOOTLOADER="$2" +fi + + +# Si existe entrada con la misma etiqueta la borramos +ogNvramDeleteEntry "$BOOTLABEL" 2>/dev/null + +efibootmgr -C -d $(ogDiskToDev $EFIDISK) -p $EFIPART -L "$BOOTLABEL" -l "$BOOTLOADER" &>/dev/null + +# Incluimos la entrada en el orden de arranque (opcional) +if [ "${3^^}" == "TRUE" ]; then + NUMENTRY=$(efibootmgr |awk -v LABEL="$BOOTLABEL" '{ if(substr($0, index($0,$2))==LABEL) print substr($1,5,4)}') + ogNvramSetOrder $(ogNvramGetOrder |tr , " ") $NUMENTRY +fi +} + + +#/** +# ogCopyEfiBootLoader int_ndisk str_repo path_image +#@brief Copia el cargador de arranque desde la partición EFI a la de sistema. +#@param int_ndisk nº de orden del disco +#@param int_part nº de partición +#@return (nada, por determinar) +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. +#@note Si existe el cargador en la partición de sistema no es válido +#*/ ## +function ogCopyEfiBootLoader () { +# Variables locales +local MNTDIR EFIDIR BOOTLABEL OSVERSION LOADER f + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_part" \ + "$FUNCNAME 1 2" + return +fi + +# Error si no se reciben 2 arámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_ndisk int_part" || return $? + +# Comprobamos que exista partición de sistema y la ESP +MNTDIR=$(ogMount $1 $2) || ogRaiseError $OG_ERR_PARTITION "$DISK $PART" || return $? +EFIDIR=$(ogMount $(ogGetEsp)) || ogRaiseError $OG_ERR_PARTITION "ESP" || return $? + +# Comprobamos que exista el cargador +BOOTLABEL=$(printf "Part-%02d-%02d" $1 $2) +OSVERSION=$(ogGetOsVersion $1 $2) +case $OSVERSION in + *Windows\ 10*) + for f in $EFIDIR/EFI/{Microsoft,$BOOTLABEL}/Boot/bootmgfw.efi; do + [ -r $f ] && LOADER=$f + done + [ -n "$LOADER" ] || ogRaiseError $OG_ERR_NOTOS "$1 $2 ($OSVERSION, EFI)" || return $? + # Si existe el directorio Boot lo borramos + [ -d $MNTDIR/ogBoot ] && rm -rf $MNTDIR/ogBoot + DIRLOADER=$(realpath "${LOADER%/*}/..") + cp -r ${DIRLOADER}/Boot $MNTDIR/ogBoot + ;; +esac +} + + +#/** +# ogNvramDeleteEntry +#@brief Borra entrada de la NVRAM identificada por la etiqueta o el orden +#@param Num_order_entry | Label_entry Número de orden o la etiqueta de la entrada a borrar. +#@return (nada) +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTUEFI UEFI no activa. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado (entrada en NVRAM). +#*/ ## +function ogNvramDeleteEntry () { +local NUMENTRY n + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ Num_order_entry | Label_entry ] " \ + "$FUNCNAME 2" \ + "$FUNCNAME \"Windows Boot Manager\"" + return +fi + +# Error si no se recibe 1 parámetro. +[ $# -eq 1 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME [ Num_order_entry | Label_entry ]" || return $? + +# Si no es equipo UEFI salir con error +ogIsEfiActive || ogRaiseError $OG_ERR_NOTUEFI || return $? + +# Distingo si es número de orden o etiqueta +if [[ $1 =~ ^([0-9a-fA-F]+)$ ]]; then + NUMENTRY=$( efibootmgr |awk -v NUM="$(printf %04x 0x$1|tr '[:lower:]' '[:upper:]')" '{ if($1~NUM) print substr($1,5,4)}') +else + NUMENTRY=$(efibootmgr |awk -v LABEL="$1" '{ if(substr($0, index($0,$2))==LABEL) print substr($1,5,4)}') +fi + +[ "$NUMENTRY" == "" ] && return $(ogRaiseError $OG_ERR_NOTFOUND "NVRAM entry '$1'") + +for n in $NUMENTRY; do + efibootmgr -B -b $n &>/dev/null +done +} + + +#/** +# ogNvramGetCurrent +#@brief Muestra la entrada del gestor de arranque (NVRAM) que ha iniciado el equipo. +#@return Entrada con la que se ha iniciado el equipo +#@exception OG_ERR_NOTUEFI UEFI no activa. +#*/ ## +function ogNvramGetCurrent () { + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" \ + "$FUNCNAME" + return +fi + +# Si no es equipo UEFI salir con error +ogIsEfiActive || ogRaiseError $OG_ERR_NOTUEFI || return $? + +efibootmgr| awk -v bootentry=99999 '{if ($1~/BootCurrent/) bootentry=$2; if ($1~bootentry) printf "%s %s %s\n", gensub(/^0{1,3}/,"",1,substr($1,5,4))," ", substr($0, index($0,$2))}' +} + + +# ogNvramGetNext +#@brief Muestra la entrada del gestor de arranque (NVRAM) que se utilizará en el próximo arranque. +#@return Entrada que se utilizará en el próximo arranque +#@exception OG_ERR_NOTUEFI UEFI no activa. +#*/ ## +function ogNvramGetNext () { +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" \ + "$FUNCNAME" + return +fi + +# Si no es equipo UEFI salir con error +ogIsEfiActive || ogRaiseError $OG_ERR_NOTUEFI || return $? + +efibootmgr|awk '{ if ($1 == "BootNext:") print $2}' +} + + +# ogNvramGetOrder +#@brief Muestra el orden de las entradas del gestor de arranque (NVRAM) +#@return Orden de las entradas +#@exception OG_ERR_NOTUEFI UEFI no activa. +#*/ ## +function ogNvramGetOrder () { +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" \ + "$FUNCNAME" + return +fi + +# Si no es equipo UEFI salir con error +ogIsEfiActive || ogRaiseError $OG_ERR_NOTUEFI || return $? + +efibootmgr|awk '{ if ($1 == "BootOrder:") print $2}' +} + + +#/** +# ogNvramGetTimeout +#@brief Muestra el tiempo de espera del gestor de arranque (NVRAM) +#@return Timeout de la NVRAM +#@exception OG_ERR_NOTUEFI UEFI no activa. +#*/ ## +function ogNvramGetTimeout () { +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" \ + "$FUNCNAME" + return +fi + +# Si no es equipo UEFI salir con error +ogIsEfiActive || ogRaiseError $OG_ERR_NOTUEFI || return $? + +efibootmgr|awk '{ if ($1 == "Timeout:") print substr($0, index($0,$2))}' +} + + +#/** +# ogGrubUefiConf int_ndisk int_part str_dir_grub +#@brief Genera el fichero grub.cfg de la ESP +#@param int_ndisk nº de orden del disco +#@param int_part nº de partición +#@param str_dir_grub prefijo del directorio de grub en la partición de sistema. ej: /boot/grubPARTITION +#@return (nada, por determinar) +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. +#@TODO Confirmar si el fichero "$EFIDIR/EFI/$BOOTLABEL/grub.cfg" es necesario. +#*/ ## +function ogGrubUefiConf () { +local EFIDIR BOOTLABEL GRUBEFI UUID DEVICE PREFIXSECONDSTAGE EFIGRUBDIR + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_part [ str_dir_grub ]" \ + "$FUNCNAME 1 2" \ + "$FUNCNAME 1 3 /boot/grubPARTITION" + return +fi + +# Error si no se reciben al menos 2 parámetros. +[ $# -ge 2 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_ndisk int_part [ str_dir_grub ]" || return $? + +# Directorio del grub en la partición de sistema +PREFIXSECONDSTAGE="$3" + +EFIDIR=$(ogMount $(ogGetEsp)) || ogRaiseError $OG_ERR_PARTITION "ESP" || return $? +BOOTLABEL=$(printf "Part-%02d-%02d" $1 $2) +EFIGRUBDIR="$EFIDIR/EFI/$BOOTLABEL/boot/grub" +# Comprobamos que existe directorio +[ -d "$EFIGRUBDIR" ] || mkdir -p "$EFIGRUBDIR" +# Parcheamos uuid y particion en grub.cfg +UUID=$(blkid -o value -s UUID $(ogDiskToDev $1 $2)) +DEVICE="hd$(expr $1 - 1 ),gpt$2" + +cat << EOT > $EFIGRUBDIR/grub.cfg +set root='$DEVICE' +set prefix=(\$root)'${PREFIXSECONDSTAGE}/boot/grub' +configfile \$prefix/grub.cfg +EOT + +# Provisional: confirmar si el segundo archivo se utiliza +cp $EFIGRUBDIR/grub.cfg "$EFIDIR/EFI/$BOOTLABEL/grub.cfg" +} + + +#/** +# ogNvramInactiveEntry +#@brief Inactiva entrada de la NVRAM identificada por la etiqueta o el orden +#@param Num_order_entry | Label_entry Número de orden o la etiqueta de la entrada a borrar. +#@return (nada) +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. +#@exception OG_ERR_NOTUEFI UEFI no activa. +#*/ ## +function ogNvramInactiveEntry () { +local NUMENTRY + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ Num_order_entry | Label_entry ] " \ + "$FUNCNAME 2" \ + "$FUNCNAME \"Windows Boot Manager\"" + return +fi + +# Error si no se recibe 1 parámetro. +[ $# -eq 1 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME [ Num_order_entry | Label_entry ]" || return $? + +# Si no es equipo UEFI salir con error +ogIsEfiActive || ogRaiseError $OG_ERR_NOTUEFI || return $? + +# Distingo si es número de orden o etiqueta +if [[ $1 =~ ^([0-9a-fA-F]+)$ ]]; then + NUMENTRY=$( efibootmgr |awk -v NUM="$(printf %04x 0x$1|tr '[:lower:]' '[:upper:]')" '{ if($1~NUM) print substr($1,5,4)}') +else + NUMENTRY=$(efibootmgr |awk -v LABEL="$1" '{ if(substr($0, index($0,$2))==LABEL) print substr($1,5,4)}') +fi + +[ "$NUMENTRY" == "" ] && return $(ogRaiseError $OG_ERR_NOTFOUND "NVRAM entry '$1'") + +efibootmgr -A -b $NUMENTRY &>/dev/null +} + + +#/** +# ogNvramList +#@brief Lista las entradas de la NVRAN (sólo equipos UEFI) +#@return Entradas de la NVRAM con el formato: orden etiqueta [* (si está activa) ] +#@exception OG_ERR_NOTUEFI UEFI no activa. +#*/ ## +function ogNvramList () { + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" \ + "$FUNCNAME" + return +fi + +# Si no es equipo UEFI salir con error +ogIsEfiActive || ogRaiseError $OG_ERR_NOTUEFI || return $? + +efibootmgr |awk '{if($1~/Boot[[:digit:]]/) ; active="" ;if ($1~/*/) active="*"; if($1~/Boot[[:digit:]]/) printf "%4s %s %s %s\n", gensub(/^0{1,3}/,"",1,substr($1,5,4))," ", substr($0, index($0,$2)), active}' +} + + +#/** +# ogNvramPxeFirstEntry +#@brief Sitúa la entrada de la tarjeta de red en el primer lugar en la NVRAM. +#@return (nada) +#@exception OG_ERR_NOTUEFI UEFI no activa. +#*/ ## +function ogNvramPxeFirstEntry (){ +local NUMENTRY ORDER + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME" \ + "$FUNCNAME" + return +fi + +# Si no es equipo UEFI salir con error +ogIsEfiActive || ogRaiseError $OG_ERR_NOTUEFI || return $? + +NUMENTRY=$(printf %04X 0x$(efibootmgr|awk '/IP[vV]{0,1}4/ {print gensub(/^0{1,3}/,"",1,substr($1,5,4))}')) + +# Si la entrada es la primera nos salimos. +[[ $(ogNvramGetOrder) =~ ^$NUMENTRY ]] && return + +# Si la entrada ya existe la borramos. +ORDER="$NUMENTRY $(ogNvramGetOrder| sed -e s/$NUMENTRY//g -e s/,/' '/g)" + +ogNvramSetOrder $ORDER +} + + +#/** +# ogRestoreEfiBootLoader int_ndisk str_repo +#@brief Copia el cargador de arranque de la partición de sistema a la partición EFI. +#@param int_ndisk nº de orden del disco +#@param int_part nº de partición +#@return (nada, por determinar) +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado (partición de sistema o EFI). +#@exception OG_ERR_NOTOS sin sistema operativo. +#*/ ## +function ogRestoreEfiBootLoader () { +# Variables locales +local MNTDIR EFIDIR BOOTLABEL OSVERSION LOADER f UUID DEVICE + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_part" \ + "$FUNCNAME 1 2" + return +fi + +# Error si no se reciben 2 arámetros. +[ $# == 2 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_ndisk int_part" || return $? + +# Comprobamos que exista partición de sistema y la ESP +MNTDIR=$(ogMount $1 $2) || ogRaiseError $OG_ERR_PARTITION "$DISK $PART" || return $? +EFIDIR=$(ogMount $(ogGetEsp)) +if [ "$EFIDIR" == "" ]; then + ogFormat $(ogGetEsp) FAT32 + EFIDIR=$(ogMount $(ogGetEsp)) || ogRaiseError $OG_ERR_PARTITION "ESP" || return $? +fi + +# Comprobamos que exista el cargador +#BOOTLABEL=$(printf "Part-%02d-%02d" $1 $2) +OSVERSION=$(ogGetOsVersion $1 $2) +case $OSVERSION in + *Windows\ 10*) + BOOTLABEL=$(printf "Part-%02d-%02d" $1 $2) + LOADER=$(ogGetPath $MNTDIR/ogBoot/bootmgfw.efi) + [ -n "$LOADER" ] || ogRaiseError $OG_ERR_NOTOS "$1 $2 ($OSVERSION, EFI)" || return $? + [ -r $EFIDIR/EFI/$BOOTLABEL ] && rm -rf $EFIDIR/EFI/$BOOTLABEL + mkdir -p $EFIDIR/EFI/$BOOTLABEL + cp -r "${LOADER%/*}" $EFIDIR/EFI/$BOOTLABEL/Boot + # Nombre OpenGnsys para cargador + cp $LOADER $EFIDIR/EFI/$BOOTLABEL/Boot/ogloader.efi + + # Si existe subcarpeta Microsoft en la partición EFI la renombramos + [ "$(ogGetPath $EFIDIR/EFI/Microsoft)" == "" ] || mv $EFIDIR/EFI/{Microsoft,Microsoft.backup.og} + ;; +esac +} + + +#/** +# ogRestoreUuidPartitions +#@brief Restaura los uuid de las particiones y la tabla de particiones +#@param int_ndisk nº de orden del disco +#@param int_nfilesys nº de orden del sistema de archivos +#@param REPO|CACHE repositorio +#@param str_imgname nombre de la imagen +#@return (nada) +#@exception OG_ERR_FORMAT Formato incorrecto. +#@exception OG_ERR_NOTFOUND No encontrado fichero de información de la imagen (con uuid) +#*/ ## +function ogRestoreUuidPartitions () { +local DISK PART IMGNAME INFOFILE DEVICE DATA GUID UUID IMGGUID +local EFIDEVICE EFIDATA EFIGUID EFIUUID EFIUUID IMGEFIGUID + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME REPO|CACHE str_imgname int_ndisk int_npart" \ + "$FUNCNAME REPO Windows 1 2" + return +fi +# Error si no se reciben 4 parámetros. +[ $# -eq 4 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME REPO|CACHE str_imgname int_ndisk int_npart" || return $? + +# Sólo se ejecuta si es UEFI +ogIsEfiActive || return + +# Parámetros de entrada +IMGNAME="$2" +INFOFILE="$OGIMG/.$IMGNAME.img.json" +[ "${1^^}" == "CACHE" ] && INFOFILE="$OGCAC$INFOFILE" +# TODO: que la función getPath soporte archivos ocultos +ls $INFOFILE &>/dev/null || ogRaiseError $OG_ERR_NOTFOUND "$INFOFILE" || return $? +DISK=$3 +PART=$4 + +DEVICE=$(ogDiskToDev $DISK) +read -e EFIDISK EFIPART <<<"$(ogGetEsp)" + +# Datos de la imagen +IMGGUID=$(jq .guid $INFOFILE|tr -d \") +IMGEFIGUID=$(jq .espguid $INFOFILE|tr -d \") + +# Datos actuales +DATA=$(sfdisk -J $DEVICE) +GUID=$(echo $DATA|jq ".partitiontable|.id"|tr -d \") + +if [ "$IMGGUID" != "$GUID" ]; then + echo sgdisk -U "$IMGGUID" "$DEVICE" + sgdisk -U "$IMGGUID" "$DEVICE" + partprobe +fi + +if [ $DISK -eq $EFIDISK ]; then + EFIDATA=$DATA + EFIDEVICE=$DEVICE +else + EFIDEVICE=$(ogDiskToDev $EFIDISK) || return $? + EFIDATA=$(sfdisk -J $EFIDEVICE) + EFIGUID=$(echo $EFIDATA|jq ".partitiontable|.id"|tr -d \") + if [ "$IMGEFIGUID" != "$EFIGUID" ]; then +echo sgdisk -U "$IMGEFIGUID" "$EFIDEVICE" + sgdisk -U "$IMGEFIGUID" "$EFIDEVICE" + partprobe + fi +fi +} + + +#/** +# ogNvramSetNext +#@brief Configura el próximo arranque con la entrada del gestor de arranque (NVRAM) identificada por la etiqueta o el orden. +#@param Num_order_entry | Label_entry Número de orden o la etiqueta de la entrada a borrar. +#@return (nada) +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTUEFI UEFI no activa. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. +#*/ ## +function ogNvramSetNext () { +local NUMENTRY + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME [ Num_order_entry | Label_entry ] " \ + "$FUNCNAME 2" \ + "$FUNCNAME \"Windows Boot Manager\"" + return +fi + +# Error si no se recibe 1 parámetro. +[ $# -eq 1 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME [ Num_order_entry | Label_entry ]" || return $? + +# Si no es equipo UEFI salir con error +ogIsEfiActive || ogRaiseError $OG_ERR_NOTUEFI || return $? + +# Distingo si es número de orden o etiqueta +if [[ $1 =~ ^([0-9a-fA-F]+)$ ]]; then + NUMENTRY=$( efibootmgr |awk -v NUM="$(printf %04x 0x$1|tr '[:lower:]' '[:upper:]')" '{ if($1~NUM) print substr($1,5,4)}') +else + NUMENTRY=$(efibootmgr |awk -v LABEL="$1" '{ if(substr($0, index($0,$2))==LABEL) print substr($1,5,4)}') +fi + +[ "$NUMENTRY" == "" ] && return $(ogRaiseError $OG_ERR_NOTFOUND "NVRAM entry '$1'") + +efibootmgr -n $NUMENTRY &>/dev/null +} + +#/** +# ogNvramSetOrder +#@brief Configura el orden de las entradas de la NVRAM +#@param Orden de las entradas separadas por espacios +#@return (nada) +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTUEFI UEFI no activa. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado (entrada NVRAM). +#*/ ## +function ogNvramSetOrder () { +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME Num_order1 [ Num_order2 ] ... " \ + "$FUNCNAME 1 3" + return +fi +# +# Error si no se recibe al menos 1 parámetro. +[ $# -ge 1 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME Num_order1 [ Num_order2 ] ..." || return $? + +# Si no es equipo UEFI salir con error +ogIsEfiActive || ogRaiseError $OG_ERR_NOTUEFI || return $? + +# Comprobamos que sean números +[[ "$@" =~ ^([0-9a-fA-F ]+)$ ]] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME Num_order1 [ Num_order2 ] ..." || return $? + +# Entradas de la NVRAM actuales +NUMENTRYS=$(efibootmgr|awk '{ if ($1~/Boot[0-9a-fA-F]{4}/) printf "0%s ", substr($1,5,4)}') + +ORDER="" +for ARG in $@; do + # Si no existe la entrada me salgo + ARG=$(printf %04X 0x$ARG) + echo $NUMENTRYS | grep "$ARG" &>/dev/null || ogRaiseError $OG_ERR_NOTFOUND "NVRAM entry order \"$ARG\"" || return $? + ORDER=${ORDER},$ARG +done + +# Cambiamos el orden +efibootmgr -o ${ORDER#,} &>/dev/null +} + + +#/** +# ogNvramSetTimeout +#@brief Configura el tiempo de espera de la NVRAM +#@param Orden de las entradas separadas por espacios +#@return (nada) + +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. +#*/ ## +function ogNvramSetTimeout () { +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_Timeout (seg)" \ + "$FUNCNAME 2" + return +fi +# +# Si no es equipo UEFI salir con error +ogIsEfiActive || ogRaiseError $OG_ERR_NOTUEFI || return $? + +# Error si no se recibe 1 parámetro. +[ $# -eq 1 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_Timeout (seg)" || return $? + +# Comprobamos que sea un número +[[ "$1" =~ ^([0-9 ]+)*$ ]] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_Timeout (seg)" || return $? + +# Cambiamos el orden +efibootmgr -t $1 &>/dev/null +} + + +#/** +# ogUuidChange int_ndisk str_repo +#@brief Reemplaza el UUID de un sistema de ficheros. +#@param int_ndisk nº de orden del disco +#@param int_part nº de partición +#@return (nada, por determinar) +#@exception OG_ERR_FORMAT formato incorrecto. +#@exception OG_ERR_NOTFOUND fichero o dispositivo no encontrado. +#*/ ## +function ogUuidChange () { +local MNTDIR DEVICE UUID NEWUUID f + +# Si se solicita, mostrar ayuda. +if [ "$*" == "help" ]; then + ogHelp "$FUNCNAME" "$FUNCNAME int_ndisk int_part" \ + "$FUNCNAME 1 2" + return +fi + +# Error si no se reciben al menos 2 parámetros. +[ $# -eq 2 ] || ogRaiseError $OG_ERR_FORMAT "$FUNCNAME int_ndisk int_part" || return $? + +# Comprobamos que exista la partición +MNTDIR=$(ogMount $1 $2) || ogRaiseError $OG_ERR_NOTFOUND "Device $1 $2" || return $? +DEVICE=$(ogDiskToDev $1 $2) +UUID=$(blkid -o value -s UUID $DEVICE) +NEWUUID=$(cat /proc/sys/kernel/random/uuid) + +# Cambiamos UUID a la partición +ogUnmount $1 $2 +tune2fs $DEVICE -U $NEWUUID + +# Cambiamos UUID en la configuración (fstab y grub) +ogMount $1 $2 +for f in $MNTDIR/etc/fstab $MNTDIR/{,boot/}{{grubMBR,grubPARTITION}/boot/,}{grub{,2},{,efi/}EFI/*}/{menu.lst,grub.cfg}; do + [ -r $f ] && sed -i s/$UUID/$NEWUUID/g $f +done +} diff --git a/client/shared/README.es.txt b/client/shared/README.es.txt new file mode 100644 index 0000000..fbc03c4 --- /dev/null +++ b/client/shared/README.es.txt @@ -0,0 +1,29 @@ +OpenGsSys Client README +========================== + + +Este directorio contiene la estructura principal de datos que será +importada por los cleintes OpenGnsys mediante Samba (o NFS en las +primeras versiones del Proyecto). + +Los subdirectorios se copian íntegramente al servidor bajo +/opt/opengnsys/client y serán importados por los clientes en +/opt/opengnsys. + +La estructura de datos es la siguiente: + +- bin scripts o binarios ejecutables por el cliente (compilados + estáticamente). +- cache directorio donde se montará la caché local del cliente. +- etc ficheros de configuración del cliente. +- lib librerías de funciones. + - engine/bin ficheros con las funciones del motor de clonación. + - httpd ficheros de configuración del servicio lighttpd. + - modules módulos extra para el Kernel del cliente. + - qtlib librerías Qt complementarias del Browser. + - qtplugins plugins Qt para el Browser. +- images repositorio de imágenes de sistemas operativos. +- log registro de incidencias de los clientes. +- scripts funciones de alto nivel ejecutables por OpenGnsys Browser + y OpenGnsys Admin. + diff --git a/client/shared/bin/EACInterfaces b/client/shared/bin/EACInterfaces new file mode 100755 index 0000000000000000000000000000000000000000..b63b1db15230b13bf5205d08670c62603bf26cd3 GIT binary patch literal 9682 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

#U@;w33p!k4TO^(bSu&I4W}5g0;xOmbCfT2uGXU zb6*J2HG1bTkV-T@+s%4mEpBgrTHiMRSUt8{^L?!WzScZ- z<1ggvE;62ZZu4;&{h5DAaIK?RN~p2Vdb!4|+C&3u`F9G!uc-1-0hKeA^itvTBz?Ap z#$+;jDZg-w{a@OZl&A_~ozir%KDZH+E2mDWD04TDjKt9lCjkR6LFo-JupcOlf7kdM zDzojM0@Sv8S1-+D>jq~>apIL@^y`PCK00ElXCx-N>V@Z~xz-G(8k#?zb;02k znIot<6bP4~5IE1rUl;S2&hKi;#ONS|boWJN@XL4XdgBu)H5gxRs2}w2{oCQU_Bi;=i)ro)Sj|(_a_MOy?;hqndWCYfq&{XrRCyIf_}v zmgk4XFTrV6_cZDzKN!l0)IftK7E3bCte&0dcEXNj04fCN27QketN1-UI&!bUwg-eq zeQgo;DqO4HG(FY^&O$DP!Im#&QW@$VN++rF*2ufvQxoU&P`)b4h{VFPQGM%3YZA zuKtN_fE)k;8=&5ZVB>2VmW{hIEIl)a!SK3RaC`b493eL8>HE8D7xmimDSn$q@O|2& zw=FMr>8;x@YrXv<`2V-J?!pMKmHhnSXzRw&bVKn4HpH>JVe%>sU$YCU1)nk=vdi@B zLU@3eZQ{$D>nRr+?sH_8i@^g;+gFL0kK%2|9%eG@r|!-VMFr`AMR4(2+&E3j8Qtie z-0?MdEM>vu58;WeTpGVtu4>muXjex!=fXacy9>4CXYYuGxISzX`iO^kCyi0W-M~GE z53%0<{Hh1R=ium9b0<+Vnzxd?Ok(xaDK%w@A$rL%)}og)-TNB97S!TIsre#dbMVJN z<99d#FFO@$ezaiLsy%WhI+%ji{IFdDDnF)EsQet|5aO9S9sN_NM`nZS#aEka;iqKB zQ9hrRI$`?P7LJuB(3gv{Zcb=#CHxT2$n=SuY_H-GKlyRoHyd9qkAUlDgh18XmL596 z&AZciJpk)OlFb7Xb>(}~;i56JemD);--Tx_8I z3@2}gD+G%Ek1r1@)~TK4SZ=$8m6Tni9xP=gsVjWB8>wokS@*7>1wyO*Gf0ObX%45IM z)4?G--$^3VNjz$?v)%C`k!g>wGLi)??mgi%+npV6w>yPDpR@r1*;HTd!hg z#P4=|luUeuaP>_2-X@VI;)s7oMnAs7vGX3nFT&1yDALM?bQF(*1NT!on*F8dJbA^` zYii13Ef3LidxQKCclG>}0$hBDJeHbGf{+^fFFMZe#dz~}v6Sfbch>I_dw+D;#YZCh z0Bx7jC z^ViwBQLTmzQ2ktw>>B;lQM6%aSG=5qk#|Ssz=U?FbTt}U=8hCA&q5O z=cum}%!);ierR23A;3b#@BnQ~k-b679Gx!aDAaM2%SX5+jcj5e zlI#~3h+=@Eoc4;~)J{=pVS!(E(G1kjVXho2DlY`@%!p}y2pTw5CzVh|R;kDw{B3Z< zx)le#4=EA0kEM3sRtW{UPwe8O&_6;G7VOQO+-u_N2SrS^^uk8DP0(AZ$$rwVGyx-7 zSZv0-Fjjni7G8+YQrp?&JStM8u#RP2RYYlauwHBk%zfUAhTFo}PjaRQ`UKf7! z6K;}dTq}K++ef%OEE;DY6bA_A!5&ZT{m&+ka;l+#=LgtfB2hdzU%*qtxD`B4_IO@F z?}}l;E%4mk@SIn~^D5d_NCu|bL3k2KFW@P*6`t1tTl8!&JP+@J=Lo}dZZ9%@v;KqAzpv`4085hR^f8hU`Heo{(W!WDUpb6Gzl4tpXNb!yi1#vsx#K=|r%fxj;z3B2YD+|IzEhXHmC2pFBl8 zzbLj~oS>R#ae?VcySB_G(X;+Z^`dl%0ADvr96BP*zt>R`hp0{IMT$vcYlz1yNG>i^ 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