Carpeta temporal que se destruyó automáticamente después de salir del proceso

Respuestas:

12

En el caso de un archivo temporal, su ejemplo en la pregunta lo crearía, luego lo desvincularía del directorio (haciéndolo "desaparecer"), y cuando el script cierra el descriptor de archivo (probablemente al finalizar), el espacio ocupado por el archivo sería reclamable por el sistema. Esta es una forma común de manejar archivos temporales en idiomas como C.

Por lo que sé, no es posible abrir un directorio de la misma manera, al menos de ninguna manera que haga que el directorio sea utilizable.

Una forma común de eliminar archivos y directorios temporales al finalizar un script es mediante la instalación de una EXITtrampa de limpieza . Los ejemplos de código que se proporcionan a continuación evitan tener que hacer malabarismos con los descriptores de archivos por completo.

tmpdir=$(mktemp -d)
tmpfile=$(mktemp)

trap 'rm -f "$tmpfile"; rm -rf "$tmpdir"' EXIT

# The rest of the script goes here.

O puede llamar a una función de limpieza:

cleanup () {
    rm -f "$tmpfile"
    rm -rf "$tmpdir"
}

tmpdir=$(mktemp -d)
tmpfile=$(mktemp)

trap cleanup EXIT

# The rest of the script goes here.

La EXITtrampa no se ejecutará al recibir la KILLseñal (que no puede ser atrapada), lo que significa que no se realizará ninguna limpieza en ese momento. Sin embargo, se ejecutará cuando finalice debido a una señal INTo TERM(si se ejecuta con basho ksh, en otros shells, es posible que desee agregar estas señales después EXITen la traplínea de comando), o cuando salga normalmente debido a que llega al final del script o ejecuta un exitllamada.

Kusalananda
fuente
55
No es solo el shell que no puede usar directorios temporales ya desvinculados, ni tampoco los programas C. El problema es que los directorios no vinculados no pueden tener archivos en ellos. Puede tener un directorio vacío no vinculado como su directorio de trabajo, pero cualquier intento de crear un archivo dará un error.
derobert
1
@derobert Y dicho directorio desvinculado ni siquiera tiene las entradas .y ... (Probado en Linux, no sé si eso es coherente en todas las plataformas)
Kasperd
1
Tenga en cuenta que la trampa EXIT tampoco se ejecuta si el script llama exec another-commandobviamente.
Stéphane Chazelas
1
Ver también: trampa de salida en dash vs ksh y bash
Stéphane Chazelas
6

Escriba una función de shell que se ejecutará cuando finalice su script. En el siguiente ejemplo, lo llamo 'limpieza' y configuro una trampa para que se ejecute en los niveles de salida, como: 0 1 2 3 6

trap cleanup 0 1 2 3 6

cleanup()
{
  [ -d $TMP ] && rm -rf $TMP
}

Vea esta publicación para más información.

Dirk Krijgsman
fuente
Esos no son "niveles de salida" sino números de señal, y la respuesta a la pregunta a la que se está vinculando explica exactamente eso. La trampa se ejecutará cleanupantes de una salida limpia (0) y al recibir SIGHUP (1), SIGINT (2), SIGQUIT (3) y SIGABRT (6). será no correr cleanupcuando las salidas de guión debido a SIGTERM, SIGSEGV, SIGKILL, SIGPIPE, etc. Esto es claramente deficiente.
mosvy
6

Puede introducirlo y luego eliminarlo, siempre que no intente utilizar rutas dentro de él después:

#! /bin/sh
dir=`mktemp -d`
cd "$dir"
exec 4>file 3<file
rm -fr "$dir"

echo yes >&4    # OK
cat <&3         # OK

cat file        # FAIL
echo yes > file # FAIL

No lo he comprobado, pero lo más probable es que sea el mismo problema cuando utilizo openat (2) en C con un directorio que ya no existe en el sistema de archivos.

Si eres root y tienes Linux, puedes jugar con un espacio de nombres separado y mount -t tmpfs tmpfs /dirdentro de él.

Las respuestas canónicas (establecer una trampa en EXIT) no funcionan si su script se ve forzado a salir impuro (por ejemplo, con SIGKILL); eso puede dejar datos confidenciales dando vueltas.

Actualizar:

Aquí hay una pequeña utilidad que implementa el enfoque de espacio de nombres. Debería compilarse con

cc -Wall -Os -s chtmp.c -o chtmp

y CAP_SYS_ADMINcapacidades de archivo dadas (como root) con

setcap CAP_SYS_ADMIN+ep chtmp

Cuando se ejecuta (como un usuario normal) como

./chtmp command args ...

dejará de compartir su espacio de nombres del sistema de archivos, montará un sistema de archivos tmpfs /proc/sysvipc, chdir y ejecutará commandcon los argumentos dados. commandserá no heredar las CAP_SYS_ADMINcapacidades.

Ese sistema de archivos no será accesible desde otro proceso que no se inicie command, y desaparecerá mágicamente (con todos los archivos que se crearon dentro de él) cuando commandy sus hijos mueran, sin importar cómo suceda eso. Tenga en cuenta que esto es solo compartir el espacio de nombres de montaje: no hay barreras duras entre commandy otros procesos ejecutados por el mismo usuario; que todavía podría colarse dentro de su espacio de nombres ya sea a través de ptrace(2), /proc/PID/cwdo por otros medios.

El secuestro de lo "inútil" /proc/sysvipces, por supuesto, una tontería, pero la alternativa hubiera sido el spam /tmpcon directorios vacíos que tendrían que ser eliminados o complicarían enormemente este pequeño programa con tenedores y esperas. Alternativamente, dirse puede cambiar por ej. /mnt/chtmpy que sea creado por root en la instalación; no lo haga configurable por el usuario y no lo configure en una ruta de propiedad del usuario, ya que eso puede exponerlo a trampas de enlaces simbólicos y otras cosas peludas que no vale la pena perder el tiempo.

chtmp.c

#define _GNU_SOURCE
#include <err.h>
#include <sched.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mount.h>
int main(int argc, char **argv){
        char *dir = "/proc/sysvipc";    /* LOL */
        if(argc < 2 || !argv[1]) errx(1, "usage: %s prog args ...", *argv);
        argv++;
        if(unshare(CLONE_NEWNS)) err(1, "unshare(CLONE_NEWNS)");
        /* "modern" systemd remounts all mount points MS_SHARED
           see the NOTES in mount_namespaces(7); YUCK */
        if(mount("none", "/", 0, MS_REC|MS_PRIVATE, 0))
                err(1, "mount(/, MS_REC|MS_PRIVATE)");
        if(mount("tmpfs", dir, "tmpfs", 0, 0)) err(1, "mount(tmpfs, %s)", dir);
        if(chdir(dir)) err(1, "chdir %s", dir);
        execvp(*argv, argv);
        err(1, "execvp %s", *argv);
}
qubert
fuente
1
Incluso si no es root, puede hacer esto con espacios de nombres creando un nuevo espacio de nombres de usuario y haciendo que el tmpfs se monte dentro de él. El acceso de contrabando al nuevo directorio al mundo exterior es un poco complicado, pero debería ser posible.
R .. GitHub DEJA DE AYUDAR AL HIELO
Eso todavía requiere CAP_SYS_ADMIN. Tengo la idea de una pequeña utilidad habilitada para setcap que hará eso, actualizaré la respuesta con ella.
Qubert
1
A menos que el núcleo se haya bloqueado para no permitirlo, la creación de espacios de nombres de usuario no es una operación privilegiada. El diseño subyacente es tal que se supone que es seguro permitir a los usuarios comunes prescindir de cualquier capacidad especial. Sin embargo, hay suficiente superficie de ataque / riesgo que muchas distribuciones lo deshabilitan, creo.
R .. GitHub DEJA DE AYUDAR A ICE
Lo intenté en la terminal. En algún directorio temporal, rm $PWDtrabajo, shell todavía está en ese directorio. Pero no se pueden poner archivos nuevos en esta "carpeta". Lo único que puede hacer es leer / escribir con el archivo & 3, y 4. Así que esto sigue siendo "archivo temporal", no "carpeta temporal".
Bob Johnson el
@BobJohnson Eso no es diferente de lo que ya estaba diciendo en mi respuesta ;-)
qubert
0

¿Necesitas un shell específico?

Si zsh es una opción, lea zshexpn(1):

Si se usa = (...) en lugar de <(...), el archivo pasado como argumento será el nombre de un archivo temporal que contiene el resultado del proceso de la lista. Esto se puede usar en lugar del formulario <para un programa que espera lseek(ver lseek(2)) en el archivo de entrada.

[...]

Otro problema surge cada vez que el shell rechaza un trabajo con una sustitución que requiere un archivo temporal, incluido el caso en el que aparece &!o &|al final de un comando que contiene una sustitución. En ese caso, el archivo temporal no se limpiará ya que el shell ya no tiene memoria del trabajo. Una solución alternativa es usar una subshell, por ejemplo,

(mycmd =(myoutput)) &!

ya que el subshell bifurcado esperará a que termine el comando y luego eliminará el archivo temporal.

Una solución general para garantizar que la sustitución de un proceso perdure durante un período de tiempo adecuado es pasarla como un parámetro a una función de shell anónima (un fragmento de código de shell que se ejecuta inmediatamente con el alcance de la función). Por ejemplo, este código:

() {
   print File $1:
   cat $1
} =(print This be the verse)

produce algo parecido a lo siguiente

File /tmp/zsh6nU0kS:
This be the verse

Por ejemplo, uso esto en rifle (parte del administrador de archivos del guardabosques) para descifrar un archivo y luego ejecutar rifle en el archivo temporal, que se elimina cuando finaliza el subproceso. (no te olvides de configurar $TERMCMD)

# ~/.config/ranger/rifle.conf
...
!ext exe, mime octet-stream$, has gpg, flag t = () { rifle -f F "$1" } =(gpg -dq "$1")
Bart
fuente