2001-12-24 09:57:46 -05:00
|
|
|
#!@PERL@
|
|
|
|
|
|
|
|
# GNU Stow - manage the installation of multiple software packages
|
2001-12-30 12:56:45 -05:00
|
|
|
# Copyright (C) 1993, 1994, 1995, 1996 by Bob Glickstein
|
2011-11-16 09:04:03 -05:00
|
|
|
# Copyright (C) 2000, 2001 Guillaume Morin
|
|
|
|
# Copyright (C) 2007 Kahlil Hodgson
|
|
|
|
#
|
2001-12-24 09:57:46 -05:00
|
|
|
# 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
2001-12-24 09:57:46 -05:00
|
|
|
|
2011-11-16 10:22:12 -05:00
|
|
|
require 5.6.1;
|
|
|
|
|
2011-11-16 10:57:17 -05:00
|
|
|
use File::Spec;
|
2011-11-16 09:04:03 -05:00
|
|
|
use POSIX qw(getcwd);
|
|
|
|
use Getopt::Long;
|
|
|
|
|
|
|
|
my $Version = '@VERSION@';
|
|
|
|
my $ProgramName = $0;
|
|
|
|
$ProgramName =~ s{.*/}{};
|
|
|
|
|
|
|
|
# Verbosity rules:
|
|
|
|
#
|
2011-11-17 08:26:04 -05:00
|
|
|
# 0: errors only
|
|
|
|
# >= 1: print operations: LINK/UNLINK/MKDIR/RMDIR
|
|
|
|
# >= 2: print trace: stow/unstow package/contents/node
|
|
|
|
# >= 3: print trace detail: "_this_ already points to _that_"
|
|
|
|
# >= 4: debug helper routines
|
2011-11-16 09:04:03 -05:00
|
|
|
#
|
|
|
|
# All output (except for version() and usage()) is to stderr to preserve
|
|
|
|
# backward compatibility.
|
|
|
|
|
|
|
|
# These are the defaults for command line options
|
|
|
|
our %Option = (
|
|
|
|
help => 0,
|
|
|
|
conflicts => 0,
|
|
|
|
action => 'stow',
|
|
|
|
simulate => 0,
|
|
|
|
verbose => 0,
|
|
|
|
paranoid => 0,
|
|
|
|
dir => undef,
|
|
|
|
target => undef,
|
|
|
|
ignore => [],
|
|
|
|
override => [],
|
|
|
|
defer => [],
|
|
|
|
);
|
|
|
|
|
|
|
|
# This becomes static after option processing
|
|
|
|
our $Stow_Path; # only use in main loop and find_stowed_path()
|
|
|
|
|
|
|
|
# Store conflicts during pre-processing
|
|
|
|
our @Conflicts = ();
|
|
|
|
|
2011-11-16 09:59:58 -05:00
|
|
|
# Store command line packages to stow (-S and -R)
|
2011-11-16 09:04:03 -05:00
|
|
|
our @Pkgs_To_Stow = ();
|
|
|
|
|
|
|
|
# Store command line packages to unstow (-D and -R)
|
|
|
|
our @Pkgs_To_Delete = ();
|
|
|
|
|
|
|
|
# The following structures are used by the abstractions that allow us to
|
2011-11-16 09:59:58 -05:00
|
|
|
# defer operating on the filesystem until after all potential conflicts have
|
2011-11-16 09:04:03 -05:00
|
|
|
# been assessed.
|
|
|
|
|
|
|
|
# our @Tasks: list of operations to be performed (in order)
|
|
|
|
# each element is a hash ref of the form
|
|
|
|
# {
|
|
|
|
# action => ...
|
|
|
|
# type => ...
|
|
|
|
# path => ... (unique)
|
|
|
|
# source => ... (only for links)
|
|
|
|
# }
|
|
|
|
our @Tasks = ();
|
|
|
|
|
|
|
|
# my %Dir_Task_For: map a path to the corresponding directory task reference
|
2011-11-16 09:59:58 -05:00
|
|
|
# This structure allows us to quickly determine if a path has an existing
|
2011-11-16 09:04:03 -05:00
|
|
|
# directory task associated with it.
|
|
|
|
our %Dir_Task_For = ();
|
|
|
|
|
|
|
|
# my %Link_Task_For: map a path to the corresponding directory task reference
|
2011-11-16 09:59:58 -05:00
|
|
|
# This structure allows us to quickly determine if a path has an existing
|
2011-11-16 09:04:03 -05:00
|
|
|
# directory task associated with it.
|
|
|
|
our %Link_Task_For = ();
|
|
|
|
|
2011-11-17 12:23:04 -05:00
|
|
|
# N.B.: directory tasks and link tasks are NOT mutually exclusive due
|
|
|
|
# to tree splitting (which involves a remove link task followed by
|
|
|
|
# a create directory task).
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
# put the main loop in this block so we can load the
|
|
|
|
# rest of the code as a module for testing
|
2011-11-17 09:17:24 -05:00
|
|
|
if (not caller()) {
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
process_options();
|
|
|
|
set_stow_path();
|
|
|
|
|
|
|
|
# current dir is now the target directory
|
|
|
|
|
|
|
|
for my $package (@Pkgs_To_Delete) {
|
2011-11-17 09:17:24 -05:00
|
|
|
if (not -d join_paths($Stow_Path, $package)) {
|
2011-11-16 09:04:03 -05:00
|
|
|
error("The given package name ($package) is not in your stow path");
|
|
|
|
}
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(2, "Unstowing package $package...");
|
2011-11-16 09:04:03 -05:00
|
|
|
if ($Option{'compat'}) {
|
|
|
|
unstow_contents_orig(
|
2011-11-17 09:17:24 -05:00
|
|
|
join_paths($Stow_Path, $package), # path to package
|
2011-11-17 11:39:02 -05:00
|
|
|
'.', # target is current_dir
|
2011-11-16 09:04:03 -05:00
|
|
|
);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
unstow_contents(
|
2011-11-17 09:17:24 -05:00
|
|
|
join_paths($Stow_Path, $package), # path to package
|
2011-11-17 11:39:02 -05:00
|
|
|
'.', # target is current_dir
|
2011-11-16 09:04:03 -05:00
|
|
|
);
|
|
|
|
}
|
2011-11-17 14:47:20 -05:00
|
|
|
debug(2, "Unstowing package $package... done");
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
for my $package (@Pkgs_To_Stow) {
|
2011-11-17 09:17:24 -05:00
|
|
|
if (not -d join_paths($Stow_Path, $package)) {
|
2011-11-16 09:04:03 -05:00
|
|
|
error("The given package name ($package) is not in your stow path");
|
|
|
|
}
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(2, "Stowing package $package...");
|
2011-11-16 09:04:03 -05:00
|
|
|
stow_contents(
|
2011-11-17 09:17:24 -05:00
|
|
|
join_paths($Stow_Path, $package), # path package
|
2011-11-17 11:39:02 -05:00
|
|
|
'.', # target is current dir
|
2011-11-17 09:17:24 -05:00
|
|
|
join_paths($Stow_Path, $package), # source from target
|
2011-11-16 09:04:03 -05:00
|
|
|
);
|
2011-11-17 14:47:20 -05:00
|
|
|
debug(2, "Stowing package $package... done");
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
# --verbose: tell me what you are planning to do
|
|
|
|
# --simulate: don't execute planned operations
|
|
|
|
# --conflicts: just list any detected conflicts
|
|
|
|
|
|
|
|
if (scalar @Conflicts) {
|
|
|
|
warn "WARNING: conflicts detected.\n";
|
|
|
|
if ($Option{'conflicts'}) {
|
|
|
|
map { warn $_ } @Conflicts;
|
|
|
|
}
|
|
|
|
warn "WARNING: all operations aborted.\n";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
process_tasks();
|
|
|
|
}
|
2001-12-24 09:57:46 -05:00
|
|
|
}
|
|
|
|
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : process_options()
|
|
|
|
# Purpose : parse command line options and update the %Option hash
|
|
|
|
# Parameters: none
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : a fatal error if a bad command line option is given
|
|
|
|
# Comments : checks @ARGV for valid package names
|
|
|
|
#============================================================================
|
|
|
|
sub process_options {
|
|
|
|
get_defaults();
|
|
|
|
#$,="\n"; print @ARGV,"\n"; # for debugging rc file
|
|
|
|
|
|
|
|
Getopt::Long::config('no_ignore_case', 'bundling', 'permute');
|
|
|
|
GetOptions(
|
2011-11-17 09:12:12 -05:00
|
|
|
\%Option,
|
|
|
|
'verbose|v:+', 'help|h', 'simulate|n|no', 'conflicts|c',
|
|
|
|
'version|V', 'compat|p', 'dir|d=s', 'target|t=s',
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
# clean and pre-compile any regex's at parse time
|
|
|
|
'ignore=s' =>
|
|
|
|
sub {
|
|
|
|
my $regex = strip_quotes($_[1]);
|
|
|
|
push @{$Option{'ignore'}}, qr($regex\z)
|
|
|
|
},
|
|
|
|
|
|
|
|
'override=s' =>
|
|
|
|
sub {
|
|
|
|
my $regex = strip_quotes($_[1]);
|
|
|
|
push @{$Option{'override'}}, qr(\A$regex)
|
|
|
|
},
|
|
|
|
|
|
|
|
'defer=s' =>
|
|
|
|
sub {
|
|
|
|
my $regex = strip_quotes($_[1]);
|
|
|
|
push @{$Option{'defer'}}, qr(\A$regex) ;
|
|
|
|
},
|
|
|
|
|
|
|
|
# a little craziness so we can do different actions on the same line:
|
|
|
|
# a -D, -S, or -R changes the action that will be performed on the
|
|
|
|
# package arguments that follow it.
|
|
|
|
'D|delete' => sub { $Option{'action'} = 'delete' },
|
|
|
|
'S|stow' => sub { $Option{'action'} = 'stow' },
|
|
|
|
'R|restow' => sub { $Option{'action'} = 'restow' },
|
|
|
|
'<>' =>
|
|
|
|
sub {
|
|
|
|
if ($Option{'action'} eq 'restow') {
|
|
|
|
push @Pkgs_To_Delete, $_[0];
|
|
|
|
push @Pkgs_To_Stow, $_[0];
|
|
|
|
}
|
|
|
|
elsif ($Option{'action'} eq 'delete') {
|
|
|
|
push @Pkgs_To_Delete, $_[0];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
push @Pkgs_To_Stow, $_[0];
|
|
|
|
}
|
|
|
|
},
|
|
|
|
) or usage();
|
|
|
|
|
|
|
|
#print "$Option{'dir'}\n"; print "$Option{'target'}\n"; exit;
|
|
|
|
|
|
|
|
# clean any leading and trailing whitespace in paths
|
|
|
|
if ($Option{'dir'}) {
|
|
|
|
$Option{'dir'} =~ s/\A +//;
|
|
|
|
$Option{'dir'} =~ s/ +\z//;
|
|
|
|
}
|
|
|
|
if ($Option{'target'}) {
|
|
|
|
$Option{'target'} =~ s/\A +//;
|
|
|
|
$Option{'target'} =~ s/ +\z//;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($Option{'help'}) {
|
|
|
|
usage();
|
|
|
|
}
|
|
|
|
if ($Option{'version'}) {
|
|
|
|
version();
|
|
|
|
}
|
|
|
|
if ($Option{'conflicts'}) {
|
|
|
|
$Option{'simulate'} = 1;
|
|
|
|
}
|
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
if (not scalar @Pkgs_To_Stow and not scalar @Pkgs_To_Delete) {
|
2011-11-16 09:04:03 -05:00
|
|
|
usage("No packages named");
|
|
|
|
}
|
|
|
|
|
|
|
|
# check package arguments
|
2011-11-17 09:17:24 -05:00
|
|
|
for my $package ((@Pkgs_To_Stow, @Pkgs_To_Delete)) {
|
2011-11-16 09:04:03 -05:00
|
|
|
$package =~ s{/+$}{}; # delete trailing slashes
|
2011-11-17 09:17:24 -05:00
|
|
|
if ($package =~ m{/}) {
|
2011-11-16 09:04:03 -05:00
|
|
|
error("Slashes are not permitted in package names");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
2001-12-24 09:57:46 -05:00
|
|
|
}
|
|
|
|
|
2011-11-17 08:26:04 -05:00
|
|
|
#===== SUBROUTINE ============================================================
|
|
|
|
# Name : debug()
|
|
|
|
# Purpose : log to STDERR based on verbosity setting
|
|
|
|
# Parameters: $level => minimum verbosity level required to output this message
|
|
|
|
# : $msg => the message
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : no exceptions
|
|
|
|
# Comments : none
|
|
|
|
#=============================================================================
|
|
|
|
sub debug {
|
|
|
|
my ($level, $msg) = @_;
|
2011-11-17 13:46:32 -05:00
|
|
|
if ($Option{'testmode'}) {
|
|
|
|
print "# $msg\n" if $ENV{TEST_VERBOSE};
|
|
|
|
}
|
|
|
|
elsif ($Option{'verbose'} >= $level) {
|
2011-11-17 08:26:04 -05:00
|
|
|
warn "$msg\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-11-16 09:04:03 -05:00
|
|
|
#===== SUBROUTINE ============================================================
|
|
|
|
# Name : get_defaults()
|
|
|
|
# Purpose : search for default settings in any .stow files
|
|
|
|
# Parameters: none
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : no exceptions
|
|
|
|
# Comments : prepends the contents '~/.stowrc' and '.stowrc' to the command
|
2011-11-16 09:59:58 -05:00
|
|
|
# : line so they get parsed just like normal arguments. (This was
|
2011-11-16 09:04:03 -05:00
|
|
|
# : hacked in so that Emil and I could set different preferences).
|
|
|
|
#=============================================================================
|
|
|
|
sub get_defaults {
|
|
|
|
my @defaults = ();
|
2011-11-17 09:17:24 -05:00
|
|
|
for my $file ($ENV{'HOME'}.'/.stowrc', '.stowrc') {
|
|
|
|
if (-r $file) {
|
2011-11-16 09:04:03 -05:00
|
|
|
warn "Loading defaults from $file\n";
|
|
|
|
open my $FILE, '<', $file
|
|
|
|
or die "Could not open $file for reading\n";
|
2011-11-17 09:17:24 -05:00
|
|
|
while (my $line = <$FILE>){
|
2011-11-16 09:04:03 -05:00
|
|
|
chomp $line;
|
|
|
|
push @defaults, split " ", $line;
|
|
|
|
}
|
|
|
|
close $FILE or die "Could not close open file: $file\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# doing this inline does not seem to work
|
|
|
|
unshift @ARGV, @defaults;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : usage()
|
|
|
|
# Purpose : print program usage message and exit
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $msg => string to prepend to the usage message
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : n/a
|
|
|
|
# Throws : n/a
|
|
|
|
# Comments : if 'msg' is given, then exit with non-zero status
|
|
|
|
#============================================================================
|
2001-12-24 09:57:46 -05:00
|
|
|
sub usage {
|
2011-11-16 09:04:03 -05:00
|
|
|
my ($msg) = @_;
|
|
|
|
|
|
|
|
if ($msg) {
|
|
|
|
print "$ProgramName: $msg\n\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
print <<"EOT";
|
|
|
|
$ProgramName (GNU Stow) version $Version
|
|
|
|
|
|
|
|
SYNOPSIS:
|
|
|
|
|
|
|
|
$ProgramName [OPTION ...] [-D|-S|-R] PACKAGE ... [-D|-S|-R] PACKAGE ...
|
|
|
|
|
|
|
|
OPTIONS:
|
|
|
|
|
|
|
|
-n, --no Do not actually make any filesystem changes
|
|
|
|
-c, --conflicts Scan for and print any conflicts, implies -n
|
|
|
|
-d DIR, --dir=DIR Set stow dir to DIR (default is current dir)
|
|
|
|
-t DIR, --target=DIR Set target to DIR (default is parent of stow dir)
|
|
|
|
-v, --verbose[=N] Increase verbosity (levels are 0,1,2,3;
|
|
|
|
-v or --verbose adds 1; --verbose=N sets level)
|
|
|
|
|
|
|
|
-S, --stow Stow the package names that follow this option
|
|
|
|
-D, --delete Unstow the package names that follow this option
|
|
|
|
-R, --restow Restow (like stow -D followed by stow -S)
|
|
|
|
-p, --compat use legacy algorithm for unstowing
|
|
|
|
|
|
|
|
--ignore=REGEX ignore files ending in this perl regex
|
|
|
|
--defer=REGEX defer stowing files begining with this perl regex
|
|
|
|
if the file is already stowed to another package
|
|
|
|
--override=REGEX force stowing files begining with this perl regex
|
|
|
|
if the file is already stowed to another package
|
|
|
|
-V, --version Show stow version number
|
|
|
|
-h, --help Show this help
|
2001-12-24 09:57:46 -05:00
|
|
|
EOT
|
2011-11-17 09:17:24 -05:00
|
|
|
exit $msg ? 1 : 0;
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : set_stow_path()
|
|
|
|
# Purpose : find the relative path to the stow directory
|
|
|
|
# Parameters: none
|
|
|
|
# Returns : a relative path
|
|
|
|
# Throws : fatal error if either default directories or those set by the
|
|
|
|
# : the command line flags are not valid.
|
|
|
|
# Comments : This sets the current working directory to $Option{target}
|
|
|
|
#============================================================================
|
|
|
|
sub set_stow_path {
|
|
|
|
# Changing dirs helps a lot when soft links are used
|
|
|
|
# Also prevents problems when 'stow_dir' or 'target' are
|
|
|
|
# supplied as relative paths (FIXME: examples?)
|
|
|
|
|
|
|
|
my $current_dir = getcwd();
|
|
|
|
|
2011-11-16 11:52:03 -05:00
|
|
|
# default stow dir is $STOW_DIR if set, otherwise the current
|
|
|
|
# directory
|
2011-11-17 09:17:24 -05:00
|
|
|
if (not $Option{'dir'}) {
|
2011-11-16 11:52:03 -05:00
|
|
|
$Option{'dir'} = exists $ENV{STOW_DIR} ? $ENV{STOW_DIR} : getcwd();
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
if (not chdir($Option{'dir'})) {
|
|
|
|
error("Cannot chdir to target tree: '$Option{'dir'}'");
|
|
|
|
}
|
|
|
|
my $stow_dir = getcwd();
|
|
|
|
|
|
|
|
# back to start in case target is relative
|
|
|
|
if (not chdir($current_dir)) {
|
|
|
|
error("Your directory does not seem to exist anymore");
|
|
|
|
}
|
|
|
|
|
|
|
|
# default target is the parent of the stow directory
|
|
|
|
if (not $Option{'target'}) {
|
|
|
|
$Option{'target'} = parent($Option{'dir'});
|
|
|
|
}
|
|
|
|
if (not chdir($Option{'target'})) {
|
|
|
|
error("Cannot chdir to target tree: $Option{'target'}");
|
|
|
|
}
|
|
|
|
|
|
|
|
# set our one global
|
2011-11-16 10:57:17 -05:00
|
|
|
$Stow_Path = File::Spec->abs2rel($stow_dir);
|
2011-11-16 09:04:03 -05:00
|
|
|
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(2, "current dir is " . getcwd());
|
2011-11-17 14:47:20 -05:00
|
|
|
debug(2, "stow dir path relative to cwd is $Stow_Path");
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : stow_contents()
|
|
|
|
# Purpose : stow the contents of the given directory
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $path => relative path to source dir from current directory
|
|
|
|
# : $target => relative path to symlink target from the current directory
|
|
|
|
# : $source => relative path to symlink source from the dir of target
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : n/a
|
|
|
|
# Throws : a fatal error if directory cannot be read
|
|
|
|
# Comments : stow_node() and stow_contents() are mutually recursive
|
|
|
|
# : $source and $target are used for creating the symlink
|
|
|
|
# : $path is used for folding/unfolding trees as necessary
|
|
|
|
#============================================================================
|
|
|
|
sub stow_contents {
|
|
|
|
my ($path, $target, $source) = @_;
|
|
|
|
|
2011-11-17 14:47:20 -05:00
|
|
|
my $cwd = getcwd();
|
|
|
|
debug(2, "Stowing contents of $path (cwd is $cwd)");
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- $target => $source");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
2011-11-17 11:39:02 -05:00
|
|
|
error("stow_contents() called with non-directory path: $path")
|
|
|
|
unless -d $path;
|
|
|
|
error("stow_contents() called with non-directory target: $target")
|
|
|
|
unless is_a_node($target);
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
opendir my $DIR, $path
|
|
|
|
or error("cannot read directory: $path");
|
|
|
|
my @listing = readdir $DIR;
|
|
|
|
closedir $DIR;
|
|
|
|
|
|
|
|
NODE:
|
|
|
|
for my $node (@listing) {
|
|
|
|
next NODE if $node eq '.';
|
|
|
|
next NODE if $node eq '..';
|
|
|
|
next NODE if ignore($node);
|
|
|
|
stow_node(
|
2011-11-17 09:17:24 -05:00
|
|
|
join_paths($path, $node), # path
|
|
|
|
join_paths($target, $node), # target
|
|
|
|
join_paths($source, $node), # source
|
2011-11-16 09:04:03 -05:00
|
|
|
);
|
|
|
|
}
|
2001-12-24 09:57:46 -05:00
|
|
|
}
|
|
|
|
|
2011-11-16 09:04:03 -05:00
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : stow_node()
|
|
|
|
# Purpose : stow the given node
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $path => relative path to source node from the current directory
|
|
|
|
# : $target => relative path to symlink target from the current directory
|
|
|
|
# : $source => relative path to symlink source from the dir of target
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal exception if a conflict arises
|
|
|
|
# Comments : stow_node() and stow_contents() are mutually recursive
|
|
|
|
# : $source and $target are used for creating the symlink
|
|
|
|
# : $path is used for folding/unfolding trees as necessary
|
|
|
|
#============================================================================
|
|
|
|
sub stow_node {
|
|
|
|
my ($path, $target, $source) = @_;
|
|
|
|
|
2011-11-17 14:47:20 -05:00
|
|
|
debug(2, "Stowing from $path");
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- $target => $source");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
2011-11-16 09:59:58 -05:00
|
|
|
# don't try to stow absolute symlinks (they can't be unstowed)
|
2011-11-16 09:04:03 -05:00
|
|
|
if (-l $source) {
|
|
|
|
my $second_source = read_a_link($source);
|
2011-11-17 09:17:24 -05:00
|
|
|
if ($second_source =~ m{\A/}) {
|
2011-11-16 09:04:03 -05:00
|
|
|
conflict("source is an absolute symlink $source => $second_source");
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "absolute symlinks cannot be unstowed");
|
2011-11-16 09:04:03 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# does the target already exist?
|
|
|
|
if (is_a_link($target)) {
|
|
|
|
|
|
|
|
# where is the link pointing?
|
|
|
|
my $old_source = read_a_link($target);
|
|
|
|
if (not $old_source) {
|
|
|
|
error("Could not read link: $target");
|
|
|
|
}
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- Evaluate existing link: $target => $old_source");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
# does it point to a node under our stow directory?
|
|
|
|
my $old_path = find_stowed_path($target, $old_source);
|
|
|
|
if (not $old_path) {
|
|
|
|
conflict("existing target is not owned by stow: $target");
|
|
|
|
return; # XXX #
|
|
|
|
}
|
|
|
|
|
|
|
|
# does the existing $target actually point to anything?
|
|
|
|
if (is_a_node($old_path)) {
|
|
|
|
if ($old_source eq $source) {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- Skipping $target as it already points to $source");
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
elsif (defer($target)) {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- deferring installation of: $target");
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
elsif (override($target)) {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- overriding installation of: $target");
|
2011-11-16 09:04:03 -05:00
|
|
|
do_unlink($target);
|
2011-11-17 09:17:24 -05:00
|
|
|
do_link($source, $target);
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
2011-11-17 09:17:24 -05:00
|
|
|
elsif (is_a_dir(join_paths(parent($target), $old_source)) &&
|
|
|
|
is_a_dir(join_paths(parent($target), $source)) ) {
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
# if the existing link points to a directory,
|
|
|
|
# and the proposed new link points to a directory,
|
2011-11-17 12:23:04 -05:00
|
|
|
# then we can unfold (split open) the tree at that point
|
2011-11-16 09:04:03 -05:00
|
|
|
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- Unfolding $target");
|
2011-11-16 09:04:03 -05:00
|
|
|
do_unlink($target);
|
|
|
|
do_mkdir($target);
|
2011-11-17 09:17:24 -05:00
|
|
|
stow_contents($old_path, $target, join_paths('..', $old_source));
|
|
|
|
stow_contents($path, $target, join_paths('..', $source));
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
conflict(
|
|
|
|
q{existing target is stowed to a different package: %s => %s},
|
|
|
|
$target,
|
|
|
|
$old_source,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
# the existing link is invalid, so replace it with a good link
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- replacing invalid link: $path");
|
2011-11-16 09:04:03 -05:00
|
|
|
do_unlink($target);
|
|
|
|
do_link($source, $target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
elsif (is_a_node($target)) {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- Evaluate existing node: $target");
|
2011-11-16 09:04:03 -05:00
|
|
|
if (is_a_dir($target)) {
|
2011-11-17 09:17:24 -05:00
|
|
|
stow_contents($path, $target, join_paths('..', $source));
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
conflict(
|
|
|
|
qq{existing target is neither a link nor a directory: $target}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
do_link($source, $target);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : unstow_contents_orig()
|
|
|
|
# Purpose : unstow the contents of the given directory
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $path => relative path to source dir from current directory
|
|
|
|
# : $target => relative path to symlink target from the current directory
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : n/a
|
|
|
|
# Throws : a fatal error if directory cannot be read
|
2011-11-17 14:10:02 -05:00
|
|
|
# Comments : unstow_node_orig() and unstow_contents_orig() are mutually recursive
|
2011-11-16 09:04:03 -05:00
|
|
|
# : Here we traverse the target tree, rather than the source tree.
|
|
|
|
#============================================================================
|
|
|
|
sub unstow_contents_orig {
|
|
|
|
my ($path, $target) = @_;
|
|
|
|
|
|
|
|
# don't try to remove anything under a stow directory
|
|
|
|
if ($target eq $Stow_Path or -e "$target/.stow" or -e "$target/.nonstow") {
|
|
|
|
return;
|
|
|
|
}
|
2011-11-17 14:47:20 -05:00
|
|
|
my $cwd = getcwd();
|
|
|
|
debug(2, "Unstowing from $target (compat mode, cwd is $cwd)");
|
|
|
|
debug(3, "--- source path is $path");
|
2011-11-17 11:39:02 -05:00
|
|
|
# In compat mode we traverse the target tree not the source tree,
|
|
|
|
# so we're unstowing the contents of /target/foo, there's no
|
|
|
|
# guarantee that the corresponding /stow/mypkg/foo exists.
|
|
|
|
error("unstow_contents_orig() called with non-directory target: $target")
|
|
|
|
unless -d $target;
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
opendir my $DIR, $target
|
|
|
|
or error("cannot read directory: $target");
|
|
|
|
my @listing = readdir $DIR;
|
|
|
|
closedir $DIR;
|
|
|
|
|
|
|
|
NODE:
|
|
|
|
for my $node (@listing) {
|
|
|
|
next NODE if $node eq '.';
|
|
|
|
next NODE if $node eq '..';
|
|
|
|
next NODE if ignore($node);
|
|
|
|
unstow_node_orig(
|
|
|
|
join_paths($path, $node), # path
|
|
|
|
join_paths($target, $node), # target
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : unstow_node_orig()
|
|
|
|
# Purpose : unstow the given node
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $path => relative path to source node from the current directory
|
|
|
|
# : $target => relative path to symlink target from the current directory
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal error if a conflict arises
|
|
|
|
# Comments : unstow_node() and unstow_contents() are mutually recursive
|
|
|
|
#============================================================================
|
|
|
|
sub unstow_node_orig {
|
|
|
|
my ($path, $target) = @_;
|
|
|
|
|
2011-11-17 14:47:20 -05:00
|
|
|
debug(2, "Unstowing $target (compat mode)");
|
|
|
|
debug(3, "--- source path is $path");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
# does the target exist
|
|
|
|
if (is_a_link($target)) {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "Evaluate existing link: $target");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
# where is the link pointing?
|
|
|
|
my $old_source = read_a_link($target);
|
|
|
|
if (not $old_source) {
|
|
|
|
error("Could not read link: $target");
|
|
|
|
}
|
|
|
|
|
|
|
|
# does it point to a node under our stow directory?
|
|
|
|
my $old_path = find_stowed_path($target, $old_source);
|
|
|
|
if (not $old_path) {
|
|
|
|
# skip links not owned by stow
|
|
|
|
return; # XXX #
|
|
|
|
}
|
|
|
|
|
2011-11-16 09:59:58 -05:00
|
|
|
# does the existing $target actually point to anything?
|
2011-11-16 09:04:03 -05:00
|
|
|
if (-e $old_path) {
|
2011-11-16 09:59:58 -05:00
|
|
|
# does link point to the right place?
|
2011-11-16 09:04:03 -05:00
|
|
|
if ($old_path eq $path) {
|
|
|
|
do_unlink($target);
|
|
|
|
}
|
|
|
|
elsif (override($target)) {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- overriding installation of: $target");
|
2011-11-16 09:04:03 -05:00
|
|
|
do_unlink($target);
|
|
|
|
}
|
|
|
|
# else leave it alone
|
|
|
|
}
|
|
|
|
else {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- removing invalid link into a stow directory: $path");
|
2011-11-16 09:04:03 -05:00
|
|
|
do_unlink($target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
elsif (-d $target) {
|
|
|
|
unstow_contents_orig($path, $target);
|
|
|
|
|
|
|
|
# this action may have made the parent directory foldable
|
|
|
|
if (my $parent = foldable($target)) {
|
2011-11-17 09:17:24 -05:00
|
|
|
fold_tree($target, $parent);
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
}
|
2011-11-17 15:10:42 -05:00
|
|
|
else {
|
|
|
|
debug(3, "$target did not exist to be unstowed");
|
|
|
|
}
|
2011-11-16 09:04:03 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : unstow_contents()
|
|
|
|
# Purpose : unstow the contents of the given directory
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $path => relative path to source dir from current directory
|
|
|
|
# : $target => relative path to symlink target from the current directory
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : n/a
|
|
|
|
# Throws : a fatal error if directory cannot be read
|
|
|
|
# Comments : unstow_node() and unstow_contents() are mutually recursive
|
2011-11-17 14:10:02 -05:00
|
|
|
# : Here we traverse the source tree, rather than the target tree.
|
2011-11-16 09:04:03 -05:00
|
|
|
#============================================================================
|
|
|
|
sub unstow_contents {
|
|
|
|
my ($path, $target) = @_;
|
|
|
|
|
|
|
|
# don't try to remove anything under a stow directory
|
|
|
|
if ($target eq $Stow_Path or -e "$target/.stow") {
|
|
|
|
return;
|
|
|
|
}
|
2011-11-17 14:47:20 -05:00
|
|
|
my $cwd = getcwd();
|
|
|
|
debug(2, "Unstowing from $target (cwd is $cwd)");
|
|
|
|
debug(3, "--- source path is $path");
|
2011-11-17 15:09:42 -05:00
|
|
|
# We traverse the source tree not the target tree, so $path must exist.
|
2011-11-17 11:39:02 -05:00
|
|
|
error("unstow_contents() called with non-directory path: $path")
|
|
|
|
unless -d $path;
|
2011-11-17 15:09:42 -05:00
|
|
|
# When called at the top level, $target should exist. And
|
|
|
|
# unstow_node() should only call this via mutual recursion if
|
|
|
|
# $target exists.
|
|
|
|
error("unstow_contents() called with invalid target: $target")
|
2011-11-17 11:39:02 -05:00
|
|
|
unless is_a_node($target);
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
opendir my $DIR, $path
|
|
|
|
or error("cannot read directory: $path");
|
|
|
|
my @listing = readdir $DIR;
|
|
|
|
closedir $DIR;
|
|
|
|
|
|
|
|
NODE:
|
|
|
|
for my $node (@listing) {
|
|
|
|
next NODE if $node eq '.';
|
|
|
|
next NODE if $node eq '..';
|
|
|
|
next NODE if ignore($node);
|
|
|
|
unstow_node(
|
|
|
|
join_paths($path, $node), # path
|
|
|
|
join_paths($target, $node), # target
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (-d $target) {
|
|
|
|
cleanup_invalid_links($target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : unstow_node()
|
|
|
|
# Purpose : unstow the given node
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $path => relative path to source node from the current directory
|
|
|
|
# : $target => relative path to symlink target from the current directory
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal error if a conflict arises
|
|
|
|
# Comments : unstow_node() and unstow_contents() are mutually recursive
|
|
|
|
#============================================================================
|
|
|
|
sub unstow_node {
|
|
|
|
my ($path, $target) = @_;
|
|
|
|
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(2, "Unstowing $path");
|
|
|
|
debug(3, "--- target is $target");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
# does the target exist
|
|
|
|
if (is_a_link($target)) {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "Evaluate existing link: $target");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
# where is the link pointing?
|
|
|
|
my $old_source = read_a_link($target);
|
|
|
|
if (not $old_source) {
|
|
|
|
error("Could not read link: $target");
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($old_source =~ m{\A/}) {
|
|
|
|
warn "ignoring a absolute symlink: $target => $old_source\n";
|
|
|
|
return; # XXX #
|
|
|
|
}
|
|
|
|
|
|
|
|
# does it point to a node under our stow directory?
|
|
|
|
my $old_path = find_stowed_path($target, $old_source);
|
|
|
|
if (not $old_path) {
|
|
|
|
conflict(
|
|
|
|
qq{existing target is not owned by stow: $target => $old_source}
|
|
|
|
);
|
|
|
|
return; # XXX #
|
|
|
|
}
|
|
|
|
|
|
|
|
# does the existing $target actually point to anything
|
2011-11-17 09:17:24 -05:00
|
|
|
if (-e $old_path) {
|
2011-11-16 09:04:03 -05:00
|
|
|
# does link points to the right place
|
|
|
|
if ($old_path eq $path) {
|
|
|
|
do_unlink($target);
|
|
|
|
}
|
|
|
|
|
|
|
|
# XXX we quietly ignore links that are stowed to a different
|
|
|
|
# package.
|
|
|
|
|
|
|
|
#elsif (defer($target)) {
|
2011-11-17 08:26:04 -05:00
|
|
|
# debug(3, "--- deferring to installation of: $target");
|
2011-11-16 09:04:03 -05:00
|
|
|
#}
|
|
|
|
#elsif (override($target)) {
|
2011-11-17 08:26:04 -05:00
|
|
|
# debug(3, "--- overriding installation of: $target");
|
2011-11-16 09:04:03 -05:00
|
|
|
# do_unlink($target);
|
|
|
|
#}
|
|
|
|
#else {
|
|
|
|
# conflict(
|
|
|
|
# q{existing target is stowed to a different package: %s => %s},
|
|
|
|
# $target,
|
|
|
|
# $old_source
|
|
|
|
# );
|
|
|
|
#}
|
|
|
|
}
|
|
|
|
else {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- removing invalid link into a stow directory: $path");
|
2011-11-16 09:04:03 -05:00
|
|
|
do_unlink($target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
elsif (-e $target) {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "Evaluate existing node: $target");
|
2011-11-16 09:04:03 -05:00
|
|
|
if (-d $target) {
|
|
|
|
unstow_contents($path, $target);
|
|
|
|
|
|
|
|
# this action may have made the parent directory foldable
|
|
|
|
if (my $parent = foldable($target)) {
|
2011-11-17 09:17:24 -05:00
|
|
|
fold_tree($target, $parent);
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
conflict(
|
|
|
|
qq{existing target is neither a link nor a directory: $target},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2011-11-17 15:10:42 -05:00
|
|
|
else {
|
|
|
|
debug(3, "$target did not exist to be unstowed");
|
|
|
|
}
|
2011-11-16 09:04:03 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : find_stowed_path()
|
|
|
|
# Purpose : determine if the given link points to a member of a
|
|
|
|
# : stowed package
|
|
|
|
# Parameters: $target => path to a symbolic link under current directory
|
|
|
|
# : $source => where that link points to
|
|
|
|
# Returns : relative path to stowed node (from the current directory)
|
|
|
|
# : or '' if link is not owned by stow
|
|
|
|
# Throws : fatal exception if link is unreadable
|
|
|
|
# Comments : allow for stow dir not being under target dir
|
|
|
|
# : we could put more logic under here for multiple stow dirs
|
|
|
|
#============================================================================
|
|
|
|
sub find_stowed_path {
|
|
|
|
my ($target, $source) = @_;
|
|
|
|
|
|
|
|
# evaluate softlink relative to its target
|
|
|
|
my $path = join_paths(parent($target), $source);
|
|
|
|
|
|
|
|
# search for .stow files
|
|
|
|
my $dir = '';
|
|
|
|
for my $part (split m{/+}, $path) {
|
2011-11-17 09:17:24 -05:00
|
|
|
$dir = join_paths($dir, $part);
|
2011-11-16 09:04:03 -05:00
|
|
|
if (-f "$dir/.stow") {
|
|
|
|
return $path;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# compare with $Stow_Path
|
|
|
|
my @path = split m{/+}, $path;
|
|
|
|
my @stow_path = split m{/+}, $Stow_Path;
|
|
|
|
|
|
|
|
# strip off common prefixes
|
2011-11-17 09:17:24 -05:00
|
|
|
while (@path && @stow_path) {
|
|
|
|
if ((shift @path) ne (shift @stow_path)) {
|
2011-11-16 09:04:03 -05:00
|
|
|
return '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (@stow_path) {
|
|
|
|
# @path is not under @stow_dir
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
return $path
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ============================================================
|
|
|
|
# Name : cleanup_invalid_links()
|
|
|
|
# Purpose : clean up invalid links that may block folding
|
|
|
|
# Parameters: $dir => path to directory to check
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : no exceptions
|
|
|
|
# Comments : removing files from a stowed package is probably a bad practice
|
|
|
|
# : so this kind of clean up is not _really_ stow's responsibility;
|
|
|
|
# : however, failing to clean up can block tree folding, so we'll do
|
|
|
|
# : it anyway
|
|
|
|
#=============================================================================
|
|
|
|
sub cleanup_invalid_links {
|
|
|
|
my ($dir) = @_;
|
|
|
|
|
|
|
|
if (not -d $dir) {
|
|
|
|
error("cleanup_invalid_links() called with a non-directory: $dir");
|
|
|
|
}
|
|
|
|
|
|
|
|
opendir my $DIR, $dir
|
|
|
|
or error("cannot read directory: $dir");
|
|
|
|
my @listing = readdir $DIR;
|
|
|
|
closedir $DIR;
|
|
|
|
|
|
|
|
NODE:
|
|
|
|
for my $node (@listing) {
|
|
|
|
next NODE if $node eq '.';
|
|
|
|
next NODE if $node eq '..';
|
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
my $node_path = join_paths($dir, $node);
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
if (-l $node_path and not exists $Link_Task_For{$node_path}) {
|
|
|
|
|
|
|
|
# where is the link pointing?
|
|
|
|
# (dont use read_a_link here)
|
|
|
|
my $source = readlink($node_path);
|
|
|
|
if (not $source) {
|
|
|
|
error("Could not read link $node_path");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
2011-11-17 09:17:24 -05:00
|
|
|
not -e join_paths($dir, $source) and # bad link
|
|
|
|
find_stowed_path($node_path, $source) # owned by stow
|
2011-11-16 09:04:03 -05:00
|
|
|
){
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- removing stale link: $node_path => " .
|
2011-11-17 09:17:24 -05:00
|
|
|
join_paths($dir, $source));
|
2011-11-16 09:04:03 -05:00
|
|
|
do_unlink($node_path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : foldable()
|
|
|
|
# Purpose : determine if a tree can be folded
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $target => path to a directory
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : path to the parent dir iff the tree can be safely folded
|
|
|
|
# Throws : n/a
|
|
|
|
# Comments : the path returned is relative to the parent of $target,
|
|
|
|
# : that is, it can be used as the source for a replacement symlink
|
|
|
|
#============================================================================
|
|
|
|
sub foldable {
|
|
|
|
my ($target) = @_;
|
|
|
|
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- Is $target foldable?");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
opendir my $DIR, $target
|
|
|
|
or error(qq{Cannot read directory "$target" ($!)\n});
|
|
|
|
my @listing = readdir $DIR;
|
|
|
|
closedir $DIR;
|
|
|
|
|
|
|
|
my $parent = '';
|
|
|
|
NODE:
|
|
|
|
for my $node (@listing) {
|
|
|
|
|
|
|
|
next NODE if $node eq '.';
|
|
|
|
next NODE if $node eq '..';
|
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
my $path = join_paths($target, $node);
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
# skip nodes scheduled for removal
|
|
|
|
next NODE if not is_a_node($path);
|
|
|
|
|
|
|
|
# if its not a link then we can't fold its parent
|
|
|
|
return '' if not is_a_link($path);
|
|
|
|
|
|
|
|
# where is the link pointing?
|
|
|
|
my $source = read_a_link($path);
|
|
|
|
if (not $source) {
|
|
|
|
error("Could not read link $path");
|
|
|
|
}
|
|
|
|
if ($parent eq '') {
|
|
|
|
$parent = parent($source)
|
|
|
|
}
|
|
|
|
elsif ($parent ne parent($source)) {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return '' if not $parent;
|
|
|
|
|
|
|
|
# if we get here then all nodes inside $target are links, and those links
|
|
|
|
# point to nodes inside the same directory.
|
|
|
|
|
|
|
|
# chop of leading '..' to get the path to the common parent directory
|
|
|
|
# relative to the parent of our $target
|
|
|
|
$parent =~ s{\A\.\./}{};
|
|
|
|
|
|
|
|
# if the resulting path is owned by stow, we can fold it
|
2011-11-17 09:17:24 -05:00
|
|
|
if (find_stowed_path($target, $parent)) {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- $target is foldable");
|
2011-11-16 09:04:03 -05:00
|
|
|
return $parent;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : fold_tree()
|
|
|
|
# Purpose : fold the given tree
|
|
|
|
# Parameters: $source => link to the folded tree source
|
|
|
|
# : $target => directory that we will replace with a link to $source
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : none
|
|
|
|
# Comments : only called iff foldable() is true so we can remove some checks
|
|
|
|
#============================================================================
|
|
|
|
sub fold_tree {
|
2011-11-17 09:17:24 -05:00
|
|
|
my ($target, $source) = @_;
|
2011-11-16 09:04:03 -05:00
|
|
|
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(3, "--- Folding tree: $target => $source");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
opendir my $DIR, $target
|
|
|
|
or error(qq{Cannot read directory "$target" ($!)\n});
|
|
|
|
my @listing = readdir $DIR;
|
|
|
|
closedir $DIR;
|
|
|
|
|
|
|
|
NODE:
|
|
|
|
for my $node (@listing) {
|
|
|
|
next NODE if $node eq '.';
|
|
|
|
next NODE if $node eq '..';
|
2011-11-17 09:17:24 -05:00
|
|
|
next NODE if not is_a_node(join_paths($target, $node));
|
|
|
|
do_unlink(join_paths($target, $node));
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
do_rmdir($target);
|
|
|
|
do_link($source, $target);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : conflict()
|
|
|
|
# Purpose : handle conflicts in stow operations
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $format => message printf format
|
|
|
|
# : @args => paths that conflict
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal exception unless 'conflicts' option is set
|
|
|
|
# Comments : indicates what type of conflict it is
|
|
|
|
#============================================================================
|
|
|
|
sub conflict {
|
2011-11-17 09:17:24 -05:00
|
|
|
my ($format, @args) = @_;
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
my $message = sprintf($format, @args);
|
|
|
|
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(1, "CONFLICT: $message");
|
|
|
|
push @Conflicts, "CONFLICT: $message\n";
|
2011-11-16 09:04:03 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ============================================================
|
|
|
|
# Name : ignore
|
|
|
|
# Purpose : determine if the given path matches a regex in our ignore list
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $path
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : Boolean
|
|
|
|
# Throws : no exceptions
|
|
|
|
# Comments : none
|
|
|
|
#=============================================================================
|
|
|
|
sub ignore {
|
|
|
|
my ($path) = @_;
|
|
|
|
|
|
|
|
for my $suffix (@{$Option{'ignore'}}) {
|
|
|
|
return 1 if $path =~ m/$suffix/;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ============================================================
|
|
|
|
# Name : defer
|
|
|
|
# Purpose : determine if the given path matches a regex in our defer list
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $path
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : Boolean
|
|
|
|
# Throws : no exceptions
|
|
|
|
# Comments : none
|
|
|
|
#=============================================================================
|
|
|
|
sub defer {
|
|
|
|
my ($path) = @_;
|
|
|
|
|
|
|
|
for my $prefix (@{$Option{'defer'}}) {
|
|
|
|
return 1 if $path =~ m/$prefix/;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ============================================================
|
|
|
|
# Name : overide
|
|
|
|
# Purpose : determine if the given path matches a regex in our override list
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $path
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : Boolean
|
|
|
|
# Throws : no exceptions
|
|
|
|
# Comments : none
|
|
|
|
#=============================================================================
|
|
|
|
sub override {
|
|
|
|
my ($path) = @_;
|
|
|
|
|
|
|
|
for my $regex (@{$Option{'override'}}) {
|
|
|
|
return 1 if $path =~ m/$regex/;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
##############################################################################
|
|
|
|
#
|
|
|
|
# The following code provides the abstractions that allow us to defer operating
|
|
|
|
# on the filesystem until after all potential conflcits have been assessed.
|
|
|
|
#
|
|
|
|
##############################################################################
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : process_tasks()
|
|
|
|
# Purpose : process each task in the @Tasks list
|
|
|
|
# Parameters: none
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal error if @Tasks is corrupted or a task fails
|
|
|
|
# Comments : task involve either creating or deleting dirs and symlinks
|
|
|
|
# : an action is set to 'skip' if it is found to be redundant
|
|
|
|
#============================================================================
|
|
|
|
sub process_tasks {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(2, "Processing tasks...");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
# strip out all tasks with a skip action
|
|
|
|
@Tasks = grep { $_->{'action'} ne 'skip' } @Tasks;
|
|
|
|
|
|
|
|
if (not scalar @Tasks) {
|
|
|
|
warn "There are no outstanding operations to perform.\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ($Option{'simulate'}) {
|
|
|
|
warn "WARNING: simulating so all operations are deferred.\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for my $task (@Tasks) {
|
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
if ($task->{'action'} eq 'create') {
|
|
|
|
if ($task->{'type'} eq 'dir') {
|
2011-11-16 09:04:03 -05:00
|
|
|
mkdir($task->{'path'}, 0777)
|
|
|
|
or error(qq(Could not create directory: $task->{'path'}));
|
|
|
|
}
|
2011-11-17 09:17:24 -05:00
|
|
|
elsif ($task->{'type'} eq 'link') {
|
2011-11-16 09:04:03 -05:00
|
|
|
symlink $task->{'source'}, $task->{'path'}
|
2011-11-17 09:17:24 -05:00
|
|
|
or error(
|
2011-11-16 09:04:03 -05:00
|
|
|
q(Could not create symlink: %s => %s),
|
|
|
|
$task->{'path'},
|
|
|
|
$task->{'source'}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
internal_error(qq(bad task type: $task->{'type'}));
|
|
|
|
}
|
|
|
|
}
|
2011-11-17 09:17:24 -05:00
|
|
|
elsif ($task->{'action'} eq 'remove') {
|
|
|
|
if ($task->{'type'} eq 'dir') {
|
2011-11-16 09:04:03 -05:00
|
|
|
rmdir $task->{'path'}
|
|
|
|
or error(qq(Could not remove directory: $task->{'path'}));
|
|
|
|
}
|
2011-11-17 09:17:24 -05:00
|
|
|
elsif ($task->{'type'} eq 'link') {
|
2011-11-16 09:04:03 -05:00
|
|
|
unlink $task->{'path'}
|
|
|
|
or error(qq(Could not remove link: $task->{'path'}));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
internal_error(qq(bad task type: $task->{'type'}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
internal_error(qq(bad task action: $task->{'action'}));
|
|
|
|
}
|
|
|
|
}
|
2011-11-17 14:47:20 -05:00
|
|
|
debug(2, "Processing tasks... done");
|
2011-11-16 09:04:03 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-11-17 13:24:53 -05:00
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : link_task_action()
|
|
|
|
# Purpose : finds the link task action for the given path, if there is one
|
|
|
|
# Parameters: $path
|
|
|
|
# Returns : 'remove', 'create', or '' if there is no action
|
|
|
|
# Throws : a fatal exception if an invalid action is found
|
|
|
|
# Comments : none
|
|
|
|
#============================================================================
|
|
|
|
sub link_task_action {
|
|
|
|
my ($path) = @_;
|
|
|
|
|
|
|
|
if (! exists $Link_Task_For{$path}) {
|
|
|
|
debug(4, " link_task_action($path): no task");
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
my $action = $Link_Task_For{$path}->{'action'};
|
|
|
|
internal_error("bad task action: $action")
|
|
|
|
unless $action eq 'remove' or $action eq 'create';
|
|
|
|
|
|
|
|
debug(4, " link_task_action($path): link task exists with action $action");
|
|
|
|
return $action;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : dir_task_action()
|
|
|
|
# Purpose : finds the dir task action for the given path, if there is one
|
|
|
|
# Parameters: $path
|
|
|
|
# Returns : 'remove', 'create', or '' if there is no action
|
|
|
|
# Throws : a fatal exception if an invalid action is found
|
|
|
|
# Comments : none
|
|
|
|
#============================================================================
|
|
|
|
sub dir_task_action {
|
|
|
|
my ($path) = @_;
|
|
|
|
|
|
|
|
if (! exists $Dir_Task_For{$path}) {
|
|
|
|
debug(4, " dir_task_action($path): no task");
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
|
|
|
my $action = $Dir_Task_For{$path}->{'action'};
|
|
|
|
internal_error("bad task action: $action")
|
|
|
|
unless $action eq 'remove' or $action eq 'create';
|
|
|
|
|
|
|
|
debug(4, " dir_task_action($path): dir task exists with action $action");
|
|
|
|
return $action;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : parent_link_scheduled_for_removal()
|
|
|
|
# Purpose : determines whether the given path or any parent thereof
|
|
|
|
# : is a link scheduled for removal
|
|
|
|
# Parameters: $path
|
|
|
|
# Returns : Boolean
|
|
|
|
# Throws : none
|
|
|
|
# Comments : none
|
|
|
|
#============================================================================
|
|
|
|
sub parent_link_scheduled_for_removal {
|
|
|
|
my ($path) = @_;
|
|
|
|
|
|
|
|
my $prefix = '';
|
|
|
|
for my $part (split m{/+}, $path) {
|
|
|
|
$prefix = join_paths($prefix, $part);
|
|
|
|
debug(4, " parent_link_scheduled_for_removal($path): prefix $prefix");
|
|
|
|
if (exists $Link_Task_For{$prefix} and
|
|
|
|
$Link_Task_For{$prefix}->{'action'} eq 'remove') {
|
|
|
|
debug(4, " parent_link_scheduled_for_removal($path): link scheduled for removal");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
debug(4, " parent_link_scheduled_for_removal($path): returning false");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-11-16 09:04:03 -05:00
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : is_a_link()
|
|
|
|
# Purpose : is the given path a current or planned link
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $path
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : Boolean
|
|
|
|
# Throws : none
|
|
|
|
# Comments : returns false if an existing link is scheduled for removal
|
2011-11-16 09:59:58 -05:00
|
|
|
# : and true if a non-existent link is scheduled for creation
|
2011-11-16 09:04:03 -05:00
|
|
|
#============================================================================
|
|
|
|
sub is_a_link {
|
|
|
|
my ($path) = @_;
|
2011-11-17 11:37:37 -05:00
|
|
|
debug(4, " is_a_link($path)");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
2011-11-17 13:24:53 -05:00
|
|
|
if (my $action = link_task_action($path)) {
|
2011-11-16 09:04:03 -05:00
|
|
|
if ($action eq 'remove') {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
elsif ($action eq 'create') {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
2011-11-17 13:24:53 -05:00
|
|
|
|
|
|
|
if (-l $path) {
|
2011-11-16 09:04:03 -05:00
|
|
|
# check if any of its parent are links scheduled for removal
|
|
|
|
# (need this for edge case during unfolding)
|
2011-11-17 13:24:53 -05:00
|
|
|
debug(4, " is_a_link($path): is a real link");
|
|
|
|
return parent_link_scheduled_for_removal($path) ? 0 : 1;
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
2011-11-17 13:24:53 -05:00
|
|
|
|
|
|
|
debug(4, " is_a_link($path): returning false");
|
2011-11-16 09:04:03 -05:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : is_a_dir()
|
|
|
|
# Purpose : is the given path a current or planned directory
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $path
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : Boolean
|
|
|
|
# Throws : none
|
|
|
|
# Comments : returns false if an existing directory is scheduled for removal
|
|
|
|
# : and true if a non-existent directory is scheduled for creation
|
|
|
|
# : we also need to be sure we are not just following a link
|
|
|
|
#============================================================================
|
|
|
|
sub is_a_dir {
|
|
|
|
my ($path) = @_;
|
2011-11-17 11:37:37 -05:00
|
|
|
debug(4, " is_a_dir($path)");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
2011-11-17 13:24:53 -05:00
|
|
|
if (my $action = dir_task_action($path)) {
|
2011-11-16 09:04:03 -05:00
|
|
|
if ($action eq 'remove') {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
elsif ($action eq 'create') {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-11-17 13:24:53 -05:00
|
|
|
return 0 if parent_link_scheduled_for_removal($path);
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
if (-d $path) {
|
2011-11-17 13:24:53 -05:00
|
|
|
debug(4, " is_a_dir($path): real dir");
|
2011-11-16 09:04:03 -05:00
|
|
|
return 1;
|
|
|
|
}
|
2011-11-17 13:24:53 -05:00
|
|
|
|
|
|
|
debug(4, " is_a_dir($path): returning false");
|
2011-11-16 09:04:03 -05:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : is_a_node()
|
|
|
|
# Purpose : is the given path a current or planned node
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $path
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : Boolean
|
|
|
|
# Throws : none
|
|
|
|
# Comments : returns false if an existing node is scheduled for removal
|
|
|
|
# : true if a non-existent node is scheduled for creation
|
|
|
|
# : we also need to be sure we are not just following a link
|
|
|
|
#============================================================================
|
|
|
|
sub is_a_node {
|
|
|
|
my ($path) = @_;
|
2011-11-17 11:37:37 -05:00
|
|
|
debug(4, " is_a_node($path)");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
2011-11-17 13:24:53 -05:00
|
|
|
my $laction = link_task_action($path);
|
|
|
|
my $daction = dir_task_action($path);
|
|
|
|
|
|
|
|
if ($laction eq 'remove') {
|
|
|
|
if ($daction eq 'remove') {
|
|
|
|
internal_error("removing link and dir: $path");
|
2011-11-16 09:04:03 -05:00
|
|
|
return 0;
|
|
|
|
}
|
2011-11-17 13:24:53 -05:00
|
|
|
elsif ($daction eq 'create') {
|
|
|
|
# Assume that we're unfolding $path, and that the link
|
|
|
|
# removal action is earlier than the dir creation action
|
|
|
|
# in the task queue. FIXME: is this a safe assumption?
|
2011-11-16 09:04:03 -05:00
|
|
|
return 1;
|
|
|
|
}
|
2011-11-17 13:24:53 -05:00
|
|
|
else { # no dir action
|
|
|
|
return 0;
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
}
|
2011-11-17 13:24:53 -05:00
|
|
|
elsif ($laction eq 'create') {
|
|
|
|
if ($daction eq 'remove') {
|
|
|
|
# Assume that we're folding $path, and that the dir
|
|
|
|
# removal action is earlier than the link creation action
|
|
|
|
# in the task queue. FIXME: is this a safe assumption?
|
|
|
|
return 1;
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
2011-11-17 13:24:53 -05:00
|
|
|
elsif ($daction eq 'create') {
|
|
|
|
internal_error("creating link and dir: $path");
|
2011-11-16 09:04:03 -05:00
|
|
|
return 1;
|
|
|
|
}
|
2011-11-17 13:24:53 -05:00
|
|
|
else { # no dir action
|
|
|
|
return 1;
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
}
|
2011-11-17 13:24:53 -05:00
|
|
|
else {
|
|
|
|
# No link action
|
|
|
|
if ($daction eq 'remove') {
|
2011-11-16 09:04:03 -05:00
|
|
|
return 0;
|
|
|
|
}
|
2011-11-17 13:24:53 -05:00
|
|
|
elsif ($daction eq 'create') {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else { # no dir action
|
|
|
|
# fall through to below
|
|
|
|
}
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
|
2011-11-17 13:24:53 -05:00
|
|
|
return 0 if parent_link_scheduled_for_removal($path);
|
|
|
|
|
2011-11-16 09:04:03 -05:00
|
|
|
if (-e $path) {
|
2011-11-17 13:24:53 -05:00
|
|
|
debug(4, " is_a_node($path): really exists");
|
2011-11-16 09:04:03 -05:00
|
|
|
return 1;
|
|
|
|
}
|
2011-11-17 13:24:53 -05:00
|
|
|
|
|
|
|
debug(4, " is_a_node($path): returning false");
|
2011-11-16 09:04:03 -05:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : read_a_link()
|
|
|
|
# Purpose : return the source of a current or planned link
|
|
|
|
# Parameters: $path => path to the link target
|
|
|
|
# Returns : a string
|
|
|
|
# Throws : fatal exception if the given path is not a current or planned
|
|
|
|
# : link
|
|
|
|
# Comments : none
|
|
|
|
#============================================================================
|
|
|
|
sub read_a_link {
|
|
|
|
my ($path) = @_;
|
|
|
|
|
2011-11-17 13:24:53 -05:00
|
|
|
if (my $action = link_task_action($path)) {
|
|
|
|
debug(4, " read_a_link($path): task exists with action $action");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
if ($action eq 'create') {
|
|
|
|
return $Link_Task_For{$path}->{'source'};
|
|
|
|
}
|
|
|
|
elsif ($action eq 'remove') {
|
|
|
|
internal_error(
|
|
|
|
"read_a_link() passed a path that is scheduled for removal: $path"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
elsif (-l $path) {
|
2011-11-17 13:24:53 -05:00
|
|
|
debug(4, " read_a_link($path): real link");
|
2011-11-16 09:04:03 -05:00
|
|
|
return readlink $path
|
|
|
|
or error("Could not read link: $path");
|
|
|
|
}
|
|
|
|
internal_error("read_a_link() passed a non link path: $path\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : do_link()
|
|
|
|
# Purpose : wrap 'link' operation for later processing
|
2011-11-17 11:35:57 -05:00
|
|
|
# Parameters: $oldfile => the existing file to link to
|
|
|
|
# : $newfile => the file to link
|
2011-11-16 09:04:03 -05:00
|
|
|
# Returns : n/a
|
|
|
|
# Throws : error if this clashes with an existing planned operation
|
|
|
|
# Comments : cleans up operations that undo previous operations
|
|
|
|
#============================================================================
|
|
|
|
sub do_link {
|
2011-11-17 09:17:24 -05:00
|
|
|
my ($oldfile, $newfile) = @_;
|
2011-11-16 09:04:03 -05:00
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
if (exists $Dir_Task_For{$newfile}) {
|
2011-11-16 09:04:03 -05:00
|
|
|
my $task_ref = $Dir_Task_For{$newfile};
|
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
if ($task_ref->{'action'} eq 'create') {
|
2011-11-16 09:04:03 -05:00
|
|
|
if ($task_ref->{'type'} eq 'dir') {
|
|
|
|
internal_error(
|
2011-11-17 09:17:24 -05:00
|
|
|
"new link (%s => %s) clashes with planned new directory",
|
2011-11-16 09:04:03 -05:00
|
|
|
$newfile,
|
|
|
|
$oldfile,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2011-11-17 09:17:24 -05:00
|
|
|
elsif ($task_ref->{'action'} eq 'remove') {
|
2011-11-16 09:04:03 -05:00
|
|
|
# we may need to remove a directory before creating a link so continue;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
internal_error("bad task action: $task_ref->{'action'}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
if (exists $Link_Task_For{$newfile}) {
|
2011-11-16 09:04:03 -05:00
|
|
|
my $task_ref = $Link_Task_For{$newfile};
|
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
if ($task_ref->{'action'} eq 'create') {
|
|
|
|
if ($task_ref->{'source'} ne $oldfile) {
|
2011-11-16 09:04:03 -05:00
|
|
|
internal_error(
|
|
|
|
"new link clashes with planned new link: %s => %s",
|
|
|
|
$task_ref->{'path'},
|
|
|
|
$task_ref->{'source'},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
else {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(1, "LINK: $newfile => $oldfile (duplicates previous action)");
|
2011-11-16 09:04:03 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2011-11-17 09:17:24 -05:00
|
|
|
elsif ($task_ref->{'action'} eq 'remove') {
|
|
|
|
if ($task_ref->{'source'} eq $oldfile) {
|
2011-11-16 09:04:03 -05:00
|
|
|
# no need to remove a link we are going to recreate
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(1, "LINK: $newfile => $oldfile (reverts previous action)");
|
2011-11-16 09:04:03 -05:00
|
|
|
$Link_Task_For{$newfile}->{'action'} = 'skip';
|
|
|
|
delete $Link_Task_For{$newfile};
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
# we may need to remove a link to replace it so continue
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
internal_error("bad task action: $task_ref->{'action'}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# creating a new link
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(1, "LINK: $newfile => $oldfile");
|
2011-11-16 09:04:03 -05:00
|
|
|
my $task = {
|
|
|
|
action => 'create',
|
|
|
|
type => 'link',
|
|
|
|
path => $newfile,
|
|
|
|
source => $oldfile,
|
|
|
|
};
|
|
|
|
push @Tasks, $task;
|
|
|
|
$Link_Task_For{$newfile} = $task;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : do_unlink()
|
|
|
|
# Purpose : wrap 'unlink' operation for later processing
|
|
|
|
# Parameters: $file => the file to unlink
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : error if this clashes with an existing planned operation
|
|
|
|
# Comments : will remove an existing planned link
|
|
|
|
#============================================================================
|
|
|
|
sub do_unlink {
|
|
|
|
my ($file) = @_;
|
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
if (exists $Link_Task_For{$file}) {
|
2011-11-16 09:04:03 -05:00
|
|
|
my $task_ref = $Link_Task_For{$file};
|
2011-11-17 09:17:24 -05:00
|
|
|
if ($task_ref->{'action'} eq 'remove') {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(1, "UNLINK: $file (duplicates previous action)");
|
2011-11-16 09:04:03 -05:00
|
|
|
return;
|
|
|
|
}
|
2011-11-17 09:17:24 -05:00
|
|
|
elsif ($task_ref->{'action'} eq 'create') {
|
2011-11-16 09:04:03 -05:00
|
|
|
# do need to create a link then remove it
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(1, "UNLINK: $file (reverts previous action)");
|
2011-11-16 09:04:03 -05:00
|
|
|
$Link_Task_For{$file}->{'action'} = 'skip';
|
|
|
|
delete $Link_Task_For{$file};
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
internal_error("bad task action: $task_ref->{'action'}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
if (exists $Dir_Task_For{$file} and $Dir_Task_For{$file} eq 'create') {
|
2011-11-16 09:04:03 -05:00
|
|
|
internal_error(
|
|
|
|
"new unlink operation clashes with planned operation: %s dir %s",
|
|
|
|
$Dir_Task_For{$file}->{'action'},
|
|
|
|
$file
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
# remove the link
|
2011-11-17 08:26:04 -05:00
|
|
|
#debug(1, "UNLINK: $file (" . (caller())[2] . ")");
|
|
|
|
debug(1, "UNLINK: $file");
|
2011-11-16 09:04:03 -05:00
|
|
|
|
|
|
|
my $source = readlink $file or error("could not readlink $file");
|
|
|
|
|
|
|
|
my $task = {
|
|
|
|
action => 'remove',
|
|
|
|
type => 'link',
|
|
|
|
path => $file,
|
|
|
|
source => $source,
|
|
|
|
};
|
|
|
|
push @Tasks, $task;
|
|
|
|
$Link_Task_For{$file} = $task;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : do_mkdir()
|
|
|
|
# Purpose : wrap 'mkdir' operation
|
|
|
|
# Parameters: $dir => the directory to remove
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal exception if operation fails
|
|
|
|
# Comments : outputs a message if 'verbose' option is set
|
|
|
|
# : does not perform operation if 'simulate' option is set
|
|
|
|
# Comments : cleans up operations that undo previous operations
|
|
|
|
#============================================================================
|
|
|
|
sub do_mkdir {
|
|
|
|
my ($dir) = @_;
|
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
if (exists $Link_Task_For{$dir}) {
|
2011-11-16 09:04:03 -05:00
|
|
|
my $task_ref = $Link_Task_For{$dir};
|
|
|
|
|
|
|
|
if ($task_ref->{'action'} eq 'create') {
|
|
|
|
internal_error(
|
|
|
|
"new dir clashes with planned new link (%s => %s)",
|
|
|
|
$task_ref->{'path'},
|
|
|
|
$task_ref->{'source'},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
elsif ($task_ref->{'action'} eq 'remove') {
|
|
|
|
# may need to remove a link before creating a directory so continue
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
internal_error("bad task action: $task_ref->{'action'}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
if (exists $Dir_Task_For{$dir}) {
|
2011-11-16 09:04:03 -05:00
|
|
|
my $task_ref = $Dir_Task_For{$dir};
|
|
|
|
|
|
|
|
if ($task_ref->{'action'} eq 'create') {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(1, "MKDIR: $dir (duplicates previous action)");
|
2011-11-16 09:04:03 -05:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
elsif ($task_ref->{'action'} eq 'remove') {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(1, "MKDIR: $dir (reverts previous action)");
|
2011-11-16 09:04:03 -05:00
|
|
|
$Dir_Task_For{$dir}->{'action'} = 'skip';
|
|
|
|
delete $Dir_Task_For{$dir};
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
internal_error("bad task action: $task_ref->{'action'}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(1, "MKDIR: $dir");
|
2011-11-16 09:04:03 -05:00
|
|
|
my $task = {
|
|
|
|
action => 'create',
|
|
|
|
type => 'dir',
|
|
|
|
path => $dir,
|
|
|
|
source => undef,
|
|
|
|
};
|
|
|
|
push @Tasks, $task;
|
|
|
|
$Dir_Task_For{$dir} = $task;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : do_rmdir()
|
|
|
|
# Purpose : wrap 'rmdir' operation
|
|
|
|
# Parameters: $dir => the directory to remove
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : fatal exception if operation fails
|
|
|
|
# Comments : outputs a message if 'verbose' option is set
|
|
|
|
# : does not perform operation if 'simulate' option is set
|
|
|
|
#============================================================================
|
|
|
|
sub do_rmdir {
|
|
|
|
my ($dir) = @_;
|
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
if (exists $Link_Task_For{$dir}) {
|
2011-11-16 09:04:03 -05:00
|
|
|
my $task_ref = $Link_Task_For{$dir};
|
|
|
|
internal_error(
|
|
|
|
"rmdir clashes with planned operation: %s link %s => %s",
|
|
|
|
$task_ref->{'action'},
|
|
|
|
$task_ref->{'path'},
|
|
|
|
$task_ref->{'source'}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
if (exists $Dir_Task_For{$dir}) {
|
2011-11-16 09:04:03 -05:00
|
|
|
my $task_ref = $Link_Task_For{$dir};
|
|
|
|
|
2011-11-17 09:17:24 -05:00
|
|
|
if ($task_ref->{'action'} eq 'remove') {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(1, "RMDIR $dir (duplicates previous action)");
|
2011-11-16 09:04:03 -05:00
|
|
|
return;
|
|
|
|
}
|
2011-11-17 09:17:24 -05:00
|
|
|
elsif ($task_ref->{'action'} eq 'create') {
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(1, "MKDIR $dir (reverts previous action)");
|
2011-11-16 09:04:03 -05:00
|
|
|
$Link_Task_For{$dir}->{'action'} = 'skip';
|
|
|
|
delete $Link_Task_For{$dir};
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
internal_error("bad task action: $task_ref->{'action'}");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-11-17 08:26:04 -05:00
|
|
|
debug(1, "RMDIR $dir");
|
2011-11-16 09:04:03 -05:00
|
|
|
my $task = {
|
|
|
|
action => 'remove',
|
|
|
|
type => 'dir',
|
|
|
|
path => $dir,
|
|
|
|
source => '',
|
|
|
|
};
|
|
|
|
push @Tasks, $task;
|
|
|
|
$Dir_Task_For{$dir} = $task;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
#############################################################################
|
|
|
|
#
|
|
|
|
# General Utilities: nothing stow specific here.
|
|
|
|
#
|
|
|
|
#############################################################################
|
|
|
|
|
|
|
|
#===== SUBROUTINE ============================================================
|
|
|
|
# Name : strip_quotes
|
|
|
|
# Purpose : remove matching outer quotes from the given string
|
|
|
|
# Parameters: none
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : no exceptions
|
|
|
|
# Comments : none
|
|
|
|
#=============================================================================
|
|
|
|
sub strip_quotes {
|
|
|
|
my ($string) = @_;
|
|
|
|
|
|
|
|
if ($string =~ m{\A\s*'(.*)'\s*\z}) {
|
|
|
|
return $1;
|
|
|
|
}
|
|
|
|
elsif ($string =~ m{\A\s*"(.*)"\s*\z}) {
|
|
|
|
return $1
|
|
|
|
}
|
|
|
|
return $string;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
2011-11-16 09:59:58 -05:00
|
|
|
# Name : join_paths()
|
2011-11-16 09:04:03 -05:00
|
|
|
# Purpose : concatenates given paths
|
|
|
|
# Parameters: path1, path2, ... => paths
|
|
|
|
# Returns : concatenation of given paths
|
|
|
|
# Throws : n/a
|
|
|
|
# Comments : factors out redundant path elements:
|
|
|
|
# : '//' => '/' and 'a/b/../c' => 'a/c'
|
|
|
|
#============================================================================
|
|
|
|
sub join_paths {
|
|
|
|
my @paths = @_;
|
|
|
|
|
|
|
|
# weed out empty components and concatenate
|
|
|
|
my $result = join '/', grep {!/\A\z/} @paths;
|
|
|
|
|
|
|
|
# factor out back references and remove redundant /'s)
|
|
|
|
my @result = ();
|
|
|
|
PART:
|
2011-11-17 09:17:24 -05:00
|
|
|
for my $part (split m{/+}, $result) {
|
2011-11-16 09:04:03 -05:00
|
|
|
next PART if $part eq '.';
|
|
|
|
if (@result && $part eq '..' && $result[-1] ne '..') {
|
|
|
|
pop @result;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
push @result, $part;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return join '/', @result;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : parent
|
|
|
|
# Purpose : find the parent of the given path
|
|
|
|
# Parameters: @path => components of the path
|
|
|
|
# Returns : returns a path string
|
|
|
|
# Throws : n/a
|
|
|
|
# Comments : allows you to send multiple chunks of the path
|
|
|
|
# : (this feature is currently not used)
|
|
|
|
#============================================================================
|
|
|
|
sub parent {
|
|
|
|
my @path = @_;
|
|
|
|
my $path = join '/', @_;
|
|
|
|
my @elts = split m{/+}, $path;
|
|
|
|
pop @elts;
|
|
|
|
return join '/', @elts;
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : internal_error()
|
|
|
|
# Purpose : output internal error message in a consistent form and die
|
|
|
|
# Parameters: $message => error message to output
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : n/a
|
|
|
|
# Comments : none
|
|
|
|
#============================================================================
|
|
|
|
sub internal_error {
|
2011-11-17 09:17:24 -05:00
|
|
|
my ($format, @args) = @_;
|
|
|
|
die "$ProgramName: INTERNAL ERROR: " . sprintf($format, @args) . "\n",
|
2011-11-16 09:04:03 -05:00
|
|
|
"This _is_ a bug. Please submit a bug report so we can fix it:-)\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : error()
|
|
|
|
# Purpose : output error message in a consistent form and die
|
|
|
|
# Parameters: $message => error message to output
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : n/a
|
|
|
|
# Comments : none
|
|
|
|
#============================================================================
|
|
|
|
sub error {
|
2011-11-17 09:17:24 -05:00
|
|
|
my ($format, @args) = @_;
|
|
|
|
die "$ProgramName: ERROR: " . sprintf($format, @args) . " ($!)\n";
|
2011-11-16 09:04:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#===== SUBROUTINE ===========================================================
|
|
|
|
# Name : version()
|
|
|
|
# Purpose : print this programs verison and exit
|
|
|
|
# Parameters: none
|
|
|
|
# Returns : n/a
|
|
|
|
# Throws : n/a
|
|
|
|
# Comments : none
|
|
|
|
#============================================================================
|
2001-12-24 09:57:46 -05:00
|
|
|
sub version {
|
2011-11-16 09:04:03 -05:00
|
|
|
print "$ProgramName (GNU Stow) version $Version\n";
|
|
|
|
exit 0;
|
2001-12-24 09:57:46 -05:00
|
|
|
}
|
|
|
|
|
2011-11-16 09:04:03 -05:00
|
|
|
1; # return true so we can load this script as a module during unit testing
|
|
|
|
|
2001-12-24 09:57:46 -05:00
|
|
|
# Local variables:
|
|
|
|
# mode: perl
|
2011-11-17 09:12:14 -05:00
|
|
|
# cperl-indent-level: 4
|
|
|
|
# end:
|
2011-11-16 09:04:03 -05:00
|
|
|
# vim: ft=perl
|