Temporärer Ordner, der nach dem Beenden des Prozesses automatisch zerstört wird

10

Können wir temporäre Ordner wie temporäre Dateien verwenden?

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

cat <&3

Was wird nach diesem Shell-Exit automatisch zerstört?

Bob Johnson
quelle
Verwandte: Ausgangsfalle in Dash gegen Ksh und Bash
Stéphane Chazelas

Antworten:

12

Im Fall einer temporären Datei würde Ihr Beispiel in der Frage sie erstellen und dann die Verknüpfung zum Verzeichnis aufheben (wodurch sie "verschwindet"). Wenn das Skript den Dateiskriptor schließt (wahrscheinlich nach Beendigung), wird der von der Datei belegte Speicherplatz belegt würde vom System zurückgefordert werden. Dies ist eine übliche Methode, um mit temporären Dateien in Sprachen wie C umzugehen.

Soweit ich weiß, ist es nicht möglich, ein Verzeichnis auf die gleiche Weise zu öffnen, zumindest nicht auf eine Weise, die das Verzeichnis nutzbar machen würde.

Eine übliche Methode zum Löschen temporärer Dateien und Verzeichnisse beim Beenden eines Skripts ist die Installation einer Bereinigungsfalle EXIT. Die unten angegebenen Codebeispiele vermeiden, dass Dateideskriptoren vollständig jongliert werden müssen.

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

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

# The rest of the script goes here.

Oder Sie können eine Bereinigungsfunktion aufrufen:

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

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

trap cleanup EXIT

# The rest of the script goes here.

Der EXITTrap wird beim Empfang des KILLSignals (das nicht abgefangen werden kann) nicht ausgeführt , was bedeutet, dass dann keine Bereinigung durchgeführt wird. Es wird jedoch ausgeführt, wenn es aufgrund eines INToder TERM-Signals beendet wird (wenn es mit bashoder ausgeführt wird ksh, in anderen Shells möchten Sie diese Signale möglicherweise nach EXITder trapBefehlszeile hinzufügen ) oder wenn es normal beendet wird, weil es am Ende des Skripts ankommt oder ein ausführt exitAnruf.

Kusalananda
quelle
5
Es ist nicht nur eine Shell, die keine nicht verknüpften temporären Verzeichnisse verwenden kann - auch keine C-Programme. Das Problem ist, dass nicht verknüpfte Verzeichnisse keine Dateien enthalten können. Sie können ein nicht verknüpftes leeres Verzeichnis als Arbeitsverzeichnis haben, aber jeder Versuch, eine Datei zu erstellen, führt zu einem Fehler.
Derobert
1
@derobert Und solch ein nicht verknüpftes Verzeichnis hat nicht einmal die .und ..Einträge. (Getestet unter Linux, ich weiß nicht, ob das plattformübergreifend konsistent ist.)
Kasperd
1
Beachten Sie, dass der EXIT-Trap auch nicht ausgeführt wird, wenn das Skript exec another-commandoffensichtlich aufruft .
Stéphane Chazelas
1
Siehe auch: Ausgangsfalle in Dash gegen Ksh und Bash
Stéphane Chazelas
6

Schreiben Sie eine Shell-Funktion, die ausgeführt wird, wenn Ihr Skript fertig ist. Im folgenden Beispiel nenne ich es "Aufräumen" und setze einen Trap, der auf Exit-Ebenen ausgeführt werden soll, wie: 0 1 2 3 6

trap cleanup 0 1 2 3 6

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

Weitere Informationen finden Sie in diesem Beitrag.

Dirk Krijgsman
quelle
Dies sind keine "Ausgangspegel", sondern Signalnummern, und die Antwort auf die Frage, auf die Sie verlinken, erklärt genau das. Die Falle läuft cleanupvor einem sauberen Ausgang (0) und beim Empfang von SIGHUP (1), SIGINT (2), SIGQUIT (3) und SIGABRT (6). Es wird nicht ausgeführt, cleanupwenn das Skript aufgrund von SIGTERM, SIGSEGV, SIGKILL, SIGPIPE usw. beendet wird. Dies ist eindeutig mangelhaft.
Mosvy
6

Sie können chdir hinein und dann entfernen, vorausgesetzt, Sie versuchen danach nicht, Pfade darin zu verwenden:

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

Ich habe nicht überprüft, aber es ist höchstwahrscheinlich das gleiche Problem, wenn openat (2) in C mit einem Verzeichnis verwendet wird, das nicht mehr im Dateisystem vorhanden ist.

Wenn Sie root und unter Linux sind, können Sie mit einem separaten Namespace und mount -t tmpfs tmpfs /dirdarin spielen.

Die kanonischen Antworten (setzen Sie eine Falle auf EXIT) funktionieren nicht, wenn Ihr Skript in einen unreinen Exit gezwungen wird (z. B. mit SIGKILL); Dadurch können vertrauliche Daten hängen bleiben.

Aktualisieren:

Hier ist ein kleines Dienstprogramm, das den Namespace-Ansatz implementiert. Es sollte mit kompiliert werden

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

und gegebene CAP_SYS_ADMINDateifunktionen (als root) mit

setcap CAP_SYS_ADMIN+ep chtmp

Beim Ausführen (als normaler) Benutzer als

./chtmp command args ...

Der Dateisystem-Namespace wird freigegeben, ein tmpfs-Dateisystem wird gemountet /proc/sysvipc, chdir wird eingefügt und commandmit den angegebenen Argumenten ausgeführt. commandwird die Fähigkeiten nicht erben CAP_SYS_ADMIN.

Auf dieses Dateisystem kann nicht über einen anderen Prozess zugegriffen werden, der nicht gestartet wurde command, und es verschwindet auf magische Weise (mit allen darin erstellten Dateien), wenn commandund seine Kinder sterben, egal wie das passiert. Beachten Sie, dass dies nur die Freigabe des Mount-Namespace aufhebt. Es gibt keine harten Barrieren zwischen commandund anderen Prozessen, die vom selben Benutzer ausgeführt werden. sie konnten nach wie vor in seinem Namensraum schleichen entweder über ptrace(2), /proc/PID/cwdoder mit anderen Mitteln.

Die Entführung der "Nutzlosen" /proc/sysvipcist natürlich albern, aber die Alternative wäre gewesen, /tmpmit leeren Verzeichnissen zu spammen , die entfernt werden müssten, oder dieses kleine Programm mit Gabeln und Wartezeiten erheblich zu komplizieren. Alternativ dirkann z. /mnt/chtmpund lassen Sie es bei der Installation von root erstellen; Machen Sie es nicht vom Benutzer konfigurierbar und setzen Sie es nicht auf einen benutzereigenen Pfad, da Sie dadurch möglicherweise Symlink-Fallen und anderen haarigen Dingen ausgesetzt werden, für die es sich nicht lohnt, Zeit zu investieren.

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
quelle
1
Auch wenn Sie kein Root sind, können Sie dies mit Namespaces tun, indem Sie einen neuen Benutzernamensraum erstellen und den darin enthaltenen tmpfs-Mount durchführen. Der Zugang zum neuen Verzeichnis nach außen zu schmuggeln ist etwas schwierig, sollte aber möglich sein.
R .. GitHub STOP HELPING ICE
Das erfordert noch CAP_SYS_ADMIN. Ich habe die Idee eines kleinen Setcap-fähigen Dienstprogramms, das dies erledigt. Ich werde die Antwort damit aktualisieren.
qubert
1
Die Erstellung von Benutzernamensräumen ist keine privilegierte Operation, es sei denn, der Kernel wurde gesperrt, um ihn nicht zuzulassen. Das zugrunde liegende Design ist so konzipiert, dass es sicher sein soll, dass normale Benutzer auf besondere Funktionen verzichten können. Es gibt jedoch genügend Angriffsfläche / Risiko, dass viele Distributionen es deaktivieren, denke ich.
R .. GitHub STOP HELPING ICE
Ich habe es im Terminal versucht. In einem temporären Verzeichnis, rm $PWDArbeit, befindet sich die Shell immer noch in diesem Verzeichnis. Es können jedoch keine neuen Dateien in diesen "Ordner" gelegt werden. Sie können nur mit den Dateien & 3, & 4 lesen / schreiben. Dies ist also immer noch "temporäre Datei", nicht "temporärer Ordner".
Bob Johnson
@ BobJohnson Das ist nicht anders als das, was ich bereits in meiner Antwort gesagt habe
;-)
0

Benötigen Sie eine bestimmte Shell?

Wenn zsh eine Option ist, lesen Sie bitte zshexpn(1):

Wenn = (...) anstelle von <(...) verwendet wird, ist die als Argument übergebene Datei der Name einer temporären Datei, die die Ausgabe des Listenprozesses enthält. Dies kann anstelle des Formulars <für ein Programm verwendet werden, das erwartet lseek(siehe lseek(2)) in der Eingabedatei.

[...]

Ein weiteres Problem tritt immer dann auf, wenn ein Job mit einer Ersetzung, für die eine temporäre Datei erforderlich ist, von der Shell abgelehnt wird, einschließlich des Falls, in dem &!oder &|am Ende eines Befehls, der eine Ersetzung enthält, angezeigt wird. In diesem Fall wird die temporäre Datei nicht bereinigt, da die Shell keinen Speicher mehr für den Job hat. Eine Problemumgehung besteht darin, eine Unterschale zu verwenden, z.

(mycmd =(myoutput)) &!

Da die gegabelte Subshell auf den Abschluss des Befehls wartet, wird die temporäre Datei entfernt.

Eine allgemeine Problemumgehung, um sicherzustellen, dass eine Prozessersetzung über einen angemessenen Zeitraum andauert, besteht darin, sie als Parameter an eine anonyme Shell-Funktion zu übergeben (ein Teil des Shell-Codes, der sofort mit Funktionsumfang ausgeführt wird). Zum Beispiel dieser Code:

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

gibt etwas aus, das dem Folgenden ähnelt

File /tmp/zsh6nU0kS:
This be the verse

Zum Beispiel verwende ich dies in Rifle (Teil des Ranger-Dateimanagers), um eine Datei zu entschlüsseln und dann das Gewehr für die temporäre Datei auszuführen, die gelöscht wird, wenn die Unterprozesse beendet werden. (nicht vergessen zu setzen $TERMCMD)

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