进程退出后自动销毁的临时文件夹


Answers:


12

对于临时文件,问题中的示例将创建它,然后从目录取消链接(使其“消失”),并且当脚本关闭filedescriptor时(可能在终止时),文件占用的空间将被系统回收。这是处理诸如C之类的临时文件的常用方法。

据我所知,不可能以相同的方式打开目录,至少不能以任何使目录可用的方式打开。

在脚本终止时删除临时文件和目录的常用方法是安装清除EXIT陷阱。下面给出的代码示例避免了完全摆弄filedescriptor。

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信号(无法捕获)后,将不会执行该陷阱,这意味着届时将不执行任何清除操作。但是,它会在由于INTTERM信号而终止时执行(如果使用bash或运行,则ksh可能需要EXITtrap命令行中添加这些信号,或者在其他shell中由于在脚本末尾或执行一个exit呼叫。


5
不仅仅是外壳不能使用已经取消链接的临时目录,C程序也不能。问题是未链接的目录中不能包含文件。您可以将未链接的空目录作为工作目录,但是任何尝试创建文件的操作都会出错。
derobert

1
@derobert这样的未链接目录甚至没有...条目。(在Linux上测试过,我不知道这在各个平台上是否一致。)
kasperd


1
请注意,如果脚本exec another-command明显调用,则也不执行EXIT陷阱。
斯特凡Chazelas


6

编写一个shell函数,该函数将在脚本完成时执行。在下面的示例中,我将其称为“清理”,并设置一个在退出级别执行的陷阱,例如: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)时运行。当脚本由于SIGTERM,SIGSEGV,SIGKILL,SIGPIPE等而退出时,它将无法运行cleanup。这显然是不足的。
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

我没有检查过,但是在C中使用openat(2)并在文件系统中不再存在目录的情况下,这可能是相同的问题。

如果您是root用户并且在Linux上,则可以mount -t tmpfs tmpfs /dir在其内部使用单独的名称空间。

如果您的脚本被迫进入不干净的出口(例如,使用SIGKILL),则规范的答案(在EXIT上设置陷阱)将不起作用。可能会使敏感数据四处徘徊。

更新:

这是一个实现命名空间方法的小实用程序。应该用

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

和给定的CAP_SYS_ADMIN文件功能(作为根)

setcap CAP_SYS_ADMIN+ep chtmp

以普通用户身份运行时

./chtmp command args ...

它将取消共享其文件系统名称空间,将tmpfs文件系统挂载到/proc/sysvipcchdir并command使用给定参数运行。command不会继承CAP_SYS_ADMIN能力。

该文件系统将无法从另一个未从开始的进程访问command,并且无论子进程如何发生,它都会神奇地消失(其中所有创建的文件都将在其中消失)command。注意,这只是取消共享命名空间- command在同一用户运行的进程与其他进程之间没有硬障碍;他们仍然可以潜行或者通过它的命名空间中ptrace(2)/proc/PID/cwd或通过其他方式。

劫持“无用” /proc/sysvipc固然很愚蠢,但替代方法是/tmp使用空目录来清除垃圾邮件,而空目录则必须被删除,或者使此小程序变得更加复杂,并且需要分叉等待。或者,dir可以更改为例如。/mnt/chtmp并在安装时由root创建;不要将其设置为用户可配置的,也不要将其设置为用户拥有的路径,因为这可能会使您接触到符号链接陷阱和其他不值得花时间的毛茸茸的东西。

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停止帮助ICE,

那仍然需要CAP_SYS_ADMIN。我有一个小型启用setcap的实用程序的想法,我将用它更新答案。
Qubert

1
除非内核已被锁定以禁止使用,否则创建用户名称空间不是特权操作。基础设计应保证普通用户无需任何特殊功能就可以安全地进行操作。但是,我认为有足够的攻击面/风险,许多发行版都将其禁用。
R .. GitHub STOP HELPING ICE

我在航站楼尝试过。在某些临时目录中,rm $PWDshell仍在该目录中。但是不能将新文件放入此“文件夹”。您唯一可以做的就是读写文件&3,&4。因此,这仍然是“临时文件”,而不是“临时文件夹”。
鲍勃·约翰逊

@BobJohnson这与我在回答中已经说过的没什么不同;-)
qubert

0

您需要特定的外壳吗?

如果选择zsh,请阅读zshexpn(1)

如果使用=(...)而不是<(...),则作为参数传递的文件将是包含列表进程输出的临时文件的名称。这可以代替<形式的一个程序,希望lseek(参见lseek(2)上输入文件)。

[...]

每当shell放弃具有需要临时文件的替换作业时,都会出现另一个问题,包括在包含替换的命令末尾出现&!&|出现的情况。在这种情况下,临时文件将不会被清理,因为外壳不再具有该作业的任何内存。一种解决方法是使用子外壳,例如,

(mycmd =(myoutput)) &!

因为分叉的子shell将等待命令完成,然后删除临时文件。

确保进程替换持续一段适当时间的一般解决方法是将其作为参数传递给匿名Shell函数(使用功能范围立即运行的一部分Shell代码)。例如,此代码:

() {
   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")
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.