#! /usr/bin/perl
# src/remsync.  Generated from remsync.in by configure.
eval "exec /usr/bin/perl -S $0 $*"
    if $running_under_some_shell;

use File::Temp qw/ :mktemp  /;

# Synchronization tool for remote directories.
# Copyright (C) 1994 Free Software Foundation, Inc.
# François Pinard <pinard@iro.umontreal.ca>, 1994.

# 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

## Parameters, but not meant to be changed.

$PACKAGE = 'sharutils';		# name of package for this program
$VERSION = '4.5.2';		# version number for the whole package
$PROGRAM = 'remsync';		# name of this particular program
$FORMAT = '1.3';		# version of format for files

$CONFIG = '.remsync';		# file containing synchronization information
$INVOICE = '.remsync.tar.gz';	# default file name of packed synchro. package
$WORKDIR = '.remsync-work';	# directory name of unpacked synchro. package
$ORDERS = 'orders';		# file name containaing synchro. directives

$DIFF = '/usr/bin/diff';		# GNU diff path
$TAR = '/usr/bin/tar';			# GNU tar path
$SH = '/usr/bin/bash';			# Bash or sh path

# Special constants.

$NEWLY_CREATED_SCAN = 2;	# Instead of 1, when by remote request

# Help strings.

$INITIAL_HELP = "$PROGRAM (format $FORMAT) - GNU $PACKAGE $VERSION
  Remote synchronization of files and directories.

The following commands are available at *any* \`$PROGRAM\' prompt:

  ?             reminder for available commands
  ! [COMMAND]   shell escape for processing COMMAND
  abort         get out of the current command right away
";

$NORMAL_HELP = "Usage: $PROGRAM [COMMANDS...]

  ! [COMMAND]   shell escape for processing COMMAND (defaults to shell)
  abort         get out of the current command right away
  quit          get out of program, saving file \`$CONFIG\' if modified

Synchronizing commands:
  chdir [DIRECTORY]     change current directory to DIRECTORY
  mode [MODE]           init (do not send contents) or noop (send nothing)
  broadcast [SET]       export a synchronization package to each site of SET
  process [FILE]        import a FILE (defaults to \`$INVOICE\')
  process [DIRECTORY]   or use an already exploded DIRECTORY (\`$WORKDIR\')

Maintenance commands:
  list                           list title, here, remotes, scans and ignores
  files                          list all files and their known signatures
  title [DESCRIPTION]            use DESCRIPTION as project title (or list it)
  here [ADDRESS [DIRECTORY]]     declare our ADDRESS, modify visited DIRECTORY
  remote [ADDRESS [DIRECTORY]]   declare remote ADDRESS, modify its DIRECTORY
  scan [PATTERN]                 scan directory with \`find\' for shell PATTERN
  ignore [REGEXP]                ignore scanned files if name matched by REGEXP
  delete TYPE DATA               delete the remote, scan or ignore having DATA

To obtain partial lists, use appropriate commands without their parameters.
Commands and keyword arguments may be abbreviated to one letter.
";

## Meaning of global variables introduced and used in this program.
##
## Config (.remsync) file:
##   $config_filename		expanded file name of the $CONFIG file
##   $new_config		if $CONFIG did not exist prior to this run
##   $fetch_config		if $CONFIG file should be read and studied
##   $save_config		if $CONFIG file should be rewritten
##
## Local description:
##   $project_title		title of the project, from $CONFIG
##   $project_title_received	title of the project, from invoice
##   $here_email		our email address, from $CONFIG
##   $here_email_received	our email address, from invoice
##   $here_home			normalized project home directory, from $CONFIG
##   $here_home_received	normalized project home directory, from invoice
##
## Remote descriptions:
##   @remote			array of remote email addresses, from $CONFIG
##   @remote_received		array of remote email addresses, from invoice
##   %remote			for each remote: home directory, from $CONFIG
##   %remote_received		for each remote: home directory, from invoice
##   @site_set			selected set of remote ordinals, counted from 0
##   @copy_list			mapping of invoice remote indices to $CONFIG's
##   $originator		index of remote having sent the package
##
## File signatures:
##   $study_files		if list of local files has to be recomputed
##   $checksum_command		system command to compute file signatures
##   $maximum_name_width	maximum length of all studied file names
##   %scan			for each defined scan, from $CONFIG
##   %scan_received		for each defined scan, from invoice
##   @scan			sorted %scan, for numbered selection
##   %ignore			for each defined ignore, from $CONFIG
##   %ignore_received		for each defined ignore, from invoice
##   @ignore			sorted %ignore, for numbered selection
##   %local_signature		for each file: its signature computed here
##   %signature			for each file: list of signatures, from $CONFIG
##   %signature_received	for each file: list of signatures, from invoice
##
## Command control:
##   $commands_ahead		semi-colon separated list of commands
##   $command_loop		if dynamically within &command_loop
##   $process_loop		if dynamically within &process_loop
##   $noop_mode			if \`mode noop\' was given
##   $init_mode			if \`mode init\' was given
##   $work_directory		expanded work directory name
##   $workdir_to_unlink         expanded work directory name, to later delete
##   $invoice_to_unlink         expanded invoice file name, to later delete

## Programming notes around probable Perl 4.X problems:
## * local($_) is avoided, so beware $_ may be destroyed by any routine.
## * @_ is always saved on each routine entry, where sub-routines are used.

foreach (@ARGV)
{
    if (/^--/)
    {
	if (length $_ == 2)
	{
	    shift;
	    last;
	}
	if ($_ eq substr ('--version', 0, length $_))
	{
	    print "$PROGRAM (format $FORMAT) - GNU $PACKAGE $VERSION\n";
	    exit 0;
	}
	if ($_ eq substr ('--help', 0, length $_))
	{
	    print $NORMAL_HELP;
	    exit 0;
	}
	&interrupt ("Unknown option \`$_\'");
    }
    else
    {
	last;
    }
}

if (@ARGV)
{
    $commands_ahead = join (';', @ARGV);
    @ARGV = ();
}
else
{
    print STDERR $INITIAL_HELP;
}

$fetch_config = 1;

&command_loop;

&maybe_save_config;

exit 0;

# Interactive command decoding.

## Read user commands and dispatch them.

sub command_loop
{
    $command_loop = 1;

  COMMAND_LOOP:
    while (1)
    {
	if ($commands_ahead)
	{
	    if ($commands_ahead =~ /^([^;]*);(.*)/)
	    {
		$_ = $1;
		$commands_ahead = $2;
	    }
	    else
	    {
		$_ = $commands_ahead;
		$commands_ahead = 'quit';
	    }
	}
	else
	{
	    if ($noop_mode)
	    {
		&query ("\nnoop>>");
	    }
	    elsif ($init_mode)
	    {
		&query ("\ninit>>");
	    }
	    else
	    {
		&query ("\n>>");
	    }
	}
	s/^ +//;
	s/ +$//;

	next if /^$/;
	next if /^#/;
	last if /^q(uit)?$/;

	if (/^c(hdir)?$/ || /^pwd$/)
	{
	    &command_list_cwd;
	}
	elsif (/^c(hdir|d)? +(.+)/)
	{
	    &command_set_cwd ($2);
	}
	elsif (/^m(ode)?$/)
	{
	    &command_list_mode;
	}
	elsif (/^m(ode)? +([^ ]+)$/)
	{
	    &command_set_mode ($2);
	}
	elsif (/^b(roadcast)?$/)
	{
	    &command_broadcast ('');
	}
	elsif (/^b(roadcast)? +(.+)$/)
	{
	    &command_broadcast ($2);
	}
	elsif (/^p(rocess)?$/)
	{
	    &command_process ('');
	}
	elsif (/^p(rocess)? +([^ ]+)$/)
	{
	    &command_process ($2);
	}
	elsif (/^l(ist)?$/)
	{
	    &command_list_almost_all;
	}
	elsif (/^f(iles)?$/)
	{
	    &command_list_files;
	}
	elsif (/^t(itle)?$/)
	{
	    &command_list_title;
	}
	elsif (/^t(itle)? +(.+)$/)
	{
	    &command_set_title ($2);
	}
	elsif (/^h(ere)?$/)
	{
	    &command_list_here;
	}
	elsif (/^h(ere)? +([^ ]+) *([^ ]*)$/)
	{
	    &command_set_here ($2, $3);
	}
	elsif (/^r(emote)?$/)
	{
	    &command_list_remote;
	}
	elsif (/^r(emote)? +([^ ]+) *([^ ]*)$/)
	{
	    &command_set_remote ($2, $3);
	}
	elsif (/^s(can)?$/)
	{
	    &command_list_scan;
	}
	elsif (/^s(can)? +([^ ]+)$/)
	{
	    &command_set_scan ($2);
	}
	elsif (/^i(gnore)?$/)
	{
	    &command_list_ignore;
	}
	elsif (/^i(gnore)? +([^ ]+)$/)
	{
	    &command_set_ignore ($2);
	}
	elsif (/^d(elete)? *r(emote)? +([^ ]+)$/)
	{
	    &command_delete_remote ($3);
	}
	elsif (/^d(elete)? *s(can)? +([^ ]+)$/)
	{
	    &command_delete_scan ($3);
	}
	elsif (/^d(elete)? *i(gnore)? +([^ ]+)$/)
	{
	    &command_delete_ignore ($3);
	}
	else
	{
	    &diagnose ("Unrecognized command \`$_\', try \`?\' for help");
	}
    }

    $command_loop = 0;
}

## List current working directory.
## Synopses: `chdir' or `pwd'.

sub command_list_cwd
{
    print `pwd`;
}

## Change current working directory.
## Synopses: `chdir DIRECTORY' or `cd DIRECTORY'.

sub command_set_cwd
{
    local ($directory) = @_;

    $directory = &expand_filename ($directory);

    if (-d $directory)
    {
	&maybe_save_config;

	if (chdir ($directory))
	{
	    $fetch_config = 1;
	}
	else
	{
	    &diagnose ("Unable to change to directory \`$directory\'");
	}
    }
    else
    {
	&diagnose ("Non-existing directory \`$directory\'");
    }
}

## List all modes.
## Synopsis: `mode'.

sub command_list_mode
{
    print STDERR "\n";
    printf STDERR
	"Init mode %-5s   Send file signatures, but no file contents\n",
	($init_mode ? '(on)' : '(off)');
    printf STDERR
	"Noop mode %-5s   Avoid sending email, do not update \`$CONFIG\'",
	($noop_mode ? '(on)' : '(off)');
    print STDERR "\n";
}

## Set one of modes.
## Synopsis: `mode MODE'.

sub command_set_mode
{
    local ($mode) = @_;

    if ($mode eq 'i' || $mode eq 'init')
    {
	$init_mode = 1;
    }
    elsif ($mode eq 'n' || $mode eq 'noop')
    {
	$noop_mode = 1;
    }
    else
    {
	&diagnose ("Unrecognized mode \`$mode\'");
    }
}

## List title, here information, all remotes, all scans and all ignores.
## Synopsis: `list'.

sub command_list_almost_all
{
    &maybe_fetch_config;

    print "\n$project_title\n\n";

    print "HERE:\n";
    &command_list_here;

    print "REMOTE:\n" if @remote;
    &command_list_remote;

    print "SCAN:\n" if %scan;
    &command_list_scan;

    print "IGNORE:\n" if %ignore;
    &command_list_ignore;
}

## List information for all files.
## Synopsis: `files'.

sub command_list_files
{
    local ($format, $field);

    &maybe_fetch_config;
    &maybe_study_files;

    $format = "  %-5s  %-${maximum_name_width}s  ";
    foreach (sort keys %signature)
    {
	printf $format, $local_signature{$_}, $_;
	foreach $field (split (/ /, $signature{$_}))
	{
	    $field = ' ...' if $field eq $local_signature{$_};
	    printf '%-7s', $field;
	}
	print "\n";
    }
}

## List the title of the project.
## Synopsis: `title'.

sub command_list_title
{
    &maybe_fetch_config;

    print "$project_title\n";
}

## Set the title of the project.
## Synopsis: `title DESCRIPTION'.

sub command_set_title
{
    local ($description) = @_;

    &maybe_fetch_config;

    if ($description ne $project_title)
    {
	$project_title = $description;
	$save_config = 1;
    }
}

## List local information.
## Synopsis: `here'.

sub command_list_here
{
    &maybe_fetch_config;

    print "  [0]\t$here_email $here_home\n";
}

## Modify our local information to ADDRESS and DIRECTORY.
## Synopsis: `here ADDRESS DIRECTORY'.

sub command_set_here
{
    local ($email, $directory) = @_;

    &maybe_fetch_config;

    $email =~ tr/A-Z/a-z/;
    if ($email ne '-' && $email ne $here_email)
    {
	$here_email = $email;
	$save_config = 1;
    }

    if ($directory && $directory ne $here_home)
    {
	$here_home = &normalize_directory ($directory);
	$config_filename = &expand_filename ("$here_home/$CONFIG");
	$save_config = 1;
    }
}

## List information for all remotes.
## Synopsis: `remote'.

sub command_list_remote
{
    local ($index, $email);

    &maybe_fetch_config;

    $index = 0;
    foreach (@remote)
    {
	$index++;
	print "  [$index]\t$_ $remote{$_}\n";
    }
}

## Create a new remote given its REMOTE address, modify its DIRECTORY.
## Synopsis: `remote REMOTE DIRECTORY'.

sub command_set_remote
{
    local ($remote, $directory) = @_;
    local ($index);

    &maybe_fetch_config;

    $remote =~ tr/A-Z/a-z/;
    $remote = $remote[$remote - 1] if ($remote > 0 && $remote <= @remote);

    if (defined $remote{$remote})
    {
	if ($directory && $remote{$remote} ne $directory)
	{
	    $remote{$remote} = $directory;
	    $save_config = 1;
	}
	elsif ($remote{$remote} ne '-')
	{
	    &diagnose ("Remote directory is known to be \`$remote{$remote}\'");
	    &query ('Do you want me to keep this knowledge (y/n)? [y]');
	    if (! /(y|yes)/i)
	    {
		$remote{$remote} = '-';
		$save_config = 1;
	    }
	}
    }
    else
    {
	if ($directory)
	{
	    &create_remote ($remote, $directory);
	}
	else
	{
	    &create_remote ($remote, '-');
	    $index = @remote;
	    &warn ("You may also use \`remote $index DIRECTORY\'"
		   . ' if you know the remote directory');
	}
    }
}

## Delete an existing remote given its ADDRESS address.
## Synopsis: `delete remote ADDRESS'.

sub command_delete_remote
{
    local ($remote) = @_;

    &maybe_fetch_config;

    $remote = $remote[$remote - 1] if ($remote > 0 && $remote <= @remote);
    &delete_remote ($remote);
}

## List information for all scans.
## Synopsis: `scan'.

sub command_list_scan
{
    local ($index);
## FIXME: local (@scan); ?

    &maybe_fetch_config;

    $index = 0;
    @scan = ();
    foreach (sort keys %scan)
    {
	$index++;
	push (@scan, $_);
	print "  [$index]\t$_\n";
    }
}

## Create a new SCAN.
## Synopsis: `scan SCAN'.

sub command_set_scan
{
    local ($scan) = @_;

    &maybe_fetch_config;

    if (defined $scan{$scan})
    {
	&diagnose ("Redundant creation of scan \`$scan\'");
    }
    else
    {
	$scan{$scan} = 1;
	$save_config = 1;
	$study_files = 1;
    }
}

## Delete an existing SCAN.
## Synopsis: `delete scan SCAN'.

sub command_delete_scan
{
    local ($scan) = @_;

    &maybe_fetch_config;

    $scan = $scan[$scan - 1] if ($scan > 0 && $scan <= @scan);
    if (defined $scan{$scan})
    {
	delete $scan{$scan};
	$save_config = 1;
	$study_files = 1;
    }
    else
    {
	&diagnose ("Cannot delete inexisting scan \`$scan\'");
    }
}

## List information for all ignores.
## Synopsis: `ignore'.

sub command_list_ignore
{
    local ($index);
## FIXME: local (@ignore); ?

    &maybe_fetch_config;

    $index = 0;
    @ignore = ();
    foreach (sort keys %ignore)
    {
	$index++;
	push (@ignore, $_);
	print "  [$index]\t$_\n";
    }
}

## Create a new IGNORE.
## Synopsis: `ignore IGNORE'.

sub command_set_ignore
{
    local ($ignore) = @_;

    &maybe_fetch_config;

    if (defined $ignore{$ignore})
    {
	&diagnose ("Redundant creation of ignore \`$ignore\'");
    }
    else
    {
	$ignore{$ignore} = 1;
	$save_config = 1;
	$study_files = 1;
    }
}

## Delete an existing IGNORE.
## Synopsis: `delete ignore IGNORE'.

sub command_delete_ignore
{
    local ($ignore) = @_;
    local ($index);

    &maybe_fetch_config;

    $ignore = $ignore[$ignore - 1] if ($ignore > 0 && $ignore <= @ignore);
    if (defined $ignore{$ignore})
    {
	delete $ignore{$ignore};
	$save_config = 1;
	$study_files = 1;
    }
    else
    {
	&diagnose ("Cannot delete inexisting ignore \`$ignore\'");
    }
}

# Broadcasting away synchronization packages.

## Export a synchronization package to each site of SET.
## Synopsis: `broadcast SET'.

sub command_broadcast
{
    local ($set) = @_;
    local ($site, $index, $ordinal, $file, @signature);

    &maybe_fetch_config;

    &decode_site_set ($set);
    foreach $site (@site_set)
    {
	&warn ('');
	&warn ("Broadcasting to address \`$remote[$site]\'");

	if (-f $INVOICE && ! $noop_mode)
	{
	    &diagnose ("The invoice \`$INVOICE\' already exists!");
	    &query ('Should I delete it for you (y/n)? [n]');
	    &interrupt ('Command aborted!') if ! /^(y|yes)/i;
	    unlink $INVOICE
		|| &interrupt ("Cannot delete file \`$INVOICE\'");
	}
	if (-d $WORKDIR && ! $noop_mode)
	{
	    &diagnose ("The work directory \`$WORKDIR\' already exists!");
	    &query ('Should I remove all of it first (y/n)? [y]');
	    &interrupt ('Command aborted!') if ! /^(y|yes)/i;
	    system "rm -rf $WORKDIR"
		|| &interrupt ("Cannot remove directory \`$WORKDIR\'");
	}

	&maybe_study_files;
	&update_file_registry;

	# Initialize the invoice.

	if (! $noop_mode)
	{
	    mkdir ($WORKDIR, 0700)
		|| &interrupt ("Unable to make directory \`$WORKDIR\'");
	    open (OUTPUT, ">$WORKDIR/$ORDERS")
		|| &interrupt ("Cannot create file \`$WORKDIR/$ORDERS\'");

	    print OUTPUT "format\t$PROGRAM $FORMAT\n";
	    print OUTPUT "title\t$project_title\n";
	    print OUTPUT "here\t$here_email $here_home\n";
	    foreach (@remote)
	    {
		print OUTPUT "remote\t$_ $remote{$_}\n";
	    }
	    foreach (sort keys %scan)
	    {
		print OUTPUT "scan\t$_\n";
	    }
	    foreach (sort keys %ignore)
	    {
		print OUTPUT "ignore\t$_\n";
	    }

	    print OUTPUT "visit\t$site\n";
	    print OUTPUT "copy\t", join (' ', @site_set), "\n";
	}

	# Transmit all file signatures and replacement orders.

	$ordinal = 0;
	foreach $file (sort keys %signature)
	{
	    if (! $noop_mode)
	    {
		print OUTPUT "check\t$file $local_signature{$file}";
		@signature = split (/ /, $signature{$file});
		foreach (@site_set)
		{
		    print OUTPUT ' ', $signature[$_];
		}
		print OUTPUT "\n";
	    }

	    next if $init_mode;
	    next if $signature[$site] eq $local_signature{$file};

	    &warn ("Packaging file \`$file\'");
	    if (! $noop_mode)
	    {
		$ordinal++;
		symlink ("../$file", "$WORKDIR/$ordinal");
		print OUTPUT "update\t$file $signature[$site] $ordinal\n";
	    }
	    $signature[$site] = $local_signature{$file};
	    $signature{$file} = join (' ', @signature);
	    $save_config = 1;
	}

	# Complete the invoice.

	if (! $noop_mode)
	{
	    close OUTPUT;
	    system "$TAR cfzh $INVOICE $WORKDIR"
		|| &interrupt ("Cannot construct invoice \`$INVOICE\'"
			       . " from directory \`$WORKDIR\'");
	    system "rm -rf $WORKDIR"
		|| &interrupt ("Cannot remove directory \`$WORKDIR\'");
	    system "mailshar $remote[$site] $INVOICE"
		|| &interrupt ("Cannot send file \`$INVOICE\'"
			       . " to address \`$remote[$site]\'");
	    unlink $INVOICE
		|| &interrupt ("Cannot delete file \`$INVOICE\'");
	}
    }
    &warn ("Command \`broadcast\' done");
}

# Processing received synchronization packages.

## Import a FILE or use an already exploded DIRECTORY.
## Synopses: `process [FILE]' or `process [DIRECTORY]'.

sub command_process
{
    local ($argument) = @_;
    local ($invoice, $prior, $file, @signature);

    $work_directory = &expand_filename ($WORKDIR);

    if ($argument)
    {
	$invoice = &expand_filename ($argument);
    }
    elsif (-f $INVOICE)
    {
	$invoice = &expand_filename ($INVOICE);
	$invoice_to_unlink = $invoice if ! $noop_mode;
    }
    elsif (-d $WORKDIR)
    {
	$invoice = $work_directory;
    }
    else
    {
	&interrupt ("No argument, no invoice \`$INVOICE\'"
		    . " and no directory \`$WORKDIR\'");
    }

    if (-f $invoice)
    {
	&warn ("Exploding invoice \`$invoice\'");

	if (-d $WORKDIR)
	{
	    &diagnose ("The work directory \`$WORKDIR\' already exists!");
	    &query ('Should I remove all of it first (y/n)? [y]');
	    &interrupt ('Command aborted!') if ! /^(y|yes)/i;
	    system "rm -rf $WORKDIR"
		|| &interrupt ("Cannot remove directory \`$WORKDIR\'");
	}

	system "$TAR xfoz $invoice"
	    || &interrupt ("Failure while untarring file \`$invoice\'");
	$workdir_to_unlink = $work_directory;
    }

    chop ($prior = `pwd`);
    open (ORDERS, "$work_directory/$ORDERS")
	|| &interrupt ("Cannot read file \`$work_directory/$ORDERS\'");

    &process_loop;

    close ORDERS;
    chdir $prior;

    if ($workdir_to_unlink)
    {
	unlink "$workdir_to_unlink/$ORDERS~"; # in case edited in place
	unlink "$workdir_to_unlink/$ORDERS"
	    || &diagnose ("Cannot delete file \`$workdir_to_unlink/$ORDERS\'");
	rmdir $workdir_to_unlink
	    || &diagnose ("Cannot remove directory \`$workdir_to_unlink\'");
	$workdir_to_unlink = '';
    }

    if ($invoice_to_unlink)
    {
	unlink $invoice_to_unlink
	    || &diagnose ("Cannot delete file \`$invoice_to_unlink\'");
	$invoice_to_unlink = '';
    }
    &warn ('Command `process\' done');
}

## Decode each received package orders, in turn.  Most validation
## is delayed until the `visit' order.

sub process_loop
{
    local (@signature, $auto);

    $process_loop = 1;

  PROCESS_LOOP:
    while (<ORDERS>)
    {
	chop;

	# Handle commands not requiring the analysis of file $CONFIG.

	if (/^format\t$PROGRAM ([^ ]+)$/o)
	{
	    &interrupt
		("Need $PROGRAM (format $FORMAT) to process this package!")
		    if $1 ne $FORMAT;
	}
	elsif (/^title\t(.*)/)
	{
	    $project_title_received = $1;
	}
	elsif (/^here\t([^ ]+) ([^ ]+)$/)
	{
	    ($here_email_received, $here_home_received) = ($1, $2);
	    $here_email_received =~ tr/A-Z/a-z/;
	}
	elsif (/^remote\t([^ ]+) ([^ ]+)$/)
	{
	    warn "Remote-1 $1 $2\n"; # FIXME
	    $_ = $1;
	    tr/A-Z/a-z/;
	    push (@remote_received, $_);
	    $remote_received{$_} = $2;
	    warn "Remote-2 $1 $2\n"; # FIXME
	}
	elsif (/^scan\t([^ ]+)$/)
	{
	    $scan_received{$1} = 1;
	}
	elsif (/^ignore\t([^ ]+)$/)
	{
	    $ignore_received{$1} = 1;
	}
	elsif (/^visit\t([^ ]+)$/)
	{
	    &process_visit ($1);
	}
	elsif (/^copy\t(.+)/)
	{
	    &process_copy ($1);
	}
	elsif (/^check\t([^ ]+) ([^ ]+) (.+)/)
	{
	    &process_check ($1, $2, $3);
	}
	elsif (/^update\t([^ ]+) ([^ ]+) ([^ ]+)$/)
	{
	    &process_update ($1, $2, $3);
	}
	else
	{
	    &interrupt ("Unrecognized command \`$_\' in process input");
	}
    }

    $process_loop = 0;
    &update_file_registry;

    if (%signature_received)
    {
	foreach $file (sort keys %signature)
	{
	    &diagnose ("File \`$file\' is unknown remotely")
		if ! defined $signature_received{$file};
	    $auto = 'warn';
	}
	if ($auto)
	{
	    &warn ('Replying `y\' (yes) deletes the said file');
	    &warn ('         `n\' (no) keeps the said file');
	    &warn ('         `a\' (all) deletes the file and all following');
	    &warn ('         `q\' (quit) keeps the file and all following');
	    $auto = '';
	}
	foreach $file (sort keys %signature)
	{
	    if (! defined $signature_received{$file})
	    {
		@signature = split (/ /, $signature{$file});
		if ($signature[$originator] ne '-')
		{
		    $signature[$originator] = '-';
		    $save_config = 1;
		    $signature{$file} = join (' ', @signature);
		}

		if ($auto)
		{
		    $_ = $auto;
		}
		else
		{
		    &query
			("Should I delete file \`$file\' here (y/n/a/q)? [n]");
		}

		if (/^(a|all)$/i)
		{
		    $auto = 'yes';
		    $_ = $auto;
		}
		elsif (/^(q|quit)$/i)
		{
		    $auto = 'no';
		    $_ = $auto;
		}

		if (/^(y|yes)$/i)
		{
		    if (! $noop_mode)
		    {
			unlink $file
			    || &diagnose ("Cannot delete file \`$file\'");
		    }
		    delete $signature{$file};
		}
	    }
	}
    }
}

## Prepare to visit a directory, conciliating all received information.
## Synopsis: `visit VISITED', where VISITED is an index in remotes.

sub process_visit
{
    local ($visited) = @_;
    local ($email, $home, $string, $scan, $ignore);

    &maybe_save_config;

    &warn ('');
    &warn ('Package being received:');
    &warn ("  from address \`$here_email_received\'");
    &warn ("  for project \`$project_title_received\'");

    # Check the recipient address.

    $email = &guess_here_email;
    $string = $remote_received[$visited];

    if (! &equivalent_email ($email, $string))
    {
	&diagnose ("This package was sent to address \`$string\'");
	&warn ("but your address is known to be \`$email\'");
	&warn ('');
	&warn ('The possibilities at this point are:');
	&warn ("  1. Correct your full email address to \`$string\'");
	&warn ("  2. Use your current email address \`$email\'");
	&warn ('  3. Specify another full email address (beware!)');
	&warn ('  4. Abandon the processing of this package');
	$_ = '';
	&query ('Which action do you choose (1-4)? [1]')
	    while ! /^[1-4]$/;
	if ($_ eq '1')
	{
	    $email = $string;
	}
	elsif ($_ eq '3')
	{
	    $_ = &guess_here_email;
	    &query ("What is your full email address, here? [$_]");
	    $email = $_;
	}
	elsif ($_ eq '4')
	{
	    &interrupt ('Command aborted!');
	}
    }

    # Check the recipient directory.

    $string = $remote_received{$string};
    $_ = &expand_filename ($string);
    if (-d $_)
    {
	$home = $string;
    }
    else
    {
	chop ($_ = `pwd`);
	$home = &normalize_directory ($_);

	&diagnose ("This package was aimed for directory \`$string\'");
	&warn ('but this directory does not exist here');
	&warn ('');
	&warn ('The possibilities at this point are:');
	&warn ("  1. Attempt creating the \`$string\' directory");
	&warn ("  2. Use the current directory \`$home\' (are you sure?)");
	&warn ('  3. Specify another synchronized directory (beware!)');
	&warn ('  4. Abandon the processing of this package');
	$_ = '';
	&query ('Which action do you choose (1-4)? [1]')
	    while ! /^[1-4]$/;
	if ($_ eq '1')
	{
	    $home = $string;
	}
	elsif ($_ eq '3')
	{
	    &query ("Which directory should be used? [$home]");
	    $home = &normalize_directory ($_);
	}
	elsif ($_ eq '4')
	{
	    &interrupt ('Command aborted!');
	}
    }

    # Force our way to the wanted directory.

    &warn ("Visiting directory \`$home',"
	   . " remote was \`$here_home_received\'");

    $home = &expand_filename ($home);
    &prepare_filename ("$home/$CONFIG");
    chdir $home || &interrupt ("Cannot change directory to \`$home\'");

    # Swallow or simulate the $CONFIG file.

    if (-f "$home/$CONFIG")
    {
	$fetch_config = 1;
	&maybe_fetch_config;

	# Reconciliate $project_title.

	if ($project_title ne $project_title_received)
	{
	    &diagnose ("The package title is \`$project_title_received\'");
	    &warn ("but \`$CONFIG\' says it should be \`$project_title\'");
	    &warn ('');
	    &warn ('The possibilities at this point are:');
	    &warn ("  1. Use \`$project_title_received\' as title");
	    &warn ("  2. Keep \`$project_title' as title\'");
	    &warn ('  3. Specify another project title');
	    $_ = '';
	    &query ('Which action do you choose (1-3)? [1]')
		while ! /^[1-3]$/;
	    if ($_ eq '1')
	    {
		$project_title = $project_title_received;
	    }
	    elsif ($_ eq '3')
	    {
		&query ('What will be the new project title?');
		$project_title = $_;
	    }
	}

	# Reconciliate $here_email.

	if (! &equivalent_email ($email, $here_email))
	{
	    &diagnose ("This package is sent to address \`$here_email\'");
	    &warn ("but \`$CONFIG\' says it should have been \`$email\'");
	    &warn ('');
	    &warn ('The possibilities at this point are:');
	    &warn ("  1. Use your current full email address \`$email\'");
	    &warn ("  2. Correct your full email address to \`$here_email\'");
	    &warn ('  3. Specify another full email address');
	    $_ = '';
	    &query ('Which action do you choose (1-3)? [1]')
		while ! /^[1-3]$/;
	    if ($_ eq '1')
	    {
		$here_email = $email;
	    }
	    elsif ($_ eq '3')
	    {
		$_ = &guess_here_email;
		&query ("What is your full email address, here? [$_]");
		$here_email = $_;
	    }
	}

	# Reconciliate $here_home.

	$home = &normalize_directory ($home);
	if ($home ne $here_home)
	{
	    &diagnose ("This package is aimed for directory \`$here_home\'");
	    &warn ("but \`$CONFIG\' says it should have been \`$home\'");
	    &warn ('');
	    &warn ('The possibilities at this point are:');
	    &warn ("  1. Record the \`$home\' directory in the configuration");
	    &warn ("  2. Correct the directory to \`$here_home\'");
	    &warn ('  3. Record another name for this directory (beware!)');
	    $_ = '';
	    &query ('Which action do you choose (1-3)? [1]')
		while ! /^[1-3]$/;
	    if ($_ eq '1')
	    {
		$here_home = $home;
		$config_filename = &expand_filename ("$here_home/$CONFIG");
	    }
	    elsif ($_ eq '3')
	    {
		&query ("Which directory should be used? [$home]");
		$here_home = &normalize_directory ($_);
		$config_filename = &expand_filename ("$here_home/$CONFIG");
	    }
	}

	# Reconciliate remote information.

	foreach $remote (sort keys %remote)
	{
	    if (defined $remote_received{$remote})
	    {
		if ($remote{$remote} ne $remote_received{$remote})
		{
		    &diagnose ("Conflicting directories for \`$remote\'");
		    &warn ("  known as \`$remote{$remote}\' here and");
		    &warn ("  as \`$remote_received{$remote}\' remotely");
		    &warn ('');
		    &warn ('The possibilities at this point are:');
		    &warn ("  1. Accept \`$remote_received{$remote}\'");
		    &warn ("  2. Keep \`$remote{$remote}\'");
		    &warn ('  3. Declare another directory...');
		    $_ = '';
		    &query ('Which action do you choose (1-3)? [1]')
			while ! /^[1-3]$/;
		    if ($_ eq '1')
		    {
			$remote{$remote} = $remote_received{$remote};
			$save_config = 1;
		    }
		    elsif ($_ eq '3')
		    {
			&query ("\
Which directory should be used? [$remote_received{$remote}]");
			if ($_ ne $remote{remote})
			{
			    $remote{$remote} = &normalize_directory ($_);
			    $save_config = 1;
			}
		    }
		}
		delete $remote_received{$remote};
	    }
	    elsif ($remote ne $here_email_received)
	    {
		&diagnose ("Remote \`$remote\' is unknown remotely");
		&query ('Should I forget it here (y/n)? [n]');
		# FIXME: this might require more adjustments?
		delete $remote{$remote} if /(y|yes)/i;
	    }
	}
	foreach $remote (sort keys %remote_received)
	{
	    if ($remote ne $here_email)
	    {
		&diagnose
		    ("Remote \`$remote\' is known remotely but not here");
		&query ('Should I learn it here (y/n)? [y]');
		&create_remote ($remote, $remote_received{$remote})
		    if (/(y|yes)/i);
	    }
	    delete $remote_received{$remote};
	}

	# At last, finally, decide who is sending the package.

	$originator = &validated_remote_index ($here_email_received);

	# Reconciliate scan information.

	foreach $scan (sort keys %scan)
	{
	    if (defined $scan_received{$scan})
	    {
		delete $scan_received{$scan};
	    }
	    else
	    {
		&diagnose ("Scan \`$scan\' is unknown remotely");
		&query ('Should I forget it here (y/n)? [n]');
		delete $scan{$scan} if /(y|yes)/i;
	    }
	}
	foreach $scan (sort keys %scan_received)
	{
	    &diagnose
		("Scan \`$scan\' is known remotely but not here");
	    &query ('Should I learn it here (y/n)? [y]');
	    $scan{$scan} = $NEWLY_CREATED_SCAN if /(y|yes)/i;
	    delete $scan_received{$scan};
	}

	# Reconciliate ignore information.

	foreach $ignore (sort keys %ignore)
	{
	    if (defined $ignore_received{$ignore})
	    {
		delete $ignore_received{$ignore};
	    }
	    else
	    {
		&diagnose ("Ignore \`$ignore\' is unknown remotely");
		&query ('Should I forget it here (y/n)? [n]');
		delete $ignore{$ignore} if /(y|yes)/i;
	    }
	}
	foreach $ignore (sort keys %ignore_received)
	{
	    &diagnose
		("Ignore \`$ignore\' is known remotely but not here");
	    &query ('Should I learn it here (y/n)? [y]');
	    $ignore{$ignore} = 1 if /(y|yes)/i;
	    delete $ignore_received{$ignore};
	}
    }
    else
    {

	# Use remote information for initializing the local one.

	&warn ('Initializing file \`$CONFIG\' from received information');

	$project_title = $project_title_received;
	$here_email = $remote_received[$visited];
	$here_home = $remote_received{$here_email};
	$config_filename = &expand_filename ("$here_home/$CONFIG");

	if ($here_email ne $here_email_received)
	{
	    $remote_received[$visited] = $here_email_received;
	    $remote_received{$here_email_received} = $here_home_received;
	    delete $remote_received{$here_email};
	}

	@remote = @remote_received;

	%remote = %remote_received;
	%remote_received = ();
	%scan = %scan_received;
	%scan_received = ();
	%ignore = %ignore_received;
	%ignore_received = ();

	$new_config = 1;
	$save_config = 1;
	$fetch_config = 0;
	$study_files = 1;
    }
}

## Package was sent to each address in SET.
## Synopsis: `copy SET'.

sub process_copy
{
    local ($set) = @_;
    local ($counter);

    &maybe_fetch_config;

    @copy_list = ();
    $counter = 0;
    foreach (split (' ', $set))
    {
	$_ = $remote_received[$_];

	$copy_list[$counter++]
	    = $_ eq $here_email ? -1 : &validated_remote_index ($_);
    }
}

## Set FILE signatures to SIGNATURE, given a SET of previous values.
## Synopsis: `check FILE SIGNATURE SET'.

sub process_check
{
    local ($file, $signature, $set) = @_;
    local (@signature, @check, $counter, $new_signature);

    @check = split (' ', $set);
    &interrupt ("Unmatching number of signatures for file \`$file\'")
	if @check != @copy_list;

#   &maybe_fetch_config;
    &maybe_study_files;

    if (defined $signature{$file})
    {
	@signature = split (/ /, $signature{$file});
    }
    else
    {
	@signature = ('-') x @remote;
    }
    if ($signature ne $signature[$originator])
    {
	$signature[$originator] = $signature;
	$save_config = 1;
    }
    for ($counter = 0; $counter < @check; $counter++)
    {
	if ($copy_list[$counter] >= 0 && $check[$counter] ne '-')
	{
	    if ($signature[$copy_list[$counter]] eq '-'
		|| $signature[$copy_list[$counter]] eq $check[$counter])
	    {
		$new_signature = $signature;
	    }
	    else
	    {
		# If we do have an idea of a remote file\'s signature, and
		# if this idea is contradicted by a synchronization
		# package, rather say we know nothing besides that the
		# file merely exists.  Give it a signature from hell.

		$new_signature = '666';
	    }
	    if ($new_signature ne $signature[$copy_list[$counter]])
	    {
		$signature[$copy_list[$counter]] = $new_signature;
		$save_config = 1;
	    }
	}
    }

    $signature{$file} = join (' ', @signature);
    $signature_received{$file} = 1;
}

## If FILE checks to SIGNATURE, replace it by PACKAGED.
## Synopsis: `update FILE SIGNATURE PACKAGED'.

sub process_update
{
    local ($file, $old_signature, $packaged) = @_;
    local ($action, $cautious, $packaged_signature);

    $packaged = "$work_directory/$packaged";

#   &maybe_fetch_config;
#   &maybe_study_files;

    if (&ignorable_file ($file))
    {
	&diagnose ("File \`$file\' is the subject of some \`ignore\'");
	&query ('Should I accept it nevertheless (y/n)? [n]');
	$action = 'UNLINK' if ! /^(y|yes)$/i;
    }

    if (! $action && -f $file && ! defined $local_signature{$file})
    {
	&diagnose ("File \`$file\' was not found here");
	$local_signature{$file} = &single_signature ($file);
	$cautious = 1;
    }

    if (! $action && -f $file && $old_signature eq $local_signature{$file})
    {
	if ($cautious)
	{
	    &query ('Show diffs before updating it (y/n)? [y]');
	    $action = /^(y|yes)$/i ? 'DIFF' : 'MOVE';
	}
	else
	{
	    &warn ("Updating file \`$file\'");
	    $action = 'MOVE';
	}
    }

    if (! $action && -f $file)
    {
	$packaged_signature = &single_signature ($packaged);
	if ($old_signature eq '-')
	{
	    if ($packaged_signature eq $local_signature{$file})
	    {
		&diagnose ("Redundant creation of file \`$file\'");
		$action = 'UNLINK';
	    }
	    else
	    {
		&diagnose ("Unexpected preexisting file \`$file'");
		$action = 'DIFF';
	    }
	}
	else
	{
	    if ($packaged_signature eq $local_signature{$file})
	    {
		&diagnose ("Redundant updating of file \`$file\'");
		$action = 'UNLINK';
	    }
	    else
	    {
		&diagnose ("Local changes occurred to file \`$file\'");
		$action = 'DIFF';
	    }
	}
    }

    if (! $action)		# $file does not exist here
    {
	if ($old_signature eq '-')
	{
	    &warn ("Creating new file \`$file\'");
	    $action = 'MOVE';
	}
	else
	{
	    &diagnose ("File \`$file\' has disappeared from here");
	    &query ('Should I recreate it from remote copy (y/n)? [y]');
	    $action = /^(y|yes)$/i ? 'MOVE' : 'UNLINK';
	}
    }

    if ($action eq 'DIFF')
    {
	&warn ('');
	&warn ("$DIFF -u $here_home/$file $packaged");
	system "$DIFF -u $here_home/$file $packaged";
	&warn ('');
	&warn ('Before replying to next question, please reconciliate:');
	&warn ("  -) \`$here_home/$file\'");
	&warn ("  +) \`$packaged\'");
	&warn ('');
	&query ('Now, which of these files should be kept (-/+)? [-]');
	$action = /^\+$/ ? 'MOVE' : 'UNLINK';
    }

    if ($action eq 'UNLINK' && ! $noop_mode)
    {
	unlink $packaged || &diagnose ("Cannot delete file \`$packaged\'");
    }

    if ($action eq 'MOVE' && ! $noop_mode)
    {
	if (-f $file)
	{
	    unlink $file || &diagnose ("Cannot delete file \`$file\'");
	}
	&prepare_filename ($file);
	system "mv $packaged $file"
	    || &interrupt ("Cannot move packaged file into \`$file\'");
	$local_signature{$file} = &single_signature ($file);
    }
}

# $CONFIG file maintainance.

## Digest in file \`$CONFIG\' if not done already.

sub maybe_fetch_config
{
    local (@signature, $index, $string);

    return if ! $fetch_config;

    %remote = ();
    %scan = ();
    %ignore = ();
    %signature = ();

    if (open (CONFIG, $CONFIG))
    {
	while (chop ($_ = <CONFIG>))
	{
	    next if /^$/;
	    next if /^#/;

	    if (/^format\t$PROGRAM ([^ ]+)$/o)
	    {
		&interrupt ("$CONFIG:$.: Unmatching format for $CONFIG")
		    if $1 ne $FORMAT;
	    }
	    elsif (/^title\t(.*)$/)
	    {
		$project_title = $1;
		&warn ("Reading configuration for project \`$project_title\'");
	    }
	    elsif (/^here\t([^ ]+) ([^ ]+)$/)
	    {
		($here_email, $here_home) = ($1, $2);
		$here_email =~ tr/A-Z/a-z/;
		$config_filename = &expand_filename ("$here_home/$CONFIG");
	    }
	    elsif (/^remote\t([^ ]+) ([^ ]+)$/)
	    {
		local ($save_config); # protect against &create_remote;

		$string = $1;
		$string =~ tr/A-Z/a-z/;
		&create_remote ($1, $2);
	    }
	    elsif (/^scan\t([^ ]+)$/)
	    {
		$scan{$1} = 1;
	    }
	    elsif (/^ignore\t([^ ]+)$/)
	    {
		if ($1 eq '\\.remsync')
		{
		    $ignore{'\\.remsync.*'} = 1;
		}
		else
		{
		    $ignore{$1} = 1;
		}
	    }
	    elsif (/^\t([^ ]+) (.*)/)
	    {

		# Temporary code, the time everything is getting updated.
		# Was: $signature{$1} = $2;

		@signature = split (/ /, $2);
		for ($index = 0; $index < @remote; $index++)
		{
		    if (! $signature[$index])
		    {
			&diagnose ("Empty signature for file \`$1\' [$index]");
			$signature[$index] = '-';
			$save_config = 1;
		    }
		}
		$signature{$1} = join (' ', @signature);
	    }
	    else
	    {
		&interrupt ("** $CONFIG:$.: Illegal format for $CONFIG");
	    }
	}
	close CONFIG;

	if (! $project_title)
	{
	    &diagnose ('There is no title for this project.');
	    &query ('Please enter a short project description:');
	    $project_title = $_;
	}
    }
    else
    {
	chop ($_ = `pwd`);
	$_ = &normalize_directory ($_);
	&diagnose ("Directory \`$_\' is not ready for synchronization");
	&query ('Should I prepare it for its first time (y/n)? [y]');
	&interrupt ('Command aborted!') if ! /^(y|yes)$/i;
	$new_config = 1;

	&query ('Please enter a short project description:');
	$project_title = $_;

	$_ = &guess_here_email;
	&query ("What is your full email address, here? [$_]");
	$here_email = $_;

	chop ($_ = `pwd`);
	$here_home = &normalize_directory ($_);
	$config_filename = &expand_filename ("$here_home/$CONFIG");

	foreach (('(.*/)?core(\\..*)?',
		  '.*,v',
		  '.*/RCS/.*',
		  '.*\\.(bak|BAK)',
		  '.*\\.[oa]',
		  '.*~',
		  "\\$CONFIG.*",
		  '\\\#.*'))
	{
	    $ignore{$_} = 1;
	}
    }

    $fetch_config = 0;
    $save_config = 1;
    $study_files = 1;
}

## Write back file \`$CONFIG\' if it has been modified.

sub maybe_save_config
{
    local ($index);

    return if ! $save_config;
    $save_config = 0;
    return if $noop_mode;

    if (! $new_config)
    {
	unlink "$config_filename.bak";
	rename ("$config_filename", "$config_filename.bak")
	    || &interrupt ("Cannot backup file \`$config_filename'");
    }

    open (CONFIG, ">$config_filename")
	|| &interrupt ("Cannot create file \`$config_filename\'");
    print CONFIG
	"# This file is maintained automatically by program \`$PROGRAM\'.",
	"  DO NOT EDIT!\n";
    print CONFIG "\n";
    print CONFIG "format\t$PROGRAM $FORMAT\n";
    print CONFIG "title\t$project_title\n";
    &diagnose ('There is no project title, yet') if ! $project_title;
    print CONFIG "here\t$here_email $here_home\n";
    &diagnose ('There are no declared remote connections, yet')
	if ! @remote;
    foreach (@remote)
    {
	print CONFIG "remote\t$_ $remote{$_}\n";
    }
    print CONFIG "\n";
    foreach (sort keys %scan)
    {
	print CONFIG "scan\t$_\n";
    }
    foreach (sort keys %ignore)
    {
	print CONFIG "ignore\t$_\n";
    }
    foreach (sort keys %signature)
    {
	print CONFIG "\t", $_, ' ', $signature{$_}, "\n";
    }
    close CONFIG;
}

## Scan for files with `find' and `sum', unless this is done already.

sub maybe_study_files
{
    local ($list, $signature, $file);

    # Do not execute this lengthy process without reason.

    return if ! $study_files;
    &warn ('Studying local files for their signature');

    # Find the proper "sum" command.

    if (! $checksum_command)
    {
	foreach (('sum', 'sum -r'))
	{
	    if (`echo x | $_` =~ /^00070 /)
	    {
		$checksum_command = $_;
		last;
	    }
	}
	&interrupt ('Cannot find BSD program `sum\' around')
	    if ! $checksum_command;
    }

    # Trigger execution of find with all the %scan parameters.

    if (%scan == 0)
    {
	$list = ' .';
    }
    else
    {
	$list = '';
	foreach (sort keys %scan)
	{
	    $list .= " '$_'";
	}
    }

    $findtempfile = mktemp( "./remsync.XXXXXX" );

    open (SCAN,
	  "find$list -type f 2> $findtempfile | xargs -r $checksum_command |")
	|| &interrupt ('Cannot launch program `find\'');

    # Process each existing file in turn.

    %local_signature = ();
    $maximum_name_width = 0;

    while (<SCAN>)
    {
	if (/^([0-9]+) +[0-9]+ +(\.\/)?(.*)/)
	{
	    ($signature, $file) = ($1, $3);
	}
	else
	{
	    chop;
	    &diagnose ("Unrecognized output from program \`sum\': \`$_\'");
	    next;
	}

	next if &ignorable_file ($file);

	$local_signature{$file} = $signature;
	$maximum_name_width = length $file
	    if length $file > $maximum_name_width;
    }
    close SCAN;

    # Clean out scanning for non-existing files.

    open (SCAN, $findtempfile);
    while (<SCAN>)
    {
	chop;
	if (/^find: (.*): No such file or directory$/)
	{
	    $file = $1;
	    &diagnose ("No files found while scanning for \`$file\'");
	    if (! defined $scan{$file})
	    {
		&diagnose ('And this is not even a valid scan.  Bizarre...');
	    }
	    elsif ($scan{$file} != $NEWLY_CREATED_SCAN)
	    {
		&warn ('\
To get rid of this warning, delete the scan or have it find something!');
		&query ('Should I delete this scan now (y/n)? [y]');
		&command_delete_scan ($file) if /^(y|yes)$/i;
	    }
	}
	else
	{
	    &diagnose ("Scan error: $_");
	}
    }
    close SCAN;
    unlink $findtempfile;

    $study_files = 0;
}

## Compute `sum' over a single file.

sub single_signature
{
    (split (' ', `$checksum_command $_[0]`))[0];
}

## Update file and signature matrix according to what exists here.

sub update_file_registry
{
    local ($cautious, $size);

    foreach (sort keys %signature)
    {
	if (! defined $local_signature{$_})
	{
	    &warn ("Forgetting file \`$_\'");
	    delete $signature{$_};
	    $save_config = 1;
	}
    }

    foreach (sort keys %local_signature)
    {
	if (! defined $signature{$_})
	{
	    $size = int (((stat $_)[7] + 999) / 1000);
	    &warn ("Noticing new file \`$_\' [${size}K]");
	    $signature{$_} = join (' ', ('-') x @remote);
	    $save_config = 1;
	    $cautious = 1 if $size > 100;
	}
    }

    if ($cautious && !$process_loop)
    {
	&diagnose ('There were new files bigger than 100K');
	&query ('Should I resume the current command (y/n)? [y]');
	&interrupt ('Command aborted!') if ! /^(y|yes)$/i;
    }
}

# Identification and filename services.

## Return a sensible suggestion for our probable email address.

sub guess_here_email
{
    return $here_email if $here_email;

    chop ($_ = `hostname`);
    if (/\./)
    {
	$_ = "$ENV{'LOGNAME'}@$_";
    }
    else
    {
	$_ .= "!$ENV{'LOGNAME'}";
    }
    tr/A-Z/a-z/;
    return $_;
}

## Use forgiving rules to test for equivalence between EMAIL_LEFT
## and EMAIL_RIGHT.

sub equivalent_email
{
    local ($email_left, $email_right) = @_;
    local ($user_left, $user_right, $domain_left, $domain_right);

    if ($email_left =~ /(.+)@(.+)/)
    {
	($user_left, $domain_left) = ($1, $2);
    }
    elsif ($email_left =~ /(.+)!([^!]+)/)
    {
	($user_left, $domain_left) = ($2, $1);
    }
    else
    {
	($user_left, $domain_left) = ($email_left, '');
    }

    if ($email_right =~ /(.+)@(.+)/)
    {
	($user_right, $domain_right) = ($1, $2);
    }
    elsif ($email_right =~ /(.+)!([^!]+)/)
    {
	($user_right, $domain_right) = ($2, $1);
    }
    else
    {
	($user_right, $domain_right) = ($email_right, '');
    }

    $domain_left =~ s/\.uucp$//;
    $domain_right =~ s/\.uucp$//;

    return 0 if ($user_left !~ /^$user_right(-batch)?$/
		 && $user_right !~ /^$user_left(-batch)?$/);

    return 0 if ($domain_left !~ /$domain_right$/
		 && $domain_right !~ /$domain_left$/);
    1;
}

## Return the given filename expanded so the system will recognize it.

sub expand_filename
{
    local ($pwd);

    $_ = $_[0];
    if (/^~/)
    {
	return $ENV{'HOME'} if /^~$/;
	s|^~/|$ENV{'HOME'}/|;
    }
    return $_ if /^\//;

    chop ($pwd = `pwd`);
    "$pwd/$_";
}

## Return the given directory normalized so the user will like
## it more.  However, still avoid relative notations.

sub normalize_directory
{
    return '~' if $_[0] eq $ENV{'HOME'};

    $_ = $_[0];
    s|^$ENV{'HOME'}/|~/|;
    chop ($_ = `cd $_; pwd`) if ! /^[~\/]/;
    $_;
}

## Ensure intermediate directories exist by creating them as needed,
## and that the appropriate permissions are set for the FILE to be
## created or replaced.

sub prepare_filename
{
    local ($filename) = @_;
    local (@filename, $counter);

    if (-e $filename)
    {
	&interrupt ("Cannot modify read-only file \`$filename\'")
	    if ! -w $filename;
	return;
    }

    @filename = split (/\//, $filename);
    pop @filename;

    for ($counter = $filename[0] ? 0 : 1; $counter < @filename; $counter++)
    {
	$filename = join ('/', @filename[0 .. $counter]);
	next if -d $filename;
	&warn ("  Creating new directory \`$filename\'");
	if (! mkdir ($filename, 0755))
	{
	    &interrupt ("Cannot create directory \`$filename\'");
	    return;
	}
    }
}

# Various services.

## Says whether if FILE should be ignored.

sub ignorable_file
{
    local ($file) = @_;

    foreach (keys %ignore)
    {
	if (/^!(.*)/)
	{
	    return 1 if $file !~ /^$1$/;
	}
	else
	{
	    return 1 if $file =~ /^$_$/;
	}
    }
    0;
}

## Initialize @site_set according to the given SET.

sub decode_site_set
{
    local ($set) = @_;
    local ($index, $counter);

    if ($set eq '')
    {
	@site_set = 0 .. @remote - 1;
    }
    elsif ($set eq '!')
    {
	@site_set = ();
    }
    elsif ($set =~ /!(.*)/)
    {
	@site_set = 0 .. @remote - 1;
	foreach (split (' ', $1))
	{
	    $site_set[&validated_remote_index ($_)] = '';
	}
	@site_set = grep (/./, @site_set);
    }
    else
    {
	@site_set = ();
	@copy_list = ();	# used to parallel "from" and "check" lines
	$counter = 0;
	foreach (split (' ', $set))
	{
	    $index = &validated_remote_index ($_);
	    $copy_list[$counter++] = $index;
	    $site_set[$index] = $index;
	}
	@site_set = grep (/./, @site_set);
    }
}

## Create a new REMOTE address with its related DIRECTORY.

sub create_remote
{
    local ($remote, $directory) = @_;

    push (@remote, $remote);
    $remote{$remote} = $directory;
    foreach (keys %signature)
    {
	$signature{$_} .= ' -';
    }
    $save_config = 1;
}

## Alter a REMOTE address to a NEW_REMOTE address, known to be equivalent.

sub change_remote
{
    local ($remote, $new_remote) = @_;

    return if $remote eq $new_remote;
    $remote[&validated_remote_index ($remote)] = $new_remote;
    $remote{$new_remote} = $remote{$remote};
    delete $remote{$remote};
    $save_config = 1;
}

## Destroy information related to a REMOTE address.

sub delete_remote
{
    local ($remote) = @_;
    local (@signature, $index);

    $index = &validated_remote_index ($remote);
    @remote = @remote[0 .. $index - 1, $index + 1 .. @remote - 1];
    delete $remote{$remote};

    foreach (keys %signature)
    {
	@signature = split (/ /, $signature{$_});
	$signature{$_}
	= join (' ',
		@signature[0 .. $index - 1, $index + 1 .. @signature - 1]);
    }
    $save_config = 1;
}

## Return the index of a given REMOTE, interrupting the command if not found.

sub validated_remote_index
{
    local ($remote) = @_;
    local ($index);

    $index = &remote_index ($remote);
    return $index if $index >= 0;
    &interrupt ("Specification \`$remote\' invalid for remote address");
}

## Return the index of a given REMOTE, or a negative value if not found.

sub remote_index
{
    local ($remote) = @_;
    local ($index);

    $remote = $remote[$remote - 1] if ($remote > 0 && $remote <= @remote);
    $index = 0;
    foreach (@remote)
    {
	return $index if $remote eq $_;
	$index++;
    }
    -1;
}

# Interactive dialog and error processing.

## Query the user interactively with QUESTION, return the reply
## in $_.  An empty reply means the default signature from the QUESTION
## if any, written as "...? [DEFAULT]".  Echo the input if used
## in process.

sub query
{
    local ($query) = @_;

    while (1)
    {
	print STDERR "\a$query ";
	$_ = <>;
	if ($_)
	{
	    print STDERR if ! -t;
	    chop;
	    if (/^\?$/)
	    {
		print STDERR $NORMAL_HELP;
		next;
	    }
	    if (/^! *(.*)$/)
	    {
		if ($1)
		{
		    system $1;
		}
		elsif (defined $ENV{$SHELL})
		{
		    system $ENV{$SHELL};
		}
		else
		{
		    system $SH;
		}
		next;
	    }
	    if (/^abort$/)
	    {
		if ($save_config)
		{
		    &diagnose
			("Modifications to file \`$CONFIG\' are unsaved");
		    &query ('Should I stop without saving them (y/n)? [n]');
		    if (/^(y|yes)$/i)
		    {
			$command_loop = 0;
			$process_loop = 0;
			&interrupt ('Program aborted!');
		    }
		}
		&interrupt ('Command aborted!');
	    }
	    $_ = $1 if (! $_ && $query =~ /\? \[(.+)\]$/);
	    return;
	}
	else
	{
	    print STDERR "quit\n";
	    $_ = 'quit';
	    return;
	}
    }
}

## Issue a message for the (possibly interactive) user.

sub warn
{
    warn "  $_[0]\n";
}

## Issue an error message for the (possibly interactive) user.

sub diagnose
{
    warn "* $_[0]\n";
}

## Issue an error message for the (possibly interactive) user, while
## interrupting the command being currently executed.  Abort if none.

sub interrupt
{
    if ($process_loop)
    {
	$workdir_to_unlink = '';
	$invoice_to_unlink = '';
	%signature_received = ();

	warn "* $_[0]\n";
	last PROCESS_LOOP;
    }
    elsif ($command_loop)
    {
	warn "* $_[0]\n";
	next COMMAND_LOOP;
    }
    else
    {
	die "** $_[0]\n";
    }
}

# Local Variables:
# mode: perl
# End:
