| # Copyright 2022 The ChromiumOS Authors | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| # | |
| # Usage: setup-data-dir <data_dir> <ttl_timespec> | |
| # | |
| # Example: | |
| # setup-data-dir ~/SomePathUnderYourHomeDir "Jun 7, 2022" | |
| # | |
| # This command disables duplicity backups for the given |data_dir| | |
| # and schedules a shredding operation around the given |ttl_timespec|. | |
| # | |
| # We use the |at| command to schedule this deletion operation. It will | |
| # run the operation in the background, without the user being logged in, | |
| # any time the machine is running after |ttl_timespec|. | |
| # Ex. If the machine is powered-off when the timespec expires, the machine | |
| # will start the operation on next boot, before the user logs in. | |
| # | |
| # Since this operation can take a long time to complete, we need to ensure | |
| # that this operation always completes. We do this by rescheduling an | |
| # additional deletion before we start the deletion, if data in the data dir | |
| # exists. | |
| # | |
| # See |man at| for help with the |ttl_timespec|. | |
| setup-data-dir() { | |
| local data_dir="$1" | |
| local timespec="$2" | |
| # Bypass symbolic links and relative paths. | |
| local data_dir_real="$(realpath "${data_dir}")" | |
| # Setup names for deletion process. | |
| local data_dir_del_script="${data_dir_real}-nuke-now.bash" | |
| local data_dir_del="${data_dir_real}-deletion-in-progress" | |
| local data_dir_del_log="${data_dir_real}-deletion-log.txt" | |
| # Check that data dir already exists. | |
| if [[ ! -d "${data_dir_real}" ]]; then | |
| echo "Error - '${data_dir_real}' doesn't exist." >&2 | |
| return 1 | |
| fi | |
| echo "# Disabling backups for data dir '${data_dir_real}'." | |
| if ! touch "${data_dir_real}/.nobackup"; then | |
| echo "Error - Failed to touch ${data_dir_real}/.nobackup." >&2 | |
| return 1 | |
| fi | |
| if ! which at >/dev/null; then | |
| echo "Error - The 'at' pkg is not installed." >&2 | |
| echo "This should have been installed through 'glinux-scheduler' pkg." | |
| exit 1 | |
| fi | |
| # An idempotent command that always tries to remove the data dir. | |
| echo "# Writing the nuke script to '${data_dir_del_script}'." | |
| cat >"${data_dir_del_script}" <<EOF | |
| #!/bin/bash | |
| # Only allow one of these scripts to run at a time using a file lock | |
| # on the output log file. | |
| # Then, redirect all output to log file. | |
| echo '# Waiting for lock on log file.' | |
| if ! exec 1>>'${data_dir_del_log}'; then | |
| echo "Error - Can't open log file." >&2 | |
| exit 1 | |
| fi | |
| flock 1 | |
| echo '# Got lock and continuing with shredding.' >&2 | |
| echo '# Log will be written to ${data_dir_del_log}.' >&2 | |
| exec 2>&1 | |
| # Clear any broken output lines from an interrupted deletion. | |
| echo | |
| if [[ ! -d '${data_dir_real}' && ! -d '${data_dir_del}' ]]; then | |
| echo '# Data dir and deletion progress dir are already deleted.' | |
| echo '# Aborting on $(date).' | |
| exit 0 | |
| fi | |
| echo '# Shred operation attempt on $(date).' | |
| echo '# Rescheduling for tomorrow.' | |
| at now + 1 day -f '${data_dir_del_script}' || echo 'Cmd at failed' | |
| # Move to deletion dir without copy, this should be idempotent. | |
| echo '# Moving data dir to ${data_dir_del}.' | |
| echo '# It is okay for this to fail if a previous attempt occurred.' | |
| mv -f '${data_dir_real}' '${data_dir_del}' || echo 'Cmd mv failure' | |
| # When using he -u argument, shred cannot be parallelized, since the | |
| # file names start to collide. | |
| # We don't use -v on shred, since that would log the file name, which | |
| # would leak participant IDs and groups. | |
| # This really isn't an issue, but it is counterproductive, since | |
| # shred tries hard to overwrite the file names. | |
| echo "# Shredding files in '${data_dir_del}'." | |
| find '${data_dir_del}' -type f | xargs shred -f -u || echo 'Cmd find failed' | |
| # If we completed successfully OR shred failed, remove all dirs | |
| # and possibly files. | |
| echo "# Removing '${data_dir_del}'." | |
| rm -rf '${data_dir_del}' || echo 'Cmd rm failed' | |
| echo "# Finished on \$(date)." | |
| exit 0 | |
| EOF | |
| chmod u=rx,g=rx "${data_dir_del_script}" | |
| # It is a rabbit hole to try to cover all situations that the user may put | |
| # us in, but we could think about creating a hard link to the data dir | |
| # in a location that won't change. This would allow for users to move the | |
| # dir on the same storage mount. | |
| # Ensure that the later mv command will not copy data across filesystems. | |
| if [[ "$(stat -c "%d" "${data_dir_real}")" != \ | |
| "$(stat -c "%d" "${data_dir_del_script}")" ]]; then | |
| echo "Error - We falied to find a safe location " \ | |
| "to move the data to for deletion." >&2 | |
| return 1 | |
| fi | |
| # The at pkg should already be installed as a dependency of | |
| # glinux-scheduler. The shred command is part of coreutils. | |
| # We schedule the deletion job as root to avoid possible permission issues | |
| # in the future. One example of how we might end up with root owned files | |
| # in the data-dir is through the use of docker, when running the FPC | |
| # BET tool. | |
| echo "# Setting up TTL for ${timespec} as root." | |
| sudo at "${timespec}" <<EOF | |
| bash "${data_dir_del_script}" | |
| EOF | |
| echo | |
| echo "# DO NOT MOVE the following data dir or nuke script:" | |
| echo "# * ${data_dir_real}" | |
| echo "# * ${data_dir_del_script}" | |
| } | |
| setup-data-dir-basic() { | |
| local data_dir="$1" | |
| local timespec="$2" | |
| touch "${data_dir}/.nobackup" | |
| # Must be done as user. | |
| at "${timespec}" <<EOF | |
| gio trash -f '${data_dir}' | |
| gio trash --empty | |
| EOF | |
| } | |
| # Testing: | |
| test-setup-data-dir() { | |
| mkdir ~/test-data-dir; pushd ~/test-data-dir | |
| for ((i = 0; i < 500; i++)); do | |
| dd if=/dev/urandom of=./data-$i.bin bs=2MB count=1 | |
| done | |
| popd | |
| echo | |
| echo | |
| echo | |
| echo | |
| echo "# Launch multiple deletion tasks at once to look for race conditions." | |
| for ((i = 0; i < 5; i++)); do | |
| setup-data-dir ~/test-data-dir "now + 2min" | |
| done | |
| echo "# Check that the .nobackup file exists in the data dir:" | |
| ls -al ~/test-data-dir/.nobackup | |
| echo "# Check that multiple jobs are scheduled:" | |
| sudo atq | |
| echo "# Check ~/test-data-dir-deletion-log.txt to ensure that only" | |
| echo " the first deletion attempt succeeded and all other were aborted." | |
| echo "# Check 'sudo atq' to ensure that one final job was scheduled" | |
| echo " for tomorrow." | |
| echo "# Check that the ~/test-data-dir and" | |
| echo " ~/test-data-dir-deletion-in-progress are gone." | |
| echo "# After all manual checks, run sudo ~/test-data-dir-nuke-now.bash" | |
| echo " to check waiting for the lock." | |
| echo "# You should also check running ~/test-data-dir-nuke-now.bash before" | |
| echo " the scheduled deletion, cause an interruption before it completes," | |
| echo " and ensure that the files are still deleted by the scheduled cmd." | |
| } | |