Lo hice y lo entendí

El blog de Vicente Navarro
24 ene

La libtrash, la papelera de la línea de comandos

Hay veces que encuentras cosas que te llaman de verdad la atención, como el artículo Papelera para la consola (linux-es.org), basado en el original Papelera para la consola (mundogeek.net) que me ha enseñado algo de verdad curioso.

La libtrash es una librería que, cuando se carga, intercepta las llamadas al sistema: unlink() y unlinkat(). Con esta intercepción podemos, en vez de efectivamente borrar el fichero, moverlo a un directorio de papelera. Esto ya suelen hacerlo los escritorios como KDE o GNOME a más alto nivel, pero si en un momento dado hacemos un rm desde un terminal, el fichero no irá a la papelera del escritorio.

Lo primero, claro, es instalar la librería. Afortunadamente, en Debian y en Ubuntu ya tenemos un paquete listo en los repositorios, que podemos instalar fácilmente con apt-get:

# apt-get install libtrash

Una vez instalado el paquete, la primera cosa curiosa con la que nos encontramos es cómo cargar la librería. Resulta que simplemente usando la variable de entorno LD_PRELOAD de la siguiente forma:

$ export LD_PRELOAD=/usr/lib/libtrash/libtrash.so.2.4 

ya conseguimos que la librería se cargue antes que ninguna otra para cada comando que lancemos. Podemos leer sobre esta variable de entorno en la página de man de ld.so:

LD_PRELOAD

A whitespace-separated list of additional, user-specified, ELF shared libraries to be loaded before all others. This can be used to selectively override functions in other shared libraries. For setuid/setgid ELF binaries, only libraries in the standard search directories that are also setgid will be loaded.

Para que esta librería se cargue por defecto al entrar en la shell, que es lo recomendable, podemos exportar la LD_PRELOAD apuntando a la librería sen el $HOME/.bashrc o en el $HOME/.bash_profile.

Pues bien, sólo con hacer eso ya está funcionando. Si borramos un fichero, éste, en vez de ser borrado será movido a $HOME/Trash (por defecto), incluso aunque el directorio Trash no existiera anteriormente:

~ $ ll Trash
ls: Trash: No such file or directory
~ $ echo prueba > fichero
~ $ rm fichero
~ $ find Trash
Trash
Trash/fichero
Trash/SYSTEM_ROOT
~ $ cat Trash/fichero          
prueba

No nos limitemos a borrar ficheros de $HOME, probemos a borrar algunos de distintos sitios, como de /tmp/ o de $HOME/directorio:

~ $ echo prueba2 > /tmp/fichero2
~ $ rm /tmp/fichero2
~ $ mkdir directorio
~ $ echo prueba3 > directorio/fichero3
~ $ rm directorio/fichero3 
~ $ find Trash 
Trash
Trash/fichero
Trash/directorio
Trash/directorio/fichero3
Trash/patata
Trash/SYSTEM_ROOT

Podemos ver que mientras que fichero3, que estaba en $HOME/directorio, se ha almacenado en Trash/directorio/fichero3, /tmp/fichero2 no ha ido a la papelera.

Para entender por qué ocurre eso, vamos al fichero de configuración /etc/libtrash.conf, extensamente autodocumentado, en el que podríamos sospechar de la variable GLOBAL_PROTECTION que es la que controla si los ficheros de fuera de nuestro $HOME pueden ir a la papelera, pero vemos que por defecto está a YES:

GLOBAL_PROTECTION = YES

pero la trampa está en que los ficheros de /tmp/ y de /var por defecto no van a la papelera:

TEMPORARY_DIRS = /tmp;/var

Si, en cambio, usamos el usuario root para escribir fuera de $HOME (con nuestro usuario normal no podemos fuera de /tmp y de varios subdirectorios de /var/), veremos que sí funciona y que el fichero se guarda bajo Trash/SYSTEM_ROOT:

 ~ # echo prueba > /usr/fichero
~ # rm /usr/fichero
~ # find Trash
Trash
Trash/SYSTEM_ROOT/usr/prueba

El fichero /etc/libtrash.conf es muy interesante y si nos decidimos a usar la libtrash deberíamos recorrerlo detenidamente para entender todas sus variables. Por ejemplo, podemos configurar los nombres de los directorios a usar:

TRASH_CAN = Trash
TRASH_SYSTEM_ROOT = SYSTEM_ROOT 

Así como configurar el nombre del fichero de configuración que cada usuario puede usar para establecer su configuración personal, y que por defecto es $HOME/.libtrash:

PERSONAL_CONF_FILE = .libtrash

También vemos que además de no enviar a la papelera por defecto los ficheros de /tmp/ y /var/, tampoco manda los ficheros con una cierta extensión:

IGNORE_EXTENSIONS = o;log;aux 

Otro aspecto muy importante e interesante es que aunque hayamos borrado varios ficheros con el mismo nombre, la libtrash guarda los nuevos con nombres distintos al anterior para no sobreescribir al previo:

~ $ echo prueba > fichero
~ $ rm fichero
~ $ echo prueba > fichero                                                          
~ $ rm fichero                                                           
~ $ echo prueba > fichero                                                          
~ $ rm fichero                                                           
~ $ find Trash
Trash
Trash/fichero[2]
Trash/fichero
Trash/fichero[1]
Trash/SYSTEM_ROOT

Otra cosa a tener en cuenta es que si queremos borrar el propio directorio de la papelera, por defecto no podremos:

~ $ rm -rf Trash/
rm: cannot remove `Trash//fichero': Permission denied

Esto ocurre por el siguiente parámetro, que está por defecto activado, y que previene el borrado de los ficheros de la papelera:

PROTECT_TRASH = YES

Si en la configuración del usuario ponemos el parámetro a NO, veremos como ya podemos borrar el directorio:

~ $ echo "PROTECT_TRASH = NO" >> .libtrash                                         
~ $ rm -rf Trash/
~ $ find Trash
find: Trash: No such file or directory
~ $ echo prueba > fichero                                                      
~ $ rm fichero 
~ $ find Trash                                                           
Trash
Trash/fichero
Trash/SYSTEM_ROOT
~ $ rm -rf Trash/
~ $ find Trash
find: Trash: No such file or directory

Si ahora queremos borrar el propio fichero .libtrash, no podremos, para preservar nuestra configuración de borrado bajo cualquier circunstancia:

 $ rm .libtrash
rm: cannot remove `.libtrash': Permission denied

Esto viene definido por la variable:

LIBTRASH_CONFIG_FILE_UNREMOVABLE = YES

Tampoco guarda en la papelera ficheros vacíos (0 bytes):

~ $ > fichero
~ $ rm fichero
~ $ find Trash
Trash
Trash/SYSTEM_ROOT

Hemos comentado al principio que la libtrash intercepta las llamadas unlink al sistema (las haga el comando rm, como hemos estado probando hasta ahora o las haga cualquier otra aplicación), pero la verdad es que es capaz de interceptar varias llamadas al sistema más:

- unlink() / unlinkat();
- rename() / renameat();
- fopen() / fopen64();
- freopen() / freopen64();
- open() / openat() / open64() / openat64() / creat() / creat64().

Pero las intercepciones de las llamadas al sistema *open*, que sirven para guardar ficheros que van a ser truncados, están por defecto deshabilitadas:

INTERCEPT_UNLINK = YES
INTERCEPT_RENAME = YES
INTERCEPT_FOPEN = NO
INTERCEPT_FREOPEN = NO
INTERCEPT_OPEN = NO

Pero vemos que no lo está, en cambio, la intercepción de rename(). Por tanto, si hacemos un mv cuyo fichero destino existe, lo guardará en la papelera antes de reemplazarlo:

~ $ echo prueba1 > fichero1
~ $ echo prueba2 > fichero2
~ $ mv fichero2 fichero1
~ $ find Trash
Trash
Trash/fichero1
Trash/SYSTEM_ROOT

Hasta ahora hemos ido viendo cómo se comporta la libtrash. Sin embargo, aún no sabemos como borrar la propia papelera (sin quitar la variable LIBTRASH_CONFIG_FILE_UNREMOVABLE) o como evitar enviar ficheros a la papelera. Para ello, podemos usar la variable de entorno TRASH_OFF:

~ $ export TRASH_OFF=YES                                                           
~ $ echo prueba > fichero                           
~ $ rm fichero                                                                    
~ $ find Trash                                                                    
find: Trash: No such file or directory
~ $ export TRASH_OFF=NO
~ $ echo prueba > fichero                                                  
~ $ rm fichero                                                             
~ $ find Trash                                                                    
Trash
Trash/fichero
Trash/SYSTEM_ROOT

El autor de la libtrash sugiere crear alias como los siguientes para poder activar estas variables rápidamente:

alias trash_on="export TRASH_OFF=NO"
alias trash_off="export TRASH_OFF=YES"

E incluso sugiere la posibilidad de un alias como:

alias hardrm="TRASH_OFF=YES rm"

para borrar sólo los fichero de ese comando en concreto definitivamente sin pasar por la papelera. Esto no es muy aconsejable, porque podemos adoptar la costumbre de usarlo perdiéndose la utilidad de la papelera. Es como quien se acostumbra a borrar en el escritorio pulsando la tecla de mayúsculas para que el fichero no vaya a la papelera y siempre llega el día en el que lamenta tener dicha costumbre.

Hay una variable, que por defecto está a NO:

SHOULD_WARN = NO

que nos permite recibir mensajes de advertencia si la libtrash está deshabilitada con TRASH_OFF=YES. El mensaje es el definido en la variable:

WARNING_STRING = Remember that libtrash is disabled.

Para automatizar un poco la limpieza de la papelera, en /usr/share/doc/libtrash/examples/cleanTrash encontramos varios scripts que borran los ficheros más antiguos de la papelera hasta alcanzar un tamaño máximo de esta que podemos especificar. Todos ellos se basan en ejecutar TRASH_OFF=YES y calcular qué ficheros son los que se deben borrar para que la papelera no exceda su tamaño máximo. Están pensados para ser planificados en el cron de cada usuario.

Hay una limitación que es importante tener presente, y es que la libtrash, cuando borramos un fichero, lo que hace es moverlo a la papelera (como si lo hiciéramos con un mv), de modo que si estamos borrando un fichero que está en un sistema de ficheros diferente al de la papelera, puede llevar mucho tiempo, en función del tamaño del fichero (igual que ocurriría con un mv). Por ello, además, tenemos que tener permisos para leer el fichero. Sin la libtrash podríamos tener permisos en el directorio para borrarlo aunque no los tuviéramos para leer el fichero, aunque es una situación raro, es conveniente tenerla presente.

Para finalizar, un listado de las variables de /etc/libtrash.conf y sus valores por defecto (en negrita las que ya hemos nombrado):

Variables que sólo se pueden cambiar al compilar la librería:

DEBUG = NO
PERSONAL_CONF_FILE = .libtrash
WARNING_STRING = Remember that libtrash is disabled.

Variables que se pueden configurar en cualquier momento:

INTERCEPT_UNLINK = YES
INTERCEPT_RENAME = YES
INTERCEPT_FOPEN = NO
INTERCEPT_FREOPEN = NO
INTERCEPT_OPEN = NO
TRASH_CAN = Trash
IN_CASE_OF_FAILURE = PROTECT
SHOULD_WARN = NO
PROTECT_TRASH = YES
IGNORE_EXTENSIONS = o;log;aux
IGNORE_HIDDEN = YES
IGNORE_EDITOR_BACKUP = YES
IGNORE_EDITOR_TEMPORARY = YES
LIBTRASH_CONFIG_FILE_UNREMOVABLE = YES
GLOBAL_PROTECTION = YES
TRASH_SYSTEM_ROOT = SYSTEM_ROOT
UNREMOVABLE_DIRS =
UNCOVER_DIRS =
TEMPORARY_DIRS = /tmp;/var
USER_TEMPORARY_DIRS =
REMOVABLE_MEDIA_MOUNT_POINTS = /mnt
EXCEPTIONS = /etc/mtab;/etc/resolv.conf;/etc/adjtime;/etc/upsstatus;/etc/dhcpc
IGNORE_RE =

:wq

Entradas relacionadas

9 Comentarios a “La libtrash, la papelera de la línea de comandos”

  • Osqui dice:

    Muy curioso, pero ¿no sería más fácil directamente hacer que la papelera fuera una carpeta normal y que el comando rm fuera un alias de move, para que al borrar lo que se hiciera realmente es mover los archivos a esa carpeta (oculta, si tú quieres, y comprimida)? Estoy diciendo una perogrullada, ya lo sé, pero si hay que estudiarse todo esto -que repito que es muy interesante a pesar de todo- para tener una simple papelera, pues vaya…

    Aprovecho para felicitar por la calidad de los artículos, y la frecuencia de su actualización. Cómo no, tengo sindicado este sitio. Muchas gracias.

  • @Osqui Sí, crear un alias de rm que realmente hiciera un mv sería fácil de hacer, pero esto va mucho más allá, porque funciona para cualquier programa que podamos ejecutar que pretenda borrar un fichero. Por ejemplo, si borramos un fichero en el Midnight Commander (mc) o en un administrador de ficheros gráfico, el fichero también irá a la papelera.

    Aunque yo he querido hacer un recorrido detallado de las capacidades del programa, lo básico es muy sencillo: Es sólo cargar la librería y elegir alguna forma para limpiar la papelera de vez en cuando.

    Me alegro de que te guste el blog y gracias por la visita.

  • Iván dice:

    Muy interesante el artículo. No sabía que existía esta papelera desde la línea de comandos. Yo creo que todos alguna vez hemos hecho un rm un poco sin pensar y luego nos hemos arrepentido. A ver si saco un rato y este finde lo pruebo con detalle!.

    Saludos, Iván.

  • @Iván Gracias. ¡Ya nos contarás si te gusta!

  • Muy útil. Sobre todo si, por algún motivo, el comando rm deja de aceptar la opción “-i” (preguntar antes de borrar), y además nos deja de funcionar el comando “mv”.

  • @Iñaki Silanes No sabría yo decir si eso ha querido ser una ironía (Burla fina y disimulada), un sarcasmo (Burla sangrienta, ironía mordaz y cruel con que se ofende o maltrata a alguien o algo) o iba en serio…

    En cualquier caso, estarás de acuerdo conmigo que la libtrash va mucho más alla del “rm -i” y de usar mv en vez de rm :-)

  • Era irónico, jejeje.

    No quiero menospreciar una herramienta que pueda resultar útil en determinadas circunstancias y a determinadas personas, pero en general no me gusta del todo el concepto de libtrash. Cuando borras algo, quieres que desaparezca, no que se “esconda”. Uno debe responsabilizarse de qué borra y qué no borra, y aprender que el borrar es un acto peligroso, que acarrea consecuencias.

    Puede pensarse que con “papeleras de reciclaje” se alivia esta “responsabilidad”, sin merma de eficacia en funcionamiento, pero no es cierto. En realidad la papelera de reciclaje solamente aplaza la decisión de borrado definitivo (hasta el momento de “vaciar papelera”) con la diferencia de que en el momento de tal borrado no prestaremos ni la milesima parte de atención al material borrado que cuando usamos “rm -rf” (eso sí que le aguza a uno los sentidos). O sea, que cambia un acto de borrado responsable y reflexionado por dos: uno más laxo (porque sabemos que tenemos la papelera de reciclaje “por si acaso”), y otro MUCHO más laxo (cuando vaciamos la papelera de toda la basura generada en cierto período de tiempo). O sea: ¡más molestia en el uso, y menos seguridad en el borrado!

    Como método de salvaguarda propongo el método “tradicional”: hacer copias de respaldo (backups). Si pasa algo “gordo”, tienes la copia, pero en general sigues teniendo el debido pánico a borrar algo, lo cual te ayuda a generar buenos hábitos, como la reflexión antes de actuar.

  • @Iñaki Silanes Gracias por tu comentario. Yo antes también rechazaba frontalmente las papeleras de ficheros. Pensaba lo mismo que tú, que si quieres borrar un fichero lo quieres borrar y ya está. Con el tiempo, y con la experiencia, he comenzado a ir apreciándolas y conforme las he ido usando me han salvado más de una vez de un apuro. Últimamente todo lo que no sea delicado, lo borro a la papelera y como últimamente tenemos tantos GB libres en los discos duros, ahí se queda y no me molesta.

    Cambios de actitud que vamos teniendo con el tiempo, supongo: Lo que antes detestaba tal y como tú describes, ahora lo aprecio…

Trackbacks y pingbacks:

Tema LHYLE09, creado por Vicente Navarro