faubackup/src Makefile.am,NONE,1.1 faubackup-find.in,NONE,1.1 faubackup-gather.c,NONE,1.1 faubackup-scatter.c,NONE,1.1 faubackup.h,NONE,1.1 faubackup.in,NONE,1.1

[email protected]
Newsgroups gmane.comp.sysutils.backup.faubackup.cvs
Message-ID <[email protected]>
Update of /cvsroot/faubackup/faubackup/src
In directory sc8-pr-cvs1:/tmp/cvs-serv3458/src

Added Files:
	Makefile.am faubackup-find.in faubackup-gather.c 
	faubackup-scatter.c faubackup.h faubackup.in 
Log Message:
restructure source tree and start to use automake


--- NEW FILE: Makefile.am ---

sbin_SCRIPTS = faubackup faubackup-find

sbin_PROGRAMS = faubackup-scatter faubackup-gather


--- NEW FILE: faubackup-find.in ---
#   FauBackup - Backup System, using a Filesystem for Storage
#   Copyright (C) 2000-2003 Martin Waitz, Dr. Volkmar Sieh
#   $Id: faubackup-find.in,v 1.1 2003/12/10 21:32:45 tali Exp $
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

use strict;

my $verbose;
my $useregexp = 1;

# little bit more shell like matching
# '*' is standard wildcard
# '**' even crosses directory boundaries
# '?' for exactly one char
# all other characters get escaped (e.g. '.')
# you can use full regexps when preceding the line with 'REGEXP:'
sub match2regexp($)
{
	my $regexp = shift;

	print STDERR "match2regexp: '$regexp' -> " if $verbose;
	if( $regexp =~ /^REGEXP:\s*(.*)$/ ) {
		$regexp = $1;
		if( !$useregexp ) {
			# replace with known invalid one, check_ignore will warn
			$regexp += "/ :-(";
		}
	} else {
		$regexp =~ s/([^\?\*a-zA-Z0-9\/])/\\$1/g; # escape everything
		$regexp =~ s/\?/./g;
		$regexp =~ s/\*\*/{}/g;			# unescaped '{}' for '**'
		$regexp =~ s/\*/[^\/]*/g;		# to not let '**' match here
		$regexp =~ s/\{\}/.*/g;
	}
	print STDERR "'$regexp'\n" if $verbose;

	return $regexp;
}

# escape all characters that could be special in a regexp
sub escape($)
{
	my $str = shift;
	$str =~ s/([^a-zA-Z0-9\/])/\\$1/g;
	return $str;
}

sub check_ignore($$)
{
	my( $ignore, $conf ) = @_;

	if( $ignore =~ /^\// ) {
		print STDERR "Warning: using absolute path in $conf\n";
		return 0;
	}
	eval { "foobar" =~ /^$ignore$/; };
	if( $@ ) {
		print STDERR "Warning: invalid ignore pattern /$ignore/" .
			" in $conf: $@\n";
		return 0;
	}

	1;
}

# read a list of patterns to ignore from the directory specified
# one entry per line, which gets prefixed by the directory name
# (to allow to specify to ignore things in subdirs
sub get_ignorelist($)
{
	my $dir = shift;
	my $ignore = "$dir/.faubackuprc";
	my @ignorelist;
	return unless -f $ignore;

	open IGNORE, $ignore or print STDERR "could not open $ignore: $!\n";
	while(<IGNORE>) {
		chomp;
		if( /^#/ ) { next; }
		if( /^\s*NoBackup:\s*(.+)\s*$/ ) {
			check_ignore($1, $ignore) or next;
			push @ignorelist, escape("$dir/") . $1;
			next;
		}
		if( /^\s*NoBackup\s*$/ ) {
			push @ignorelist, ".*";
			next;
		}
		if( /^\s*Ignore\s+(.+)\s*$/ ) {
			my $regexp = match2regexp($1);
			check_ignore($regexp, $ignore) or next;
			push @ignorelist, escape("$dir/") . $regexp;
			next;
		}
		# more things to come...
	}
	close IGNORE or print STDERR "could not close $ignore: $!";

	return @ignorelist;
}

# search one directory and print the path of every item contained therein
# recurses on subdirectories
sub process_dir($@)
{
	my( $dir, @parentlist ) = @_;
	my( $file, $ignore );
	my @entries;

	# merge list from parent with this one:
	my @ignorelist = get_ignorelist($dir);
	#push @ignorelist, @parentlist;
	foreach (@parentlist) { push @ignorelist, $_ };

	# traverse all directory entries
	unless( opendir DIR, $dir ) {
		print STDERR "could not open $dir/: $!";
		return;
	}
	@entries = readdir DIR;
	closedir DIR or die "could not close $dir/: $!";
	FILE:
	foreach $file (@entries) {
		next if $file eq '.' || $file eq '..';
		$file = "$dir/$file";
		# check ignore lists
		foreach (@ignorelist) {
			next FILE if $file =~ /^$_$/;
		}
		# not matched any filter
		# recurse if this is a directory we can cd into
		if( !-l $file && -d _ && -x _ ) {
			&process_dir( $file, @ignorelist );
		}
		# output file name
		print "$file\000";
	}

}


# command line arguments are used as wildcards of files to ignore
my @ignorelist;
foreach (@ARGV) {
	if( /^--noregexp$/ ) { #check for the only option
		$useregexp = 0;
		next;
	}
	# interpret argument as an ignore-match
	my $regexp = match2regexp $_;
	check_ignore($regexp, "command line ignore list") or next;
	push @ignorelist, "\\./$regexp";
}
# start search
process_dir( ".", @ignorelist );
print ".\000";

--- NEW FILE: faubackup-gather.c ---
/*
 *  FauBackup - Backup System, using a Filesystem for Storage
 *  Copyright (C) 2000-2003 Martin Waitz, Dr. Volkmar Sieh
 *  $Id: faubackup-gather.c,v 1.1 2003/12/10 21:32:45 tali Exp $
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */



#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <utime.h>

#include "faubackup.h"


int error;
char* progname;


static void
send_buf(void *buf, long long len)
{
	int ret;

	while( len > 0 ) {
		ret = write( 1, buf, len );
		if( ret < 0 ) {
			fprintf( stderr, "%s: write(1, ...): %s\n",
					progname, strerror(errno));
			exit(1);
		}
		buf += ret;
		len -= ret;
	}
}

static void
send_string(char *str)
{
	unsigned int tmp, len;

	len = strlen(str);
	tmp = htonl(len);

	send_buf( &tmp, sizeof(tmp) );
	send_buf( str, len );
}

static void
send_inode(struct stat64 *st)
{
	unsigned int tmp;
	int size64 = 0;

	/*
	 * send contents of inode
	 */
	assert( sizeof(tmp) == 4 );
	tmp = htonl(st->st_dev); send_buf( &tmp, sizeof(tmp) );
	tmp = htonl(st->st_ino); send_buf( &tmp, sizeof(tmp) );
	tmp = st->st_mode & 07777;
	switch( st->st_mode & ~07777 ) {
	case S_IFBLK: tmp |= B_IFBLK; break;
	case S_IFCHR: tmp |= B_IFCHR; break;
	case S_IFDIR: tmp |= B_IFDIR; break;
	case S_IFIFO: tmp |= B_IFIFO; break;
	case S_IFLNK: tmp |= B_IFLNK; break;
	case S_IFREG: tmp |= B_IFREG; break;
	case S_IFSOCK: tmp |= B_IFSOCK; break;
	default: assert(0); break;
	}
	if(st->st_size>>32) {
		tmp |= B_IFSIZE64;
		size64 = 1;
	}
	tmp = htonl(tmp);
	send_buf( &tmp, sizeof(tmp) );
	tmp = htonl(st->st_uid); send_buf( &tmp, sizeof(tmp) );
	tmp = htonl(st->st_gid); send_buf( &tmp, sizeof(tmp) );
	tmp = htonl(st->st_rdev); send_buf( &tmp, sizeof(tmp) );
	tmp = htonl(st->st_size); send_buf( &tmp, sizeof(tmp) );
	if( size64 ) {
		tmp = htonl(st->st_size>>32); send_buf( &tmp, sizeof(tmp) );
	}
/*	tmp = htonl(st->st_atime); send_buf( &tmp, sizeof(tmp) );*/
	tmp = htonl(st->st_mtime); send_buf( &tmp, sizeof(tmp) );
}


static void
gather_blk(char *path, struct stat64 *st)
{
	assert( S_ISBLK(st->st_mode) );

	send_inode( st );
	send_string( path );
}


static void
gather_chr(char *path, struct stat64 *st)
{
	assert( S_ISCHR(st->st_mode) );

	send_inode( st );
	send_string( path );
}


static void
gather_reg(char *path, struct stat64 *st)
{
	int fd;
	int ret;
	long long pos;
	struct utimbuf times;

	assert( S_ISREG(st->st_mode) );

	fd = open64( path, O_RDONLY );
	if( fd < 0) {
		fprintf( stderr, "%s: open64(%s, O_RDONLY): %s\n",
				progname, path, strerror(errno));
		error++;
		return;
	}

	send_inode( st );
	send_string( path );

	for( pos = 0; pos < st->st_size; ) {
		char buf[1024*1024];
		long long count;
		long long len;

		count = st->st_size - pos;
		if( sizeof(buf) < count ) {
			count = (long long)sizeof(buf);
		}
		len = read(fd, buf, count);
		if( len < 0) {
			fprintf(stderr, "%s: read %s: %s\n",
					progname, path, strerror(errno));
			error++;

			/* ignore any error and send `count' bytes */
			/* otherwise we will get a protocol error */
			len = count;
		}

		send_buf( buf, len );

		pos += len;
	}

	ret = close(fd);
	if( ret < 0) {
		fprintf(stderr, "%s: close %s: %s\n",
				progname, path, strerror(errno));
		error++;
		return;
	}

	/* reset atime of file */
	times.actime = st->st_atime;
	times.modtime = st->st_mtime;
	ret = utime( path, &times );
	if( ret<0 && errno != EROFS && errno != EPERM ) {
		fprintf(stderr, "%s: utime %s: %s\n",
				progname, path, strerror(errno));
		return;
	}
}


static void
gather_lnk(char *path, struct stat64 *st)
{
	char link[MAXLEN];
	int ret;

	assert( S_ISLNK(st->st_mode) );

	ret = readlink(path, link, sizeof(link) - 1);
	if( ret < 0) {
		fprintf(stderr, "%s: readlink %s: %s\n",
				progname, path, strerror(errno));
		error++;
		return;
	}
	link[ret] = '\0';

	send_inode( st );
	send_string( path );
	send_string( link );
}


static void
gather_dir(char *path, struct stat64 *st)
{
	assert( S_ISDIR(st->st_mode) );

	send_inode( st );
	send_string( path );
}

static void
gather(char *path)
{
	int ret;
	struct stat64 st;

	ret = stat64(".", &st);
	if( ret < 0) {
		fprintf(stderr, "%s: stat64(\".\", &st): %s\n",
				progname, strerror(errno));
		error++;
		exit( 1 );
	}

	ret = lstat64( path, &st );
	if( ret < 0) {
		fprintf(stderr, "%s: lstat64 %s: %s\n",
				progname, path, strerror(errno));
		error++;
		return;
	}

	if( S_ISBLK(st.st_mode) ) {
		gather_blk(path, &st);
	} else if( S_ISCHR(st.st_mode) ) {
		gather_chr(path, &st);
	} else if( S_ISREG(st.st_mode) ) {
		gather_reg(path, &st);
	} else if( S_ISLNK(st.st_mode) ) {
		gather_lnk(path, &st);
	} else if( S_ISDIR(st.st_mode) ) {
		gather_dir(path, &st);
	} else if( S_ISFIFO(st.st_mode)
		|| S_ISSOCK(st.st_mode) ) {
		/* skip fifo/socket */
		/* nothing to do */
	} else {
		fprintf( stderr, "%s: %s: unknown mode 0%o\n",
				progname, path, (int) st.st_mode );
		error++;
		return;
	}

}

int
main(int argc, char* argv[])
{
	char path[MAXLEN];
	int verbose;
	int c, len;

	error=0;
	progname=argv[0];
	verbose=0;

	if( argc==2 && strcmp(argv[1], "-v")==0 ) {
		verbose=1;
	} else if( argc>1 ) {
		fprintf(stderr, "Usage: %s [-v]\n", progname);
		exit( 1 );
	}

	len = 0;
	while( (c=fgetc(stdin)) != EOF ) {
		path[len++] = c;

		if( len==sizeof(path) ) {
			/* didn't fit in here -> skip */
			path[sizeof(path)-1] = '\0';
			fprintf(stderr, "path too long: %s...!\n", path);
			while( fgetc(stdin)>0 );
			len = 0;
			continue;
		}

		if( c!='\0' ) continue;
		/* one filename read, process it */

		if( verbose) {
			fprintf(stderr, "%s\n", path);
		}

		gather( path );
		len = 0;
	}

	return( error!=0 );
}

--- NEW FILE: faubackup-scatter.c ---
/*
 *  FauBackup - Backup System, using a Filesystem for Storage
 *  Copyright (C) 2000-2003 Martin Waitz, Dr. Volkmar Sieh
 *  $Id: faubackup-scatter.c,v 1.1 2003/12/10 21:32:45 tali Exp $
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <utime.h>

#include "faubackup.h"


#define MAXDATE 256

int error;
int verbose;
char* progname;
static char lastdate[MAXDATE];


static int can_set_owner = 1;


static int
recv_buf(void *buf, long long len, int mayfail)
{
	void *b;
	long long ret;

	b = buf;
	while( len>0 ) {
		ret = read(0, b, len);
		if( ret == 0 ) break;
		if( ret < 0 ) {
			fprintf(stderr, "%s: read: %s\n",
					progname, strerror(errno));
			break;
		}
		b += ret;
		len -= ret;
	}

	if( len!=0 && ! mayfail ) {
		fprintf(stderr, "%s: protocol error (%lld bytes missing)\n",
				progname, len);
		exit(1);
	}

	return( b-buf );
}

static char*
recv_string()
{
	unsigned int tmp;
	char* str;

	recv_buf( &tmp, sizeof(tmp), 0 );
	tmp = ntohl(tmp);

	str = malloc( tmp+1 );

	recv_buf(str, tmp, 0);
	str[tmp] = '\0';

	return str;
}

static struct stat64*
recv_inode()
{
	static struct stat64 st;
	unsigned int tmp;
	int size64=0;

	/*
	 * recv contents of inode
	 */
	if( recv_buf( &tmp, sizeof(tmp), 1 ) != sizeof(tmp) ) return (NULL);
	st.st_dev = ntohl(tmp);

	recv_buf( &tmp, sizeof(tmp), 0 );
	st.st_ino = ntohl(tmp);

	recv_buf( &tmp, sizeof(tmp), 0 );
	tmp = ntohl(tmp);

	if(tmp & B_IFSIZE64) {
		size64 = 1;
		tmp &= ~B_IFSIZE64;
	}
	st.st_mode = tmp & 07777;
	switch (tmp & ~07777) {
	case B_IFBLK:	st.st_mode |= S_IFBLK; break;
	case B_IFCHR:	st.st_mode |= S_IFCHR; break;
	case B_IFDIR:	st.st_mode |= S_IFDIR; break;
	case B_IFIFO:	st.st_mode |= S_IFIFO; break;
	case B_IFLNK:	st.st_mode |= S_IFLNK; break;
	case B_IFREG:	st.st_mode |= S_IFREG; break;
	case B_IFSOCK:	st.st_mode |= S_IFSOCK; break;
	default:
		fprintf( stderr, "%s: protocol error: mode 0%o\n",
				progname, tmp );
		exit(1);
	}
	recv_buf( &tmp, sizeof(tmp), 0 ); st.st_uid = ntohl(tmp);
	recv_buf( &tmp, sizeof(tmp), 0 ); st.st_gid = ntohl(tmp);
	recv_buf( &tmp, sizeof(tmp), 0 ); st.st_rdev = ntohl(tmp);
	recv_buf( &tmp, sizeof(tmp), 0 ); st.st_size = ntohl(tmp);
	if(size64) {
		recv_buf( &tmp, sizeof(tmp), 0 );
		st.st_size += (__off64_t) ntohl(tmp)<<32;
	}
	/*recv_buf( &tmp, sizeof(tmp), 0 ); st.st_atime = ntohl(tmp);*/
	recv_buf( &tmp, sizeof(tmp), 0 ); st.st_mtime = ntohl(tmp);

	return( &st );
}


static int
make_path(char *path)
{
	char *dir;
	char *d,*p;
	int ret;

	dir = malloc( strlen(path)+1 );
	strcpy(dir, "");
	d=dir; p=path;

	while( 1 ) {
		/* copy one directory entry to dir */
		while( *p!='\0' && *p!='/' ) *d++ = *p++;
		if( *p=='\0' ) break;
		*d='\0';
		/* create directory */
		ret = mkdir(dir, 0700);
		if( ret<0 && errno!=EEXIST ) {
			fprintf( stderr, "%s: mkdir(\"%s\", 0x700): %s\n",
					progname, dir, strerror(errno) );
			return( ret );
		}
		/* goto next part of path */
		*d++='/'; p++;
	}
	free( dir );

	return(0);
}

static void
dir_getlast()
{
	DIR *d;
	struct dirent *de;
	struct stat64 st;
	time_t lastt;
	int ret;

	d = opendir(".");
	if( d == (DIR *) 0 ) {
		fprintf(stderr, "%s: opendir(\".\"): %s\n",
				progname, strerror(errno));
		exit(1);
	}
	lastt = 0;
	while( (de = readdir(d)) != (struct dirent *) 0 ) {
		char *p;

		if( strcmp(de->d_name, ".") == 0
		 || strcmp(de->d_name, "..") == 0 ) {
			continue;
		}

		p = de->d_name;
		(void) strtol(p, &p, 10);
		if( *p != '-') continue;
		p++;
		(void) strtol(p, &p, 10);
		if( *p != '-') continue;
		p++;
		(void) strtol(p, &p, 10);
		if( *p != '@') continue;
		p++;
		(void) strtol(p, &p, 10);
		if( *p != ':') continue;
		p++;
		(void) strtol(p, &p, 10);
		if( *p != ':') continue;
		p++;
		(void) strtol(p, &p, 10);
		if( *p != '\0') continue;

		ret = lstat64( de->d_name, &st );
		if( ret < 0 ) {
			fprintf(stderr, "%s: lstat64(%s, &newst): %s\n",
					progname, de->d_name, strerror(errno));
			exit(1);
		}

		if( ! S_ISDIR(st.st_mode) ) {
			continue;
		}

		if( lastt < st.st_mtime ) {
			lastt = st.st_mtime;
			strncpy( lastdate, de->d_name, MAXDATE );
		}
	}
	closedir(d);

	if( lastt == 0 ) {
		strcpy( lastdate, "1970-01-01@00:00:00" );
	}

	if( verbose ) {
		printf( "date of last backup: %s\n", lastdate );
	}
}

/*
 * create a temporary directory for the backup and chdir into it
 * will return the directories name
 */
static char *
dir_create()
{
	static char workdir[MAXLEN];
	int ret;

	snprintf( workdir, MAXDATE, "working-%d", getpid() );
	ret = mkdir( workdir, 0700 );
	if( ret<0 ) {
		fprintf( stderr, "%s: mkdir(\"%s\"): %s\n",
				progname, workdir, strerror(errno) );
		exit( 1 );
	}

	ret = chdir( workdir );
	if( ret<0 ) {
		fprintf( stderr, "%s: chdir(\"%s\"): %s\n",
				progname, workdir, strerror(errno) );
		exit( 1 );
	}

	return( workdir );
}

/*
 * change name and time of the temporary directory to match the current time
 */
static int
dir_finalize(char *workdir)
{
	char newdir[MAXDATE];
	time_t thist;
	struct tm *tm;
	struct utimbuf u;
	int ret;

	time(&thist);
	tm = localtime(&thist);

	time(&thist);
	tm = localtime(&thist);
	u.actime = thist;
	u.modtime = thist;
	ret = utime( ".", &u );
	if( ret < 0 ) {
		fprintf( stderr, "%s: utime(\".\", [...]): %s\n",
				progname, strerror(errno) );
		error++;
	}

	ret = chdir("..");
	if( ret<0 ) {
		fprintf( stderr, "%s: chdir(\"..\"): %s\n",
				progname, strerror(errno) );
		return( ret );
	}
	snprintf( newdir, MAXDATE, "%04d-%02d-%02d@%02d:%02d:%02d",
			tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
			tm->tm_hour, tm->tm_min, tm->tm_sec );
	if( error>0 ) {
		strncat( newdir, "-broken", MAXDATE );
	}
	
	ret = rename( workdir, newdir );
	if( ret<0 ) {
		fprintf( stderr, "%s: rename(\"%s\", \"%s\"): %s\n",
				progname, workdir, newdir, strerror(errno) );
		return( ret );
	}

	return( 0 );
}


static char *
actdevinopath(dev_t dev, ino_t ino)
{
	static char path[MAXLEN];

	snprintf( path, MAXLEN, "..inodes/%08x/%02x/%02x/%02x/%02x",
			(int) dev,
			((int) ino >> 24) & 0xff,
			((int) ino >> 16) & 0xff,
			((int) ino >>  8) & 0xff,
			((int) ino >>  0) & 0xff );
	return( path );
}


static char *
olddevinopath(dev_t dev, ino_t ino)
{
	static char path[MAXLEN];

	snprintf( path, MAXLEN, "../%s/..inodes/%08x/%02x/%02x/%02x/%02x",
			lastdate, (int) dev,
			((int) ino >> 24) & 0xff,
			((int) ino >> 16) & 0xff,
			((int) ino >>  8) & 0xff,
			((int) ino >>  0) & 0xff );
	return( path );
}

static void
cannot_set_owner(void)
{
	if( !can_set_owner ) {
		/* only print warning once */
		return;
	}
	fprintf( stderr, "%s: WARNING: cannot set ownership of files\n",
			progname );
	can_set_owner = 0;
}

static const int SET_OWNER	= 1;
static const int SET_MODE	= 2;
static const int SET_TIME	= 4;
static const int SET_ALL = -1;

/*
 * Set ownership, access mode and timestamps
 */
static void
set_perms(char *path, struct stat64 *st, int flags)
{
	mode_t mode;
	struct utimbuf u;
	int ret;

	mode = st->st_mode & 07777;

	if( flags & SET_TIME ) {
		u.actime = st->st_atime;
		u.modtime = st->st_mtime;
		ret = utime( path, &u );
		if( ret < 0 ) {
			fprintf( stderr, "%s: utime(%s, [...]): %s\n",
					progname, path, strerror(errno) );
			error++;
		}
	}

	/* set owner, unset suid/sgid bits if chown fails */
	if( flags & SET_OWNER ) {
		ret = lchown(path, st->st_uid, st->st_gid);
		if( ret < 0 && errno == EPERM ) {
			cannot_set_owner();
			mode &= ~06000;
		} else if( ret < 0 ) {
			fprintf( stderr, "%s: lchown(%s, %d, %d): %s\n",
					progname, path, (int) st->st_uid,
					(int) st->st_gid, strerror(errno) );
			error++;
			mode &= ~06000;
		}
	}

	if( flags & SET_MODE ) {
		ret = chmod(path, mode);
		if( ret < 0 ) {
			fprintf( stderr, "%s: chmod(%s, %03o): %s\n",
					progname, path, (int) mode,
					strerror(errno) );
			error++;
		}
	}
}

static void
scatter_blk(char *path, struct stat64 *st)
{
	int ret;

	assert( S_ISBLK(st->st_mode) );

	/*
	 * create node
	 */
	ret = mknod( path, S_IFBLK, st->st_rdev );
	if( ret < 0 ) {
		fprintf( stderr, "%s: mknod(%s, S_IFBLK, 0x%x): %s\n",
				progname, path,
				(int) st->st_rdev, strerror(errno) );
		if( errno==ENOSPC ) exit(1);
		error++;
		return;
	}

	set_perms( path, st, SET_ALL );
}


static void
scatter_chr(char *path, struct stat64 *st)
{
	int ret;

	assert( S_ISCHR(st->st_mode) );

	/*
	 * create node
	 */
	ret = mknod( path, S_IFCHR, st->st_rdev );
	if( ret < 0 ) {
		fprintf(stderr, "%s: mknod(%s, S_IFCHR, 0x%x): %s\n",
				progname, path,
				(int) st->st_rdev, strerror(errno) );
		if( errno==ENOSPC ) exit(1);
		error++;
		return;
	}

	set_perms( path, st, SET_ALL );
}


static void
scatter_reg(char *path, struct stat64 *st)
{
	char *actfile;
	char *oldfile;
	struct stat64 oldst;
	int equal;
	long long pos;
	int tfd;
	int ofd;
	long long ret;


	assert( S_ISREG(st->st_mode) );

	oldfile = olddevinopath( st->st_dev, st->st_ino );
	actfile = actdevinopath( st->st_dev, st->st_ino );
	make_path( actfile );

	equal = 1;

	/*
	 * compare file with file saved last time
	 */

	ret = lstat64( oldfile, &oldst );
	if( ret < 0 && errno == ENOENT ) {
		equal = 0;
	} else if( ret < 0 ) {
		fprintf( stderr, "%s: lstat64 %s: %s\n",
				progname, oldfile, strerror(errno) );
		error++;
		equal = 0;
	}

	/* !! do _not_ check atime; will be changed by backup itself !! */
	if( st->st_mode != oldst.st_mode
	 || st->st_size != oldst.st_size
	 /* || st->st_atime != oldst.st_atime */
	 || st->st_mtime != oldst.st_mtime ) {
		/* different inode => use new one */
		equal = 0;
	}
	
	/* ================================================= */
	/* copy file to local disk and compare with old file */
	/* ================================================= */

	tfd = open64( actfile, O_WRONLY | O_CREAT | O_EXCL, st->st_mode & 0700 );
	if( tfd < 0 ) {
		if( errno!=EEXIST ) {
			fprintf( stderr, "%s: open64(%s, O_WRONLY): %s\n",
					progname, actfile, strerror(errno) );
			if( errno==ENOSPC ) exit(1);
			error++;
		}
		equal = 0;
		/* can't write this file, but go on reading it,
		 * to be able to handle the following files
		 */
		tfd = open64( "/dev/null", O_WRONLY );
		if( tfd < 0 ) {
			fprintf( stderr, "%s: open64(\"/dev/null\", O_WRONLY): %s\n",
					progname, strerror(errno) );
			exit( 1 );
		}
	}
	if( equal ) {
		ofd = open64( oldfile, O_RDONLY );
		if( ofd < 0 && errno != ENOENT ) {
			/*
			 * problem with Solaris:
			 * Solaris will report "Permission denied"
			 * with "-rw--l---" files even with "no_root_squash"
			 */
			fprintf( stderr, "%s: WARNING: open64(%s, O_RDONLY): %s\n",
					progname, oldfile, strerror(errno) );
			error++;
		}
		if( ofd < 0 ) {
			equal = 0;
		}
	} else {
		ofd=-1;
	}

	for( pos = 0; pos < st->st_size; ) {
		static char zero[4096] = { 0, 0, /* ... */ };
		char buf[4096];
		char obuf[4096];
		long long len;
		long long n_read, n_written;

		n_read=0;
		n_written=0;

		if( st->st_size - pos < (long long)sizeof(buf) ) {
			len = st->st_size - pos;
		} else {
			len = (long long)sizeof(buf);
		}
		len = recv_buf( buf, len, 0 );

		/* compare files */
		if( equal && ofd>=0 ) while( (n_read < len) && equal ) {
			ret = read( ofd, obuf, len-n_read );
			if( ret < 0 ) {
				equal = 0;
			} else if( memcmp(buf, obuf, len) != 0 ) {
				equal = 0;
			}
			n_read += ret;
		}
		if( len==(long long)sizeof(zero) && pos + len != st->st_size
				&& memcmp(buf, zero, len) == 0 ) {
			/* found an empty page, create hole in target file */
			ret = lseek64( tfd, len, SEEK_CUR );
			if( ret < 0 ) {
				fprintf( stderr, "%s: lseek64(%d, %lld, SEEK_CUR) on %s : %lld: %s\n",
						progname, tfd, len, actfile, ret, strerror(errno) );
				error++;
			}
			/*
			 * do not assume 'ret' contains meaningful
			 * position data (32-bit problem)
			 */
			/* assert(ret == pos + len); */

		} else while( n_written<len ) {
			/* write this page into a temp. file */
			ret = write( tfd, buf, len );
			if( ret < 0 ) {
				/* try again? */
				if( errno==EAGAIN || errno==EINTR ) continue;

				fprintf( stderr, "%s: write %s: %s\n",
						progname, actfile, strerror(errno) );
				/* fatal error on 'no space left on device' */
				if( errno==ENOSPC ) exit( 1 );
				error++;
				ret = 0;
			}
			n_written += ret;
		}
		pos += len;
	}

	if( ofd>=0 ) {
		ret = close( ofd );
		if( ret < 0 ) {
			fprintf( stderr, "%s: close %s: %s\n",
					progname, oldfile, strerror(errno) );
			error++;
		}
	}
	ret = close(tfd);
	if( ret < 0 ) {
		fprintf( stderr, "%s: close %s: %s\n",
				progname, actfile, strerror(errno) );
		error++;
	}

	set_perms( actfile, st, SET_ALL );

	/*
	 * check difference in uid/gid only after we tried to set
	 * them, as we must not check for them when we are unable
	 * to change ownership, which may happen when we are not
	 * called by root
	 */
	if( st->st_uid != oldst.st_uid || st->st_gid != oldst.st_gid ) {
		if( can_set_owner ) equal = 0;
	}

	if( equal ) {
		ret = unlink(actfile);
		if( ret < 0 ) {
			fprintf( stderr, "%s: unlink %s: %s\n",
					progname, actfile, strerror(errno) );
			error++;
		} else {
			ret = link( oldfile, actfile );
			if( ret < 0 ) {
				fprintf( stderr, "%s: link %s, %s: %s\n",
						progname, oldfile, actfile, strerror(errno) );
				error++;
			}
		}
	}

	/*
	 * link file into directory
	 */
	ret = link( actfile, path );
	if( ret < 0 ) {
		fprintf( stderr, "%s: link %s, %s: %s\n",
				progname, actfile, path, strerror(errno) );
		error++;
	}
}


static void
scatter_lnk(char *path, struct stat64 *st)
{
	char *link;
	int ret;

	assert( S_ISLNK(st->st_mode) );

	/*
	 * read link
	 */
	link = recv_string();

	/*
	 * create link
	 */
	ret = symlink( link, path );
	if( ret < 0 ) {
		fprintf( stderr, "%s: symlink %s, %s: %s\n",
				progname, link, path, strerror(errno) );
		if( errno==ENOSPC ) exit(1);
		error++;
		return;
	}

	free( link );

	set_perms( path, st, SET_OWNER );
}


static void
scatter_dir(char *path, struct stat64 *st)
{
	int ret;

	assert( S_ISDIR(st->st_mode) );

	/*
	 * generate new directory
	 */
	ret = mkdir( path, 0000 );
	if( ret < 0 && errno!=EEXIST ) {
		fprintf( stderr, "%s: mkdir(%s, 0000): %s\n",
				progname, path, strerror(errno) );
		if( errno==ENOSPC ) exit(1);
		error++;
		return;
	}

	set_perms( path, st, SET_ALL );
}


int
main(int argc, char* argv[])
{
	char *path, *workdir;
	struct stat64 *st;
	int ret;

	error=0;
	progname=argv[0];
	verbose=0;

	if( argc==2 && strcmp(argv[1], "-v")==0 ) {
		verbose=1;
	} else if( argc>1 ) {
		fprintf( stderr, "Usage: %s [-v]\n", progname );
		exit( 1 );
	}


	/* ======================= */
	/* get date of last backup */
	/* ======================= */

	dir_getlast();

	/* =========== */
	/* unset umask */
	/* =========== */

	ret = umask(0000);
	if( ret < 0 ) {
		fprintf( stderr, "%s: umask(0): %s\n",
				progname, strerror(errno) );
		exit( 1 );
	}

	/* ========= */
	/* do backup */
	/* ========= */

	workdir = dir_create();

	for(st=recv_inode(); st; st=recv_inode()) {
		path=recv_string();
		if( verbose ) {
			printf( "%s\n", path );
		}
		if( path[0]=='/' || strncmp(path, "../", 3)==0
				|| strstr( path, "/../" ) ) {
			fprintf( stderr, "%s: %s: illegal path name, aborting\n",
				progname, path );
			exit( 1 );
		}
		make_path( path );
		if( S_ISBLK(st->st_mode) ) {
			scatter_blk( path, st );
		} else if( S_ISCHR(st->st_mode) ) {
			scatter_chr( path, st );
		} else if( S_ISREG(st->st_mode) ) {
			scatter_reg( path, st );
		} else if( S_ISLNK(st->st_mode) ) {
			scatter_lnk( path, st );
		} else if( S_ISDIR(st->st_mode) ) {
			scatter_dir( path, st );
		} else if( S_ISFIFO(st->st_mode)
			|| S_ISSOCK(st->st_mode)) {
			/* shouldn't be gathered */
			assert(0);
			abort();
		} else {
			fprintf( stderr, "%s: %s: unknown mode 0%o\n",
					progname, path, (int) st->st_mode );
		}
		
		ret = strcmp(path, ".");
		free( path );
	}

	/* check that backup was complete */
	if( ret!=0 ) {
		fprintf( stderr, "backup not complete! '.' should be last.\n");
		error++;
	}
	/* rename backup dir to commit our work */
	dir_finalize( workdir );

	return( error!=0 );
}

--- NEW FILE: faubackup.h ---
/*
 *  FAUBackup - Backup System, using a Filesystem for Storage
 *  Copyright (C) 2000-2003 Martin Waitz, Dr. Volkmar Sieh
 *  $Id: faubackup.h,v 1.1 2003/12/10 21:32:45 tali Exp $
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#define B_IFIFO		0010000
#define B_IFCHR		0020000
#define B_IFDIR		0040000
#define B_IFBLK		0060000
#define B_IFREG		0100000
#define B_IFLNK		0120000
#define B_IFSOCK	0140000

#define B_IFSIZE64	0200000

#define MAXLEN		1024


--- NEW FILE: faubackup.in ---
#   FauBackup - Backup System, using a Filesystem for Storage
#   Copyright (C) 2000-2003 Martin Waitz, Dr. Volkmar Sieh
#   $Id: faubackup.in,v 1.1 2003/12/10 21:32:45 tali Exp $
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

use strict;

use File::stat;
use Getopt::Long;

my $version	= '@VERSION@';
my $conffile	= '/etc/faubackup.conf';

my $usage= <<END;
Usage:	$0 [options] [machine:]srcdir [destdir]
	$0 [options] --clean [destdir]
	$0 [--list[=destdir]]
END
my $help = $usage . <<END;

This Program uses a filesystem on a hard drive for incremental and full backups.

Some Options:
	-v	Verbose mode, print processed directories, size when listing
	--list	show info about backups already created

	--clean	remove old backups before/instead making a fresh one.

	machine	if given, backup will be done from remote machine.
	srcdir	This Directory gets backed up.
	destdir	Place to hold the backup.
		A default directory will be chosen if omitted.

See faubackup(8) for more information about using $0.
$0 relies on faubackup-gather and faubackup-scatter to do the actual backup.

FauBackup version $version,
Copyright (c) 2000-2003 Martin Waitz, Dr. Volkmar Sieh.
Developed at Friedrich Alexander University Erlangen-Nuremberg.
FauBackup comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it under
certain conditions; see the GNU General Public License for details.
END


#####################################################################

#
# defaults
#
$FAUBACKUP::backup	= "/backup/MACHINE:DIR";
$FAUBACKUP::autocreate	= 1;
$FAUBACKUP::keepyears	= 2;
$FAUBACKUP::keepmonths	= 12;
$FAUBACKUP::keepweeks	= 4;
$FAUBACKUP::keepdays	= 7;
$FAUBACKUP::keeplastofday = 0;
$FAUBACKUP::rsh		= "rsh";
$FAUBACKUP::gather	= "faubackup-gather";
$FAUBACKUP::scatter	= "faubackup-scatter";
$FAUBACKUP::find	= "faubackup-find";
$FAUBACKUP::getroot     = "";
@FAUBACKUP::ignore	= ();

#
# read configuration
#
do $conffile or die "could not read $conffile: $!" if -r $conffile;

my( $backup, $autocreate, $rsh, $keep, $keeplastofday );
my( $find, $getroot, @ignore );
$backup = $FAUBACKUP::backup;
$autocreate = $FAUBACKUP::autocreate;
$keep->{YEAR} = $FAUBACKUP::keepyears;
$keep->{MONTH} = $FAUBACKUP::keepmonths;
$keep->{WEEK} = $FAUBACKUP::keepweeks;
$keep->{DAY} = $FAUBACKUP::keepdays;
$keeplastofday = $FAUBACKUP::keeplastofday;
$rsh = $FAUBACKUP::rsh;
$find = $FAUBACKUP::find;
$getroot = $FAUBACKUP::getroot;
@ignore = @FAUBACKUP::ignore;

$rsh=$ENV{'FAUBACKUP_RSH'} if $ENV{'FAUBACKUP_RSH'};

# globals
my $time = time;
my @localtime = localtime($time);


# global variables set by command line
my $show_help=0;
my $show_usage=0;
my $show_version=0;
my $verbose=0;
my $srcdir;
my $destdir;
my @list=();
my @clean=();
my $gatherdir;
my $scatterdir;
my $ignored;


#####################################################################

# return a list of backup directories
sub backups_in($)
{
	my $expr = shift;
	$expr =~ s#MACHINE#*#;
	$expr =~ s#DIR#*#;
	return glob $expr;
}

sub backupdir(@)
{
	return backups_in($backup) if @_==1 && $_[0] eq '';
	return @_;
}


sub backup($)
{
	my( $dir ) = @_;

	#
	# get date of all backups
	#
	my( $entry, @backup );
	opendir DIR, $dir or die "opendir $dir: $!";
	foreach $entry (readdir DIR) {
		if( $entry =~ /^(working|broken)-\d+$/ ) {
			print STDERR "warning: there still is '$entry' in $dir\n";
			next;
		}
		next unless $entry =~ /^\d+-\d+-\d+@\d+:\d+:\d+$/;
		my( $date, $sb );
		$date = {};
		$date->{ENTRY} = $entry;
		$date->{DIR} = "$dir/$entry";
		$sb = lstat($date->{DIR});
		# safety checks:
		next unless -d _;
		next unless -d "$date->{DIR}/..inodes";

		$date->{MTIME} = $sb->mtime;
		my @time = localtime($date->{MTIME});
		# calculate how old this date is:
		$date->{YEAR} = $localtime[5] - $time[5];
		$date->{MONTH} = $date->{YEAR}*12 + $localtime[4] - $time[4];
		# 5.1.1970 was a monday -> substract 4 days to get #weeks:
		$date->{WEEK} = int(($time-4*24*3600) / (7*24*3600)) -
				int(($date->{MTIME}-4*24*3600) / (7*24*3600));
		$date->{DAY} =  int($time / (24*3600)) -
				int($date->{MTIME} / (24*3600));
		push @backup, $date;
	}
	closedir DIR or die "closedir $dir: $!";

	return sort {$a->{MTIME} <=> $b->{MTIME}} @backup;
}

sub collect_metadata($$); # to make perl happy with recursion
sub collect_metadata($$)
{
	my( $date, $dir ) = @_;

	my $entry;
	my @subdirs;
	opendir DIR, $dir or die "opendir $dir: $!";
	foreach $entry (readdir DIR) {
		next if $entry eq '.' || $entry eq '..';
		my $s = lstat "$dir/$entry";
		if( -d _ ) {
			push @subdirs, "$dir/$entry";
		} else {
			# size in K
			my $size = $s->blocks / 2; # assumes 512byte blocks
			my $dev = $s->dev;
			my $ino = $s->ino;
			$date->{FILES}{"$dev/$ino"} = $size;
			if( -f _ && $s->nlink==2 ) {
				# this file is only used by this backup
				$date->{SIZE} += $size;
			}
		}
	}
	closedir DIR or die "closedir $dir: $!";

	foreach $dir (@subdirs) { collect_metadata($date, $dir) }
}

sub compare_space($$)
{
	my( $date, $last ) = @_;

	$date->{FADD} = $date->{FREM} = 0;
	$date->{SADD} = $date->{SREM} = 0;
	$date->{SIZE} = 0;

	$date->{FILES} = {};

	collect_metadata( $date, "$date->{DIR}/..inodes" );

	my $file;
	foreach $file (keys %{$date->{FILES}}) {
		if( $last->{FILES}{$file} ) {
			# this file is linked with old backupd
			delete $last->{FILES}{$file};
		} else {
			# new/changed file
			$date->{FADD}++;
			$date->{SADD} += $date->{FILES}{$file};
		}
	}
	foreach $file (keys %{$last->{FILES}}) {
		# removed/changed file
		$date->{FREM}++;
		$date->{SREM} += $last->{FILES}{$file};
	}
}



#
# Cleanup Funktion
#

sub cleanup($)
{
	my( $dir ) = @_;

	print STDERR "checking for obsolete backups in '$dir'\n" if $verbose>1;

	my( @backup, @obsolete );
	@backup = backup $dir;

	#
	# remove obsolete backups
	#
	my( $date, $last );
	$last = { YEAR=>-1, MONTH=>-1, WEEK=>-1, DAY=>-1 };
	foreach $date (@backup) {
		my $keepme = 0;

		my $type;
		foreach $type (qw(DAY WEEK MONTH YEAR)) {
			# we will keep it if this is the first backup
			# of an interval (day, week, etc)
			$keepme ||= ($date->{$type}!=$last->{$type} &&
					$date->{$type}<=$keep->{$type});
		}

		if( $keeplastofday && $date->{DAY}==$last->{DAY} &&
					$date->{DAY}<=$keep->{DAY} ) {
			# this backup was made on the same day as the last
			# and the user wants to keep the most recent backup
			$keepme = 1;
			push @obsolete, $last;
		}
		
		push @obsolete, $date unless $keepme;
		$last = $date;
	}

	foreach $date (@obsolete) {
		my @cmd = ('/bin/rm', '-rf', '--', $date->{DIR});
		print STDERR "deleting old $date->{DIR}\n" if $verbose==1;
		print STDERR "@cmd\n" if $verbose>1;
		(system {$cmd[0]} @cmd) == 0 or
			die "@cmd: exit $?";
	}
}

sub list($)
{
	my( $dir ) = @_;

	print "$dir\n";

	my( @backup );
	@backup = backup $dir;

	my( $date, $last, %first );
	$last = { YEAR=>-1, MONTH=>-1, WEEK=>-1, DAY=>-1 };
	%first = ( YEAR=>"", MONTH=>"", WEEK=>"", DAY=>"" );
	foreach $date (@backup) {
		my $type;
		my @types=();
		# this is similar to cleanup computation
		# FIXME: dosn't honor $keeplastofday yet
		foreach $type (qw(YEAR MONTH WEEK DAY)) {
			if( $date->{$type}<=$keep->{$type} ) {
				$first{$type} ||= $date->{ENTRY};
				push @types, $type
					if $date->{$type}!=$last->{$type};
			}
		}
		# get information about backup sizes if requested
		if( $verbose ) {
			compare_space($date, $last);
			print "\t$date->{ENTRY} " .
				"f+$date->{FADD}-$date->{FREM} " .
				"size+$date->{SADD}-$date->{SREM}:$date->{SIZE} " .
				"@types\n";
		}
		$last = $date;
	}

	if( !$verbose ) {
		print "\t  first daily backup on $first{DAY}\n" if $first{DAY};
		print "\t first weekly backup on $first{WEEK}\n" if $first{WEEK} ne $first{DAY};
		print "\tfirst monthly backup on $first{MONTH}\n" if $first{MONTH} ne $first{WEEK};
		print "\t first yearly backup on $first{YEAR}\n" if $first{YEAR} ne $first{MONTH};
	}
	print "\tdoes not contain backups\n" unless $first{DAY}||$first{WEEK}||$first{MONTH}||$first{YEAR};
	print "\n";
}

# escape for use in shell command
sub escape($)
{
	my( $str ) = @_;

	$str =~ s/'/'\\''/g;
	return "\'$str\'";
}


#####################################################################

#
# command line parsing
#


# Configure and "...+" entries doesn't work in perl5.004 :(
#Getopt::Long::Configure( qw(bundling) );
GetOptions(	"help|h" => \$show_help,
		"usage" => \$show_usage,
		"version" => \$show_version,
#		"verbose|v+" => \$verbose,
		"verbose|v" => \$verbose,
		"debuglevel=i" => \$verbose,
		"ignore=s@" => \@ignore,
		"rsh=s" => \$rsh,
		"clean:s@" => \@clean,
		"list|l:s@" => \@list,
		"years|y=i" => \$keep->{YEAR},
		"months|m=i" => \$keep->{MONTH},
		"weeks|w=i" => \$keep->{WEEK},
		"days|d=i" => \$keep->{DAY},
		"keep-last" => \$keeplastofday,
		# deprecated options:
		"p" => \$ignored,
		"i=s" => \$scatterdir,
		"o=s" => \$gatherdir,
) or $show_usage=1;


if( $show_help ) {
	print $help;
	exit 0;
}
if( $show_version ) {
	print "faubackup $version\n";
	exit 0;
}

# default action is '--list'
@list=('') if @list==0 && @ARGV==0 && @clean==0 && !$scatterdir && !$gatherdir;

$show_usage=1 if @ARGV>2;
$show_usage=1 if @list>0 && (@ARGV>0 || @clean || $scatterdir || $gatherdir);

if( $show_usage ) {
	print STDERR $usage;
	print STDERR "See '$0 --help' for details\n";
	exit 1;
}


#####################################################################

#
# now do the work
#

if( @list>0 ) {
	foreach (backupdir @list ) { list($_); }
	exit 0;
}

# now clean these directories
if( @clean>0 ) {
	foreach (backupdir @clean) { cleanup($_); }
}


my( $gather, $scatter );
@ignore = map {escape($_)} @ignore;
$gather = "$getroot $find @ignore | $getroot $FAUBACKUP::gather";
$gather .= " -v" if $verbose && $gatherdir;
$scatter = "$getroot $FAUBACKUP::scatter" . ($verbose?" -v":"");

#
# process old options
#
if( $gatherdir ) {
	chdir $gatherdir or die "chdir $gatherdir: $!";
	exec $gather;
	die "exec $FAUBACKUP::gather: $!";
}
if( $scatterdir ) {
	chdir $scatterdir or die "chdir $scatterdir: $!";
	exec $scatter
	die "exec $FAUBACKUP::scatter: $!";
}


#
# process new backup syntax
#
exit 0 unless @ARGV; # nothing more to do?

# parse remote source machine
$srcdir = $ARGV[0];
my( $srchost, $desthost );
{
	my $dir;
	( $srchost, $dir ) = $srcdir =~ /^([\@a-zA-Z0-9\.-]+):(.*)$/;
	$srcdir=$dir if $srchost;
}

# build up destdir if neccessary
if( @ARGV>1 ) {
	$destdir = $ARGV[1];
} else {
	my $dir = $srcdir;
	$dir = $srcdir;
	$dir =~ s#^/##;
	$dir =~ s#/$##;
	$dir =~ s#/#-#g;
	my $hostname = $srchost;
	$hostname ||= `hostname||cat /etc/hostname`;
	chomp $hostname;
	$destdir = $backup;
	$destdir =~ s#MACHINE#$hostname#;
	$destdir =~ s#DIR#$dir#;
}

# parse remote destination machine
{
	my $dir;
	( $desthost, $dir ) = $destdir =~ /^([\@a-zA-Z0-9\._-]+):(.*)$/;
	$destdir=$dir if $desthost;
}

# build commands to execute
$gather = "cd " . escape($srcdir) . "&&" . $gather;
$gather = "$rsh $srchost " . escape($gather)
		if $srchost;

$scatter = "cd " . escape($destdir) . "&&" . $scatter;
$scatter = "$getroot mkdir -p " . escape($destdir) . "&&" . $scatter
		if $autocreate;
$scatter = "$rsh $desthost " . escape($scatter)
		if $desthost;

print STDERR "source=$gather\n" if $verbose>1;
print STDERR "drain=$scatter\n" if $verbose>1;

$| = 1;
open STDIN, "$gather |" or die "fork: $!";
system( $scatter )==0 or exit $?;
close STDIN or exit $?;

exit 0;




-------------------------------------------------------
This SF.net email is sponsored by: SF.net Giveback Program.
Does SourceForge.net help you be more productive?  Does it
help you create better code?  SHARE THE LOVE, and help us help
YOU!  Click Here: http://sourceforge.net/donate/
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.