Folder tymczasowy, który jest automatycznie niszczony po zakończeniu procesu

Odpowiedzi:

12

W przypadku pliku tymczasowego twój przykład w pytaniu utworzy go, a następnie odłączy go od katalogu (sprawiając, że „zniknie”), a gdy skrypt zamknie deskryptor plików (prawdopodobnie po zakończeniu), miejsce zajmowane przez plik byłby możliwy do odzyskania przez system. Jest to powszechny sposób postępowania z plikami tymczasowymi w językach takich jak C.

O ile mi wiadomo, nie można otworzyć katalogu w ten sam sposób, a przynajmniej nie w żaden sposób, który uczyniłby katalog użytecznym.

Częstym sposobem usuwania plików tymczasowych i katalogów po zakończeniu skryptu jest zainstalowanie EXITpułapki czyszczenia . Poniższe przykłady kodu pozwalają uniknąć konieczności żonglowania deskryptorami plików.

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

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

# The rest of the script goes here.

Lub możesz wywołać funkcję czyszczenia:

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

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

trap cleanup EXIT

# The rest of the script goes here.

EXITPułapka nie zostanie wykonana po otrzymaniu KILLsygnału (który nie może być uwięziony), co oznacza, że nie będzie żadnego czyszczenia wykonywane wtedy. Będzie on jednak wykonywany po zakończeniu z powodu sygnału INTlub TERM(jeśli działa z bashlub kshw innych powłokach, możesz chcieć dodać te sygnały po EXITw trapwierszu poleceń), lub gdy kończy się normalnie z powodu dojścia do końca skryptu lub wykonania exitpołączenie.

Kusalananda
źródło
5
To nie tylko powłoka, która nie może używać już niepowiązanych katalogów tymczasowych - podobnie jak programy w C. Problem polega na tym, że niepowiązane katalogi nie mogą zawierać plików. Jako katalog roboczy możesz mieć niepowiązany pusty katalog, ale każda próba utworzenia pliku spowoduje błąd.
derobert,
1
@derobert I taki niepowiązany katalog nie ma nawet wpisów .i ... (Testowany w systemie Linux, nie wiem, czy jest spójny na różnych platformach).
kasperd
1
Zauważ, że pułapka EXIT nie jest wykonywana, jeśli skrypt wywołuje exec another-commandoczywiście.
Stéphane Chazelas
1
Zobacz także: wyjście z pułapki w desce rozdzielczej vs ksh i bash
Stéphane Chazelas,
6

Napisz funkcję powłoki, która zostanie wykonana po zakończeniu skryptu. W poniższym przykładzie nazywam to „porządkiem” i ustawiam pułapkę do wykonania na poziomach wyjścia, np .: 0 1 2 3 6

trap cleanup 0 1 2 3 6

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

Zobacz ten post, aby uzyskać więcej informacji.

Dirk Krijgsman
źródło
To nie są „poziomy wyjścia”, ale liczby sygnałów, a odpowiedź na pytanie, do którego się łączysz, właśnie to wyjaśnia. Pułapka będzie działać cleanupprzed czystym wyjściem (0) i po otrzymaniu SIGHUP (1), SIGINT (2), SIGQUIT (3) i SIGABRT (6). będzie nie działać cleanup, gdy wychodzi script powodu SIGTERM, SIGSEGV, SIGKILL, SIGPIPE itp Jest to wyraźnie niewystarczające.
mosvy
6

Możesz do niego przejść, a następnie go usunąć, pod warunkiem, że później nie będziesz próbował używać ścieżek:

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

Nie sprawdziłem, ale najprawdopodobniej ten sam problem występuje podczas używania openat (2) w C z katalogiem, który już nie istnieje w systemie plików.

Jeśli jesteś rootem i na Linuksie, możesz grać z oddzielną przestrzenią nazw i mount -t tmpfs tmpfs /dirwewnątrz niej.

Odpowiedzi kanoniczne (ustaw pułapkę na EXIT) nie działają, jeśli twój skrypt jest zmuszony do nieczystego wyjścia (np. Za pomocą SIGKILL); które mogą pozostawić wrażliwe dane.

Aktualizacja:

Oto małe narzędzie, które implementuje podejście do przestrzeni nazw. Należy go skompilować

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

i dane CAP_SYS_ADMINmożliwości pliku (jako root) za pomocą

setcap CAP_SYS_ADMIN+ep chtmp

Po uruchomieniu (jako normalny) użytkownik jako

./chtmp command args ...

usunie udostępnienie przestrzeni nazw systemu plików, zamontuje system plików tmpfs /proc/sysvipc, chdir i uruchomi commandpodane argumenty. commandbędzie nie dziedziczą CAP_SYS_ADMINmożliwości.

Ten system plików nie będzie dostępny z innego procesu, od którego nie został uruchomiony command, i magicznie zniknie (wraz ze wszystkimi plikami, które zostały w nim utworzone), kiedy commandi jego dzieci umrą, bez względu na to, jak to się stanie. Zauważ, że jest to po prostu udostępnianie przestrzeni nazw montowania - nie ma twardych barier między commandinnymi procesami uruchamianymi przez tego samego użytkownika; wciąż mogli się zakraść w przestrzeni nazw albo przez ptrace(2), /proc/PID/cwdalbo w inny sposób.

Porwanie „bezużytecznego” /proc/sysvipcjest oczywiście głupie, ale alternatywą byłoby spamowanie /tmppustymi katalogami, które należałoby usunąć lub znacznie komplikować ten mały program widelcami i czekaniem. Alternatywnie dirmożna zmienić na np. /mnt/chtmpi niech utworzy go root podczas instalacji; nie konfiguruj go i nie ustawiaj na ścieżkę należącą do użytkownika, ponieważ może to narazić Cię na pułapki dowiązań symbolicznych i inne włochate rzeczy, na których nie warto spędzać czasu.

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
źródło
1
Nawet jeśli nie jesteś rootem, możesz to zrobić za pomocą przestrzeni nazw, tworząc nową przestrzeń nazw użytkownika i wykonując w niej montowanie tmpfs. Przemycenie dostępu do nowego katalogu na zewnątrz jest trochę trudne, ale powinno być możliwe.
R .. GitHub PRZESTAŃ POMÓC LODOWI
To wciąż wymaga CAP_SYS_ADMIN. Mam pomysł małego narzędzia obsługującego setcap, które to zrobi, zaktualizuję odpowiedź.
qubert
1
O ile jądro nie zostało zablokowane, aby go zabronić, tworzenie przestrzeni nazw użytkowników nie jest operacją uprzywilejowaną. Podstawowy projekt jest taki, że powinien być bezpieczny, aby zwykli użytkownicy mogli się obejść bez żadnych specjalnych możliwości. Sądzę jednak, że istnieje wystarczająca powierzchnia ataku / ryzyko, że wiele dystrybucji ją dezaktywuje.
R .. GitHub PRZESTAŃ POMÓC LODOWI
Próbowałem w terminalu. W jakimś tymczasowym katalogu, rm $PWDpraca, skorupa wciąż jest w tym katalogu. Ale w tym „folderze” nie można umieszczać nowych plików. Jedyne, co możesz zrobić, to odczyt / zapis za pomocą plików i 3 oraz 4. Jest to więc nadal „plik tymczasowy”, a nie „folder tymczasowy”.
Bob Johnson
@BobJohnson Nie różni się to od tego, co już powiedziałem w mojej odpowiedzi ;-)
qubert
0

Czy potrzebujesz konkretnej powłoki?

Jeśli zsh jest opcją, przeczytaj zshexpn(1):

Jeśli zamiast <(...) zostanie użyta wartość = (...), plik przekazany jako argument będzie nazwą pliku tymczasowego zawierającego dane wyjściowe procesu listy. Można tego użyć zamiast formularza <dla programu, który oczekuje lseek(patrz lseek(2)) w pliku wejściowym.

[...]

Kolejny problem pojawia się za każdym razem, gdy powłoka odrzuca zadanie z podstawieniem, które wymaga pliku tymczasowego, w tym przypadek, w którym &!lub &|pojawia się na końcu polecenia zawierającego podstawienie. W takim przypadku plik tymczasowy nie zostanie wyczyszczony, ponieważ powłoka nie ma już pamięci zadania. Obejściem tego problemu jest użycie podpowłoki, na przykład

(mycmd =(myoutput)) &!

ponieważ rozwidlona podpowłoka będzie czekać na zakończenie polecenia, a następnie usunie plik tymczasowy.

Ogólnym obejściem zapewniającym, że podstawienie procesu trwa przez odpowiedni czas, jest przekazanie go jako parametru do anonimowej funkcji powłoki (fragment kodu powłoki uruchamiany natychmiast z zakresem funkcji). Na przykład ten kod:

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

wyprowadza coś podobnego do następującego

File /tmp/zsh6nU0kS:
This be the verse

Na przykład używam tego w karabinie (część menedżera plików ranger), aby odszyfrować plik, a następnie uruchomić karabin na pliku tymczasowym, który zostanie usunięty po zakończeniu podprocesów. (nie zapomnij ustawić $TERMCMD)

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