Мне часто приходится затирать жёсткие диски пачками, например при модернизации стораджей, и мне был очень необходим скоростной источник потока данных которыми затирается целевой диск. Чтобы более менее надёжно затереть данные на диске неразрушив сам диск, недостаточно затереть его нулями из /dev/zero. В тоже время поток данных из /dev/urandom слишком медленный чтобы быстро затирать современные жёсткие диски.
Поскольку необходимый мне уровень стирания не претендовал на уровень военной разведки то я решил выкрутиться через большой массив случайных (псевдослучайных) чисел. А чтобы массив этот быстро работал я решил закинуть его в память. И /dev/shm – идеальное место.
Для стирания я использовал raid контроллер чтобы затирать сразу множество дисков. А для автоматизации процесса написал bash-скрипт. Скрипт имеет свой счётчик продвижения и его можно прервать Ctrl+C и затем продожить с той позиции где он был прерван.
Предупреждение! Скриптом можно уничтожить важные данные, поэтому используйте его на свой страх и риск.
#!/bin/bash
#
# Script to high speed destoy all data on HDD with random data
#
# randomdatafile in memory fs
randomdatafile=/dev/shm/randombaloon.raw
# file to continue writing if script was aborted
cfgfile=$0.continue
# one block will be 4Mb
blocksize="$((4*1024*1024))"
if [[ -z $1 ]]; then
DISKLIST=`fdisk -l 2>/dev/null | grep '^Disk[ \t]\+/dev/' | grep -v mapper`
DISKLIST1=`echo -e "${DISKLIST}" | sed -e 's/\://g' -e 's/\,//g' | awk '{print $2" "$3" "$4" "$5" "$6}'`
PS3='Please enter block device to destroy data: '
IFS=$'\n' read -d '' -r -a options <<< "${DISKLIST1}"
select opt in "${options[@]}" "Quit"
do
if (( REPLY == 1 + ${#options[@]} )) ; then
echo "Exit"
exit 1
elif (( REPLY > 0 && REPLY <= ${#options[@]} )) ; then
target=`echo ${opt} | awk '{print $1}'`
break
else
echo "Incorrect choice. Try again."
fi
done
else
target="$1"
fi
if [[ ! -b $target ]]; then
echo "$target: is not a block device"
exit 1
elif [[ ! -w $target ]]; then
echo "$target: has no write permission"
exit 1
elif grep -q $target /etc/mtab; then
echo "$target: is mounted! Script is aborted!"
exit 1
fi
if [[ -f ${cfgfile} ]] ; then
# read previous counter
start="$(< ${cfgfile})"
read -p "Found prevous conter. Continue from [${start}]?" input
if [[ ! -z ${input} ]] ; then
if [[ -n ${input//[0-9]/} ]]; then
echo "Value must be an integer!"
echo "Aborting."
exit 1
else
start=${input}
fi
fi
else
start=0
fi
if [[ ! -e ${randomdatafile} ]] ; then
echo "Generate initial random data array and write 16x4M blocks to /dev/shm/"
# with /dev/urandom pseudorandom generator
# for i in `seq 0 15` ; do echo -en "Write block:\t$i\t-\t"; head -c ${blocksize} < /dev/urandom | 2> /dev/null | dd of=${randomdatafile} bs=4M seek=$i conv=notrunc 2>&1 | grep copied | awk '{print $8" "$9}' ; done
# with openssl pseudorandom generator
for i in `seq 0 15` ; do echo -en "Write block:\t$i\t-\t"; openssl rand -rand /dev/urandom ${blocksize} 2> /dev/null | dd of=${randomdatafile} bs=4M seek=$i conv=notrunc 2>&1 | grep copied | awk '{print $8" "$9}' ; done
else
echo "${randomdatafile} file exists. Ok."
fi
filesize=`stat -c%s ${randomdatafile}`
if [[ ${filesize} -ne $(($blocksize*16)) ]] ; then
echo "File size ${filesize} is not equal $(($blocksize*16))"
exit 1
fi
echo "${randomdatafile} file size is Ok."
echo "Ready to write data to ${target}"
fdisk_str=`fdisk -l ${target} 2>/dev/null | grep ${target}`
echo -e "${fdisk_str}"
echo
echo "WARNING: IT WILL DESTROY ALL DATA ON THE DISK"
echo "Are you sure you want to proceed?"
read -p 'Type YES if you really want to proceed: ' input
echo
if [[ ${input} != YES ]]; then
echo "Ok. Aborting."
exit 1
fi
# calculate disk size
disksize=`echo -e "${fdisk_str}" | grep '^Disk' | grep 'bytes$' | awk '{print $(NF-1)}'`
disksizeinblocks=$(($disksize / $blocksize))
# write data
for ((i=${start}; 1; i++)); do
position=$((${i}*16))
size=$((${i}*64))
echo -en "Writed from ${size}M\tpos:${position} of ${disksizeinblocks}(BLKs)\ti:${i}\t"
dd if=${randomdatafile} of=${target} bs=4M seek=${position} oflag=direct 2>&1 | grep copied | awk '{print $8" "$9}'
A=("${PIPESTATUS[@]}")
if [[ "${A[0]}" -ne "0" ]] ; then
echo -e "dd has returned an error ${A[0]}."
exit ${A[0]}
fi
if [[ "${position}" -ge "${disksizeinblocks}" ]] ; then
echo -e "Something wrond. dd sould return an error because the end is reached on destenation device."
exit 1
fi
echo "$i" > "${cfgfile}.new"
mv "${cfgfile}.new" "${cfgfile}"
done
echo "Done..."
UPDATE
Первая версия скрипта была с pipefail от которых я решил избавиться так как меня интересует не любая ошибка в цепочке команд, а только ошибка первой команды dd. Я сделал это через массив ${PIPESTATUS[@] который содержит результат последего выполнения цепочки команд.