#! /bin/bash # Copyright (c) 1999 Tim Barbour. All Rights Reserved. # This script may be used and distributed under the terms # of the GNU GPL, as published by the Free Software Foundation. # this is the main cdpio script usage() { # -v is for verbose echo "usage: $0 [ -v ]" >&2 echo "reads pathnames from stdin and copies them to backup CDs" >&2 } set -e # halt on error # source rc file . /etc/cdpio.rc # source functions (init_backup_fs find_fs_image) . `dirname $0`/backup_functions image=`find_fs_image $target_filesystem` contents=/tmp/cdpio-contents.$$ contents_scratch_dir=/tmp/cdpio-contents-scratch-dir.$$ error_log=/tmp/cdpio-log.$$ flush_log=/tmp/cdpio-flush-log.$$ # cp_volchange source_path destination_directory # 'cp -a' a file unless target filesystem is full, in which case we change the volume and try again cp_volchange() { if ! ( try_add_pathname "$1" "$2" ); then flush_volume change_volume # try_add_pathname will recreate recreate the directory path on the new volume if ! ( try_add_pathname "$1" "$2" ); then echo -n "Error: " >&2 tail -1 $error_log >&2 false fi fi } # try_add_pathname pathname destination # For this function, a directory is also considered to be a file. # Fails if the target filesystem lacks sufficient free space try_add_pathname() { # ensure the parent directory exists (it may not for various reasons) # in particular it may not have been created yet, or not yet on this volume try_recreate_directory_path "$2" `dirname "$1"` && # then add the file try_add_file "$1" "$2" } # try_add_file file destination # For this function, a directory is also considered to be a file # Fails if the target filesystem lacks sufficient free space try_add_file() { # Use afio here; GNU cpio returns sucess when it fails to create a directory! # but afio fails if source file does not exist, so we compensate with test. # Even so, afio does not preserve modification times of directories (they become the current time); # failure to correctly restore directory permission is mentioned in the bugs section of the afio man page. if ( echo "$1" | afio -p "$2" 2>> $error_log || test ! -e "$1" ); then add_to_contents "$1" if [ -n "$verbose" ]; then echo "$1" fi else handle_partially_written_data "$1" "$2" false fi } add_to_contents() { echo "$volume_number : $1" >> $contents } # handle_partially_written_data source_path destination_directory handle_partially_written_data() { # was the partially written item a directory ? if [ -d "$1" ]; then # a directory cannot be partially written, so in this case either: if ! [ -e "$2/$1" ]; then # there was insufficient space (inodes) to create it true # nothing to do in this case else # there was enough space, but something else went wrong - report the error echo -n "Error: while creating a directory: " >&2 tail -1 $error_log >&2 # the error must be serious, so we exit here, with an error code exit 3 fi else # cleanup any partially written file (it does not necessarily exist, so we use -f) rm -f "$2/$1" fi } flush_volume() { until ( /usr/local/sbin/dump-cdr-image $verbose 1> $flush_log 2>&1 ); do ask_user_action "CDR failed verification" \ "Please discard the disk in $verify_device, replace with a new one (also labelled \ as volume $volume_number), and reply to this message when done. The verification \ output was: `cat $flush_log`" done } init_volume() { # we quote $target_filesystem_size_blocks in case it is empty init_backup_fs $image "$target_filesystem_size_blocks" $target_filesystem "$volume_number" "gzip1" label_volume } change_volume() { volume_number=$[ volume_number + 1] ask_user_action "change volume" \ "Please insert volume $volume_number into $verify_device, reply to this message when done" init_volume } label_volume() { mkdir -m 755 "$target_filesystem/.disk" echo $volume_number > "$target_filesystem/.disk/volume" chmod 644 "$target_filesystem/.disk/volume" } # try_recreate_directory_path base_directory pathname # Exactly recreate (including permissions etc) entire pathname under base_directory . # Fails if the target filesystem lacks sufficient free space try_recreate_directory_path() { # handle the common case first (a directory that has many files probably already exists) if [ -e "$1/$2" ]; then # nothing to do (pathname already exists under base_directory - includes case where pathname # is trivial e.g. '.' or '/', since these exist trivially). return elif [ ! -e "$1" ]; then # be careful to avoid unbounded recursion echo -n "Error: try_recreate_directory_path: base_directory does not exist" >&2 exit 4 else # create parent directory path try_recreate_directory_path "$1" `dirname "$2"` && # then add final directory try_add_file "$2" "$1" fi } # add the list of contents to the last volume under .disk/set add_contents() { mkdir -p -m 755 $contents_scratch_dir/set cp "$1" $contents_scratch_dir/set/contents chmod 644 $contents_scratch_dir/set/contents cd $contents_scratch_dir cp_volchange "set/contents" "$2/.disk" cd - } cleanup() { rm -f $contents rm -fr $contents_scratch_dir rm -f $error_log rm -f $flush_log } while getopts v c do case $c in v) verbose="-v" shift `expr $OPTIND - 1`;; \?) usage exit 2;; esac done # trap some signals for cleanup trap cleanup 1 2 3 15 cat /dev/null > $contents # setup list of contents cat /dev/null > $error_log # setup error_log volume_number=1 # setup volume labeller init_volume # setup first volume # cp -a files one by one until full, then request a volume change and continue while read path; do cp_volchange "$path" "$target_filesystem" done add_contents "$contents" "$target_filesystem" flush_volume # flush out last volume to CD-R cleanup