Dossier temporaire détruit automatiquement après la sortie du processus

10

Pouvons-nous utiliser des dossiers temporaires comme des fichiers temporaires

TMP=$(mktemp ... )
exec 3<>$TMP
rm $TMP

cat <&3

qui sera détruit automatiquement après cette sortie du shell?

Bob Johnson
la source
EN RELATION: Piège de sortie dans Dash vs Ksh et Bash
Stéphane Chazelas

Réponses:

12

Dans le cas d'un fichier temporaire, votre exemple dans la question le créerait, puis le dissocierait du répertoire (le faisant "disparaître"), et lorsque le script ferme le descripteur de fichier (probablement à la fin), l'espace occupé par le fichier serait récupérable par le système. Il s'agit d'une façon courante de gérer les fichiers temporaires dans des langues comme C.

Pour autant que je sache, il n'est pas possible d'ouvrir un répertoire de la même manière, du moins en aucune manière qui rendrait le répertoire utilisable.

Une façon courante de supprimer des fichiers et des répertoires temporaires à la fin d'un script est d'installer un EXITpiège de nettoyage . Les exemples de code donnés ci-dessous évitent d'avoir à jongler complètement avec les descripteurs de fichiers.

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

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

# The rest of the script goes here.

Ou vous pouvez appeler une fonction de nettoyage:

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

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

trap cleanup EXIT

# The rest of the script goes here.

L' EXITinterruption ne sera pas exécutée lors de la réception du KILLsignal (qui ne peut pas être intercepté), ce qui signifie qu'aucun nettoyage ne sera alors effectué. Il s'exécutera cependant à la fin en raison d'un INTou d' un TERMsignal (s'il fonctionne avecbash ou ksh, dans d'autres shells, vous voudrez peut-être ajouter ces signaux après EXITdans la trapligne de commande), ou lorsque vous quittez normalement en raison de l'arrivée à la fin du script ou de l'exécution d'un exitappel.

Kusalananda
la source
5
Ce n'est pas seulement un shell qui ne peut pas utiliser des répertoires temporaires déjà non liés - ni les programmes C. Le problème est que les répertoires non liés ne peuvent pas contenir de fichiers. Vous pouvez avoir un répertoire vide non lié comme répertoire de travail, mais toute tentative de création d'un fichier donnera une erreur.
derobert
1
@derobert Et un tel répertoire non lié n'a même pas les entrées .et ... (Testé sur Linux, je ne sais pas si c'est cohérent sur toutes les plateformes.)
kasperd
1
Notez que le trap EXIT n'est pas exécuté non plus si le script appelle exec another-commandévidemment.
Stéphane Chazelas
1
Voir aussi: exit trap in dash vs ksh and bash
Stéphane Chazelas
6

Écrivez une fonction shell qui sera exécutée lorsque votre script sera terminé. Dans l'exemple ci-dessous, je l'appelle «nettoyage» et je définis un piège à exécuter sur les niveaux de sortie, comme: 0 1 2 3 6

trap cleanup 0 1 2 3 6

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

Voir cet article pour plus d'informations.

Dirk Krijgsman
la source
Ce ne sont pas des "niveaux de sortie" mais des numéros de signaux, et la réponse à la question à laquelle vous liez explique cela. Le piège s'exécutera cleanupavant une sortie propre (0) et à la réception de SIGHUP (1), SIGINT (2), SIGQUIT (3) et SIGABRT (6). il ne s'exécutera pascleanup lorsque le script se fermera à cause de SIGTERM, SIGSEGV, SIGKILL, SIGPIPE, etc. Ceci est clairement déficient.
mosvy
6

Vous pouvez y chdir puis le supprimer, à condition de ne pas essayer d'utiliser des chemins à l'intérieur par la suite:

#! /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

Je n'ai pas vérifié, mais c'est probablement le même problème lorsque vous utilisez openat (2) en C avec un répertoire qui n'existe plus dans le système de fichiers.

Si vous êtes root et sous Linux, vous pouvez jouer avec un espace de noms séparé, et mount -t tmpfs tmpfs /dir intérieur.

Les réponses canoniques (définir un piège sur EXIT) ne fonctionnent pas si votre script est forcé dans une sortie impure (par exemple avec SIGKILL); qui peut laisser traîner des données sensibles.

Mettre à jour:

Voici un petit utilitaire qui implémente l'approche de l'espace de noms. Il doit être compilé avec

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

et CAP_SYS_ADMINdes capacités de fichier données (en tant que root) avec

setcap CAP_SYS_ADMIN+ep chtmp

Lors de l'exécution (en tant qu'utilisateur normal) en tant qu'utilisateur

./chtmp command args ...

il partagera son espace de noms de système de fichiers, montera un système de fichiers tmpfs /proc/sysvipc, y chdir et s'exécutera commandavec les arguments donnés. commandne pas hériter des CAP_SYS_ADMINcapacités.

Ce système de fichiers ne sera pas accessible à partir d'un autre processus non démarré command, et il disparaîtra comme par magie (avec tous les fichiers qui ont été créés à l'intérieur) quand commandet ses enfants meurent, peu importe comment cela se produit. Notez que cela ne fait que partager l'espace de noms de montage - il n'y a pas de barrière dure entre commandet d'autres processus exécutés par le même utilisateur; ils pourraient toujours se faufiler à l'intérieur de son espace de noms via ptrace(2),/proc/PID/cwd ou par d' autres moyens.

Le détournement de "l'inutile" /proc/sysvipcest, bien sûr, idiot, mais l'alternative aurait été de spam /tmpavec des répertoires vides qui devraient être supprimés ou compliquer grandement ce petit programme avec des fourchettes et des attentes. Alternativement, dirpeut être changé par exemple. /mnt/chtmpet le faire créer par root lors de l'installation; ne le rendez pas configurable par l'utilisateur et ne le définissez pas sur un chemin appartenant à l'utilisateur, car cela pourrait vous exposer à des pièges de lien symbolique et à d'autres trucs poilus qui ne valent pas la peine d'être consacrés.

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
la source
1
Même si vous n'êtes pas root, vous pouvez le faire avec des espaces de noms en créant un nouvel espace de noms d'utilisateurs et en effectuant le montage tmpfs à l'intérieur. La contrebande d'accès au nouveau répertoire vers le monde extérieur est un peu délicate mais devrait être possible.
R .. GitHub STOP HELPING ICE
Cela nécessite toujours CAP_SYS_ADMIN. J'ai l'idée d'un petit utilitaire compatible setcap qui le fera, je mettrai à jour la réponse avec.
qubert
1
Sauf si le noyau a été verrouillé pour le refuser, la création d'espaces de noms d'utilisateurs n'est pas une opération privilégiée. La conception sous-jacente est telle qu'elle est censée être sûre pour permettre aux utilisateurs ordinaires de se passer de toute capacité spéciale. Cependant, il y a suffisamment de surface d'attaque / risque que de nombreuses distributions le désactivent, je pense.
R .. GitHub STOP HELPING ICE
J'ai essayé dans le terminal. Dans un répertoire temporaire, rm $PWDwork, shell est toujours dans ce répertoire. Mais aucun nouveau fichier ne peut être placé dans ce "dossier". Vous ne pouvez que lire / écrire avec le fichier & 3, & 4. Il s'agit donc toujours d'un "fichier temporaire" et non d'un "dossier temporaire".
Bob Johnson
@BobJohnson Ce n'est pas différent de ce que je disais déjà dans ma réponse ;-)
qubert
0

Avez-vous besoin d'un shell spécifique?

Si zsh est une option, veuillez lire zshexpn(1):

Si = (...) est utilisé à la place de <(...), le fichier passé en argument sera le nom d'un fichier temporaire contenant la sortie du processus de liste. Ceci peut être utilisé à la place du formulaire <pour un programme qui s'attend à lseek(voir lseek(2)) sur le fichier d'entrée.

[...]

Un autre problème survient chaque fois qu'un travail avec une substitution qui nécessite un fichier temporaire est désavoué par le shell, y compris le cas où &!ou &|apparaît à la fin d'une commande contenant une substitution. Dans ce cas, le fichier temporaire ne sera pas nettoyé car le shell n'a plus de mémoire du travail. Une solution de contournement consiste à utiliser un sous-shell, par exemple,

(mycmd =(myoutput)) &!

car le sous-shell forké attendra la fin de la commande, puis supprimez le fichier temporaire.

Une solution de contournement générale pour garantir qu'une substitution de processus dure pendant une durée appropriée consiste à la transmettre en tant que paramètre à une fonction shell anonyme (un morceau de code shell exécuté immédiatement avec l'étendue de la fonction). Par exemple, ce code:

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

produit quelque chose qui ressemble à ce qui suit

File /tmp/zsh6nU0kS:
This be the verse

Par exemple, j'utilise ceci dans rifle (qui fait partie du gestionnaire de fichiers ranger) pour décrypter un fichier, puis exécuter rifle sur le fichier temporaire, qui est supprimé lorsque les sous-processus se terminent. (n'oubliez pas de régler $TERMCMD)

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