On Friday 18 March 2011 05:50:18 pm Kaz Kylheku wrote:
> On Fri, 18 Mar 2011 10:18:27 +0100, Jean Delvare <[email protected]>
> 
> wrote:
> > Hi Kaz,
> >
> > On Friday 18 March 2011 02:26:27 am Kaz Kylheku wrote:
> >> Hey everyone,
> >>
> >> Recently I became interested in a quilt that consists only of
> >> shell scripts.
> >
> > You're not the only one. I'm happy to see momentum grow in this
> > direction.
> 
> I did some further hacking on this last night, sliding the program
> under quilt and getting it to work, including adding new files,
> and pushes and pops with file creating/deleting patches.

That's not enough. Quilt comes with a non-regression test suite, which 
your script should pass. Try it: "make check". I did, your code failed 
(even the update you send half an hour ago.)

I also wrote a dedicated test case for backup-files, which I am 
attaching so that you can test your code against it. It assumes that 
backup-files is in quilt/scripts/, change the path if it isn't the case. 
You current code doesn't pass.

But again, please keep in mind that backup-files is not trivial (you'll 
understand when you see it fail the various test cases), and I have 
spent a lot of time on my own bash version of it. Once you'll be done 
with correctness, you'll have to deal with portability (my code works on 
many flavors of Linux + FreeBSD 8.1, does yours? I bet not) and 
performance (I see that your code creates 3 temporary files 
unconditionally, so I expect the performance of "quilt add" and "quilt 
remove" on single files to suffer.)

> This version does not change the backup format, so everything works.
> 
> I will fix the touch logic so that it doesn't diddle the backup
>  files. It's clear that we can't do a faithful restore with
>  timestamps intact if it's done the way  I have it.

Quick review, only pointing out the most obvious problems:

> #!/bin/bash
> 
> #
> # Shell script replacement for quilt's backup-files utility
> # Tested to some extent under quilt.
> # Mar 18, 2011
> # Kaz Kylheku <[email protected]>
> #
> 
> #
> # Main concepts:
> #
> # - Goal is to avoid invoking a process for each file name
> # - We use the CPIO utility for creating hard-linked backups; CPIO
> #   pass-through mode can take list of names and create hard links
>  (in #   pass-through mode).

You mentioned "pass-through mode" twice, this seems redundant.

> # - Use tree-to-tree recursive cp to restore a backup (taking
> everything
> #   in the backup, ignoring the file list).
> # - To touch files on restore, if requested, we can do a find + xargs
>  + #   touch over the backup instead, prior to restoring it. [WRONG]
>  # - Added files (i.e. backups of nonexistent files) are represented
>  as a
> #   specially named file containing an explicit list, and not as
> #   zero-length files. This eases the implementation, and lets us
>  back #   up/restore zero-length files!
> #
> 
> set -eu # bail on any errors, and unbound variable uses
> 
> opt_prefix=
> opt_suffix=
> opt_file=
> opt_backup=
> opt_restore=
> opt_remove_backup=
> opt_keep_backups=
> opt_silent=
> opt_touch=
> opt_nolink=
> 
> all_backup_files=
> 
> usage()
> {
> cat <<!
> Usage: $0 [-B prefix] [-f {file|-}] [-sktL] [-b|-r|-x] {file|-} ...
> 
>       Create hard linked backup copies of a list of files
>       read from a file (or standard input), or from the
>       argument list.
> 
>       The argument list "-" means "every regular file in
>       the backup directory specified by the -B prefix.
> 
>       -b      Create backup
>       -r      Restore the backup
>       -x      Remove backup files and empty parent directories
>       -k      When doing a restore, keep the backup files
>       -B      Path name prefix for backup files. Should have trailing slash.
>               Without a trailing slash, the behavior is different from the
>               C implementation.
>       -z      Unsupported, obsolete option

Why list it at all then?

>       -s      Silent operation; only print error messages
>       -f      Read the filenames to process from file (- = standard input)
>       -t      Touch original files after restore (update their mtimes)
> 
>       -L      Ensure that when finished, the source file has a link count of 1
> !
> }
> 
> if ! options=`getopt -o B:f:brxkstLh -- "$@"` ; then
>       usage
>       exit 1
> fi
> 
> eval set -- "$options"
> 
> while true
> do
>       case "$1" in
>       -B)
>               opt_prefix="$2"
>               shift 2 ;;
>       -f)
>               opt_file="$2"
>               shift 2 ;;
>       -b)
>               opt_backup=B
>               shift ;;
>       -r)
>               opt_restore=R
>               shift ;;
>       -x)
>               opt_remove_backup=D
>               shift ;;
>       -k)
>               opt_keep_backups=y
>               shift ;;
>       -s)
>               opt_silent=y
>               shift ;;
>       -t)
>               opt_touch=y
>               shift ;;
>       -L)
>               opt_nolink=y
>               shift ;;
>       -h)
>               usage
>               exit 0 ;;
>       --)
>               shift
>               break ;;
>       esac
> done
> 
> if [ $# -eq 0 -a -z "$opt_file" ] ; then
>       echo "Error: specify input file names as arguments or via -f option"
>       echo
>       usage
>       exit 1
> fi
> 
> if [ $# -ge 1 -a -n "$opt_file" ] ; then
>       echo "Error: conflict: both -f and file name argument given"
>       echo
>       usage
>       exit 1
> fi
> 
> if [ $# -gt 1 -a "$1" == "-" ] ; then
>       echo "Error: if - is specified, then no other arguments can be
>  added" echo
>       usage
>       exit 1
> fi
> 
> 
> if [ -z "$opt_prefix" ] ; then
>       echo "Error: specify backup/restore directory with -B"
>       echo
>       usage
>       exit 1
> fi
> 
> # temp file that holds raw list of names
> temp_list=$(mktemp "${TMP_DIR:-/tmp}/backup-files-tl-XXXXXX")
> 
> # temp file that holds names of files that exist
> file_list=$(mktemp "${TMP_DIR:-/tmp}/backup-files-fl-XXXXXX")
> 
> # temp file that holds names of nonexistent files
> noex_list=$(mktemp "${TMP_DIR:-/tmp}/backup-files-ne-XXXXXX")
> 
> cleanup()
> {
>       rm -f $temp_list $file_list $noex_list
> }
> 
> trap cleanup exit
> 
> #
> # capture the file list into the $temp_list file
> #
> 
> if [ -n "$opt_file" -a "$opt_file" == "-" ] ; then
>       cat > $temp_list
> elif [ -n "$opt_file" ] ; then
>       cat "$opt_file" > $temp_list
> elif [ "$1" == "-" ] ; then
>       all_backup_files=y
> else
>       # IFS trick. The string literal here contains a newline
>       ( IFS="
> "
>         echo "$*" > $temp_list )
> fi
> 
> #
> # separate name list into existing and nonexisting
> #
> 
> > $file_list
> > $noex_list
> 
> while read name ; do
>       if [ -e "$name" ] ; then
>               echo "$name" >> $file_list
>       else
>               echo "$name" >> $noex_list
>       fi
> done < $temp_list
> 
> case $opt_backup$opt_restore$opt_remove_backup in
> B )
>       if [ $all_backup_files ] ; then
>               echo "Error: \"-\" argument makes no sense for backup action"
>               echo
>               usage
>               exit 1
>       fi
> 
>       if [ $opt_nolink ] ; then
>               cpio --quiet -pd "$opt_prefix" < $file_list
>       else
>               cpio --quiet -pdl "$opt_prefix" < $file_list
>       fi

You have implemented the "no link" backup improperly. "No link" means 
that the _source_ ends unlinked. The backup is still linked to all other 
files the source was originally linked to. Your code does it the other 
way around.

> 
>       umask 0111 # so touched files are 0666

Why would you want touched files to be world-writable?

> 
>       # escaping issues here: we are assuming opt_prefix
>       # doesn't contain # or things interpreted by sed
>   sed -e "s#.*#$opt_prefix&#" < $noex_list | xargs touch
>       ;;
> R )
>       if [ -z $all_backup_files ] ; then
>               echo "Error: restoring individual files is not supported"
>               exit 1
>       fi

This is mandatory for "quilt remove", "quilt diff" and "quilt revert".

> 
>       if [ $opt_nolink ] ; then
>               cp -a "$opt_prefix"/. .

FWIW, this is never used by quilt.

>       else
>               if [ $opt_touch ] ; then
>                       find "$opt_prefix" -type f | xargs touch
>               fi
> 
>               cp -rlf "$opt_prefix"/. .

Not all implementations of cp support option -l, nor --
preserve=mode,ownership which you used in the update.

>       fi
> 
>       find "$opt_prefix" -type f -size 0c \
>         | cut -c "$((1+${#opt_prefix}))-" \
>         | xargs rm -f
> 
>       if [ -z "$opt_keep_backups" ] ; then
>               rm -rf "$opt_prefix"
>       fi
>       ;;
> D )
>       rm -rf "$opt_prefix"
>       ;;

FWIW, quilt doesn't use this.

> * )
>       # either no operation was specified, or multiple operations
>       # were been specified, like -b and -r.
>       usage
>       exit 1
>       ;;
> esac
> 

But then again... it seems to be a waste of time that you work on a 
problem which is already solved.

-- 
Jean Delvare
Suse L3
Unit test of the backup-files script.

        # Test backup without options; it should link, not copy
        $ echo foo > foo
        $ echo bar > "space bar"
        $ sleep 1
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -b foo
        > Copying foo

        $ ls -l foo | awk '{ print $2 }'
        > 2
        $ ls -l backup/foo | awk '{ print $2 }'
        > 2
        $ [ backup/foo -nt foo ] && echo "mtimes differ"

        # Test remove
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -x foo
        $ ls -l foo | awk '{ print $2 }'
        > 1

        # Test silent backup with -L; it should copy, not link
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -b -s -L foo

        $ ls -l foo | awk '{ print $2 }'
        > 1
        $ ls -l backup/foo | awk '{ print $2 }'
        > 1
        $ [ backup/foo -nt foo ] && echo "mtimes differ"

        # Test restore without options
        $ echo modified > foo
        $ sleep 1
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -r foo
        > Restoring foo

        $ cat foo
        > foo
        $ [ -e backup/foo ] && echo "backup/foo not deleted"
        $ [ -e backup ] && echo "backup directory not deleted"

        # Test backup files with hard links
        $ ln foo foo2
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -b -s -L foo
        $ ls -l foo | awk '{ print $2 }'
        > 1
        $ ls -l backup/foo | awk '{ print $2 }'
        > 2

        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -b -s -L foo2
        $ ls -l foo | awk '{ print $2 }'
        > 1
        $ ls -l foo2 | awk '{ print $2 }'
        > 1
        $ ls -l backup/foo | awk '{ print $2 }'
        > 2
        $ ls -l backup/foo2 | awk '{ print $2 }'
        > 2

        # Test restore of files with hard links
        $ rm -f foo foo2
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -r -s foo
        $ ls -l foo | awk '{ print $2 }'
        > 2
        $ ls -l backup/foo2 | awk '{ print $2 }'
        > 2
        $ [ -e backup/foo ] && echo "backup/foo not deleted"
        $ [ ! -e foo2 ] || echo "file foo2 shouldn't exist"

        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -r -s foo2
        $ ls -l foo | awk '{ print $2 }'
        > 2
        $ ls -l foo2 | awk '{ print $2 }'
        > 2
        $ [ -e backup/foo2 ] && echo "backup/foo2 not deleted"
        $ [ -e backup ] && echo "backup directory not deleted"

        # Test restore with -t
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -b -s foo
        $ touch -r foo foo.timeref
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -r -s -t foo

        $ ls -l foo | awk '{ print $2 }'
        > 2
        $ [ foo -nt foo.timeref ] || echo "touch failed"

        # Test restore with -k
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -b -s -L foo "space bar"
        $ touch -r "space bar" bar.timeref
        $ ls -l backup/foo | awk '{ print $2 }'
        > 2
        $ ls -l "backup/space bar" | awk '{ print $2 }'
        > 1
        $ mkdir tmp
        $ cd tmp
        $ %{QUILT_DIR}/scripts/backup-files -B ../backup/ -r -s -k foo "space 
bar"
        $ cd ..

        $ ls -l foo | awk '{ print $2 }'
        > 1
        $ ls -l "space bar" | awk '{ print $2 }'
        > 1
        $ ls -l backup/foo | awk '{ print $2 }'
        > 3
        $ ls -l "backup/space bar" | awk '{ print $2 }'
        > 2
        $ ls -l tmp/foo | awk '{ print $2 }'
        > 3
        $ ls -l "tmp/space bar" | awk '{ print $2 }'
        > 2
        $ rm -rf tmp

        # Test restore all (-)
        $ rm -f foo "space bar"
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -r -s -t -
        $ cat foo
        > foo
        $ cat "space bar"
        > bar
        $ [ "space bar" -nt bar.timeref ] || echo "touch failed"

        # Backup and restore a non-existing files
        $ mkdir "dir with spaces"
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -b -s -L new "dir with 
spaces/space file"
        $ echo data > new
        $ echo data2 > "dir with spaces/space file"
        $ ls -l backup/new | awk '{ print $5 }'
        > 0
        $ ls -l "backup/dir with spaces/space file" | awk '{ print $5 }'
        > 0
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -r -s -
        $ [ ! -e backup/new ] || echo "file backup/new shouldn't exist"
        $ [ ! -e new ] || echo "file new shouldn't exist"
        $ [ ! -e "backup/dir with spaces/space file" ] || echo "file backup/dir 
with spaces/space file shouldn't exist"
        $ [ ! -e "dir with spaces/space file" ] || echo "file dir with 
spaces/space file shouldn't exist"

        # Test restore involving a dir name with spaces
        $ echo data > "dir with spaces/space file"
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -b -L "dir with 
spaces/space file"
        > Copying dir with spaces/space file
        $ rm -rf "dir with spaces"
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -r -
        > Restoring dir with spaces/space file
        $ cat "dir with spaces/space file"
        > data
        $ [ -e backup ] && echo "backup directory not deleted"

        # Test backup reading file list from a file
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -b -s -L -f -
        < foo
        < new
        < space bar
        $ echo data > new
        $ echo modified > foo
        $ rm -f "space bar"
        $ [ -e backup/new -a ! -s backup/new ] || echo "file backup/new isn't 
empty"
        $ ls -l backup/new | awk '{ print $5 }'
        > 0
        $ cat backup/foo
        > foo
        $ cat "backup/space bar"
        > bar
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -r -s -f -
        < foo
        < new
        < space bar
        $ [ ! -e backup/new ] || echo "file backup/new shouldn't exist"
        $ [ ! -e new ] || echo "file new shouldn't exist"
        $ cat foo
        > foo
        $ cat "space bar"
        > bar

        # Test the special -L alone case
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -b -L -f -
        < new
        < foo
        < space bar
        > New file new
        > Copying foo
        > Copying space bar
        $ ln "space bar" "linked space"
        $ ls -l foo | awk '{ print $2 }'
        > 1
        $ ls -l "space bar" | awk '{ print $2 }'
        > 2
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -L -
        $ ls -l foo | awk '{ print $2 }'
        > 1
        $ ls -l "space bar" | awk '{ print $2 }'
        > 1
        $ [ ! -e new ] || echo "file new shouldn't exist"
        # Second call should be idempotent
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -L -
        $ ls -l foo | awk '{ print $2 }'
        > 1
        $ ls -l "space bar" | awk '{ print $2 }'
        > 1
        $ [ ! -e new ] || echo "file new shouldn't exist"
        $ %{QUILT_DIR}/scripts/backup-files -B backup/ -r - | sort
        > Removing new
        > Restoring foo
        > Restoring space bar
_______________________________________________
Quilt-dev mailing list
[email protected]
http://lists.nongnu.org/mailman/listinfo/quilt-dev

Reply via email to