semi_atomic_cross_fs_copy

28th October 2016 at 3:48pm
Bash CodeSnippets
#!/bin/bash

set -eu -o pipefail -o noclobber

semi_atomic_cross_filesystem_copy () {
  declare src="$1"
  declare dst="$2"
  declare dst_tmp="${dst}.part"
  declare -i tmp_rc=
  
  # Ensure noblobber is on for this to work.
  cat "$src" > "$dst_tmp" || tmp_rc=$?
  
  if [[ $tmp_rc -ne 0 ]] ; then
    # Someone else beat us to it and owns the file.
    return 2
  elif [[ -e "$dst" ]] ; then
    # Someone else beat us to move the temp file over the destination.
    rm -v "$dst_tmp"
    return 2
  fi
  
  # Move the temporary file to the destination.
  mv -v "$dst_tmp" "$dst"
}

main () {
  declare tgt_dir="/tmp/nicolaw/target_dir"
  declare src_dir="/tmp/nicolaw/source_dir"
  declare file

  # Create some example files.
  mkdir -pv "$tgt_dir" "$src_dir"
  for file in {1..10} ; do
    [[ -e "$src_dir/my$file.dat" ]] \
      || dd if=/dev/zero of="$src_dir/my$file.dat" count=1000000 bs=1024
  done

  for file in "$src_dir"/*.dat ; do
    declare src="$file"
    declare dst="$tgt_dir/${file##*/}"

    # Skip if the file exists.
    if [[ -e "$dst" ]]  ; then
      echo "File $dst already exists."
    else
      # Try to copy it semi-atomically.
      declare -i copy_rc=
      semi_atomic_cross_filesystem_copy "$src" "$dst" || copy_rc=$?
      if [[ $copy_rc -eq 0 ]] ; then
        echo "File $dst copied OK."
      elif [[ $copy_rc -eq 2 ]] ; then
        >&2 echo "Copy to $dst already in progress by another process."
      else
        >&2 echo "Copy to $dst failed."
      fi
    fi
  done
}

main "$@"