Тимчасова папка, яка автоматично знищується після виходу з процесу


10

Чи можемо ми використовувати тимчасові папки, як тимчасові файли

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

cat <&3

який буде автоматично знищений після цього виходу з оболонки?


Відповіді:


12

У випадку тимчасового файлу ваш приклад у запитанні створить його, потім від’єднайте його від каталогу (змусивши його "зникнути"), а коли скрипт закриє fileescriptor (можливо, після припинення), простір, який займає файл буде відшкодовано системою. Це поширений спосіб поводження з тимчасовими файлами такими мовами, як C.

Наскільки я знаю, неможливо відкрити каталог таким же чином, принаймні, не будь-яким способом, який би зробив цей каталог корисним.

Поширений спосіб видалення тимчасових файлів і каталогів після закінчення сценарію - встановлення EXITпастки очищення . Наведені нижче приклади коду дозволяють уникнути необхідності повністю жонглювати файлописниками.

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

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

# The rest of the script goes here.

Або ви можете викликати функцію очищення:

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

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

trap cleanup EXIT

# The rest of the script goes here.

EXITПастка не виконуватиметься після прийому KILLсигналу (який не може бути в пастці), що означає , що не буде ніякого очищення не виконується тоді. Однак він буде виконуватися при завершенні через сигнал INTабо TERMсигнал (якщо він працює з bashабо kshв інших оболонках, ви можете додати ці сигнали після EXITв trapкомандному рядку) або при нормальному виході внаслідок надходження в кінці сценарію або виконання exitдзвінок.


5
Це не лише оболонка, яка не може використовувати тимчасові каталоги, що вже не пов'язані, - а також програми C. Проблема полягає в тому, що в незв'язаних каталогах файли не можуть бути в них. Ви можете мати беззв’язаний порожній каталог як свій робочий каталог, але будь-яка спроба створити файл призведе до помилки.
дероберт

1
@derobert І такого зв'язаного каталогу навіть немає .і ..записів. (Тестовано на Linux, я не знаю, чи це відповідає всім платформам.)
kasperd


1
Зауважте, що пастка EXIT не виконується, якщо сценарій викликає exec another-commandочевидно.
Стефан Шазелас


6

Напишіть функцію оболонки, яка буде виконуватися, коли ваш сценарій буде завершено. У наведеному нижче прикладі я називаю це «очищенням» і встановлюю пастку, яку потрібно виконати на рівнях виходу, наприклад: 0 1 2 3 6

trap cleanup 0 1 2 3 6

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

Дивіться цю публікацію для отримання додаткової інформації.


Це не "рівні виходу", а номери сигналів, і відповідь на питання, на яке ви посилаєтесь, пояснює саме це. Пастка запускається cleanupперед чистим виходом (0) та після отримання SIGHUP (1), SIGINT (2), SIGQUIT (3) та SIGABRT (6). він не запускається, cleanupколи сценарій закінчується через SIGTERM, SIGSEGV, SIGKILL, SIGPIPE тощо. Це явно недостатньо.
mosvy

6

Ви можете chdir до нього, а потім видалити, за умови, що після цього не намагатиметеся використовувати шляхи всередині нього:

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

Я не перевіряв, але це, мабуть, та сама проблема при використанні openat (2) в C із каталогом, якого більше не існує у файловій системі.

Якщо ви root і в Linux, ви можете грати з окремим простором імен і mount -t tmpfs tmpfs /dirвсередині нього.

Канонічні відповіді (встановити пастку на EXIT) не спрацьовують, якщо ваш сценарій вимушений вийти з нечистого виходу (наприклад, за допомогою SIGKILL); які можуть залишити чутливі дані навколо.

Оновлення:

Ось невелика утиліта, яка реалізує підхід у просторі імен. Він повинен бути складений

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

і задані CAP_SYS_ADMINможливості файлу (як root) з

setcap CAP_SYS_ADMIN+ep chtmp

При запуску (як звичайний) користувач як

./chtmp command args ...

він скасує простір імен файлової системи, змонтує файлову систему tmpfs /proc/sysvipc, включить chdir у неї та запуститься commandіз заданими аргументами. commandбуде НЕ успадковують CAP_SYS_ADMINможливості.

Ця файлова система не буде доступною в іншому процесі, не запущеному з неї command, і вона магічно зникне (зі всіма файлами, які були створені всередині неї), коли commandі її діти помруть, як би це не відбулося . Зауважте, що це просто видалення простору імен для монтування - немає жорстких бар'єрів між commandіншими процесами, якими керує той самий користувач; вони все ще могли прокрастися до його простору імен або через ptrace(2), /proc/PID/cwdабо іншими способами.

Викрадення "непотрібних" /proc/sysvipc, звичайно, нерозумно, але альтернативою було б спам /tmpіз порожніми каталогами, які доведеться видалити або значно ускладнити цю невелику програму вилами та чеками. Крім того, dirможе бути змінено на напр. /mnt/chtmpі створити його коренем при встановленні; не робіть його налаштованим користувачем і не встановлюйте його на дорогу, що належить користувачеві, оскільки це може піддавати вас посиланням на пастки та інші волохаті речі, на які не варто витрачати часу.

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);
}

1
Навіть якщо ви не root, ви можете зробити це за допомогою просторів імен, створивши новий простір імен користувачів та виконавши змонтування tmpfs всередині нього. Контрабандний доступ до нового режиму до зовнішнього світу є дещо складним, але це має бути можливим.
R .. GitHub СТОП ДОПОМОГАЄТЬСЯ

Для цього ще потрібен CAP_SYS_ADMIN. У мене є ідея невеликої утиліти з підтримкою setcap, яка зробить це, я оновлю відповідь за допомогою неї.
qubert

1
Якщо ядро ​​не заблоковано, щоб заборонити його, створення просторів імен користувачів не є привілейованою операцією. Основа конструкції така, що звичайним користувачам можна безпечно дозволити обійтися без особливих можливостей. Однак я думаю, що існує достатня поверхня атаки / ризик, що багато дистрибутивів її відключать.
R .. GitHub СТОП ДОПОМОГАЄТЬСЯ

Я спробував у терміналі. У якомусь тимчасовому режимі rm $PWDробота, шкаралупа все ще знаходиться в тому ресі. Але жодні нові файли не можуть бути поміщені в цю "папку". Ви можете лише читати / писати з файлу & 3, & 4. Отже, це все ще "тимчасовий файл", а не "тимчасова папка".
Боб Джонсон

@BobJohnson Це не відрізняється від того, про що я вже говорив у своїй відповіді ;-)
qubert

0

Вам потрібна конкретна оболонка?

Якщо zsh є варіантом, будь ласка, прочитайте zshexpn(1):

Якщо замість <(...) використовується = (...), тоді файл, переданий як аргумент, буде ім'ям тимчасового файлу, що містить вихід із процесу списку. Це може використовуватися замість <форми для програми, яка очікує lseek(див. lseek(2)) У вхідному файлі.

[...]

Інша проблема виникає кожного разу, коли завдання із заміною, яка вимагає тимчасового файлу, відхиляється від оболонки, включаючи випадок, коли &!або &|з’являється в кінці команди, що містить підміну. У цьому випадку тимчасовий файл не буде очищений, оскільки в оболонці більше немає пам’яті завдання. Вирішення проблеми полягає у використанні підзаголовка, наприклад,

(mycmd =(myoutput)) &!

оскільки роздвоєна підпакет буде чекати, коли команда закінчиться, тоді видаліть тимчасовий файл.

Загальне рішення для забезпечення заміни процесу триває протягом відповідного проміжку часу - це передати його як параметр анонімній функції оболонки (фрагмент коду оболонки, який запускається негайно з областю функції). Наприклад, цей код:

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

виводить щось подібне до наступного

File /tmp/zsh6nU0kS:
This be the verse

Наприклад, я використовую це у гвинтівці (частина менеджера файлів рейнджера), щоб розшифрувати файл, а потім запустити гвинтівку у тимчасовий файл, який видаляється, коли підпроцеси завершуються. (не забудьте встановити $TERMCMD)

# ~/.config/ranger/rifle.conf
...
!ext exe, mime octet-stream$, has gpg, flag t = () { rifle -f F "$1" } =(gpg -dq "$1")
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.