Folder sementara yang secara otomatis dihancurkan setelah proses keluar

10

Bisakah kita menggunakan folder sementara seperti file sementara

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

cat <&3

yang akan dihancurkan secara otomatis setelah shell keluar ini?

Bob Johnson
sumber
Terkait: keluar perangkap di dasbor vs ksh dan bash
Stéphane Chazelas

Jawaban:

12

Dalam kasus file sementara, contoh Anda dalam pertanyaan akan membuatnya, lalu memutuskan tautannya dari direktori (menjadikannya "menghilang"), dan ketika skrip menutup arsip yang diajukan (mungkin setelah penghentian), ruang yang diambil oleh file tersebut akan dapat direklamasi oleh sistem. Ini adalah cara umum untuk menangani file sementara dalam bahasa seperti C.

Sejauh yang saya tahu, tidak mungkin untuk membuka direktori dengan cara yang sama, setidaknya tidak dengan cara apa pun yang membuat direktori tersebut dapat digunakan.

Cara umum untuk menghapus file dan direktori sementara pada saat penghentian skrip adalah dengan menginstal EXITperangkap pembersihan . Contoh kode yang diberikan di bawah ini menghindari keharusan menyulap para pembuat arsip sepenuhnya.

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

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

# The rest of the script goes here.

Atau Anda dapat memanggil fungsi pembersihan:

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

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

trap cleanup EXIT

# The rest of the script goes here.

The EXITperangkap tidak akan dieksekusi setelah menerima KILLsinyal (yang tidak dapat terperangkap), yang berarti bahwa tidak akan ada pembersihan dilakukan kemudian. Namun itu akan dieksekusi ketika mengakhiri karena INTatau TERMsinyal (jika berjalan dengan bashatau ksh, dalam shell lain Anda mungkin ingin menambahkan sinyal ini setelah EXITdi trapbaris perintah), atau ketika keluar secara normal karena sampai di akhir skrip atau menjalankan exitpanggilan.

Kusalananda
sumber
5
Bukan hanya shell yang tidak dapat menggunakan direktori sementara yang sudah tidak terhubung - program C juga tidak bisa. Masalahnya adalah direktori yang tidak ditautkan tidak dapat memiliki file di dalamnya. Anda dapat memiliki direktori kosong yang tidak ditautkan sebagai direktori kerja Anda, tetapi setiap upaya untuk membuat file akan memberikan kesalahan.
derobert
1
@derobert Dan direktori tanpa tautan seperti itu bahkan tidak memiliki .dan ..entri. (Diuji di Linux, saya tidak tahu apakah itu konsisten di semua platform.)
kasperd
1
Perhatikan bahwa jebakan EXIT tidak dijalankan jika skrip memanggil dengan exec another-commandjelas.
Stéphane Chazelas
1
Lihat juga: exit trap di dash vs ksh dan bash
Stéphane Chazelas
6

Tulis fungsi-shell yang akan dieksekusi ketika skrip Anda jika selesai. Dalam contoh di bawah ini saya menyebutnya 'pembersihan' dan mengatur jebakan yang akan dieksekusi pada tingkat keluar, seperti: 0 1 2 3 6

trap cleanup 0 1 2 3 6

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

Lihat posting ini untuk info lebih lanjut.

Dirk Krijgsman
sumber
Itu bukan "level keluar" tetapi nomor sinyal, dan jawaban untuk pertanyaan yang Anda tautkan menjelaskan hal itu. Perangkap akan berjalan cleanupsebelum keluar bersih (0) dan saat menerima SIGHUP (1), SIGINT (2), SIGQUIT (3) dan SIGABRT (6). itu tidak akan berjalan cleanupketika skrip keluar karena SIGTERM, SIGSEGV, SIGKILL, SIGPIPE, dll. Ini jelas kurang.
Mosvy
6

Anda dapat chdir ke dalamnya dan kemudian menghapusnya, asalkan Anda tidak mencoba menggunakan jalur di dalamnya setelah itu:

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

Saya belum memeriksa, tetapi kemungkinan besar masalah yang sama ketika menggunakan openat (2) di C dengan direktori yang tidak lagi ada di sistem file.

Jika Anda root dan di Linux, Anda bisa bermain dengan namespace yang terpisah, dan mount -t tmpfs tmpfs /dirdi dalamnya.

Jawaban kanonik (setel perangkap pada EXIT) tidak berfungsi jika skrip Anda dipaksa keluar secara tidak bersih (mis. Dengan SIGKILL); yang dapat meninggalkan data sensitif berkeliaran.

Memperbarui:

Berikut ini adalah utilitas kecil yang mengimplementasikan pendekatan namespace. Ini harus dikompilasi dengan

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

dan diberikan CAP_SYS_ADMINkemampuan file (sebagai root) dengan

setcap CAP_SYS_ADMIN+ep chtmp

Ketika dijalankan (seperti biasa) pengguna sebagai

./chtmp command args ...

ia akan berhenti menggunakan namespace filesystem-nya, me-mount sebuah filesystem tmpfs aktif /proc/sysvipc, chdir ke dalamnya dan menjalankan commanddengan argumen yang diberikan. tidakcommand akan mewarisi kemampuan.CAP_SYS_ADMIN

Sistem file itu tidak akan dapat diakses dari proses lain yang tidak dimulai command, dan itu akan secara ajaib menghilang (dengan semua file yang dibuat di dalamnya) kapan commanddan anak-anaknya mati, tidak peduli bagaimana itu terjadi. Perhatikan bahwa ini hanya unsharing the namespace mount - tidak ada hambatan keras antara commanddan proses lain yang dijalankan oleh pengguna yang sama; mereka masih bisa menyelinap ke dalam ruang namanya baik melalui ptrace(2), /proc/PID/cwdatau dengan cara lain.

Pembajakan "tidak berguna" /proc/sysvipctentu saja konyol, tetapi alternatifnya adalah spam /tmpdengan direktori kosong yang harus dihapus atau sangat menyulitkan program kecil ini dengan garpu dan menunggu. Atau, dirdapat diubah menjadi misalnya. /mnt/chtmpdan membuatnya dibuat oleh root pada saat instalasi; jangan membuatnya bisa dikonfigurasi pengguna dan jangan setel ke jalur yang dimiliki pengguna karena hal itu dapat membuat Anda terkena symlink traps dan hal-hal berbulu lainnya yang tidak layak menghabiskan waktu.

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
sumber
1
Bahkan jika Anda tidak root, Anda dapat melakukan ini dengan namespace dengan membuat namespace pengguna baru dan melakukan mount tmpfs di dalamnya. Akses penyelundupan ke dir baru ke dunia luar agak rumit tetapi harus dimungkinkan.
R .. GitHub BERHENTI MEMBANTU ICE
Itu masih membutuhkan CAP_SYS_ADMIN. Saya memiliki gagasan tentang utilitas berkemampuan setcap kecil yang akan melakukan itu, saya akan memperbarui jawabannya.
qubert
1
Kecuali jika kernel telah dikunci untuk melarangnya, pembuatan ruang nama pengguna bukan operasi istimewa. Desain yang mendasarinya sedemikian rupa sehingga seharusnya aman untuk memungkinkan pengguna biasa melakukan tanpa kemampuan khusus. Namun ada permukaan serangan yang cukup / risiko bahwa banyak distro menonaktifkannya, saya pikir.
R .. GitHub BERHENTI MEMBANTU ICE
Saya mencoba di terminal. Di beberapa dir sementara, rm $PWDkerja, shell masih di dir itu. Tetapi tidak ada file baru yang dapat dimasukkan ke dalam "folder" ini. Yang bisa Anda lakukan hanyalah membaca / menulis dengan file & 3, & 4. Jadi ini masih "file sementara", bukan "folder sementara".
Bob Johnson
@ BobJohnson Itu tidak berbeda dari apa yang sudah saya katakan dalam jawaban saya ;-)
qubert
0

Apakah Anda memerlukan shell tertentu?

Jika zsh adalah opsi, harap baca zshexpn(1):

Jika = (...) digunakan alih-alih <(...), maka file yang dilewatkan sebagai argumen akan menjadi nama file sementara yang berisi output dari proses daftar. Ini dapat digunakan sebagai ganti dari <form untuk program yang mengharapkan lseek(lihat lseek(2)) pada file input.

[...]

Masalah lain muncul setiap kali suatu pekerjaan dengan substitusi yang memerlukan file sementara tidak diakui oleh shell, termasuk kasus di mana &!atau &|muncul di akhir perintah yang mengandung substitusi. Dalam hal ini file sementara tidak akan dibersihkan karena shell tidak lagi memiliki memori pekerjaan. Solusinya adalah dengan menggunakan subkulit, misalnya,

(mycmd =(myoutput)) &!

sebagai subkulit bercabang akan menunggu perintah untuk menyelesaikan kemudian menghapus file sementara.

Sebuah solusi umum untuk memastikan proses substitusi bertahan untuk jangka waktu yang sesuai adalah melewatinya sebagai parameter ke fungsi shell anonim (sepotong kode shell yang dijalankan segera dengan lingkup fungsi). Misalnya, kode ini:

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

menampilkan sesuatu yang menyerupai berikut ini

File /tmp/zsh6nU0kS:
This be the verse

Sebagai contoh saya menggunakan ini di rifle (bagian dari manajer file ranger) untuk mendekripsi file dan kemudian menjalankan rifle pada file sementara, yang akan dihapus ketika subproces berakhir. (jangan lupa atur $TERMCMD)

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