#!/usr/bin/perl # (C) Luis Angel Cofiño - Marzo 2009 # # Este programa perl hace una copia de seguridad incremental y programada. use strict; use File::Path; #use Text::CSV_XS; use Cwd; # Variables y entorno {{{ my $config="/etc/backup/backup.conf"; my $pid="/var/run/backup.pid"; my $opcion=""; my $mensaje; my $ejecuta=1; my $aborta=0; my $completo=0; my $ultrabackup=0; my $debug=0; my %ajuste; my $fecha=`date`; chomp($fecha); my @codigo; my @ultracode; my $out_espacio; my $out_ocupado; my $out_total; my $ultra_out_espacio; my $ultra_out_ocupado; my $ultra_out_total; my $mountbackup; my $mountultra; # }}} # ---------------- Verdadero programa ------------------------------- comprueba_linea_comandos(); $ejecuta=existepid(); if ($ejecuta) { creapid(); $aborta=existeconfig(); if (!$aborta) { mueve_contadores(); monta_unidad(); monta_ultraunidad() if ($ultrabackup); $mountbackup=montajes(); if ($mountbackup) { ejecuta(); # Si debug, funcionara, pero en modo dry-run graba_config() if !$debug; } desmonta_unidad(); } if ($debug) { # Si debug, saca a pantalla, si no a fichero saca_log(); } else { graba_log(); } borrapid(); } else { print "La ejecución está bloqueada por $pid. Compruébalo.\n"; } # ---------------- Verdadero programa ------------------------------- # Subrutina para debug. Imprime los valores actuales. {{{ sub debug { print "\n"; print "El valor de 'total' es $ajuste{'total'}\n"; print "El valor de 'maximo' es $ajuste{'maximo'}\n"; print "El valor de 'contador' es $ajuste{'contador'}\n"; print "El valor de 'nivelultra' es $ajuste{'nivelultra'}\n"; print "El valor de 'serie' es $ajuste{'serie'}\n"; print "El valor de 'directorios' es $ajuste{'directorios'}\n"; print "El valor de 'excluidos' es $ajuste{'excluidos'}\n"; print "El valor de 'log' es $ajuste{'log'}\n"; print "El valor de 'tipo' es $ajuste{'tipo'}\n"; print "El valor de 'disco1' es $ajuste{'disco1'}\n"; print "El valor de 'disco2' es $ajuste{'disco2'}\n"; print "El valor de 'ultradev' es $ajuste{'ultradev'}\n"; print "El valor de 'ultrauni' es $ajuste{'ultrauni'}\n"; print "\n"; print "El valor de 'aborta' es $aborta\n"; print "\n"; return; } # }}} # Ayuda {{{ sub info { print "\n"; print "Uso: backup [--ayuda | --simulacro | --debug]\n"; print "\n"; if (!$aborta) { print "-- $fecha ----------------\n"; print "La ultima copia de seguridad fue $ajuste{'serie'} - $ajuste{'contador'}\n"; print "Los directorios a salvar son:\n"; system("cat $ajuste{'directorios'}"); print "El nivel maximo esta establecido en $ajuste{'maximo'}\n"; print "El archivo de log es $ajuste{'log'}\n"; print "\n"; } else { print "La configuración no era correcta. Tendrás que revisarla.\n"; print "En concreto, revisa $config\n"; } } # }}} # Comprobación de la línea de comandos {{{ sub comprueba_linea_comandos { my $argumento; if (@ARGV>0) { while ($argumento=shift(@ARGV)) { if ($argumento eq "--ayuda") { $aborta=existeconfig(); info(); exit 0; } elsif ($argumento eq "--simulacro") { $debug=1; } elsif ($argumento eq "--debug") { $debug=1; } else { print "La opción $argumento no es válida.\n"; $aborta=existeconfig(); info(); exit 1; } } } } # }}} # Comprueba que todos los ficheros de configuración existen {{{ sub existeconfig { if (-f $config) { $aborta=leeconfig(); # Si existe, leemos el fichero de configuración debug() if $debug; # Si es un simulacro, listado de variables recién cargadas. } else { mkpath("/etc/backup") if (!-d "/etc/backup"); # Si no existe, creamos un borrador creaconfig(); $aborta=1; } # En cualquier caso, nos aseguramos de que los demás ficheros existen # y si no, creamos unos por defecto. # Pero en este caso, el backup continúa con los valores por defecto. creadir() if (!-f $ajuste{'directorios'}); creaexcluidos() if (!-f $ajuste{'excluidos'}); return $aborta; } # }}} # Lectura de la configuración {{{ sub leeconfig { my $linea; my $llave; my $valor; open(ARCH, "<", $config) || die "No se pudo abrir el fichero de configuración en modo lectura.\n"; while ($linea=) { # Leemos el fichero línea a línea rellenando el hash. ($llave,$valor)=split(/=/,$linea); chomp($llave); chomp($valor); if ($llave ne "" && $llave=~ /^[a-zA-Z0-9]/) { # Solo rellenaremos si la línea es válida. $ajuste{"$llave"}=$valor; } } close ARCH; $aborta=comprueba_config(); # Hemos leído el fichero. Ahora vamos a comprobarlo y si no sirve, abortamos. return $aborta; } # }}} # Comprueba que el fichero de configuración dice lo correcto y no tonterías {{{ sub comprueba_config { my $existellave=1; my $llave; my $valor; while (($llave,$valor) = each %ajuste) { # Recorremos todo el array comprobando llaves y valores if ($llave eq "total") { if ($valor =~ /\D+/) { # Si "total" contiene algo que no sea número, mal. $existellave=0; $mensaje="La linea \"$llave=$valor\" no es válida: el total debe ser un número entero.\n"; } } elsif ($llave eq "maximo") { # Si "maximo" es menor de 1 o mayor de 30, mal. if ($valor<1 || $valor>30) { $existellave=0; $mensaje="La linea \"$llave=$valor\" no es válida: el máximo debe ser entre 1 y 30.\n"; } } elsif ($llave eq "contador") { # Si "contador" es menor de uno o mayor de 30 o mayor que "maximo", fatal. if ($valor<1 || $valor>30 || $ajuste{'contador'}>$ajuste{'maximo'}) { $existellave=0; $mensaje="La linea \"$llave=$valor\" no es válida: el contador debe ser entre 0 y máximo.\n"; } } elsif ($llave eq "nivelultra") { # Si nivel ultra no es un número, mal. Y si es menor que "maximo", también. if ($valor =~ /\D+/ || $ajuste{'nivelultra'}<$ajuste{'maximo'}) { $existellave=0; $mensaje="La linea \"$llave=$valor\" no es válida: el nivel ultra debe ser un número entero.\n"; } } elsif ($llave eq "serie") { # Si la serie no es ni par ni impar, mal. if ($valor ne "par" && $valor ne "impar") { $existellave=0; $mensaje="La linea \"$llave=$valor\" no es válida: la serie debe ser \"par\" o \"impar\".\n"; } } elsif ($llave eq "directorios") { # Si el archivo de directorios especificado no existe, lo creamos por defecto. if (!-f $valor) { mkpath("/etc/backup") if (!-d "/etc/backup"); creadir(); } } elsif ($llave eq "excluidos") { # Si el archivo de excluidos no existe, lo creamos por defecto. if (!-f $valor) { mkpath("/etc/backup") if (!-d "/etc/backup"); creaexcluidos() } } elsif ($llave eq "log") { # Si el archivo de log no existe, lo creamos por defecto vacío. if (!-f $valor) { $valor="/var/log/seguridad.log"; system("touch $valor"); $ajuste{'log'}=$valor; } } elsif ($llave eq "tipo") { # Si el tipo no es ext3, ext4 ni vfat, no nos gusta. if ($valor ne "ext3" && $valor ne "ext4" && $valor ne "vfat") { $existellave=0; $mensaje="La linea \"$llave=$valor\" no es válida: el tipo debe ser \"ext3\", \"ext4\" o \"vfat\".\n"; } } elsif ($llave eq "disco1") { # Si el UUID de disco1 no está puesto o no está en fstab, mal. if ($valor =~ /^\@\@/ || !estaen_fstab($valor)) { $existellave=0; $mensaje="La linea \"$llave=$valor\" no es válida: el UUID del disco debe estar en /etc/fstab.\n"; } } elsif ($llave eq "disco2") { # Si el UUID de disco 2 no está puesto o no está en fstab, mal. if ($valor =~ /^\@\@/ || !estaen_fstab($valor)) { $existellave=0; $mensaje="La linea \"$llave=$valor\" no es válida: el UUID del disco debe estar en /etc/fstab.\n"; } } elsif ($llave eq "ultradev") { # Si el UUID de ultrabackup no está puesto o no está en fstab, mal. if ($valor =~ /^\@\@/ || !estaen_fstab($valor)) { $existellave=0; $mensaje="La linea \"$llave=$valor\" no es válida: el UUID del disco debe estar en /etc/fstab.\n"; } } elsif ($llave eq "ultrauni") { # Si el punto de montaje de ultrabackup no está puesto o no está en fstab, mal. if ($valor =~ /^\@\@/ || !estaen_fstab($valor)) { $existellave=0; $mensaje="La linea \"$llave=$valor\" no es válida: el UUID del disco debe estar en /etc/fstab.\n"; } } else { # Y si has llegado hasta aquí, es que esa opción no existe. $existellave=0; $mensaje="La linea \"$llave=$valor\" no es válida: esa opción no existe actualmente.\n"; } } if ($existellave) { # Si falló cualquier cosa, abortamos. return $aborta=0; } else { return $aborta=1; } } # }}} # Crea un nuevo fichero de configuración {{{ sub creaconfig { open(ARCH, ">" , $config) || die "No se pudo abrir el fichero de configuración en modo escritura.\n"; print ARCH "# Configuración de backup\n"; print ARCH "total=1\n"; print ARCH "maximo=30\n"; print ARCH "contador=30\n"; print ARCH "nivelultra=182\n"; print ARCH "serie=par\n"; print ARCH "directorios=/etc/backup/directorios.conf\n"; print ARCH "excluidos=/etc/backup/excluidos.conf\n"; print ARCH "log=/var/log/seguridad.log\n"; print ARCH "tipo=ext3\n"; print ARCH "disco1=@@ aquí el UUID del primer disco de backup @@\n"; print ARCH "disco2=@@ aquí el UUID del segundo disco de backup @@\n"; print ARCH "ultradev=@@ aquí el UUID del disco de ultrabackup @@\n"; print ARCH "ultrauni=@@ aquí dónde se monta el disco de ultrabackup @@\n"; close ARCH; return; } # }}} # Crea un nuevo fichero de directorios {{{ sub creadir { $ajuste{'directorios'}="/etc/backup/directorios.conf" if (!-f $ajuste{'directorios'}); open (ARCH, ">" , $ajuste{'directorios'}) || die "No se pudo crear el fichero de directorios.\n"; print ARCH "/home\n"; print ARCH "/root\n"; print ARCH "/etc\n"; print ARCH "/var/lib\n"; print ARCH "/var/log\n"; close ARCH; return; } # }}} # Crea un nuevo fichero de excluidos {{{ sub creaexcluidos { $ajuste{'excluidos'}="/etc/backup/excluidos.conf" if (!-f $ajuste{'excluidos'}); open (ARCH, ">" , $ajuste{'excluidos'}) || die "No se pudo crear el fichero de excluidos.\n"; print ARCH ".gvfs\n"; close ARCH; return; } # }}} # Escritura de la configuración {{{ sub graba_config { open(ARCH, ">" , $config) || die "No se pudo abrir el fichero de configuración en modo escritura.\n"; print ARCH "# Configuración de backup\n"; print ARCH "total=$ajuste{'total'}\n"; print ARCH "maximo=$ajuste{'maximo'}\n"; print ARCH "contador=$ajuste{'contador'}\n"; print ARCH "nivelultra=$ajuste{'nivelultra'}\n"; print ARCH "serie=$ajuste{'serie'}\n"; print ARCH "directorios=$ajuste{'directorios'}\n"; print ARCH "excluidos=$ajuste{'excluidos'}\n"; print ARCH "log=$ajuste{'log'}\n"; print ARCH "tipo=$ajuste{'tipo'}\n"; print ARCH "disco1=$ajuste{'disco1'}\n"; print ARCH "disco2=$ajuste{'disco2'}\n"; print ARCH "ultradev=$ajuste{'ultradev'}\n"; print ARCH "ultrauni=$ajuste{'ultrauni'}\n"; close ARCH; } # }}} # Mueve los contadores apropiadamente {{{ sub mueve_contadores { if ($ajuste{'contador'} == $ajuste{'maximo'}) { # Si el contador está al máximo, reseteamos y cambiamos de serie. $completo=1; $ajuste{'contador'}=1; $opcion="--delete --whole-file"; if ($ajuste{'serie'} eq "impar") { $ajuste{'serie'}="par"; } elsif ($ajuste{'serie'} eq "par") { $ajuste{'serie'}="impar"; } else { # Nunca llegaremos aquí, pero por si escaso. $ajuste{'serie'}="par"; } } else { # Si el contador no está al máximo, solo sumamos uno y ya. $ajuste{'contador'}++; } $ajuste{'total'}++; # Sumamos uno también al total. if ($ajuste{'total'}%$ajuste{'nivelultra'}==0) { # Si hemos llegado de nuevo al nivelultra, habrá ultrabackup. $ultrabackup=1; } debug() if $debug; # Si es un debug, mostramos los contadores justo ahora. return; } # }}} # Crea el fichero de bloqueo {{{ sub creapid { system("touch $pid"); } # }}} # Borra el fichero de bloqueo {{{ sub borrapid { unlink("$pid"); } # }}} # Comprueba existencia del fichero de bloqueo {{{ sub existepid { if (-f $pid) { $ejecuta=0; } else { $ejecuta=1; } return $ejecuta; } # }}} # Monta unidad de backup {{{ sub monta_unidad { my $dispositivo; if ($ajuste{'serie'} eq "par") { $dispositivo=$ajuste{'disco2'}; } elsif ($ajuste{'serie'} eq "impar") { $dispositivo=$ajuste{'disco1'}; } else { # Hemos comprobado tanto que nunca deberíamos llegar aquí. Pero en fin. print "Error en la configuración. El dispositivo no existe.\n"; exit 1; } system("umount /media/Backup_$ajuste{'serie'}") if esta_montado("/media/Backup_$ajuste{'serie'}"); system("mount -t $ajuste{'tipo'} -o rw UUID=$dispositivo /media/Backup_$ajuste{'serie'}"); return; } # }}} # Monta unidad de ultrabackup {{{ sub monta_ultraunidad { system("umount $ajuste{'ultrauni'}") if esta_montado("$ajuste{'ultrauni'}"); system("mount -t $ajuste{'tipo'} -o rw UUID=$ajuste{'ultradev'} $ajuste{'ultrauni'}"); return; } # }}} # Subrutina que comprueba si una unidad está en fstab {{{ sub estaen_fstab { (my $unidad)=@_; my $linea; my $resultado=0; open (LISTA,"/etc/fstab"); while ($linea=) { # Leemos fstab línea y línea y comprobamos si la unidad está. $resultado=1 if ($linea =~ /$unidad/); } close LISTA; return $resultado; } # }}} # Subrutina que comprueba si una unidad concreta está montada {{{ sub esta_montado { (my $dir)=@_; my $linea; my $resultado=0; open (LISTA,"/proc/mounts"); while ($linea=) { # Leemos línea a línea y nos fijamos en el segundo campo. my ($device,$carpeta)=(split /\s+/,$linea)[0,1]; if ($carpeta eq $dir) { $resultado=1; } } close LISTA; return $resultado; } # }}} # Comprueba si las unidades de backup están montadas (llama a la subrutina previa) {{{ sub montajes { $mountbackup=esta_montado("/media/Backup_$ajuste{'serie'}"); $mountultra=esta_montado("$ajuste{'ultrauni'}"); return $mountbackup; # Ahora solo nos importa el backup normal. Pero el ultrabackup también quedará registrado. } # }}} # Desmonta unidad de backup {{{ sub desmonta_unidad { system("umount /media/Backup_$ajuste{'serie'}"); system("mount /media/Backup_$ajuste{'serie'}"); return; } # }}} # Ejecución {{{ sub ejecuta { my $dir; my $e=0; my $ue=0; my $dry=""; $dry="--dry-run" if $debug; # Si es un debug, rsync se ejecutará en simulacro. if ($completo && !$debug) { # Si es el primero de la serie y es real, borramos todo. rmtree("/media/Backup_$ajuste{'serie'}/espejo"); mkdir "/media/Backup_$ajuste{'serie'}/espejo", 0755; } # Ejecutamos el backup --------------------------------------------------------- open (DIRS, "<", $ajuste{'directorios'}) || die "No se pudo abrir el fichero de directorios en modo lectura.\n"; while ($dir=) { # Con cada línea que contenga un directorio, ejecutamos rsync y recogemos el código de error en un array. chomp($dir); if ($dir ne "" && $dir=~ /^\//) { $codigo[$e]=system("rsync -qarzH $dry --exclude-from=$ajuste{'excluidos'} $opcion $dir /media/Backup_$ajuste{'serie'}/espejo"); $e++; } } close (DIRS); # Ejecutamos el ultrabackup ---------------------------------------------------- if ($ultrabackup) { # Si es un ultrabackup... if ($mountultra) { # ...y si la unidad está montada. open (DIRS, "<", $ajuste{'directorios'}) || die "No se pudo abrir el fichero de directorios en modo lectura.\n"; while ($dir=) { # Leemos los directorios y ejecutamos rsync, pero grabando a un directorio nuevo. chomp($dir); if ($dir ne "" && $dir=~ /^\//) { $ultracode[$ue]=system("rsync -qarzH $dry --exclude-from=$ajuste{'excluidos'} $opcion $dir $ajuste{'ultrauni'}/espejonew"); $ue++; } } close (DIRS); if (!$debug) { rmtree("$ajuste{'ultrauni'}/espejo"); # Borramos el directorio antiguo y renombramos el nuevo. rename("$ajuste{'ultrauni'}/espejonew","$ajuste{'ultrauni'}/espejo"); } } } # Comprobamos la ocupación de los discos y lo registramos ----------------------- my $ocupacion=`df -h | grep /media/Backup_$ajuste{'serie'}`; ($out_espacio,$out_ocupado,$out_total)=(split /\s+/,$ocupacion)[4,2,1]; if ($ultrabackup) { my $ultraocupacion=`df -h | grep $ajuste{'ultrauni'}`; ($ultra_out_espacio,$ultra_out_ocupado,$ultra_out_total)=(split /\s+/,$ultraocupacion)[4,2,1]; } } # }}} # Graba el log a disco {{{ sub graba_log { my $dir; my $e=0; my $ue=0; open (LOG, ">>", $ajuste{'log'}) || die "No se pudo abrir el fichero de log en modo escritura.\n"; # Imprimimos la cabecera ---------------------- print LOG " \n"; print LOG "--- $fecha - Serie $ajuste{'serie'} / Nivel $ajuste{'contador'} (ejecución nº$ajuste{'total'}) ---\n"; # Si hubo un fallo de configuración y se abortó, dejamos nota y nos vamos ---------- {{{ if ($aborta) { print LOG "El fichero de configuración $config no es válido. Backup abortado.\n"; print LOG "$mensaje\n"; return; } # }}} # Si la unidad de backup no se montó es un error crítico --------------------------- {{{ if ($mountbackup) { print LOG "Se ha montado la unidad /media/Backup_$ajuste{'serie'}\n"; } else { print "Hubo un error. La unidad de backup /media/Backup_$ajuste{'serie'} no está montada.\n"; print "Los cambios de set y de nivel han sido grabados. Backup abortado.\n"; close LOG; return; } # }}} # Si es ultrabackup dejamos una nota, pero si no se montó no es un error crítico ---- {{{ if ($ultrabackup) { if ($mountultra) { print LOG "(Hecha copia completa ultrabackup a unidad $ajuste{'ultrauni'})\n"; } else { print "Hubo un error. La unidad de backup $ajuste{'ultrauni'} no está montada.\n"; print "No es un error crítico. Se continúa solo con /media/Backup_$ajuste{'serie'}\n"; $ultrabackup=0; } } # }}} # Resultados del Backup ----------------------- {{{ open (DIRS, "<", $ajuste{'directorios'}) || die "No se pudo abrir el fichero de directorios en modo lectura.\n"; while ($dir=) { # Volvemos a leer el archivo de directorios y vamos grabando a log los códigos de error si hubo. chomp($dir); if ($dir ne "" && $dir=~ /^\//) { if ($codigo[$e]==0) { print LOG "Sincronizado $dir correctamente\n"; } else { print LOG "Hubo un error con $dir: el código fue $codigo[$e]\n"; } } $e++; } close (DIRS); print LOG "La ocupación de la unidad $ajuste{'serie'} es del $out_espacio ($out_ocupado de $out_total)\n"; # }}} # Resultados del Ultrabackup ------------------- {{{ if ($ultrabackup) { print LOG "Utilizando unidad $ajuste{'ultrauni'} para Ultrabackup\n"; open (DIRS, "<", $ajuste{'directorios'}) || die "No se pudo abrir el fichero de directorios en modo lectura.\n"; while ($dir=) { # Hacemos lo mismo que con el backup pero para el ultrabackup. chomp($dir); if ($dir ne "" && $dir=~ /^\//) { if ($ultracode[$ue]==0) { print LOG "Salvado $dir correctamente\n"; } else { print LOG "Hubo un error con $dir: el código fue $codigo[$e]\n"; } } $ue++; } close (DIRS); print LOG "La ocupación de la unidad $ajuste{'ultrauni'} es del $ultra_out_espacio ($ultra_out_ocupado de $ultra_out_total)\n"; } close LOG; # }}} } # }}} # Saca el log a pantalla {{{ sub saca_log { my $dir; my $e=0; my $ue=0; # Mostramos en pantalla la cabecera ------------------------------------------ print " \n"; print "--- $fecha - Serie $ajuste{'serie'} / Nivel $ajuste{'contador'} (ejecución nº$ajuste{'total'}) ---\n"; # Primero unos cuantos mensajes si se abortó todo o parte -------------------- if ($aborta) { print "El fichero de configuración $config no es válido. Backup abortado.\n"; print "$mensaje\n"; return; } if ($mountbackup) { print "Se ha montado la unidad /media/Backup_$ajuste{'serie'}\n"; } else { print "Hubo un error. La unidad de backup /media/Backup_$ajuste{'serie'} no está montada.\n"; print "Los cambios de set y de nivel han sido grabados. Backup abortado.\n"; return; } if ($ultrabackup) { if ($mountultra) { print "(Hecha copia completa ultrabackup a unidad $ajuste{'ultrauni'})\n"; } else { print "Hubo un error. La unidad de backup $ajuste{'ultrauni'} no está montada.\n"; print "No es un error crítico. Se continúa solo con /media/Backup_$ajuste{'serie'}\n"; $ultrabackup=0; } } # Resultados del Backup ----------------------- open (DIRS, "<", $ajuste{'directorios'}) || die "No se pudo abrir el fichero de directorios en modo lectura.\n"; while ($dir=) { chomp($dir); if ($dir ne "" && $dir=~ /^\//) { if ($codigo[$e]==0) { print "Sincronizado $dir correctamente\n"; } else { print "Hubo un error con $dir: el código fue $codigo[$e]\n"; } } $e++; } close (DIRS); print "La ocupación de la unidad $ajuste{'serie'} es del $out_espacio ($out_ocupado de $out_total)\n"; # Resultados del Ultrabackup ------------------- if ($ultrabackup) { print "Utilizando unidad $ajuste{'ultrauni'} para Ultrabackup\n"; open (DIRS, "<", $ajuste{'directorios'}) || die "No se pudo abrir el fichero de directorios en modo lectura.\n"; while ($dir=) { chomp($dir); if ($dir ne "" && $dir=~ /^\//) { if ($ultracode[$ue]==0) { print "Salvado $dir correctamente\n"; } else { print "Hubo un error con $dir: el código fue $codigo[$e]\n"; } } $ue++; } close (DIRS); print "La ocupación de la unidad $ajuste{'ultrauni'} es del $ultra_out_espacio ($ultra_out_ocupado de $ultra_out_total)\n"; } print "\n"; } # }}} # Códigos de error {{{ sub codigo { # Esta subrutina nunca se ejecutará. Está ahí por si hago algo con ella algún día. my($n)=@_; if ($n==0) { return "Correcto"; } elsif ($n==1) { return "Error de sintaxis"; } elsif ($n==2) { return "Incompatibilidad de protocolos"; } elsif ($n==3) { return "Error selecionando archivos de entrada/salida"; } elsif ($n==4) { return "La acción solicitada no está soportada por el cliente o por el servidor"; } elsif ($n==5) { return "Error iniciando protocolo cliente-servidor"; } elsif ($n==6) { return "El demonio fue incapaz de abrir el archivo de log"; } elsif ($n==10) { return "Error I/O en el socket"; } elsif ($n==11) { return "Error I/O en el archivo"; } elsif ($n==12) { return "Error en el protocolo rsync data stream"; } elsif ($n==13) { return "Error en el programa de diagnóstico"; } elsif ($n==14) { return "Error en el código IPC"; } elsif ($n==20) { return "Se recibió la señal SIGUSR1 o SIGINT"; } elsif ($n==21) { return "Hubo algún error en waitpid()"; } elsif ($n==22) { return "Error colocando buffers de memoria en el core"; } elsif ($n==23) { return "Transferencia parcial debido a un error"; } elsif ($n==24) { return "Transferencia parcial debido a archivos eliminados en el origen"; } elsif ($n==25) { return "La opción --max-delete limitó el borrado de ficheros"; } elsif ($n==30) { return "Se agotó el tiempo de espera en envió o recepción de datos"; } elsif ($n==35) { return "Se agotó el tiempo de espera para la conexión del demonio"; } else { return "El código $n no está especificado"; } } # }}}