当文件 a 存在时“cp /dev/zero a”的效果

当文件 a 存在时“cp /dev/zero a”的效果

如果a存在并且我输入

cp /dev/zero a

我可以确定 的旧内容a会被覆盖吗,或者我会简单地得到相当于

rm a
cp /dev/zero a

附言。我并不是说这是安全删除文件的正确方法;我只是好奇某个命令的效果。

答案1

为什么要复制/dev/zero到某个文件?

  • 您想安全删除吗a?然后cp是错误的工具。查看shred您是否使用硬盘以及fstrim是否使用 SSD。 (很可能还有其他我不知道的工具,所以谷歌搜索“安全删除”会很好。)

  • 你明白/dev/zero无限大吗?它将无限期地返回零,因此cp在包含的文件系统a填满之前永远不会完成。

  • 但是,这种情况可能永远不会发生,因为cp作为稀疏文件检测的一部分,将抑制全零页的写入。

  • 您想创建一个给定大小且全是零的文件吗?cp仍然是错误的工具。您需要使用dd以便指定要从中复制的字节数/dev/zero

总的来说,用于cp将普通文件复制到另一个普通文件,您所关心的是副本在逻辑上是等效的。

如果您正在使用设备文件,或者您正在尝试控制原始文件块的处理方式,您将需要使用另一个程序。

答案2

我怀疑,当您打开一个文件并从头开始写入时,所有现有块将立即释放(仍包含其现有内容),并且您的 cp 将获取新块以进行零填充。

此外,您的文件将被扩展,直到填满整个分区:原始大小将不被保留。

dd命令有一个选项conv=notrunc,您可以使用 stat 找到原始大小,将其四舍五入到整个块,并使用countbs选项将零的大小调整为与原始块相同的大小。

编辑:通过测试确认 shell > 重定向保留相同的 inode 编号,但立即将文件大小减少到零块并将它们释放到可用空间。

cp 上的 strace 显示通过 mmap 区域对文件进行普通覆盖:

open("foo.tiny", O_RDONLY)              = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=8192, ...}) = 0
open("foo.copy", O_WRONLY|O_CREAT|O_EXCL, 0644) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc295d30000
read(3, "\0\0\0\0"..., 131072) = 8192
write(4, "\0\0\0\0"..., 8192) = 8192
read(3, "", 131072)                     = 0
close(4)                                = 0
close(3)                                = 0
munmap(0x7fc295d30000, 139264)          = 0
lseek(0, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
close(0)                                = 0
close(1)                                = 0
close(2)                                = 0

结论一定是,这cp会将您的机密数据丢弃到空闲列表中,并覆盖随后分配的任何块。

此脚本演示了 dd 将文件中的部分或全部现有块归零,而不释放块,也不更改 inode。您可能需要从 stat 命令捕获实际的 BLKSZ 和文件大小,并使用 bash 算术将大小四舍五入为整数块。还表明 dd 将扩展文件,并将写入稀疏数据。

这会产生大约 100 行输出,所以我不会发布它。它是良性的。归零函数是它的核心。

#! /bin/bash

FN='./Erase.data'
BLKSZ=4096

#.. Zeroise a specified range of blocks (zero-based).

Zero () {   #:: (from, to)

    dd 2>&1 ibs="${BLKSZ}" obs="${BLKSZ}" \
        seek="${1}" count="$(( $2 - $1 + 1 ))" \ 
        conv=notrunc if="/dev/zero" of="${FN}"
}

#.. Create a file of 8 * 4096B blocks, each labelled in every character.

Make () {   #:: (void)

    AWK='   
function Block (sz, id, Local, buf) {
    buf = sprintf ("%*s", sz, "");
    gsub (/./, id, buf);
    printf ("%s", buf);
}
{ for (f = 2; f <= NF; ++f) Block( $1, $(f)); }
'
    echo "${BLKSZ}" {A..H} | awk "${AWK}" > "${FN}" 
}

#.. Reveal the file.

Show () {

    echo; ls -l "${FN}"; stat "${FN}"; od -A d -t a "${FN}"; sleep 2 
}

#### Script Body Starts Here.

    #.. Make the file and prove its contents.
    Make > "${FN}" && Show 
    Zero 3 6 && Show 
    Zero 0 1 && Show 
    Zero 0 7 && Show 
    Zero 220 231 && Show 

这是生产版本的近似值。

#! /bin/bash

Usage () { expand -t 4 <<'EOF'

    Usage: ZeroAllBlocks [-h] [files ...]
        Warning: this command is as brutal as rm -f.
        -h: shows this message.

        Zeroises (binary zero) all blocks of all the files named.
        Sparse blocks will then consume real disk space.
EOF
}

#.. Zeroise a specified range of blocks (zero-based).

Zero () {   #:: (Fn, blksz, seek, count)

    local Fn="${1}" blksz="${2}" seek="${3}" count="${4}"

    dd status=none ibs="${blksz}" obs="${blksz}" \ 
        seek="${seek}" count="${count}" \
        conv=notrunc if="/dev/zero" of="${Fn}"
}

#.. Process a file.

File () {   #:: (filename)

    local Fn="${1}" szFile szBlock nBlock

    [[ -f "${Fn}" ]] || { printf '%s: No such file\n' "${Fn}"; return; }
    [[ -w "${Fn}" ]] || { printf '%s: Not writable\n' "${Fn}"; return; }
    read -r szFile szBlock <<<$( stat --printf='%s %o\n' "${Fn}" )
    nBlock="$(( (szFile + szBlock - 1) / szBlock ))"
    Zero "${Fn}" "${szBlock}" 0 "${nBlock}"
}

#### Script Body Starts Here.

    [[ "${1}" = "-h" ]] && { Usage; exit 2; }

    for Fn in "${@}"; do File "${Fn}"; done

答案3

这取决于所使用的文件系统和存储设备。一般来说,文件系统会在您告诉它们时进行覆盖,除非您使用的是针对原始闪存优化的文件系统;这些特殊的文件系统可能会在 FS 级别实现磨损均衡,因此会写入不同的位置。

但如果存储设备是常规 SATA 或 NVMe SSD,它可能会在内部进行磨损均衡,并且真实的物理存储块将与原始块设备显示的不同。因此,即使文件系统认为它肯定会覆盖特定的块,“覆盖”最终也会到达不同的物理位置。

但是,突破 SSD 的磨损均衡系统并有意义地读取原始存储应该是一个不小的技术障碍,需要专业知识,可能还需要特殊的硬件工具。而且 SSD 可能会尽快先发制人地擦除任何“覆盖”块,以尽可能多地擦除可用于写入的块,因为擦除通常是 SSD 性能的限制因素。

相关内容