I suspect that, when you open a file to write it from the start, all the existing blocks will be freed immediately (still containing their existing contents), and your cp will acquire new blocks to zero-fill.
Further, your file will be extended until it fills the whole partition: the original size will not be honoured.
The dd
command has an option conv=notrunc
, and you would be able to find the original size using stat, round that up to whole blocks, and use the count
and bs
options to size the zeros to the same blocks as the original.
Edit: Confirmed by test that a shell > redirect retains same inode number, but immediately reduces file size to zero blocks and releases them to available space.
strace on cp shows vanilla overwrite of the file via mmap area:
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
Conclusion has to be that cp
will discard your confidential data into the free list, and overwrite whatever blocks it gets allocated afterwards.
This script demonstrates that dd will zeroise part or all of the existing blocks in a file, without freeing blocks, and without changing the inode. You may need to capture the actual BLKSZ and file Size from a stat command, and to use bash arithmetic to round up the size to a whole number of blocks. Also shows that dd will extend a file, and will write sparse data.
This produces about 100 lines of output, so I won't post that. It is benign. The Zero function is the meat of it.
#! /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
This is an approximation to a production version.
#! /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
shred
, it is specifically designed for that. Caveats apply, of course. – marcelm Aug 01 '20 at 20:52/dev/zero
can be read indefinitely;cp
won't end until its writes fail (e.g. withENOSPC
) or you kill it. Was that intentional to fill the entire free space (including the just-truncated old contents ofa
) with zeros? That's a very slow way to go about it on a non-CoW FS. – Peter Cordes Aug 02 '20 at 02:01shred
isn't reliable on SSDs. You're looking for "secure delete". – chrylis -cautiouslyoptimistic- Aug 02 '20 at 03:59rm a; cp /dev/zero a
, because it will keep the same i-node. – David Ongaro Aug 02 '20 at 06:12shred
isn't even reliable on journalling filesystems. And for COW filesystems like btrfs you're totally out of luck. – allo Aug 02 '20 at 10:06