Skip to content

How to fill a disk with random data for encryption preparation – reloaded

May 4, 2013

Time ago i wrote a post How to fill a disk with random data for encryption preparation. It is no more valid. I has been never valid actually.
There I gave a way to fill up a disk to prepare it for encryption. I have found a new, cleaner and safe way to do that.
It starts from Something similar to /dev/zero and exploit the option of dd to seek into the output.
With script below you can fill up the disk with random data in more session (stopping and resuming it).
Usage is very simple: wiper .
To enable stop/resume script save a session file (which name is derived from device). To stop the session simply touch a stop file which name matches the session file name with .stop appended. To resume the session simply run again wiper and if session file is found then wiping (i.e. fill up) starts from where previously was stopped.
Some tips and tricks:

  1. stop/resume works is disk preserve it device (mapping) between sessions;
  2. if session was stopped then stop file must be removed in order to resume it;
  3. if session was completed (all disk wiped) then to run again wiper session file must be removed first.
#!/bin/sh

# WARNING
# this script incorporates fragments from common in order to
# be indipendent

#
# Either wipe a block device or fill a disk (partition) with random data 
# supporting for resume.
# Session information are saved in a file that can be used later to resume 
# a session
# Example
# a) wiper /dev/sda
#    wipes the sda disk; session information are saved in _dev_sda.wiper-session;
#    if session file is available then session is resumed using information 
#    saved within it; session can be stopped touching _dev_sda.wiper-session.end;
# b) wiper /dev/sda1
#    wipes the partion 1 of sda disk; session information are saved in 
#    _dev_sda1.wiper-session; if session file is available then session is resumed using  
#    information saved within it; session can be stopped touching 
#    _dev_sda1.wiper-session.end;
# c) wiper /media/usb-dongle
#    either wipes the device that is mounted in the specified directory
#    or the device which the directory belong to; session information are 
#    saved in _media_usb-dongle.wiper-session; if session file is available then session is
#    resumed using information saved; session can be stopped touching 
#    _media_usb-dongle.wiper-session.end;
#
# @version 1.1.0
# @date 2013-04-16
# lock and unlock can use both lockfile-create and lockfile.
#



######################################################################
#                                                                    #
# common                                                             #
#                                                                    #
######################################################################

USEUI=y
if [ -z "$DISPLAY" ] ; then
	USEUI=n
fi

# Asks for a question and wait for an answer to be in a specified set
# @param [1, mandatory] prompt
# @param [2, optional, y] default answer 
# @param [3, optional, n] other answers
# @author drjxzoidberg@gmail.com
# @version 1.0.0 2012-11-21
ask ()
{
	local prompt="$1"
	local default=${2:-y}
	local answers=${3:-n}
	answers=$(echo ${answers} | tr [[:upper:]] [[:lower:]])
	a=""
	while [ "$a" = "" ] ; do
		read -p "$prompt " a
		if [ -z "$a" ] ; then
			a=$default
		elif [ ${#a} = 1 ] ; then
			a=$(echo ${default}${answers} | tr -c -d $a)
		else
			a=""		
		fi
	done
	echo $a
}

# parameters should be 1: question, 2: default value
askText ()
{
	if [ "${USEUI}" = "y" ] ; then
		askTextUI "$@"
	else
		askTextConsole "$@"
	fi
}


askConfirmationUI ()
{
		zenity --title "${APP_NAME}" --question --text "$1"
}

askConfirmationConsole ()
{
	local answer=$(ask "$1 [Y/n]" y n)
	if [ "${answer}" = "y" ] ; then
		return 0
	else
		return 1
	fi
}

askConfirmation ()
{
	local question="${1:-Confirm?}"
	if [ "${USEUI}" = "y" ] ; then
		askConfirmationUI "${question}"
	else
		askConfirmationConsole "${question}"
	fi
}

######################################################################
#                                                                    #
# !common                                                            #
#                                                                    #
######################################################################



DEBUG=n

UPDATE_INTERVAL=30
WIPER_STATS=/tmp/wiper_stats



msg ()
{
	echo "$(date +%Y%m%d%H%M%S%N) $@"
}

debug ()
{
	if [ "${DEBUG}" = "y" ] ; then
		msg "[DD]" "$@"
	fi
}

error ()
{
	msg "[EE]" "$@"
}

warning ()
{
	msg "[WW]" "$@"
}

info ()
{
	msg "[II]" "$@"
}

generate_session_file ()
{
	echo "$1.wiper-session" | tr / _
}

start_wiper_backend ()
{
	local bs=4096
	
	local target="$1"
	local progress=$2
	local session_file="$3"
	
	debug "start_wiper_backend: hello."
	debug "start_wiper_backend: seek to '${progress}'."

	dd if=/dev/urandom of="${target}" bs=${bs} seek=${progress} \
	 2>"${WIPER_STATS}" &
	local PID=$!
	debug "start_wiper_backend: wiper started with pid '${PID}'."
	if lock session ; then
		update_session_pid ${PID} "${session_file}"
		unlock session
	fi
	debug "start_wiper_backend: waiting for wiper ending."
	wait ${PID}
	debug "start_wiper_backend: wiper ended."
	if lock ; then 
		update_session_pid 0 "${session_file}"
		debug "removing wiper stats file '${WIPER_STATS}'."
		rm -f "${WIPER_STATS}"
		unlock
	fi
	debug "start_wiper_backend: bye bye."
}

stop_wiper_backend ()
{
	local session_file="$1"

	local PID=$(get_session_pid "${session_file}")

	if [ ! -d /proc/${PID} ] ; then
		debug "uh-oh '${PID}' no more active!?"
		return 0
	else
		debug "killing '${PID}' ($(readlink /proc/${PID}/exe))."
		kill ${PID}
	fi
}

get_session_type ()
{
	local ret="$(cat "$1" | grep '^type: ' | tail -n1)"
	echo "${ret#type: }"
}

get_session_target ()
{
	local ret="$(cat "$1" | grep '^target: ' | tail -n1)"
	echo "${ret#target: }"
}

get_session_progress ()
{
	local ret="$(cat "$1" | grep '^progress: ' | tail -n1)"
	echo "${ret#progress: }"
}

update_session_progress ()
{
# $1 is progress
# $2 is session file
	if [ $# -eq 1 ] ; then
		debug "*not* updating progress: only 1 argument passed."
	else
		debug "updating progress to '$1' in '$2'"
		sed -i -e s/'^progress: .*$'/"progress: $1"/1 "$2"
	fi
}

get_session_pid ()
{
	local ret="$(cat "$1" | grep '^pid: ' | tail -n1)"
	echo "${ret#pid: }"
}

update_session_pid ()
{
# $1 is pid
# $2 is session file
	sed -i -e s/'^pid: .*$'/"pid: $1"/1 "$2"
}

get_session_status ()
{
	local ret="$(cat "$1" | grep '^status: ' | tail -n1)"
	echo "${ret#status: }"
}

update_session_status ()
{
# $1 is status
# $2 is session file
	debug "updating status to '$1'."
	sed -i -e s/'^status: .*$'/"status: $1"/1 "$2"
}

is_end_session ()
{
	[ -f "$1.end" ]
}

wait_for_wiper_running ()
{
	local session_file="$1"

	debug "waiting for wiper starting."

	local i=0

	local PID=0
	while [ ${PID} -eq 0 -a ${i} -ge 0 ] ; do
		PID=$(get_session_pid "${session_file}")
		if [ ${PID} -eq 0 ] ; then
			sleep 0.25s
			i=$((i + 1))
			# timeout is 20 seconds
			if [ ${i} -ge 20 ] ; then
				i=-1
			fi
		else
			debug "wiper started with pid '${PID}'."
		fi
	done

	[ ${PID} -gt 0 ]
}

get_out ()
{
	grep '^[[:digit:]]\++[[:digit:]]\+ record' | tail -n1 | sed -e s/'+.*$'/''/1
}

get_current_progress ()
{
# $1 is dd pid
	if [ $1 -ne 0 ] ; then
		if lock backend ; then
#			debug "get_current_progress: forcing wiper backend stats update" >>/tmp/x
			/bin/kill -USR1 $1 >/dev/null 2>&1
			sleep 1s
			unlock backend
		fi
	fi

	if lock backend ; then
		if [ -f "${WIPER_STATS}" ] ; then
			echo $(cat "${WIPER_STATS}" | get_out)
			unlock backend
			return 0
		else
#			debug "get_current_progress: '${WIPER_STATS}' is not available." >>/tmp/x
			unlock backend
			return 1
		fi
	else
#		debug "could not get backend lock." >>/tmp/x
		return 1
	fi
}

get_lock_file ()
{
	if [ $# -eq 0 ] ; then
		set -- session backend
	fi
	
	for l in "$@" ; do
		case ${l} in 
			session)
				echo /tmp/wiper-session.lock
			;;
			
			backend)
				echo /tmp/wiper-backend.lock
			;;
		esac
	done
}

lock ()
{
	if which lockfile-create 2>&1 >/dev/null ; then
		local f
		for f in $(get_lock_file "$@") ; do
			lockfile-create --lock-name "${f}"
		done
	elif which lockfile 2>&1 >/dev/null ; then
		lockfile -1 -r10 $(get_lock_file "$@")
	else
		exit 1
	fi
}

unlock ()
{
	if which lockfile-create 2>&1 >/dev/null ; then
		local f
		for f in $(get_lock_file "$@") ; do
			lockfile-remove --lock-name "${f}"
		done
	else
		rm -f $(get_lock_file "$@")
	fi
}

do_wipe ()
{
	local target="$1"
	local progress=$2
	local session_file="$3"
	
	local PID=0

	start_wiper_backend "${target}" ${progress} "${session_file}" &
	
	if ! wait_for_wiper_running "${session_file}" ; then
		error "error wiper did not start."
		return 1
	fi
	
	local progress
	
	if lock session ; then
		update_session_status running "${session_file}"
		unlock session
	fi
	
	local end=0
	local i=0
	while ! is_end_session "${session_file}" && [ ${end} -eq 0 ] ; do
		PID=$(get_session_pid "${session_file}")
		if [ ${PID} -eq 0 ] ; then
			debug "wiper terminated; likely wiping completed."
			end=1
			progress="$(get_current_progress ${PID})"
			if lock session ; then
				update_session_progress ${progress} "${session_file}"
				update_session_status completed "${session_file}"
				unlock session
			fi
		elif [ ${i} -ge ${UPDATE_INTERVAL} ] ; then
			debug "update interval expired: progress to be updated."
			progress="$(get_current_progress ${PID})"
			if lock session ; then
				update_session_progress ${progress} "${session_file}"
				unlock session
			fi
			i=0
			# in debug mode force stop after first update
			# [ ${DEBUG} = "y" ] && touch "${session_file}.end"
		else
			debug "update interval !expired: sleeping."
			i=$((i + 1))
			sleep 1s
		fi
	done
	
	debug "outside the loop, stupendo sensation!"
	
	if [ ${end} -eq 0 ] ; then
		# stop request
		debug "stop requested"
		if lock session ; then
			update_session_status stopped "${session_file}"
			unlock session
		fi
		stop_wiper_backend "${session_file}"
	fi
}

run_session_filesystem ()
{
	local target="$1"
	local progress=$2
	local session_file="$3"

	target="${target}/$(date +%Y%m%d%H%M%S%N).wiper"

	# target is a file into the 'target' directory
	# progress is 0: each resume writes on a new file
	do_wipe "${target}" 0 "$3"
}

run_session_device ()
{
	do_wipe "$1" $2 "$3"
}

resume_session ()
{
	local type="$(get_session_type "$1")"
	local target="$(get_session_target "$1")"
	local progress="$(get_session_progress "$1")"
	local status="$(get_session_status "$1")"

	debug "resume_session: session status is '${status}'."
	if [ "${status}" = "completed" ] ; then
		info "session '$1' is already completed."
		return 0
	else
		info "resuming session '$1'."
	fi
	
	run_session_${type} "${target}" "${progress}" "$1"
}

start_session_filesystem ()
{
	info "starting new filesystem session '$1'."

	echo "type: filesystem" >"$2"
	echo "target: $1" >>"$2"
	echo "progress: 0" >>"$2"
	echo "pid: 0" >>"$2"
	echo "status: new" >>"$2"

	run_session_filesystem "$1" 0 "$2"
}

start_session_device ()
{
	info "starting new device session '$1'."

	echo "type: device" >"$2"
	echo "target: $1" >>"$2"
	echo "progress: 0" >>"$2"
	echo "pid: 0" >>"$2"
	echo "status: new" >>"$2"

	run_session_device "$1" 0 "$2"
}

######################################################################
#                                                                    #
# main                                                               #
#                                                                    #
######################################################################

usage ()
{
	echo "$(basename "$0") <device> | <directory> | <session file>"
}

ask_run_confirmation ()
{
	local session_file="$1"
	
	debug "ask_run_confirmation: session file '${session_file}'."
	
	if [ ! -f "${session_file}.end" ] ; then
		debug "ask_run_confirmation: no need to ask."
		return 0
	fi
# this code is commented out as must be investigated why stop request file 
# is present but session one is not
# [A]
#	if [ ! -f "${session_file}" ] ; then
#		debug "ask_run_confirmation: stop file not found but session one did not: no need to ask."
#		return 0
#	fi
# ![A]
	
	if askConfirmation "A session stop request file is present. Do you really want to continue?" ; then
		rm -f "${session_file}.end"
		return 0
	else
		return 1
	fi
}

run ()
{
	case $1 in
		h | help | usage | "")
			usage
			exit 0
		;;
	esac
	
	local session_file="$(generate_session_file "$1")"
	if ! ask_run_confirmation "${session_file}" ; then
		warning "program execution aborted."
		exit 0
	fi
	
	if [ -f "${session_file}" ] ; then
		resume_session "${session_file}"
	elif [ -d "$1" ] ; then
		start_session_filesystem "$1" "${session_file}"
	elif [ -b "$1" ] ; then
		start_session_device "$1" "${session_file}"
	else 
		echo "invalid '$1'."
		usage
		exit 1
	fi
}

run "$1"

######################################################################
#                                                                    #
# !main                                                              #
#                                                                    #
######################################################################

From → Technology

Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: