summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2020-06-01 17:29:10 -0700
committerMike Crute <mike@crute.us>2020-06-01 17:29:10 -0700
commit7d51c7d60ef8b8f7fb717a418945e6116df83032 (patch)
tree12e4ddb6d505b500700723399267cd2388a8498f /bin
parent330ba93bf5a2465422ba1170f3c8aa5b8866393b (diff)
downloaddotfiles-7d51c7d60ef8b8f7fb717a418945e6116df83032.tar.bz2
dotfiles-7d51c7d60ef8b8f7fb717a418945e6116df83032.tar.xz
dotfiles-7d51c7d60ef8b8f7fb717a418945e6116df83032.zip
Combine stow into one file
Diffstat (limited to 'bin')
-rw-r--r--bin/Stow.pm2299
-rwxr-xr-xbin/stow2311
2 files changed, 2307 insertions, 2303 deletions
diff --git a/bin/Stow.pm b/bin/Stow.pm
deleted file mode 100644
index c63e164..0000000
--- a/bin/Stow.pm
+++ /dev/null
@@ -1,2299 +0,0 @@
1#!/usr/bin/perl
2
3package Stow;
4
5=head1 NAME
6
7Stow - manage the installation of multiple software packages
8
9=head1 SYNOPSIS
10
11 my $stow = new Stow(%$options);
12
13 $stow->plan_unstow(@pkgs_to_unstow);
14 $stow->plan_stow (@pkgs_to_stow);
15
16 my %conflicts = $stow->get_conflicts;
17 $stow->process_tasks() unless %conflicts;
18
19=head1 DESCRIPTION
20
21This is the backend Perl module for GNU Stow, a program for managing
22the installation of software packages, keeping them separate
23(C</usr/local/stow/emacs> vs. C</usr/local/stow/perl>, for example)
24while making them appear to be installed in the same place
25(C</usr/local>).
26
27Stow doesn't store an extra state between runs, so there's no danger
28of mangling directories when file hierarchies don't match the
29database. Also, stow will never delete any files, directories, or
30links that appear in a stow directory, so it is always possible to
31rebuild the target tree.
32
33=cut
34
35use strict;
36use warnings;
37
38use Carp qw(carp cluck croak confess longmess);
39use File::Copy qw(move);
40use File::Spec;
41use POSIX qw(getcwd);
42
43=head1 NAME
44
45Stow::Util - general utilities
46
47=head1 SYNOPSIS
48
49 use Stow::Util qw(debug set_debug_level error ...);
50
51=head1 DESCRIPTION
52
53Supporting utility routines for L<Stow>.
54
55=cut
56
57use base qw(Exporter);
58our @EXPORT_OK = qw(
59 error debug set_debug_level set_test_mode
60 join_paths parent canon_path restore_cwd
61);
62
63our $ProgramName = 'stow';
64our $VERSION = '2.2.2';
65
66#############################################################################
67#
68# General Utilities: nothing stow specific here.
69#
70#############################################################################
71
72=head1 IMPORTABLE SUBROUTINES
73
74=head2 error($format, @args)
75
76Outputs an error message in a consistent form and then dies.
77
78=cut
79
80sub error {
81 my ($format, @args) = @_;
82 die "$ProgramName: ERROR: " . sprintf($format, @args) . "\n";
83}
84
85=head2 set_debug_level($level)
86
87Sets verbosity level for C<debug()>.
88
89=cut
90
91our $debug_level = 0;
92
93sub set_debug_level {
94 my ($level) = @_;
95 $debug_level = $level;
96}
97
98=head2 set_test_mode($on_or_off)
99
100Sets testmode on or off.
101
102=cut
103
104our $test_mode = 0;
105
106sub set_test_mode {
107 my ($on_or_off) = @_;
108 if ($on_or_off) {
109 $test_mode = 1;
110 }
111 else {
112 $test_mode = 0;
113 }
114}
115
116=head2 debug($level, $msg)
117
118Logs to STDERR based on C<$debug_level> setting. C<$level> is the
119minimum verbosity level required to output C<$msg>. All output is to
120STDERR to preserve backward compatibility, except for in test mode,
121when STDOUT is used instead. In test mode, the verbosity can be
122overridden via the C<TEST_VERBOSE> environment variable.
123
124Verbosity rules:
125
126=over 4
127
128=item 0: errors only
129
130=item >= 1: print operations: LINK/UNLINK/MKDIR/RMDIR/MV
131
132=item >= 2: print operation exceptions
133
134e.g. "_this_ already points to _that_", skipping, deferring,
135overriding, fixing invalid links
136
137=item >= 3: print trace detail: trace: stow/unstow/package/contents/node
138
139=item >= 4: debug helper routines
140
141=item >= 5: debug ignore lists
142
143=back
144
145=cut
146
147sub debug {
148 my ($level, $msg) = @_;
149 if ($debug_level >= $level) {
150 if ($test_mode) {
151 print "# $msg\n";
152 }
153 else {
154 warn "$msg\n";
155 }
156 }
157}
158
159#===== METHOD ===============================================================
160# Name : join_paths()
161# Purpose : concatenates given paths
162# Parameters: path1, path2, ... => paths
163# Returns : concatenation of given paths
164# Throws : n/a
165# Comments : factors out redundant path elements:
166# : '//' => '/' and 'a/b/../c' => 'a/c'
167#============================================================================
168sub join_paths {
169 my @paths = @_;
170
171 # weed out empty components and concatenate
172 my $result = join '/', grep {! /\A\z/} @paths;
173
174 # factor out back references and remove redundant /'s)
175 my @result = ();
176 PART:
177 for my $part (split m{/+}, $result) {
178 next PART if $part eq '.';
179 if (@result && $part eq '..' && $result[-1] ne '..') {
180 pop @result;
181 }
182 else {
183 push @result, $part;
184 }
185 }
186
187 return join '/', @result;
188}
189
190#===== METHOD ===============================================================
191# Name : parent
192# Purpose : find the parent of the given path
193# Parameters: @path => components of the path
194# Returns : returns a path string
195# Throws : n/a
196# Comments : allows you to send multiple chunks of the path
197# : (this feature is currently not used)
198#============================================================================
199sub parent {
200 my @path = @_;
201 my $path = join '/', @_;
202 my @elts = split m{/+}, $path;
203 pop @elts;
204 return join '/', @elts;
205}
206
207#===== METHOD ===============================================================
208# Name : canon_path
209# Purpose : find absolute canonical path of given path
210# Parameters: $path
211# Returns : absolute canonical path
212# Throws : n/a
213# Comments : is this significantly different from File::Spec->rel2abs?
214#============================================================================
215sub canon_path {
216 my ($path) = @_;
217
218 my $cwd = getcwd();
219 chdir($path) or error("canon_path: cannot chdir to $path from $cwd");
220 my $canon_path = getcwd();
221 restore_cwd($cwd);
222
223 return $canon_path;
224}
225
226sub restore_cwd {
227 my ($prev) = @_;
228 chdir($prev) or error("Your current directory $prev seems to have vanished");
229}
230
231=head1 BUGS
232
233=head1 SEE ALSO
234
235=cut
236
237
238our $LOCAL_IGNORE_FILE = '.stow-local-ignore';
239our $GLOBAL_IGNORE_FILE = '.stow-global-ignore';
240
241our @default_global_ignore_regexps =
242 __PACKAGE__->get_default_global_ignore_regexps();
243
244# These are the default options for each Stow instance.
245our %DEFAULT_OPTIONS = (
246 conflicts => 0,
247 simulate => 0,
248 verbose => 0,
249 paranoid => 0,
250 compat => 0,
251 test_mode => 0,
252 adopt => 0,
253 'no-folding' => 0,
254 ignore => [],
255 override => [],
256 defer => [],
257);
258
259=head1 CONSTRUCTORS
260
261=head2 new(%options)
262
263=head3 Required options
264
265=over 4
266
267=item * dir - the stow directory
268
269=item * target - the target directory
270
271=back
272
273=head3 Non-mandatory options
274
275See the documentation for the F<stow> CLI front-end for information on these.
276
277=over 4
278
279=item * conflicts
280
281=item * simulate
282
283=item * verbose
284
285=item * paranoid
286
287=item * compat
288
289=item * test_mode
290
291=item * adopt
292
293=item * no-folding
294
295=item * ignore
296
297=item * override
298
299=item * defer
300
301=back
302
303N.B. This sets the current working directory to the target directory.
304
305=cut
306
307sub new {
308 my $self = shift;
309 my $class = ref($self) || $self;
310 my %opts = @_;
311
312 my $new = bless { }, $class;
313
314 $new->{action_count} = 0;
315
316 for my $required_arg (qw(dir target)) {
317 croak "$class->new() called without '$required_arg' parameter\n"
318 unless exists $opts{$required_arg};
319 $new->{$required_arg} = delete $opts{$required_arg};
320 }
321
322 for my $opt (keys %DEFAULT_OPTIONS) {
323 $new->{$opt} = exists $opts{$opt} ? delete $opts{$opt}
324 : $DEFAULT_OPTIONS{$opt};
325 }
326
327 if (%opts) {
328 croak "$class->new() called with unrecognised parameter(s): ",
329 join(", ", keys %opts), "\n";
330 }
331
332 set_debug_level($new->get_verbosity());
333 set_test_mode($new->{test_mode});
334 $new->set_stow_dir();
335 $new->init_state();
336
337 return $new;
338}
339
340sub get_verbosity {
341 my $self = shift;
342
343 return $self->{verbose} unless $self->{test_mode};
344
345 return 0 unless exists $ENV{TEST_VERBOSE};
346 return 0 unless length $ENV{TEST_VERBOSE};
347
348 # Convert TEST_VERBOSE=y into numeric value
349 $ENV{TEST_VERBOSE} = 3 if $ENV{TEST_VERBOSE} !~ /^\d+$/;
350
351 return $ENV{TEST_VERBOSE};
352}
353
354=head2 set_stow_dir([$dir])
355
356Sets a new stow directory. This allows the use of multiple stow
357directories within one Stow instance, e.g.
358
359 $stow->plan_stow('foo');
360 $stow->set_stow_dir('/different/stow/dir');
361 $stow->plan_stow('bar');
362 $stow->process_tasks;
363
364If C<$dir> is omitted, uses the value of the C<dir> parameter passed
365to the L<new()> constructor.
366
367=cut
368
369sub set_stow_dir {
370 my $self = shift;
371 my ($dir) = @_;
372 if (defined $dir) {
373 $self->{dir} = $dir;
374 }
375
376 my $stow_dir = canon_path($self->{dir});
377 my $target = canon_path($self->{target});
378 $self->{stow_path} = File::Spec->abs2rel($stow_dir, $target);
379
380 debug(2, "stow dir is $stow_dir");
381 debug(2, "stow dir path relative to target $target is $self->{stow_path}");
382}
383
384sub init_state {
385 my $self = shift;
386
387 # Store conflicts during pre-processing
388 $self->{conflicts} = {};
389 $self->{conflict_count} = 0;
390
391 # Store command line packages to stow (-S and -R)
392 $self->{pkgs_to_stow} = [];
393
394 # Store command line packages to unstow (-D and -R)
395 $self->{pkgs_to_delete} = [];
396
397 # The following structures are used by the abstractions that allow us to
398 # defer operating on the filesystem until after all potential conflicts have
399 # been assessed.
400
401 # $self->{tasks}: list of operations to be performed (in order)
402 # each element is a hash ref of the form
403 # {
404 # action => ... ('create' or 'remove' or 'move')
405 # type => ... ('link' or 'dir' or 'file')
406 # path => ... (unique)
407 # source => ... (only for links)
408 # dest => ... (only for moving files)
409 # }
410 $self->{tasks} = [];
411
412 # $self->{dir_task_for}: map a path to the corresponding directory task reference
413 # This structure allows us to quickly determine if a path has an existing
414 # directory task associated with it.
415 $self->{dir_task_for} = {};
416
417 # $self->{link_task_for}: map a path to the corresponding directory task reference
418 # This structure allows us to quickly determine if a path has an existing
419 # directory task associated with it.
420 $self->{link_task_for} = {};
421
422 # N.B.: directory tasks and link tasks are NOT mutually exclusive due
423 # to tree splitting (which involves a remove link task followed by
424 # a create directory task).
425}
426
427=head1 METHODS
428
429=head2 plan_unstow(@packages)
430
431Plan which symlink/directory creation/removal tasks need to be executed
432in order to unstow the given packages. Any potential conflicts are then
433accessible via L<get_conflicts()>.
434
435=cut
436
437sub plan_unstow {
438 my $self = shift;
439 my @packages = @_;
440
441 $self->within_target_do(sub {
442 for my $package (@packages) {
443 my $path = join_paths($self->{stow_path}, $package);
444 if (not -d $path) {
445 error("The stow directory $self->{stow_path} does not contain package $package");
446 }
447 debug(2, "Planning unstow of package $package...");
448 if ($self->{compat}) {
449 $self->unstow_contents_orig(
450 $self->{stow_path},
451 $package,
452 '.',
453 );
454 }
455 else {
456 $self->unstow_contents(
457 $self->{stow_path},
458 $package,
459 '.',
460 );
461 }
462 debug(2, "Planning unstow of package $package... done");
463 $self->{action_count}++;
464 }
465 });
466}
467
468=head2 plan_stow(@packages)
469
470Plan which symlink/directory creation/removal tasks need to be executed
471in order to stow the given packages. Any potential conflicts are then
472accessible via L<get_conflicts()>.
473
474=cut
475
476sub plan_stow {
477 my $self = shift;
478 my @packages = @_;
479
480 $self->within_target_do(sub {
481 for my $package (@packages) {
482 my $path = join_paths($self->{stow_path}, $package);
483 if (not -d $path) {
484 error("The stow directory $self->{stow_path} does not contain package $package");
485 }
486 debug(2, "Planning stow of package $package...");
487 $self->stow_contents(
488 $self->{stow_path},
489 $package,
490 '.',
491 $path, # source from target
492 );
493 debug(2, "Planning stow of package $package... done");
494 $self->{action_count}++;
495 }
496 });
497}
498
499#===== METHOD ===============================================================
500# Name : within_target_do()
501# Purpose : execute code within target directory, preserving cwd
502# Parameters: $code => anonymous subroutine to execute within target dir
503# Returns : n/a
504# Throws : n/a
505# Comments : This is done to ensure that the consumer of the Stow interface
506# : doesn't have to worry about (a) what their cwd is, and
507# : (b) that their cwd might change.
508#============================================================================
509sub within_target_do {
510 my $self = shift;
511 my ($code) = @_;
512
513 my $cwd = getcwd();
514 chdir($self->{target})
515 or error("Cannot chdir to target tree: $self->{target} ($!)");
516 debug(3, "cwd now $self->{target}");
517
518 $self->$code();
519
520 restore_cwd($cwd);
521 debug(3, "cwd restored to $cwd");
522}
523
524#===== METHOD ===============================================================
525# Name : stow_contents()
526# Purpose : stow the contents of the given directory
527# Parameters: $stow_path => relative path from current (i.e. target) directory
528# : to the stow dir containing the package to be stowed
529# : $package => the package whose contents are being stowed
530# : $target => subpath relative to package and target directories
531# : $source => relative path from the (sub)dir of target
532# : to symlink source
533# Returns : n/a
534# Throws : a fatal error if directory cannot be read
535# Comments : stow_node() and stow_contents() are mutually recursive.
536# : $source and $target are used for creating the symlink
537# : $path is used for folding/unfolding trees as necessary
538#============================================================================
539sub stow_contents {
540 my $self = shift;
541 my ($stow_path, $package, $target, $source) = @_;
542
543 my $path = join_paths($stow_path, $package, $target);
544
545 return if $self->should_skip_target_which_is_stow_dir($target);
546
547 my $cwd = getcwd();
548 my $msg = "Stowing contents of $path (cwd=$cwd)";
549 $msg =~ s!$ENV{HOME}(/|$)!~$1!g;
550 debug(3, $msg);
551 debug(4, " => $source");
552
553 error("stow_contents() called with non-directory path: $path")
554 unless -d $path;
555 error("stow_contents() called with non-directory target: $target")
556 unless $self->is_a_node($target);
557
558 opendir my $DIR, $path
559 or error("cannot read directory: $path ($!)");
560 my @listing = readdir $DIR;
561 closedir $DIR;
562
563 NODE:
564 for my $node (@listing) {
565 next NODE if $node eq '.';
566 next NODE if $node eq '..';
567 my $node_target = join_paths($target, $node);
568 next NODE if $self->ignore($stow_path, $package, $node_target);
569 $self->stow_node(
570 $stow_path,
571 $package,
572 $node_target, # target
573 join_paths($source, $node), # source
574 );
575 }
576}
577
578#===== METHOD ===============================================================
579# Name : stow_node()
580# Purpose : stow the given node
581# Parameters: $stow_path => relative path from current (i.e. target) directory
582# : to the stow dir containing the node to be stowed
583# : $package => the package containing the node being stowed
584# : $target => subpath relative to package and target directories
585# : $source => relative path to symlink source from the dir of target
586# Returns : n/a
587# Throws : fatal exception if a conflict arises
588# Comments : stow_node() and stow_contents() are mutually recursive
589# : $source and $target are used for creating the symlink
590# : $path is used for folding/unfolding trees as necessary
591#============================================================================
592sub stow_node {
593 my $self = shift;
594 my ($stow_path, $package, $target, $source) = @_;
595
596 my $path = join_paths($stow_path, $package, $target);
597
598 debug(3, "Stowing $stow_path / $package / $target");
599 debug(4, " => $source");
600
601 # Don't try to stow absolute symlinks (they can't be unstowed)
602 if (-l $source) {
603 my $second_source = $self->read_a_link($source);
604 if ($second_source =~ m{\A/}) {
605 $self->conflict(
606 'stow',
607 $package,
608 "source is an absolute symlink $source => $second_source"
609 );
610 debug(3, "Absolute symlinks cannot be unstowed");
611 return;
612 }
613 }
614
615 # Does the target already exist?
616 if ($self->is_a_link($target)) {
617 # Where is the link pointing?
618 my $existing_source = $self->read_a_link($target);
619 if (not $existing_source) {
620 error("Could not read link: $target");
621 }
622 debug(4, " Evaluate existing link: $target => $existing_source");
623
624 # Does it point to a node under any stow directory?
625 my ($existing_path, $existing_stow_path, $existing_package) =
626 $self->find_stowed_path($target, $existing_source);
627 if (not $existing_path) {
628 $self->conflict(
629 'stow',
630 $package,
631 "existing target is not owned by stow: $target"
632 );
633 return; # XXX #
634 }
635
636 # Does the existing $target actually point to anything?
637 if ($self->is_a_node($existing_path)) {
638 if ($existing_source eq $source) {
639 debug(2, "--- Skipping $target as it already points to $source");
640 }
641 elsif ($self->defer($target)) {
642 debug(2, "--- Deferring installation of: $target");
643 }
644 elsif ($self->override($target)) {
645 debug(2, "--- Overriding installation of: $target");
646 $self->do_unlink($target);
647 $self->do_link($source, $target);
648 }
649 elsif ($self->is_a_dir(join_paths(parent($target), $existing_source)) &&
650 $self->is_a_dir(join_paths(parent($target), $source)) ) {
651
652 # If the existing link points to a directory,
653 # and the proposed new link points to a directory,
654 # then we can unfold (split open) the tree at that point
655
656 debug(2, "--- Unfolding $target which was already owned by $existing_package");
657 $self->do_unlink($target);
658 $self->do_mkdir($target);
659 $self->stow_contents(
660 $existing_stow_path,
661 $existing_package,
662 $target,
663 join_paths('..', $existing_source),
664 );
665 $self->stow_contents(
666 $self->{stow_path},
667 $package,
668 $target,
669 join_paths('..', $source),
670 );
671 }
672 else {
673 $self->conflict(
674 'stow',
675 $package,
676 "existing target is stowed to a different package: "
677 . "$target => $existing_source"
678 );
679 }
680 }
681 else {
682 # The existing link is invalid, so replace it with a good link
683 debug(2, "--- replacing invalid link: $path");
684 $self->do_unlink($target);
685 $self->do_link($source, $target);
686 }
687 }
688 elsif ($self->is_a_node($target)) {
689 debug(4, " Evaluate existing node: $target");
690 if ($self->is_a_dir($target)) {
691 $self->stow_contents(
692 $self->{stow_path},
693 $package,
694 $target,
695 join_paths('..', $source),
696 );
697 }
698 else {
699 if ($self->{adopt}) {
700 $self->do_mv($target, $path);
701 $self->do_link($source, $target);
702 }
703 else {
704 $self->conflict(
705 'stow',
706 $package,
707 "existing target is neither a link nor a directory: $target"
708 );
709 }
710 }
711 }
712 elsif ($self->{'no-folding'} && -d $path && ! -l $path) {
713 $self->do_mkdir($target);
714 $self->stow_contents(
715 $self->{stow_path},
716 $package,
717 $target,
718 join_paths('..', $source),
719 );
720 }
721 else {
722 $self->do_link($source, $target);
723 }
724 return;
725}
726
727#===== METHOD ===============================================================
728# Name : should_skip_target_which_is_stow_dir()
729# Purpose : determine whether target is a stow directory which should
730# : not be stowed to or unstowed from
731# Parameters: $target => relative path to symlink target from the current directory
732# Returns : true iff target is a stow directory
733# Throws : n/a
734# Comments : none
735#============================================================================
736sub should_skip_target_which_is_stow_dir {
737 my $self = shift;
738 my ($target) = @_;
739
740 # Don't try to remove anything under a stow directory
741 if ($target eq $self->{stow_path}) {
742 warn "WARNING: skipping target which was current stow directory $target\n";
743 return 1;
744 }
745
746 if ($self->marked_stow_dir($target)) {
747 warn "WARNING: skipping protected directory $target\n";
748 return 1;
749 }
750
751 debug (4, "$target not protected");
752 return 0;
753}
754
755sub marked_stow_dir {
756 my $self = shift;
757 my ($target) = @_;
758 for my $f (".stow", ".nonstow") {
759 if (-e join_paths($target, $f)) {
760 debug(4, "$target contained $f");
761 return 1;
762 }
763 }
764 return 0;
765}
766
767#===== METHOD ===============================================================
768# Name : unstow_contents_orig()
769# Purpose : unstow the contents of the given directory
770# Parameters: $stow_path => relative path from current (i.e. target) directory
771# : to the stow dir containing the package to be unstowed
772# : $package => the package whose contents are being unstowed
773# : $target => relative path to symlink target from the current directory
774# Returns : n/a
775# Throws : a fatal error if directory cannot be read
776# Comments : unstow_node_orig() and unstow_contents_orig() are mutually recursive
777# : Here we traverse the target tree, rather than the source tree.
778#============================================================================
779sub unstow_contents_orig {
780 my $self = shift;
781 my ($stow_path, $package, $target) = @_;
782
783 my $path = join_paths($stow_path, $package, $target);
784
785 return if $self->should_skip_target_which_is_stow_dir($target);
786
787 my $cwd = getcwd();
788 my $msg = "Unstowing from $target (compat mode, cwd=$cwd, stow dir=$self->{stow_path})";
789 $msg =~ s!$ENV{HOME}(/|$)!~$1!g;
790 debug(3, $msg);
791 debug(4, " source path is $path");
792 # In compat mode we traverse the target tree not the source tree,
793 # so we're unstowing the contents of /target/foo, there's no
794 # guarantee that the corresponding /stow/mypkg/foo exists.
795 error("unstow_contents_orig() called with non-directory target: $target")
796 unless -d $target;
797
798 opendir my $DIR, $target
799 or error("cannot read directory: $target ($!)");
800 my @listing = readdir $DIR;
801 closedir $DIR;
802
803 NODE:
804 for my $node (@listing) {
805 next NODE if $node eq '.';
806 next NODE if $node eq '..';
807 my $node_target = join_paths($target, $node);
808 next NODE if $self->ignore($stow_path, $package, $node_target);
809 $self->unstow_node_orig($stow_path, $package, $node_target);
810 }
811}
812
813#===== METHOD ===============================================================
814# Name : unstow_node_orig()
815# Purpose : unstow the given node
816# Parameters: $stow_path => relative path from current (i.e. target) directory
817# : to the stow dir containing the node to be stowed
818# : $package => the package containing the node being stowed
819# : $target => relative path to symlink target from the current directory
820# Returns : n/a
821# Throws : fatal error if a conflict arises
822# Comments : unstow_node() and unstow_contents() are mutually recursive
823#============================================================================
824sub unstow_node_orig {
825 my $self = shift;
826 my ($stow_path, $package, $target) = @_;
827
828 my $path = join_paths($stow_path, $package, $target);
829
830 debug(3, "Unstowing $target (compat mode)");
831 debug(4, " source path is $path");
832
833 # Does the target exist?
834 if ($self->is_a_link($target)) {
835 debug(4, " Evaluate existing link: $target");
836
837 # Where is the link pointing?
838 my $existing_source = $self->read_a_link($target);
839 if (not $existing_source) {
840 error("Could not read link: $target");
841 }
842
843 # Does it point to a node under any stow directory?
844 my ($existing_path, $existing_stow_path, $existing_package) =
845 $self->find_stowed_path($target, $existing_source);
846 if (not $existing_path) {
847 # We're traversing the target tree not the package tree,
848 # so we definitely expect to find stuff not owned by stow.
849 # Therefore we can't flag a conflict.
850 return; # XXX #
851 }
852
853 # Does the existing $target actually point to anything?
854 if (-e $existing_path) {
855 # Does link point to the right place?
856 if ($existing_path eq $path) {
857 $self->do_unlink($target);
858 }
859 elsif ($self->override($target)) {
860 debug(2, "--- overriding installation of: $target");
861 $self->do_unlink($target);
862 }
863 # else leave it alone
864 }
865 else {
866 debug(2, "--- removing invalid link into a stow directory: $path");
867 $self->do_unlink($target);
868 }
869 }
870 elsif (-d $target) {
871 $self->unstow_contents_orig($stow_path, $package, $target);
872
873 # This action may have made the parent directory foldable
874 if (my $parent = $self->foldable($target)) {
875 $self->fold_tree($target, $parent);
876 }
877 }
878 elsif (-e $target) {
879 $self->conflict(
880 'unstow',
881 $package,
882 "existing target is neither a link nor a directory: $target",
883 );
884 }
885 else {
886 debug(2, "$target did not exist to be unstowed");
887 }
888 return;
889}
890
891#===== METHOD ===============================================================
892# Name : unstow_contents()
893# Purpose : unstow the contents of the given directory
894# Parameters: $stow_path => relative path from current (i.e. target) directory
895# : to the stow dir containing the package to be unstowed
896# : $package => the package whose contents are being unstowed
897# : $target => relative path to symlink target from the current directory
898# Returns : n/a
899# Throws : a fatal error if directory cannot be read
900# Comments : unstow_node() and unstow_contents() are mutually recursive
901# : Here we traverse the source tree, rather than the target tree.
902#============================================================================
903sub unstow_contents {
904 my $self = shift;
905 my ($stow_path, $package, $target) = @_;
906
907 my $path = join_paths($stow_path, $package, $target);
908
909 return if $self->should_skip_target_which_is_stow_dir($target);
910
911 my $cwd = getcwd();
912 my $msg = "Unstowing from $target (cwd=$cwd, stow dir=$self->{stow_path})";
913 $msg =~ s!$ENV{HOME}/!~/!g;
914 debug(3, $msg);
915 debug(4, " source path is $path");
916 # We traverse the source tree not the target tree, so $path must exist.
917 error("unstow_contents() called with non-directory path: $path")
918 unless -d $path;
919 # When called at the top level, $target should exist. And
920 # unstow_node() should only call this via mutual recursion if
921 # $target exists.
922 error("unstow_contents() called with invalid target: $target")
923 unless $self->is_a_node($target);
924
925 opendir my $DIR, $path
926 or error("cannot read directory: $path ($!)");
927 my @listing = readdir $DIR;
928 closedir $DIR;
929
930 NODE:
931 for my $node (@listing) {
932 next NODE if $node eq '.';
933 next NODE if $node eq '..';
934 my $node_target = join_paths($target, $node);
935 next NODE if $self->ignore($stow_path, $package, $node_target);
936 $self->unstow_node($stow_path, $package, $node_target);
937 }
938 if (-d $target) {
939 $self->cleanup_invalid_links($target);
940 }
941}
942
943#===== METHOD ===============================================================
944# Name : unstow_node()
945# Purpose : unstow the given node
946# Parameters: $stow_path => relative path from current (i.e. target) directory
947# : to the stow dir containing the node to be stowed
948# : $package => the package containing the node being unstowed
949# : $target => relative path to symlink target from the current directory
950# Returns : n/a
951# Throws : fatal error if a conflict arises
952# Comments : unstow_node() and unstow_contents() are mutually recursive
953#============================================================================
954sub unstow_node {
955 my $self = shift;
956 my ($stow_path, $package, $target) = @_;
957
958 my $path = join_paths($stow_path, $package, $target);
959
960 debug(3, "Unstowing $path");
961 debug(4, " target is $target");
962
963 # Does the target exist?
964 if ($self->is_a_link($target)) {
965 debug(4, " Evaluate existing link: $target");
966
967 # Where is the link pointing?
968 my $existing_source = $self->read_a_link($target);
969 if (not $existing_source) {
970 error("Could not read link: $target");
971 }
972
973 if ($existing_source =~ m{\A/}) {
974 warn "Ignoring an absolute symlink: $target => $existing_source\n";
975 return; # XXX #
976 }
977
978 # Does it point to a node under any stow directory?
979 my ($existing_path, $existing_stow_path, $existing_package) =
980 $self->find_stowed_path($target, $existing_source);
981 if (not $existing_path) {
982 $self->conflict(
983 'unstow',
984 $package,
985 "existing target is not owned by stow: $target => $existing_source"
986 );
987 return; # XXX #
988 }
989
990 # Does the existing $target actually point to anything?
991 if (-e $existing_path) {
992 # Does link points to the right place?
993 if ($existing_path eq $path) {
994 $self->do_unlink($target);
995 }
996
997 # XXX we quietly ignore links that are stowed to a different
998 # package.
999
1000 #elsif (defer($target)) {
1001 # debug(2, "--- deferring to installation of: $target");
1002 #}
1003 #elsif ($self->override($target)) {
1004 # debug(2, "--- overriding installation of: $target");
1005 # $self->do_unlink($target);
1006 #}
1007 #else {
1008 # $self->conflict(
1009 # 'unstow',
1010 # $package,
1011 # "existing target is stowed to a different package: "
1012 # . "$target => $existing_source"
1013 # );
1014 #}
1015 }
1016 else {
1017 debug(2, "--- removing invalid link into a stow directory: $path");
1018 $self->do_unlink($target);
1019 }
1020 }
1021 elsif (-e $target) {
1022 debug(4, " Evaluate existing node: $target");
1023 if (-d $target) {
1024 $self->unstow_contents($stow_path, $package, $target);
1025
1026 # This action may have made the parent directory foldable
1027 if (my $parent = $self->foldable($target)) {
1028 $self->fold_tree($target, $parent);
1029 }
1030 }
1031 else {
1032 $self->conflict(
1033 'unstow',
1034 $package,
1035 "existing target is neither a link nor a directory: $target",
1036 );
1037 }
1038 }
1039 else {
1040 debug(2, "$target did not exist to be unstowed");
1041 }
1042 return;
1043}
1044
1045#===== METHOD ===============================================================
1046# Name : path_owned_by_package()
1047# Purpose : determine whether the given link points to a member of a
1048# : stowed package
1049# Parameters: $target => path to a symbolic link under current directory
1050# : $source => where that link points to
1051# Returns : the package iff link is owned by stow, otherwise ''
1052# Throws : n/a
1053# Comments : lossy wrapper around find_stowed_path()
1054#============================================================================
1055sub path_owned_by_package {
1056 my $self = shift;
1057 my ($target, $source) = @_;
1058
1059 my ($path, $stow_path, $package) =
1060 $self->find_stowed_path($target, $source);
1061 return $package;
1062}
1063
1064#===== METHOD ===============================================================
1065# Name : find_stowed_path()
1066# Purpose : determine whether the given link points to a member of a
1067# : stowed package
1068# Parameters: $target => path to a symbolic link under current directory
1069# : $source => where that link points to (needed because link
1070# : might not exist yet due to two-phase approach,
1071# : so we can't just call readlink())
1072# Returns : ($path, $stow_path, $package) where $path and $stow_path are
1073# : relative from the current (i.e. target) directory. $path
1074# : is the full relative path, $stow_path is the relative path
1075# : to the stow directory, and $package is the name of the package.
1076# : or ('', '', '') if link is not owned by stow
1077# Throws : n/a
1078# Comments : Needs
1079# : Allow for stow dir not being under target dir.
1080# : We could put more logic under here for multiple stow dirs.
1081#============================================================================
1082sub find_stowed_path {
1083 my $self = shift;
1084 my ($target, $source) = @_;
1085
1086 # Evaluate softlink relative to its target
1087 my $path = join_paths(parent($target), $source);
1088 debug(4, " is path $path owned by stow?");
1089
1090 # Search for .stow files - this allows us to detect links
1091 # owned by stow directories other than the current one.
1092 my $dir = '';
1093 my @path = split m{/+}, $path;
1094 for my $i (0 .. $#path) {
1095 my $part = $path[$i];
1096 $dir = join_paths($dir, $part);
1097 if ($self->marked_stow_dir($dir)) {
1098 # FIXME - not sure if this can ever happen
1099 internal_error("find_stowed_path() called directly on stow dir")
1100 if $i == $#path;
1101
1102 debug(4, " yes - $dir was marked as a stow dir");
1103 my $package = $path[$i + 1];
1104 return ($path, $dir, $package);
1105 }
1106 }
1107
1108 # If no .stow file was found, we need to find out whether it's
1109 # owned by the current stow directory, in which case $path will be
1110 # a prefix of $self->{stow_path}.
1111 my @stow_path = split m{/+}, $self->{stow_path};
1112
1113 # Strip off common prefixes until one is empty
1114 while (@path && @stow_path) {
1115 if ((shift @path) ne (shift @stow_path)) {
1116 debug(4, " no - either $path not under $self->{stow_path} or vice-versa");
1117 return ('', '', '');
1118 }
1119 }
1120
1121 if (@stow_path) { # @path must be empty
1122 debug(4, " no - $path is not under $self->{stow_path}");
1123 return ('', '', '');
1124 }
1125
1126 my $package = shift @path;
1127
1128 debug(4, " yes - by $package in " . join_paths(@path));
1129 return ($path, $self->{stow_path}, $package);
1130}
1131
1132#===== METHOD ================================================================
1133# Name : cleanup_invalid_links()
1134# Purpose : clean up invalid links that may block folding
1135# Parameters: $dir => path to directory to check
1136# Returns : n/a
1137# Throws : no exceptions
1138# Comments : removing files from a stowed package is probably a bad practice
1139# : so this kind of clean up is not _really_ stow's responsibility;
1140# : however, failing to clean up can block tree folding, so we'll do
1141# : it anyway
1142#=============================================================================
1143sub cleanup_invalid_links {
1144 my $self = shift;
1145 my ($dir) = @_;
1146
1147 if (not -d $dir) {
1148 error("cleanup_invalid_links() called with a non-directory: $dir");
1149 }
1150
1151 opendir my $DIR, $dir
1152 or error("cannot read directory: $dir ($!)");
1153 my @listing = readdir $DIR;
1154 closedir $DIR;
1155
1156 NODE:
1157 for my $node (@listing) {
1158 next NODE if $node eq '.';
1159 next NODE if $node eq '..';
1160
1161 my $node_path = join_paths($dir, $node);
1162
1163 if (-l $node_path and not exists $self->{link_task_for}{$node_path}) {
1164
1165 # Where is the link pointing?
1166 # (don't use read_a_link() here)
1167 my $source = readlink($node_path);
1168 if (not $source) {
1169 error("Could not read link $node_path");
1170 }
1171
1172 if (
1173 not -e join_paths($dir, $source) and # bad link
1174 $self->path_owned_by_package($node_path, $source) # owned by stow
1175 ){
1176 debug(2, "--- removing stale link: $node_path => " .
1177 join_paths($dir, $source));
1178 $self->do_unlink($node_path);
1179 }
1180 }
1181 }
1182 return;
1183}
1184
1185
1186#===== METHOD ===============================================================
1187# Name : foldable()
1188# Purpose : determine whether a tree can be folded
1189# Parameters: $target => path to a directory
1190# Returns : path to the parent dir iff the tree can be safely folded
1191# Throws : n/a
1192# Comments : the path returned is relative to the parent of $target,
1193# : that is, it can be used as the source for a replacement symlink
1194#============================================================================
1195sub foldable {
1196 my $self = shift;
1197 my ($target) = @_;
1198
1199 debug(3, "--- Is $target foldable?");
1200 if ($self->{'no-folding'}) {
1201 debug(3, "--- no because --no-folding enabled");
1202 return '';
1203 }
1204
1205 opendir my $DIR, $target
1206 or error(qq{Cannot read directory "$target" ($!)\n});
1207 my @listing = readdir $DIR;
1208 closedir $DIR;
1209
1210 my $parent = '';
1211 NODE:
1212 for my $node (@listing) {
1213
1214 next NODE if $node eq '.';
1215 next NODE if $node eq '..';
1216
1217 my $path = join_paths($target, $node);
1218
1219 # Skip nodes scheduled for removal
1220 next NODE if not $self->is_a_node($path);
1221
1222 # If it's not a link then we can't fold its parent
1223 return '' if not $self->is_a_link($path);
1224
1225 # Where is the link pointing?
1226 my $source = $self->read_a_link($path);
1227 if (not $source) {
1228 error("Could not read link $path");
1229 }
1230 if ($parent eq '') {
1231 $parent = parent($source)
1232 }
1233 elsif ($parent ne parent($source)) {
1234 return '';
1235 }
1236 }
1237 return '' if not $parent;
1238
1239 # If we get here then all nodes inside $target are links, and those links
1240 # point to nodes inside the same directory.
1241
1242 # chop of leading '..' to get the path to the common parent directory
1243 # relative to the parent of our $target
1244 $parent =~ s{\A\.\./}{};
1245
1246 # If the resulting path is owned by stow, we can fold it
1247 if ($self->path_owned_by_package($target, $parent)) {
1248 debug(3, "--- $target is foldable");
1249 return $parent;
1250 }
1251 else {
1252 return '';
1253 }
1254}
1255
1256#===== METHOD ===============================================================
1257# Name : fold_tree()
1258# Purpose : fold the given tree
1259# Parameters: $source => link to the folded tree source
1260# : $target => directory that we will replace with a link to $source
1261# Returns : n/a
1262# Throws : none
1263# Comments : only called iff foldable() is true so we can remove some checks
1264#============================================================================
1265sub fold_tree {
1266 my $self = shift;
1267 my ($target, $source) = @_;
1268
1269 debug(3, "--- Folding tree: $target => $source");
1270
1271 opendir my $DIR, $target
1272 or error(qq{Cannot read directory "$target" ($!)\n});
1273 my @listing = readdir $DIR;
1274 closedir $DIR;
1275
1276 NODE:
1277 for my $node (@listing) {
1278 next NODE if $node eq '.';
1279 next NODE if $node eq '..';
1280 next NODE if not $self->is_a_node(join_paths($target, $node));
1281 $self->do_unlink(join_paths($target, $node));
1282 }
1283 $self->do_rmdir($target);
1284 $self->do_link($source, $target);
1285 return;
1286}
1287
1288
1289#===== METHOD ===============================================================
1290# Name : conflict()
1291# Purpose : handle conflicts in stow operations
1292# Parameters: $package => the package involved with the conflicting operation
1293# : $message => a description of the conflict
1294# Returns : n/a
1295# Throws : none
1296# Comments : none
1297#============================================================================
1298sub conflict {
1299 my $self = shift;
1300 my ($action, $package, $message) = @_;
1301
1302 debug(2, "CONFLICT when ${action}ing $package: $message");
1303 $self->{conflicts}{$action}{$package} ||= [];
1304 push @{ $self->{conflicts}{$action}{$package} }, $message;
1305 $self->{conflict_count}++;
1306
1307 return;
1308}
1309
1310=head2 get_conflicts()
1311
1312Returns a nested hash of all potential conflicts discovered: the keys
1313are actions ('stow' or 'unstow'), and the values are hashrefs whose
1314keys are stow package names and whose values are conflict
1315descriptions, e.g.:
1316
1317 (
1318 stow => {
1319 perl => [
1320 "existing target is not owned by stow: bin/a2p"
1321 "existing target is neither a link nor a directory: bin/perl"
1322 ]
1323 }
1324 )
1325
1326=cut
1327
1328sub get_conflicts {
1329 my $self = shift;
1330 return %{ $self->{conflicts} };
1331}
1332
1333=head2 get_conflict_count()
1334
1335Returns the number of conflicts found.
1336
1337=cut
1338
1339sub get_conflict_count {
1340 my $self = shift;
1341 return $self->{conflict_count};
1342}
1343
1344=head2 get_tasks()
1345
1346Returns a list of all symlink/directory creation/removal tasks.
1347
1348=cut
1349
1350sub get_tasks {
1351 my $self = shift;
1352 return @{ $self->{tasks} };
1353}
1354
1355=head2 get_action_count()
1356
1357Returns the number of actions planned for this Stow instance.
1358
1359=cut
1360
1361sub get_action_count {
1362 my $self = shift;
1363 return $self->{action_count};
1364}
1365
1366#===== METHOD ================================================================
1367# Name : ignore
1368# Purpose : determine if the given path matches a regex in our ignore list
1369# Parameters: $stow_path => the stow directory containing the package
1370# : $package => the package containing the path
1371# : $target => the path to check against the ignore list
1372# : relative to its package directory
1373# Returns : true iff the path should be ignored
1374# Throws : no exceptions
1375# Comments : none
1376#=============================================================================
1377sub ignore {
1378 my $self = shift;
1379 my ($stow_path, $package, $target) = @_;
1380
1381 internal_error(__PACKAGE__ . "::ignore() called with empty target")
1382 unless length $target;
1383
1384 for my $suffix (@{ $self->{ignore} }) {
1385 if ($target =~ m/$suffix/) {
1386 debug(4, " Ignoring path $target due to --ignore=$suffix");
1387 return 1;
1388 }
1389 }
1390
1391 my $package_dir = join_paths($stow_path, $package);
1392 my ($path_regexp, $segment_regexp) =
1393 $self->get_ignore_regexps($package_dir);
1394 debug(5, " Ignore list regexp for paths: " .
1395 (defined $path_regexp ? "/$path_regexp/" : "none"));
1396 debug(5, " Ignore list regexp for segments: " .
1397 (defined $segment_regexp ? "/$segment_regexp/" : "none"));
1398
1399 if (defined $path_regexp and "/$target" =~ $path_regexp) {
1400 debug(4, " Ignoring path /$target");
1401 return 1;
1402 }
1403
1404 (my $basename = $target) =~ s!.+/!!;
1405 if (defined $segment_regexp and $basename =~ $segment_regexp) {
1406 debug(4, " Ignoring path segment $basename");
1407 return 1;
1408 }
1409
1410 debug(5, " Not ignoring $target");
1411 return 0;
1412}
1413
1414sub get_ignore_regexps {
1415 my $self = shift;
1416 my ($dir) = @_;
1417
1418 # N.B. the local and global stow ignore files have to have different
1419 # names so that:
1420 # 1. the global one can be a symlink to within a stow
1421 # package, managed by stow itself, and
1422 # 2. the local ones can be ignored via hardcoded logic in
1423 # GlobsToRegexp(), so that they always stay within their stow packages.
1424
1425 my $local_stow_ignore = join_paths($dir, $LOCAL_IGNORE_FILE);
1426 my $global_stow_ignore = join_paths($ENV{HOME}, $GLOBAL_IGNORE_FILE);
1427
1428 for my $file ($local_stow_ignore, $global_stow_ignore) {
1429 if (-e $file) {
1430 debug(5, " Using ignore file: $file");
1431 return $self->get_ignore_regexps_from_file($file);
1432 }
1433 else {
1434 debug(5, " $file didn't exist");
1435 }
1436 }
1437
1438 debug(4, " Using built-in ignore list");
1439 return @default_global_ignore_regexps;
1440}
1441
1442my %ignore_file_regexps;
1443
1444sub get_ignore_regexps_from_file {
1445 my $self = shift;
1446 my ($file) = @_;
1447
1448 if (exists $ignore_file_regexps{$file}) {
1449 debug(4, " Using memoized regexps from $file");
1450 return @{ $ignore_file_regexps{$file} };
1451 }
1452
1453 if (! open(REGEXPS, $file)) {
1454 debug(4, " Failed to open $file: $!");
1455 return undef;
1456 }
1457
1458 my @regexps = $self->get_ignore_regexps_from_fh(\*REGEXPS);
1459 close(REGEXPS);
1460
1461 $ignore_file_regexps{$file} = [ @regexps ];
1462 return @regexps;
1463}
1464
1465=head2 invalidate_memoized_regexp($file)
1466
1467For efficiency of performance, regular expressions are compiled from
1468each ignore list file the first time it is used by the Stow process,
1469and then memoized for future use. If you expect the contents of these
1470files to change during a single run, you will need to invalidate the
1471memoized value from this cache. This method allows you to do that.
1472
1473=cut
1474
1475sub invalidate_memoized_regexp {
1476 my $self = shift;
1477 my ($file) = @_;
1478 if (exists $ignore_file_regexps{$file}) {
1479 debug(4, " Invalidated memoized regexp for $file");
1480 delete $ignore_file_regexps{$file};
1481 }
1482 else {
1483 debug(2, " WARNING: no memoized regexp for $file to invalidate");
1484 }
1485}
1486
1487sub get_ignore_regexps_from_fh {
1488 my $self = shift;
1489 my ($fh) = @_;
1490 my %regexps;
1491 while (<$fh>) {
1492 chomp;
1493 s/^\s+//;
1494 s/\s+$//;
1495 next if /^#/ or length($_) == 0;
1496 s/\s+#.+//; # strip comments to right of pattern
1497 s/\\#/#/g;
1498 $regexps{$_}++;
1499 }
1500
1501 # Local ignore lists should *always* stay within the stow directory,
1502 # because this is the only place stow looks for them.
1503 $regexps{"^/\Q$LOCAL_IGNORE_FILE\E\$"}++;
1504
1505 return $self->compile_ignore_regexps(%regexps);
1506}
1507
1508sub compile_ignore_regexps {
1509 my $self = shift;
1510 my (%regexps) = @_;
1511
1512 my @segment_regexps;
1513 my @path_regexps;
1514 for my $regexp (keys %regexps) {
1515 if (index($regexp, '/') < 0) {
1516 # No / found in regexp, so use it for matching against basename
1517 push @segment_regexps, $regexp;
1518 }
1519 else {
1520 # / found in regexp, so use it for matching against full path
1521 push @path_regexps, $regexp;
1522 }
1523 }
1524
1525 my $segment_regexp = join '|', @segment_regexps;
1526 my $path_regexp = join '|', @path_regexps;
1527 $segment_regexp = @segment_regexps ?
1528 $self->compile_regexp("^($segment_regexp)\$") : undef;
1529 $path_regexp = @path_regexps ?
1530 $self->compile_regexp("(^|/)($path_regexp)(/|\$)") : undef;
1531
1532 return ($path_regexp, $segment_regexp);
1533}
1534
1535sub compile_regexp {
1536 my $self = shift;
1537 my ($regexp) = @_;
1538 my $compiled = eval { qr/$regexp/ };
1539 die "Failed to compile regexp: $@\n" if $@;
1540 return $compiled;
1541}
1542
1543sub get_default_global_ignore_regexps {
1544 my $class = shift;
1545 # Bootstrap issue - first time we stow, we will be stowing
1546 # .cvsignore so it might not exist in ~ yet, or if it does, it could
1547 # be an old version missing the entries we need. So we make sure
1548 # they are there by hardcoding some crucial entries.
1549 return $class->get_ignore_regexps_from_fh(\*DATA);
1550}
1551
1552#===== METHOD ================================================================
1553# Name : defer
1554# Purpose : determine if the given path matches a regex in our defer list
1555# Parameters: $path
1556# Returns : Boolean
1557# Throws : no exceptions
1558# Comments : none
1559#=============================================================================
1560sub defer {
1561 my $self = shift;
1562 my ($path) = @_;
1563
1564 for my $prefix (@{ $self->{defer} }) {
1565 return 1 if $path =~ m/$prefix/;
1566 }
1567 return 0;
1568}
1569
1570#===== METHOD ================================================================
1571# Name : override
1572# Purpose : determine if the given path matches a regex in our override list
1573# Parameters: $path
1574# Returns : Boolean
1575# Throws : no exceptions
1576# Comments : none
1577#=============================================================================
1578sub override {
1579 my $self = shift;
1580 my ($path) = @_;
1581
1582 for my $regex (@{ $self->{override} }) {
1583 return 1 if $path =~ m/$regex/;
1584 }
1585 return 0;
1586}
1587
1588##############################################################################
1589#
1590# The following code provides the abstractions that allow us to defer operating
1591# on the filesystem until after all potential conflcits have been assessed.
1592#
1593##############################################################################
1594
1595#===== METHOD ===============================================================
1596# Name : process_tasks()
1597# Purpose : process each task in the tasks list
1598# Parameters: none
1599# Returns : n/a
1600# Throws : fatal error if tasks list is corrupted or a task fails
1601# Comments : none
1602#============================================================================
1603sub process_tasks {
1604 my $self = shift;
1605
1606 debug(2, "Processing tasks...");
1607
1608 # Strip out all tasks with a skip action
1609 $self->{tasks} = [ grep { $_->{action} ne 'skip' } @{ $self->{tasks} } ];
1610
1611 if (not @{ $self->{tasks} }) {
1612 return;
1613 }
1614
1615 $self->within_target_do(sub {
1616 for my $task (@{ $self->{tasks} }) {
1617 $self->process_task($task);
1618 }
1619 });
1620
1621 debug(2, "Processing tasks... done");
1622}
1623
1624#===== METHOD ===============================================================
1625# Name : process_task()
1626# Purpose : process a single task
1627# Parameters: $task => the task to process
1628# Returns : n/a
1629# Throws : fatal error if task fails
1630# Comments : Must run from within target directory.
1631# : Task involve either creating or deleting dirs and symlinks
1632# : an action is set to 'skip' if it is found to be redundant
1633#============================================================================
1634sub process_task {
1635 my $self = shift;
1636 my ($task) = @_;
1637
1638 if ($task->{action} eq 'create') {
1639 if ($task->{type} eq 'dir') {
1640 mkdir($task->{path}, 0777)
1641 or error("Could not create directory: $task->{path} ($!)");
1642 return;
1643 }
1644 elsif ($task->{type} eq 'link') {
1645 symlink $task->{source}, $task->{path}
1646 or error(
1647 "Could not create symlink: %s => %s ($!)",
1648 $task->{path},
1649 $task->{source}
1650 );
1651 return;
1652 }
1653 }
1654 elsif ($task->{action} eq 'remove') {
1655 if ($task->{type} eq 'dir') {
1656 rmdir $task->{path}
1657 or error("Could not remove directory: $task->{path} ($!)");
1658 return;
1659 }
1660 elsif ($task->{type} eq 'link') {
1661 unlink $task->{path}
1662 or error("Could not remove link: $task->{path} ($!)");
1663 return;
1664 }
1665 }
1666 elsif ($task->{action} eq 'move') {
1667 if ($task->{type} eq 'file') {
1668 # rename() not good enough, since the stow directory
1669 # might be on a different filesystem to the target.
1670 move $task->{path}, $task->{dest}
1671 or error("Could not move $task->{path} -> $task->{dest} ($!)");
1672 return;
1673 }
1674 }
1675
1676 # Should never happen.
1677 internal_error("bad task action: $task->{action}");
1678}
1679
1680#===== METHOD ===============================================================
1681# Name : link_task_action()
1682# Purpose : finds the link task action for the given path, if there is one
1683# Parameters: $path
1684# Returns : 'remove', 'create', or '' if there is no action
1685# Throws : a fatal exception if an invalid action is found
1686# Comments : none
1687#============================================================================
1688sub link_task_action {
1689 my $self = shift;
1690 my ($path) = @_;
1691
1692 if (! exists $self->{link_task_for}{$path}) {
1693 debug(4, " link_task_action($path): no task");
1694 return '';
1695 }
1696
1697 my $action = $self->{link_task_for}{$path}->{action};
1698 internal_error("bad task action: $action")
1699 unless $action eq 'remove' or $action eq 'create';
1700
1701 debug(4, " link_task_action($path): link task exists with action $action");
1702 return $action;
1703}
1704
1705#===== METHOD ===============================================================
1706# Name : dir_task_action()
1707# Purpose : finds the dir task action for the given path, if there is one
1708# Parameters: $path
1709# Returns : 'remove', 'create', or '' if there is no action
1710# Throws : a fatal exception if an invalid action is found
1711# Comments : none
1712#============================================================================
1713sub dir_task_action {
1714 my $self = shift;
1715 my ($path) = @_;
1716
1717 if (! exists $self->{dir_task_for}{$path}) {
1718 debug(4, " dir_task_action($path): no task");
1719 return '';
1720 }
1721
1722 my $action = $self->{dir_task_for}{$path}->{action};
1723 internal_error("bad task action: $action")
1724 unless $action eq 'remove' or $action eq 'create';
1725
1726 debug(4, " dir_task_action($path): dir task exists with action $action");
1727 return $action;
1728}
1729
1730#===== METHOD ===============================================================
1731# Name : parent_link_scheduled_for_removal()
1732# Purpose : determine whether the given path or any parent thereof
1733# : is a link scheduled for removal
1734# Parameters: $path
1735# Returns : Boolean
1736# Throws : none
1737# Comments : none
1738#============================================================================
1739sub parent_link_scheduled_for_removal {
1740 my $self = shift;
1741 my ($path) = @_;
1742
1743 my $prefix = '';
1744 for my $part (split m{/+}, $path) {
1745 $prefix = join_paths($prefix, $part);
1746 debug(4, " parent_link_scheduled_for_removal($path): prefix $prefix");
1747 if (exists $self->{link_task_for}{$prefix} and
1748 $self->{link_task_for}{$prefix}->{action} eq 'remove') {
1749 debug(4, " parent_link_scheduled_for_removal($path): link scheduled for removal");
1750 return 1;
1751 }
1752 }
1753
1754 debug(4, " parent_link_scheduled_for_removal($path): returning false");
1755 return 0;
1756}
1757
1758#===== METHOD ===============================================================
1759# Name : is_a_link()
1760# Purpose : determine if the given path is a current or planned link
1761# Parameters: $path
1762# Returns : Boolean
1763# Throws : none
1764# Comments : returns false if an existing link is scheduled for removal
1765# : and true if a non-existent link is scheduled for creation
1766#============================================================================
1767sub is_a_link {
1768 my $self = shift;
1769 my ($path) = @_;
1770 debug(4, " is_a_link($path)");
1771
1772 if (my $action = $self->link_task_action($path)) {
1773 if ($action eq 'remove') {
1774 debug(4, " is_a_link($path): returning 0 (remove action found)");
1775 return 0;
1776 }
1777 elsif ($action eq 'create') {
1778 debug(4, " is_a_link($path): returning 1 (create action found)");
1779 return 1;
1780 }
1781 }
1782
1783 if (-l $path) {
1784 # Check if any of its parent are links scheduled for removal
1785 # (need this for edge case during unfolding)
1786 debug(4, " is_a_link($path): is a real link");
1787 return $self->parent_link_scheduled_for_removal($path) ? 0 : 1;
1788 }
1789
1790 debug(4, " is_a_link($path): returning 0");
1791 return 0;
1792}
1793
1794#===== METHOD ===============================================================
1795# Name : is_a_dir()
1796# Purpose : determine if the given path is a current or planned directory
1797# Parameters: $path
1798# Returns : Boolean
1799# Throws : none
1800# Comments : returns false if an existing directory is scheduled for removal
1801# : and true if a non-existent directory is scheduled for creation
1802# : we also need to be sure we are not just following a link
1803#============================================================================
1804sub is_a_dir {
1805 my $self = shift;
1806 my ($path) = @_;
1807 debug(4, " is_a_dir($path)");
1808
1809 if (my $action = $self->dir_task_action($path)) {
1810 if ($action eq 'remove') {
1811 return 0;
1812 }
1813 elsif ($action eq 'create') {
1814 return 1;
1815 }
1816 }
1817
1818 return 0 if $self->parent_link_scheduled_for_removal($path);
1819
1820 if (-d $path) {
1821 debug(4, " is_a_dir($path): real dir");
1822 return 1;
1823 }
1824
1825 debug(4, " is_a_dir($path): returning false");
1826 return 0;
1827}
1828
1829#===== METHOD ===============================================================
1830# Name : is_a_node()
1831# Purpose : determine whether the given path is a current or planned node
1832# Parameters: $path
1833# Returns : Boolean
1834# Throws : none
1835# Comments : returns false if an existing node is scheduled for removal
1836# : true if a non-existent node is scheduled for creation
1837# : we also need to be sure we are not just following a link
1838#============================================================================
1839sub is_a_node {
1840 my $self = shift;
1841 my ($path) = @_;
1842 debug(4, " is_a_node($path)");
1843
1844 my $laction = $self->link_task_action($path);
1845 my $daction = $self->dir_task_action($path);
1846
1847 if ($laction eq 'remove') {
1848 if ($daction eq 'remove') {
1849 internal_error("removing link and dir: $path");
1850 return 0;
1851 }
1852 elsif ($daction eq 'create') {
1853 # Assume that we're unfolding $path, and that the link
1854 # removal action is earlier than the dir creation action
1855 # in the task queue. FIXME: is this a safe assumption?
1856 return 1;
1857 }
1858 else { # no dir action
1859 return 0;
1860 }
1861 }
1862 elsif ($laction eq 'create') {
1863 if ($daction eq 'remove') {
1864 # Assume that we're folding $path, and that the dir
1865 # removal action is earlier than the link creation action
1866 # in the task queue. FIXME: is this a safe assumption?
1867 return 1;
1868 }
1869 elsif ($daction eq 'create') {
1870 internal_error("creating link and dir: $path");
1871 return 1;
1872 }
1873 else { # no dir action
1874 return 1;
1875 }
1876 }
1877 else {
1878 # No link action
1879 if ($daction eq 'remove') {
1880 return 0;
1881 }
1882 elsif ($daction eq 'create') {
1883 return 1;
1884 }
1885 else { # no dir action
1886 # fall through to below
1887 }
1888 }
1889
1890 return 0 if $self->parent_link_scheduled_for_removal($path);
1891
1892 if (-e $path) {
1893 debug(4, " is_a_node($path): really exists");
1894 return 1;
1895 }
1896
1897 debug(4, " is_a_node($path): returning false");
1898 return 0;
1899}
1900
1901#===== METHOD ===============================================================
1902# Name : read_a_link()
1903# Purpose : return the source of a current or planned link
1904# Parameters: $path => path to the link target
1905# Returns : a string
1906# Throws : fatal exception if the given path is not a current or planned
1907# : link
1908# Comments : none
1909#============================================================================
1910sub read_a_link {
1911 my $self = shift;
1912 my ($path) = @_;
1913
1914 if (my $action = $self->link_task_action($path)) {
1915 debug(4, " read_a_link($path): task exists with action $action");
1916
1917 if ($action eq 'create') {
1918 return $self->{link_task_for}{$path}->{source};
1919 }
1920 elsif ($action eq 'remove') {
1921 internal_error(
1922 "read_a_link() passed a path that is scheduled for removal: $path"
1923 );
1924 }
1925 }
1926 elsif (-l $path) {
1927 debug(4, " read_a_link($path): real link");
1928 my $target = readlink $path or error("Could not read link: $path ($!)");
1929 return $target;
1930 }
1931 internal_error("read_a_link() passed a non link path: $path\n");
1932}
1933
1934#===== METHOD ===============================================================
1935# Name : do_link()
1936# Purpose : wrap 'link' operation for later processing
1937# Parameters: $oldfile => the existing file to link to
1938# : $newfile => the file to link
1939# Returns : n/a
1940# Throws : error if this clashes with an existing planned operation
1941# Comments : cleans up operations that undo previous operations
1942#============================================================================
1943sub do_link {
1944 my $self = shift;
1945 my ($oldfile, $newfile) = @_;
1946
1947 if (exists $self->{dir_task_for}{$newfile}) {
1948 my $task_ref = $self->{dir_task_for}{$newfile};
1949
1950 if ($task_ref->{action} eq 'create') {
1951 if ($task_ref->{type} eq 'dir') {
1952 internal_error(
1953 "new link (%s => %s) clashes with planned new directory",
1954 $newfile,
1955 $oldfile,
1956 );
1957 }
1958 }
1959 elsif ($task_ref->{action} eq 'remove') {
1960 # We may need to remove a directory before creating a link so continue.
1961 }
1962 else {
1963 internal_error("bad task action: $task_ref->{action}");
1964 }
1965 }
1966
1967 if (exists $self->{link_task_for}{$newfile}) {
1968 my $task_ref = $self->{link_task_for}{$newfile};
1969
1970 if ($task_ref->{action} eq 'create') {
1971 if ($task_ref->{source} ne $oldfile) {
1972 internal_error(
1973 "new link clashes with planned new link: %s => %s",
1974 $task_ref->{path},
1975 $task_ref->{source},
1976 )
1977 }
1978 else {
1979 debug(1, "LINK: $newfile => $oldfile (duplicates previous action)");
1980 return;
1981 }
1982 }
1983 elsif ($task_ref->{action} eq 'remove') {
1984 if ($task_ref->{source} eq $oldfile) {
1985 # No need to remove a link we are going to recreate
1986 debug(1, "LINK: $newfile => $oldfile (reverts previous action)");
1987 $self->{link_task_for}{$newfile}->{action} = 'skip';
1988 delete $self->{link_task_for}{$newfile};
1989 return;
1990 }
1991 # We may need to remove a link to replace it so continue
1992 }
1993 else {
1994 internal_error("bad task action: $task_ref->{action}");
1995 }
1996 }
1997
1998 # Creating a new link
1999 debug(1, "LINK: $newfile => $oldfile");
2000 my $task = {
2001 action => 'create',
2002 type => 'link',
2003 path => $newfile,
2004 source => $oldfile,
2005 };
2006 push @{ $self->{tasks} }, $task;
2007 $self->{link_task_for}{$newfile} = $task;
2008
2009 return;
2010}
2011
2012#===== METHOD ===============================================================
2013# Name : do_unlink()
2014# Purpose : wrap 'unlink' operation for later processing
2015# Parameters: $file => the file to unlink
2016# Returns : n/a
2017# Throws : error if this clashes with an existing planned operation
2018# Comments : will remove an existing planned link
2019#============================================================================
2020sub do_unlink {
2021 my $self = shift;
2022 my ($file) = @_;
2023
2024 if (exists $self->{link_task_for}{$file}) {
2025 my $task_ref = $self->{link_task_for}{$file};
2026 if ($task_ref->{action} eq 'remove') {
2027 debug(1, "UNLINK: $file (duplicates previous action)");
2028 return;
2029 }
2030 elsif ($task_ref->{action} eq 'create') {
2031 # Do need to create a link then remove it
2032 debug(1, "UNLINK: $file (reverts previous action)");
2033 $self->{link_task_for}{$file}->{action} = 'skip';
2034 delete $self->{link_task_for}{$file};
2035 return;
2036 }
2037 else {
2038 internal_error("bad task action: $task_ref->{action}");
2039 }
2040 }
2041
2042 if (exists $self->{dir_task_for}{$file} and $self->{dir_task_for}{$file} eq 'create') {
2043 internal_error(
2044 "new unlink operation clashes with planned operation: %s dir %s",
2045 $self->{dir_task_for}{$file}->{action},
2046 $file
2047 );
2048 }
2049
2050 # Remove the link
2051 debug(1, "UNLINK: $file");
2052
2053 my $source = readlink $file or error("could not readlink $file ($!)");
2054
2055 my $task = {
2056 action => 'remove',
2057 type => 'link',
2058 path => $file,
2059 source => $source,
2060 };
2061 push @{ $self->{tasks} }, $task;
2062 $self->{link_task_for}{$file} = $task;
2063
2064 return;
2065}
2066
2067#===== METHOD ===============================================================
2068# Name : do_mkdir()
2069# Purpose : wrap 'mkdir' operation
2070# Parameters: $dir => the directory to remove
2071# Returns : n/a
2072# Throws : fatal exception if operation fails
2073# Comments : outputs a message if 'verbose' option is set
2074# : does not perform operation if 'simulate' option is set
2075# Comments : cleans up operations that undo previous operations
2076#============================================================================
2077sub do_mkdir {
2078 my $self = shift;
2079 my ($dir) = @_;
2080
2081 if (exists $self->{link_task_for}{$dir}) {
2082 my $task_ref = $self->{link_task_for}{$dir};
2083
2084 if ($task_ref->{action} eq 'create') {
2085 internal_error(
2086 "new dir clashes with planned new link (%s => %s)",
2087 $task_ref->{path},
2088 $task_ref->{source},
2089 );
2090 }
2091 elsif ($task_ref->{action} eq 'remove') {
2092 # May need to remove a link before creating a directory so continue
2093 }
2094 else {
2095 internal_error("bad task action: $task_ref->{action}");
2096 }
2097 }
2098
2099 if (exists $self->{dir_task_for}{$dir}) {
2100 my $task_ref = $self->{dir_task_for}{$dir};
2101
2102 if ($task_ref->{action} eq 'create') {
2103 debug(1, "MKDIR: $dir (duplicates previous action)");
2104 return;
2105 }
2106 elsif ($task_ref->{action} eq 'remove') {
2107 debug(1, "MKDIR: $dir (reverts previous action)");
2108 $self->{dir_task_for}{$dir}->{action} = 'skip';
2109 delete $self->{dir_task_for}{$dir};
2110 return;
2111 }
2112 else {
2113 internal_error("bad task action: $task_ref->{action}");
2114 }
2115 }
2116
2117 debug(1, "MKDIR: $dir");
2118 my $task = {
2119 action => 'create',
2120 type => 'dir',
2121 path => $dir,
2122 source => undef,
2123 };
2124 push @{ $self->{tasks} }, $task;
2125 $self->{dir_task_for}{$dir} = $task;
2126
2127 return;
2128}
2129
2130#===== METHOD ===============================================================
2131# Name : do_rmdir()
2132# Purpose : wrap 'rmdir' operation
2133# Parameters: $dir => the directory to remove
2134# Returns : n/a
2135# Throws : fatal exception if operation fails
2136# Comments : outputs a message if 'verbose' option is set
2137# : does not perform operation if 'simulate' option is set
2138#============================================================================
2139sub do_rmdir {
2140 my $self = shift;
2141 my ($dir) = @_;
2142
2143 if (exists $self->{link_task_for}{$dir}) {
2144 my $task_ref = $self->{link_task_for}{$dir};
2145 internal_error(
2146 "rmdir clashes with planned operation: %s link %s => %s",
2147 $task_ref->{action},
2148 $task_ref->{path},
2149 $task_ref->{source}
2150 );
2151 }
2152
2153 if (exists $self->{dir_task_for}{$dir}) {
2154 my $task_ref = $self->{link_task_for}{$dir};
2155
2156 if ($task_ref->{action} eq 'remove') {
2157 debug(1, "RMDIR $dir (duplicates previous action)");
2158 return;
2159 }
2160 elsif ($task_ref->{action} eq 'create') {
2161 debug(1, "MKDIR $dir (reverts previous action)");
2162 $self->{link_task_for}{$dir}->{action} = 'skip';
2163 delete $self->{link_task_for}{$dir};
2164 return;
2165 }
2166 else {
2167 internal_error("bad task action: $task_ref->{action}");
2168 }
2169 }
2170
2171 debug(1, "RMDIR $dir");
2172 my $task = {
2173 action => 'remove',
2174 type => 'dir',
2175 path => $dir,
2176 source => '',
2177 };
2178 push @{ $self->{tasks} }, $task;
2179 $self->{dir_task_for}{$dir} = $task;
2180
2181 return;
2182}
2183
2184#===== METHOD ===============================================================
2185# Name : do_mv()
2186# Purpose : wrap 'move' operation for later processing
2187# Parameters: $src => the file to move
2188# : $dst => the path to move it to
2189# Returns : n/a
2190# Throws : error if this clashes with an existing planned operation
2191# Comments : alters contents of package installation image in stow dir
2192#============================================================================
2193sub do_mv {
2194 my $self = shift;
2195 my ($src, $dst) = @_;
2196
2197 if (exists $self->{link_task_for}{$src}) {
2198 # I don't *think* this should ever happen, but I'm not
2199 # 100% sure.
2200 my $task_ref = $self->{link_task_for}{$src};
2201 internal_error(
2202 "do_mv: pre-existing link task for $src; action: %s, source: %s",
2203 $task_ref->{action}, $task_ref->{source}
2204 );
2205 }
2206 elsif (exists $self->{dir_task_for}{$src}) {
2207 my $task_ref = $self->{dir_task_for}{$src};
2208 internal_error(
2209 "do_mv: pre-existing dir task for %s?! action: %s",
2210 $src, $task_ref->{action}
2211 );
2212 }
2213
2214 # Remove the link
2215 debug(1, "MV: $src -> $dst");
2216
2217 my $task = {
2218 action => 'move',
2219 type => 'file',
2220 path => $src,
2221 dest => $dst,
2222 };
2223 push @{ $self->{tasks} }, $task;
2224
2225 # FIXME: do we need this for anything?
2226 #$self->{mv_task_for}{$file} = $task;
2227
2228 return;
2229}
2230
2231
2232#############################################################################
2233#
2234# End of methods; subroutines follow.
2235# FIXME: Ideally these should be in a separate module.
2236
2237
2238#===== PRIVATE SUBROUTINE ===================================================
2239# Name : internal_error()
2240# Purpose : output internal error message in a consistent form and die
2241# Parameters: $message => error message to output
2242# Returns : n/a
2243# Throws : n/a
2244# Comments : none
2245#============================================================================
2246sub internal_error {
2247 my ($format, @args) = @_;
2248 my $error = sprintf($format, @args);
2249 my $stacktrace = Carp::longmess();
2250 die <<EOF;
2251
2252$ProgramName: INTERNAL ERROR: $error$stacktrace
2253
2254This _is_ a bug. Please submit a bug report so we can fix it! :-)
2255See http://www.gnu.org/software/stow/ for how to do this.
2256EOF
2257}
2258
2259=head1 BUGS
2260
2261=head1 SEE ALSO
2262
2263=cut
2264
22651;
2266
2267# Local variables:
2268# mode: perl
2269# cperl-indent-level: 4
2270# end:
2271# vim: ft=perl
2272
2273#############################################################################
2274# Default global list of ignore regexps follows
2275# (automatically appended by the Makefile)
2276
2277__DATA__
2278# Comments and blank lines are allowed.
2279
2280RCS
2281.+,v
2282
2283CVS
2284\.\#.+ # CVS conflict files / emacs lock files
2285\.cvsignore
2286
2287\.svn
2288_darcs
2289\.hg
2290
2291\.git
2292\.gitignore
2293
2294.+~ # emacs backup files
2295\#.*\# # emacs autosave files
2296
2297^/README.*
2298^/LICENSE.*
2299^/COPYING
diff --git a/bin/stow b/bin/stow
index 888dcda..e25b37e 100755
--- a/bin/stow
+++ b/bin/stow
@@ -1,4 +1,2302 @@
1#!/usr/bin/perl 1#!/usr/bin/perl
2#
3# GNU Stow in a single file
4#
5# Just a hacked up version of stow to make it live and work in a single file.
6# Doesn't really change much except the way the module is constructed,
7# duplicates the parent function, and removes the __DATA__ section of the Stow
8# module.
9#
10
11{
12package Stow;
13
14=head1 NAME
15
16Stow - manage the installation of multiple software packages
17
18=head1 SYNOPSIS
19
20 my $stow = new Stow(%$options);
21
22 $stow->plan_unstow(@pkgs_to_unstow);
23 $stow->plan_stow (@pkgs_to_stow);
24
25 my %conflicts = $stow->get_conflicts;
26 $stow->process_tasks() unless %conflicts;
27
28=head1 DESCRIPTION
29
30This is the backend Perl module for GNU Stow, a program for managing
31the installation of software packages, keeping them separate
32(C</usr/local/stow/emacs> vs. C</usr/local/stow/perl>, for example)
33while making them appear to be installed in the same place
34(C</usr/local>).
35
36Stow doesn't store an extra state between runs, so there's no danger
37of mangling directories when file hierarchies don't match the
38database. Also, stow will never delete any files, directories, or
39links that appear in a stow directory, so it is always possible to
40rebuild the target tree.
41
42=cut
43
44use strict;
45use warnings;
46
47use Carp qw(carp cluck croak confess longmess);
48use File::Copy qw(move);
49use File::Spec;
50use POSIX qw(getcwd);
51
52=head1 NAME
53
54Stow::Util - general utilities
55
56=head1 SYNOPSIS
57
58 use Stow::Util qw(debug set_debug_level error ...);
59
60=head1 DESCRIPTION
61
62Supporting utility routines for L<Stow>.
63
64=cut
65
66use base qw(Exporter);
67our @EXPORT_OK = qw(
68 error debug set_debug_level set_test_mode
69 join_paths parent canon_path restore_cwd
70);
71
72our $ProgramName = 'stow';
73our $VERSION = '2.2.2';
74
75#############################################################################
76#
77# General Utilities: nothing stow specific here.
78#
79#############################################################################
80
81=head1 IMPORTABLE SUBROUTINES
82
83=head2 error($format, @args)
84
85Outputs an error message in a consistent form and then dies.
86
87=cut
88
89sub error {
90 my ($format, @args) = @_;
91 die "$ProgramName: ERROR: " . sprintf($format, @args) . "\n";
92}
93
94=head2 set_debug_level($level)
95
96Sets verbosity level for C<debug()>.
97
98=cut
99
100our $debug_level = 0;
101
102sub set_debug_level {
103 my ($level) = @_;
104 $debug_level = $level;
105}
106
107=head2 set_test_mode($on_or_off)
108
109Sets testmode on or off.
110
111=cut
112
113our $test_mode = 0;
114
115sub set_test_mode {
116 my ($on_or_off) = @_;
117 if ($on_or_off) {
118 $test_mode = 1;
119 }
120 else {
121 $test_mode = 0;
122 }
123}
124
125=head2 debug($level, $msg)
126
127Logs to STDERR based on C<$debug_level> setting. C<$level> is the
128minimum verbosity level required to output C<$msg>. All output is to
129STDERR to preserve backward compatibility, except for in test mode,
130when STDOUT is used instead. In test mode, the verbosity can be
131overridden via the C<TEST_VERBOSE> environment variable.
132
133Verbosity rules:
134
135=over 4
136
137=item 0: errors only
138
139=item >= 1: print operations: LINK/UNLINK/MKDIR/RMDIR/MV
140
141=item >= 2: print operation exceptions
142
143e.g. "_this_ already points to _that_", skipping, deferring,
144overriding, fixing invalid links
145
146=item >= 3: print trace detail: trace: stow/unstow/package/contents/node
147
148=item >= 4: debug helper routines
149
150=item >= 5: debug ignore lists
151
152=back
153
154=cut
155
156sub debug {
157 my ($level, $msg) = @_;
158 if ($debug_level >= $level) {
159 if ($test_mode) {
160 print "# $msg\n";
161 }
162 else {
163 warn "$msg\n";
164 }
165 }
166}
167
168#===== METHOD ===============================================================
169# Name : join_paths()
170# Purpose : concatenates given paths
171# Parameters: path1, path2, ... => paths
172# Returns : concatenation of given paths
173# Throws : n/a
174# Comments : factors out redundant path elements:
175# : '//' => '/' and 'a/b/../c' => 'a/c'
176#============================================================================
177sub join_paths {
178 my @paths = @_;
179
180 # weed out empty components and concatenate
181 my $result = join '/', grep {! /\A\z/} @paths;
182
183 # factor out back references and remove redundant /'s)
184 my @result = ();
185 PART:
186 for my $part (split m{/+}, $result) {
187 next PART if $part eq '.';
188 if (@result && $part eq '..' && $result[-1] ne '..') {
189 pop @result;
190 }
191 else {
192 push @result, $part;
193 }
194 }
195
196 return join '/', @result;
197}
198
199#===== METHOD ===============================================================
200# Name : parent
201# Purpose : find the parent of the given path
202# Parameters: @path => components of the path
203# Returns : returns a path string
204# Throws : n/a
205# Comments : allows you to send multiple chunks of the path
206# : (this feature is currently not used)
207#============================================================================
208sub parent {
209 my @path = @_;
210 my $path = join '/', @_;
211 my @elts = split m{/+}, $path;
212 pop @elts;
213 return join '/', @elts;
214}
215
216#===== METHOD ===============================================================
217# Name : canon_path
218# Purpose : find absolute canonical path of given path
219# Parameters: $path
220# Returns : absolute canonical path
221# Throws : n/a
222# Comments : is this significantly different from File::Spec->rel2abs?
223#============================================================================
224sub canon_path {
225 my ($path) = @_;
226
227 my $cwd = getcwd();
228 chdir($path) or error("canon_path: cannot chdir to $path from $cwd");
229 my $canon_path = getcwd();
230 restore_cwd($cwd);
231
232 return $canon_path;
233}
234
235sub restore_cwd {
236 my ($prev) = @_;
237 chdir($prev) or error("Your current directory $prev seems to have vanished");
238}
239
240=head1 BUGS
241
242=head1 SEE ALSO
243
244=cut
245
246
247our $LOCAL_IGNORE_FILE = '.stow-local-ignore';
248our $GLOBAL_IGNORE_FILE = '.stow-global-ignore';
249
250our @default_global_ignore_regexps =
251 __PACKAGE__->get_default_global_ignore_regexps();
252
253# These are the default options for each Stow instance.
254our %DEFAULT_OPTIONS = (
255 conflicts => 0,
256 simulate => 0,
257 verbose => 0,
258 paranoid => 0,
259 compat => 0,
260 test_mode => 0,
261 adopt => 0,
262 'no-folding' => 0,
263 ignore => [],
264 override => [],
265 defer => [],
266);
267
268=head1 CONSTRUCTORS
269
270=head2 new(%options)
271
272=head3 Required options
273
274=over 4
275
276=item * dir - the stow directory
277
278=item * target - the target directory
279
280=back
281
282=head3 Non-mandatory options
283
284See the documentation for the F<stow> CLI front-end for information on these.
285
286=over 4
287
288=item * conflicts
289
290=item * simulate
291
292=item * verbose
293
294=item * paranoid
295
296=item * compat
297
298=item * test_mode
299
300=item * adopt
301
302=item * no-folding
303
304=item * ignore
305
306=item * override
307
308=item * defer
309
310=back
311
312N.B. This sets the current working directory to the target directory.
313
314=cut
315
316sub new {
317 my $self = shift;
318 my $class = ref($self) || $self;
319 my %opts = @_;
320
321 my $new = bless { }, $class;
322
323 $new->{action_count} = 0;
324
325 for my $required_arg (qw(dir target)) {
326 croak "$class->new() called without '$required_arg' parameter\n"
327 unless exists $opts{$required_arg};
328 $new->{$required_arg} = delete $opts{$required_arg};
329 }
330
331 for my $opt (keys %DEFAULT_OPTIONS) {
332 $new->{$opt} = exists $opts{$opt} ? delete $opts{$opt}
333 : $DEFAULT_OPTIONS{$opt};
334 }
335
336 if (%opts) {
337 croak "$class->new() called with unrecognised parameter(s): ",
338 join(", ", keys %opts), "\n";
339 }
340
341 set_debug_level($new->get_verbosity());
342 set_test_mode($new->{test_mode});
343 $new->set_stow_dir();
344 $new->init_state();
345
346 return $new;
347}
348
349sub get_verbosity {
350 my $self = shift;
351
352 return $self->{verbose} unless $self->{test_mode};
353
354 return 0 unless exists $ENV{TEST_VERBOSE};
355 return 0 unless length $ENV{TEST_VERBOSE};
356
357 # Convert TEST_VERBOSE=y into numeric value
358 $ENV{TEST_VERBOSE} = 3 if $ENV{TEST_VERBOSE} !~ /^\d+$/;
359
360 return $ENV{TEST_VERBOSE};
361}
362
363=head2 set_stow_dir([$dir])
364
365Sets a new stow directory. This allows the use of multiple stow
366directories within one Stow instance, e.g.
367
368 $stow->plan_stow('foo');
369 $stow->set_stow_dir('/different/stow/dir');
370 $stow->plan_stow('bar');
371 $stow->process_tasks;
372
373If C<$dir> is omitted, uses the value of the C<dir> parameter passed
374to the L<new()> constructor.
375
376=cut
377
378sub set_stow_dir {
379 my $self = shift;
380 my ($dir) = @_;
381 if (defined $dir) {
382 $self->{dir} = $dir;
383 }
384
385 my $stow_dir = canon_path($self->{dir});
386 my $target = canon_path($self->{target});
387 $self->{stow_path} = File::Spec->abs2rel($stow_dir, $target);
388
389 debug(2, "stow dir is $stow_dir");
390 debug(2, "stow dir path relative to target $target is $self->{stow_path}");
391}
392
393sub init_state {
394 my $self = shift;
395
396 # Store conflicts during pre-processing
397 $self->{conflicts} = {};
398 $self->{conflict_count} = 0;
399
400 # Store command line packages to stow (-S and -R)
401 $self->{pkgs_to_stow} = [];
402
403 # Store command line packages to unstow (-D and -R)
404 $self->{pkgs_to_delete} = [];
405
406 # The following structures are used by the abstractions that allow us to
407 # defer operating on the filesystem until after all potential conflicts have
408 # been assessed.
409
410 # $self->{tasks}: list of operations to be performed (in order)
411 # each element is a hash ref of the form
412 # {
413 # action => ... ('create' or 'remove' or 'move')
414 # type => ... ('link' or 'dir' or 'file')
415 # path => ... (unique)
416 # source => ... (only for links)
417 # dest => ... (only for moving files)
418 # }
419 $self->{tasks} = [];
420
421 # $self->{dir_task_for}: map a path to the corresponding directory task reference
422 # This structure allows us to quickly determine if a path has an existing
423 # directory task associated with it.
424 $self->{dir_task_for} = {};
425
426 # $self->{link_task_for}: map a path to the corresponding directory task reference
427 # This structure allows us to quickly determine if a path has an existing
428 # directory task associated with it.
429 $self->{link_task_for} = {};
430
431 # N.B.: directory tasks and link tasks are NOT mutually exclusive due
432 # to tree splitting (which involves a remove link task followed by
433 # a create directory task).
434}
435
436=head1 METHODS
437
438=head2 plan_unstow(@packages)
439
440Plan which symlink/directory creation/removal tasks need to be executed
441in order to unstow the given packages. Any potential conflicts are then
442accessible via L<get_conflicts()>.
443
444=cut
445
446sub plan_unstow {
447 my $self = shift;
448 my @packages = @_;
449
450 $self->within_target_do(sub {
451 for my $package (@packages) {
452 my $path = join_paths($self->{stow_path}, $package);
453 if (not -d $path) {
454 error("The stow directory $self->{stow_path} does not contain package $package");
455 }
456 debug(2, "Planning unstow of package $package...");
457 if ($self->{compat}) {
458 $self->unstow_contents_orig(
459 $self->{stow_path},
460 $package,
461 '.',
462 );
463 }
464 else {
465 $self->unstow_contents(
466 $self->{stow_path},
467 $package,
468 '.',
469 );
470 }
471 debug(2, "Planning unstow of package $package... done");
472 $self->{action_count}++;
473 }
474 });
475}
476
477=head2 plan_stow(@packages)
478
479Plan which symlink/directory creation/removal tasks need to be executed
480in order to stow the given packages. Any potential conflicts are then
481accessible via L<get_conflicts()>.
482
483=cut
484
485sub plan_stow {
486 my $self = shift;
487 my @packages = @_;
488
489 $self->within_target_do(sub {
490 for my $package (@packages) {
491 my $path = join_paths($self->{stow_path}, $package);
492 if (not -d $path) {
493 error("The stow directory $self->{stow_path} does not contain package $package");
494 }
495 debug(2, "Planning stow of package $package...");
496 $self->stow_contents(
497 $self->{stow_path},
498 $package,
499 '.',
500 $path, # source from target
501 );
502 debug(2, "Planning stow of package $package... done");
503 $self->{action_count}++;
504 }
505 });
506}
507
508#===== METHOD ===============================================================
509# Name : within_target_do()
510# Purpose : execute code within target directory, preserving cwd
511# Parameters: $code => anonymous subroutine to execute within target dir
512# Returns : n/a
513# Throws : n/a
514# Comments : This is done to ensure that the consumer of the Stow interface
515# : doesn't have to worry about (a) what their cwd is, and
516# : (b) that their cwd might change.
517#============================================================================
518sub within_target_do {
519 my $self = shift;
520 my ($code) = @_;
521
522 my $cwd = getcwd();
523 chdir($self->{target})
524 or error("Cannot chdir to target tree: $self->{target} ($!)");
525 debug(3, "cwd now $self->{target}");
526
527 $self->$code();
528
529 restore_cwd($cwd);
530 debug(3, "cwd restored to $cwd");
531}
532
533#===== METHOD ===============================================================
534# Name : stow_contents()
535# Purpose : stow the contents of the given directory
536# Parameters: $stow_path => relative path from current (i.e. target) directory
537# : to the stow dir containing the package to be stowed
538# : $package => the package whose contents are being stowed
539# : $target => subpath relative to package and target directories
540# : $source => relative path from the (sub)dir of target
541# : to symlink source
542# Returns : n/a
543# Throws : a fatal error if directory cannot be read
544# Comments : stow_node() and stow_contents() are mutually recursive.
545# : $source and $target are used for creating the symlink
546# : $path is used for folding/unfolding trees as necessary
547#============================================================================
548sub stow_contents {
549 my $self = shift;
550 my ($stow_path, $package, $target, $source) = @_;
551
552 my $path = join_paths($stow_path, $package, $target);
553
554 return if $self->should_skip_target_which_is_stow_dir($target);
555
556 my $cwd = getcwd();
557 my $msg = "Stowing contents of $path (cwd=$cwd)";
558 $msg =~ s!$ENV{HOME}(/|$)!~$1!g;
559 debug(3, $msg);
560 debug(4, " => $source");
561
562 error("stow_contents() called with non-directory path: $path")
563 unless -d $path;
564 error("stow_contents() called with non-directory target: $target")
565 unless $self->is_a_node($target);
566
567 opendir my $DIR, $path
568 or error("cannot read directory: $path ($!)");
569 my @listing = readdir $DIR;
570 closedir $DIR;
571
572 NODE:
573 for my $node (@listing) {
574 next NODE if $node eq '.';
575 next NODE if $node eq '..';
576 my $node_target = join_paths($target, $node);
577 next NODE if $self->ignore($stow_path, $package, $node_target);
578 $self->stow_node(
579 $stow_path,
580 $package,
581 $node_target, # target
582 join_paths($source, $node), # source
583 );
584 }
585}
586
587#===== METHOD ===============================================================
588# Name : stow_node()
589# Purpose : stow the given node
590# Parameters: $stow_path => relative path from current (i.e. target) directory
591# : to the stow dir containing the node to be stowed
592# : $package => the package containing the node being stowed
593# : $target => subpath relative to package and target directories
594# : $source => relative path to symlink source from the dir of target
595# Returns : n/a
596# Throws : fatal exception if a conflict arises
597# Comments : stow_node() and stow_contents() are mutually recursive
598# : $source and $target are used for creating the symlink
599# : $path is used for folding/unfolding trees as necessary
600#============================================================================
601sub stow_node {
602 my $self = shift;
603 my ($stow_path, $package, $target, $source) = @_;
604
605 my $path = join_paths($stow_path, $package, $target);
606
607 debug(3, "Stowing $stow_path / $package / $target");
608 debug(4, " => $source");
609
610 # Don't try to stow absolute symlinks (they can't be unstowed)
611 if (-l $source) {
612 my $second_source = $self->read_a_link($source);
613 if ($second_source =~ m{\A/}) {
614 $self->conflict(
615 'stow',
616 $package,
617 "source is an absolute symlink $source => $second_source"
618 );
619 debug(3, "Absolute symlinks cannot be unstowed");
620 return;
621 }
622 }
623
624 # Does the target already exist?
625 if ($self->is_a_link($target)) {
626 # Where is the link pointing?
627 my $existing_source = $self->read_a_link($target);
628 if (not $existing_source) {
629 error("Could not read link: $target");
630 }
631 debug(4, " Evaluate existing link: $target => $existing_source");
632
633 # Does it point to a node under any stow directory?
634 my ($existing_path, $existing_stow_path, $existing_package) =
635 $self->find_stowed_path($target, $existing_source);
636 if (not $existing_path) {
637 $self->conflict(
638 'stow',
639 $package,
640 "existing target is not owned by stow: $target"
641 );
642 return; # XXX #
643 }
644
645 # Does the existing $target actually point to anything?
646 if ($self->is_a_node($existing_path)) {
647 if ($existing_source eq $source) {
648 debug(2, "--- Skipping $target as it already points to $source");
649 }
650 elsif ($self->defer($target)) {
651 debug(2, "--- Deferring installation of: $target");
652 }
653 elsif ($self->override($target)) {
654 debug(2, "--- Overriding installation of: $target");
655 $self->do_unlink($target);
656 $self->do_link($source, $target);
657 }
658 elsif ($self->is_a_dir(join_paths(parent($target), $existing_source)) &&
659 $self->is_a_dir(join_paths(parent($target), $source)) ) {
660
661 # If the existing link points to a directory,
662 # and the proposed new link points to a directory,
663 # then we can unfold (split open) the tree at that point
664
665 debug(2, "--- Unfolding $target which was already owned by $existing_package");
666 $self->do_unlink($target);
667 $self->do_mkdir($target);
668 $self->stow_contents(
669 $existing_stow_path,
670 $existing_package,
671 $target,
672 join_paths('..', $existing_source),
673 );
674 $self->stow_contents(
675 $self->{stow_path},
676 $package,
677 $target,
678 join_paths('..', $source),
679 );
680 }
681 else {
682 $self->conflict(
683 'stow',
684 $package,
685 "existing target is stowed to a different package: "
686 . "$target => $existing_source"
687 );
688 }
689 }
690 else {
691 # The existing link is invalid, so replace it with a good link
692 debug(2, "--- replacing invalid link: $path");
693 $self->do_unlink($target);
694 $self->do_link($source, $target);
695 }
696 }
697 elsif ($self->is_a_node($target)) {
698 debug(4, " Evaluate existing node: $target");
699 if ($self->is_a_dir($target)) {
700 $self->stow_contents(
701 $self->{stow_path},
702 $package,
703 $target,
704 join_paths('..', $source),
705 );
706 }
707 else {
708 if ($self->{adopt}) {
709 $self->do_mv($target, $path);
710 $self->do_link($source, $target);
711 }
712 else {
713 $self->conflict(
714 'stow',
715 $package,
716 "existing target is neither a link nor a directory: $target"
717 );
718 }
719 }
720 }
721 elsif ($self->{'no-folding'} && -d $path && ! -l $path) {
722 $self->do_mkdir($target);
723 $self->stow_contents(
724 $self->{stow_path},
725 $package,
726 $target,
727 join_paths('..', $source),
728 );
729 }
730 else {
731 $self->do_link($source, $target);
732 }
733 return;
734}
735
736#===== METHOD ===============================================================
737# Name : should_skip_target_which_is_stow_dir()
738# Purpose : determine whether target is a stow directory which should
739# : not be stowed to or unstowed from
740# Parameters: $target => relative path to symlink target from the current directory
741# Returns : true iff target is a stow directory
742# Throws : n/a
743# Comments : none
744#============================================================================
745sub should_skip_target_which_is_stow_dir {
746 my $self = shift;
747 my ($target) = @_;
748
749 # Don't try to remove anything under a stow directory
750 if ($target eq $self->{stow_path}) {
751 warn "WARNING: skipping target which was current stow directory $target\n";
752 return 1;
753 }
754
755 if ($self->marked_stow_dir($target)) {
756 warn "WARNING: skipping protected directory $target\n";
757 return 1;
758 }
759
760 debug (4, "$target not protected");
761 return 0;
762}
763
764sub marked_stow_dir {
765 my $self = shift;
766 my ($target) = @_;
767 for my $f (".stow", ".nonstow") {
768 if (-e join_paths($target, $f)) {
769 debug(4, "$target contained $f");
770 return 1;
771 }
772 }
773 return 0;
774}
775
776#===== METHOD ===============================================================
777# Name : unstow_contents_orig()
778# Purpose : unstow the contents of the given directory
779# Parameters: $stow_path => relative path from current (i.e. target) directory
780# : to the stow dir containing the package to be unstowed
781# : $package => the package whose contents are being unstowed
782# : $target => relative path to symlink target from the current directory
783# Returns : n/a
784# Throws : a fatal error if directory cannot be read
785# Comments : unstow_node_orig() and unstow_contents_orig() are mutually recursive
786# : Here we traverse the target tree, rather than the source tree.
787#============================================================================
788sub unstow_contents_orig {
789 my $self = shift;
790 my ($stow_path, $package, $target) = @_;
791
792 my $path = join_paths($stow_path, $package, $target);
793
794 return if $self->should_skip_target_which_is_stow_dir($target);
795
796 my $cwd = getcwd();
797 my $msg = "Unstowing from $target (compat mode, cwd=$cwd, stow dir=$self->{stow_path})";
798 $msg =~ s!$ENV{HOME}(/|$)!~$1!g;
799 debug(3, $msg);
800 debug(4, " source path is $path");
801 # In compat mode we traverse the target tree not the source tree,
802 # so we're unstowing the contents of /target/foo, there's no
803 # guarantee that the corresponding /stow/mypkg/foo exists.
804 error("unstow_contents_orig() called with non-directory target: $target")
805 unless -d $target;
806
807 opendir my $DIR, $target
808 or error("cannot read directory: $target ($!)");
809 my @listing = readdir $DIR;
810 closedir $DIR;
811
812 NODE:
813 for my $node (@listing) {
814 next NODE if $node eq '.';
815 next NODE if $node eq '..';
816 my $node_target = join_paths($target, $node);
817 next NODE if $self->ignore($stow_path, $package, $node_target);
818 $self->unstow_node_orig($stow_path, $package, $node_target);
819 }
820}
821
822#===== METHOD ===============================================================
823# Name : unstow_node_orig()
824# Purpose : unstow the given node
825# Parameters: $stow_path => relative path from current (i.e. target) directory
826# : to the stow dir containing the node to be stowed
827# : $package => the package containing the node being stowed
828# : $target => relative path to symlink target from the current directory
829# Returns : n/a
830# Throws : fatal error if a conflict arises
831# Comments : unstow_node() and unstow_contents() are mutually recursive
832#============================================================================
833sub unstow_node_orig {
834 my $self = shift;
835 my ($stow_path, $package, $target) = @_;
836
837 my $path = join_paths($stow_path, $package, $target);
838
839 debug(3, "Unstowing $target (compat mode)");
840 debug(4, " source path is $path");
841
842 # Does the target exist?
843 if ($self->is_a_link($target)) {
844 debug(4, " Evaluate existing link: $target");
845
846 # Where is the link pointing?
847 my $existing_source = $self->read_a_link($target);
848 if (not $existing_source) {
849 error("Could not read link: $target");
850 }
851
852 # Does it point to a node under any stow directory?
853 my ($existing_path, $existing_stow_path, $existing_package) =
854 $self->find_stowed_path($target, $existing_source);
855 if (not $existing_path) {
856 # We're traversing the target tree not the package tree,
857 # so we definitely expect to find stuff not owned by stow.
858 # Therefore we can't flag a conflict.
859 return; # XXX #
860 }
861
862 # Does the existing $target actually point to anything?
863 if (-e $existing_path) {
864 # Does link point to the right place?
865 if ($existing_path eq $path) {
866 $self->do_unlink($target);
867 }
868 elsif ($self->override($target)) {
869 debug(2, "--- overriding installation of: $target");
870 $self->do_unlink($target);
871 }
872 # else leave it alone
873 }
874 else {
875 debug(2, "--- removing invalid link into a stow directory: $path");
876 $self->do_unlink($target);
877 }
878 }
879 elsif (-d $target) {
880 $self->unstow_contents_orig($stow_path, $package, $target);
881
882 # This action may have made the parent directory foldable
883 if (my $parent = $self->foldable($target)) {
884 $self->fold_tree($target, $parent);
885 }
886 }
887 elsif (-e $target) {
888 $self->conflict(
889 'unstow',
890 $package,
891 "existing target is neither a link nor a directory: $target",
892 );
893 }
894 else {
895 debug(2, "$target did not exist to be unstowed");
896 }
897 return;
898}
899
900#===== METHOD ===============================================================
901# Name : unstow_contents()
902# Purpose : unstow the contents of the given directory
903# Parameters: $stow_path => relative path from current (i.e. target) directory
904# : to the stow dir containing the package to be unstowed
905# : $package => the package whose contents are being unstowed
906# : $target => relative path to symlink target from the current directory
907# Returns : n/a
908# Throws : a fatal error if directory cannot be read
909# Comments : unstow_node() and unstow_contents() are mutually recursive
910# : Here we traverse the source tree, rather than the target tree.
911#============================================================================
912sub unstow_contents {
913 my $self = shift;
914 my ($stow_path, $package, $target) = @_;
915
916 my $path = join_paths($stow_path, $package, $target);
917
918 return if $self->should_skip_target_which_is_stow_dir($target);
919
920 my $cwd = getcwd();
921 my $msg = "Unstowing from $target (cwd=$cwd, stow dir=$self->{stow_path})";
922 $msg =~ s!$ENV{HOME}/!~/!g;
923 debug(3, $msg);
924 debug(4, " source path is $path");
925 # We traverse the source tree not the target tree, so $path must exist.
926 error("unstow_contents() called with non-directory path: $path")
927 unless -d $path;
928 # When called at the top level, $target should exist. And
929 # unstow_node() should only call this via mutual recursion if
930 # $target exists.
931 error("unstow_contents() called with invalid target: $target")
932 unless $self->is_a_node($target);
933
934 opendir my $DIR, $path
935 or error("cannot read directory: $path ($!)");
936 my @listing = readdir $DIR;
937 closedir $DIR;
938
939 NODE:
940 for my $node (@listing) {
941 next NODE if $node eq '.';
942 next NODE if $node eq '..';
943 my $node_target = join_paths($target, $node);
944 next NODE if $self->ignore($stow_path, $package, $node_target);
945 $self->unstow_node($stow_path, $package, $node_target);
946 }
947 if (-d $target) {
948 $self->cleanup_invalid_links($target);
949 }
950}
951
952#===== METHOD ===============================================================
953# Name : unstow_node()
954# Purpose : unstow the given node
955# Parameters: $stow_path => relative path from current (i.e. target) directory
956# : to the stow dir containing the node to be stowed
957# : $package => the package containing the node being unstowed
958# : $target => relative path to symlink target from the current directory
959# Returns : n/a
960# Throws : fatal error if a conflict arises
961# Comments : unstow_node() and unstow_contents() are mutually recursive
962#============================================================================
963sub unstow_node {
964 my $self = shift;
965 my ($stow_path, $package, $target) = @_;
966
967 my $path = join_paths($stow_path, $package, $target);
968
969 debug(3, "Unstowing $path");
970 debug(4, " target is $target");
971
972 # Does the target exist?
973 if ($self->is_a_link($target)) {
974 debug(4, " Evaluate existing link: $target");
975
976 # Where is the link pointing?
977 my $existing_source = $self->read_a_link($target);
978 if (not $existing_source) {
979 error("Could not read link: $target");
980 }
981
982 if ($existing_source =~ m{\A/}) {
983 warn "Ignoring an absolute symlink: $target => $existing_source\n";
984 return; # XXX #
985 }
986
987 # Does it point to a node under any stow directory?
988 my ($existing_path, $existing_stow_path, $existing_package) =
989 $self->find_stowed_path($target, $existing_source);
990 if (not $existing_path) {
991 $self->conflict(
992 'unstow',
993 $package,
994 "existing target is not owned by stow: $target => $existing_source"
995 );
996 return; # XXX #
997 }
998
999 # Does the existing $target actually point to anything?
1000 if (-e $existing_path) {
1001 # Does link points to the right place?
1002 if ($existing_path eq $path) {
1003 $self->do_unlink($target);
1004 }
1005
1006 # XXX we quietly ignore links that are stowed to a different
1007 # package.
1008
1009 #elsif (defer($target)) {
1010 # debug(2, "--- deferring to installation of: $target");
1011 #}
1012 #elsif ($self->override($target)) {
1013 # debug(2, "--- overriding installation of: $target");
1014 # $self->do_unlink($target);
1015 #}
1016 #else {
1017 # $self->conflict(
1018 # 'unstow',
1019 # $package,
1020 # "existing target is stowed to a different package: "
1021 # . "$target => $existing_source"
1022 # );
1023 #}
1024 }
1025 else {
1026 debug(2, "--- removing invalid link into a stow directory: $path");
1027 $self->do_unlink($target);
1028 }
1029 }
1030 elsif (-e $target) {
1031 debug(4, " Evaluate existing node: $target");
1032 if (-d $target) {
1033 $self->unstow_contents($stow_path, $package, $target);
1034
1035 # This action may have made the parent directory foldable
1036 if (my $parent = $self->foldable($target)) {
1037 $self->fold_tree($target, $parent);
1038 }
1039 }
1040 else {
1041 $self->conflict(
1042 'unstow',
1043 $package,
1044 "existing target is neither a link nor a directory: $target",
1045 );
1046 }
1047 }
1048 else {
1049 debug(2, "$target did not exist to be unstowed");
1050 }
1051 return;
1052}
1053
1054#===== METHOD ===============================================================
1055# Name : path_owned_by_package()
1056# Purpose : determine whether the given link points to a member of a
1057# : stowed package
1058# Parameters: $target => path to a symbolic link under current directory
1059# : $source => where that link points to
1060# Returns : the package iff link is owned by stow, otherwise ''
1061# Throws : n/a
1062# Comments : lossy wrapper around find_stowed_path()
1063#============================================================================
1064sub path_owned_by_package {
1065 my $self = shift;
1066 my ($target, $source) = @_;
1067
1068 my ($path, $stow_path, $package) =
1069 $self->find_stowed_path($target, $source);
1070 return $package;
1071}
1072
1073#===== METHOD ===============================================================
1074# Name : find_stowed_path()
1075# Purpose : determine whether the given link points to a member of a
1076# : stowed package
1077# Parameters: $target => path to a symbolic link under current directory
1078# : $source => where that link points to (needed because link
1079# : might not exist yet due to two-phase approach,
1080# : so we can't just call readlink())
1081# Returns : ($path, $stow_path, $package) where $path and $stow_path are
1082# : relative from the current (i.e. target) directory. $path
1083# : is the full relative path, $stow_path is the relative path
1084# : to the stow directory, and $package is the name of the package.
1085# : or ('', '', '') if link is not owned by stow
1086# Throws : n/a
1087# Comments : Needs
1088# : Allow for stow dir not being under target dir.
1089# : We could put more logic under here for multiple stow dirs.
1090#============================================================================
1091sub find_stowed_path {
1092 my $self = shift;
1093 my ($target, $source) = @_;
1094
1095 # Evaluate softlink relative to its target
1096 my $path = join_paths(parent($target), $source);
1097 debug(4, " is path $path owned by stow?");
1098
1099 # Search for .stow files - this allows us to detect links
1100 # owned by stow directories other than the current one.
1101 my $dir = '';
1102 my @path = split m{/+}, $path;
1103 for my $i (0 .. $#path) {
1104 my $part = $path[$i];
1105 $dir = join_paths($dir, $part);
1106 if ($self->marked_stow_dir($dir)) {
1107 # FIXME - not sure if this can ever happen
1108 internal_error("find_stowed_path() called directly on stow dir")
1109 if $i == $#path;
1110
1111 debug(4, " yes - $dir was marked as a stow dir");
1112 my $package = $path[$i + 1];
1113 return ($path, $dir, $package);
1114 }
1115 }
1116
1117 # If no .stow file was found, we need to find out whether it's
1118 # owned by the current stow directory, in which case $path will be
1119 # a prefix of $self->{stow_path}.
1120 my @stow_path = split m{/+}, $self->{stow_path};
1121
1122 # Strip off common prefixes until one is empty
1123 while (@path && @stow_path) {
1124 if ((shift @path) ne (shift @stow_path)) {
1125 debug(4, " no - either $path not under $self->{stow_path} or vice-versa");
1126 return ('', '', '');
1127 }
1128 }
1129
1130 if (@stow_path) { # @path must be empty
1131 debug(4, " no - $path is not under $self->{stow_path}");
1132 return ('', '', '');
1133 }
1134
1135 my $package = shift @path;
1136
1137 debug(4, " yes - by $package in " . join_paths(@path));
1138 return ($path, $self->{stow_path}, $package);
1139}
1140
1141#===== METHOD ================================================================
1142# Name : cleanup_invalid_links()
1143# Purpose : clean up invalid links that may block folding
1144# Parameters: $dir => path to directory to check
1145# Returns : n/a
1146# Throws : no exceptions
1147# Comments : removing files from a stowed package is probably a bad practice
1148# : so this kind of clean up is not _really_ stow's responsibility;
1149# : however, failing to clean up can block tree folding, so we'll do
1150# : it anyway
1151#=============================================================================
1152sub cleanup_invalid_links {
1153 my $self = shift;
1154 my ($dir) = @_;
1155
1156 if (not -d $dir) {
1157 error("cleanup_invalid_links() called with a non-directory: $dir");
1158 }
1159
1160 opendir my $DIR, $dir
1161 or error("cannot read directory: $dir ($!)");
1162 my @listing = readdir $DIR;
1163 closedir $DIR;
1164
1165 NODE:
1166 for my $node (@listing) {
1167 next NODE if $node eq '.';
1168 next NODE if $node eq '..';
1169
1170 my $node_path = join_paths($dir, $node);
1171
1172 if (-l $node_path and not exists $self->{link_task_for}{$node_path}) {
1173
1174 # Where is the link pointing?
1175 # (don't use read_a_link() here)
1176 my $source = readlink($node_path);
1177 if (not $source) {
1178 error("Could not read link $node_path");
1179 }
1180
1181 if (
1182 not -e join_paths($dir, $source) and # bad link
1183 $self->path_owned_by_package($node_path, $source) # owned by stow
1184 ){
1185 debug(2, "--- removing stale link: $node_path => " .
1186 join_paths($dir, $source));
1187 $self->do_unlink($node_path);
1188 }
1189 }
1190 }
1191 return;
1192}
1193
1194
1195#===== METHOD ===============================================================
1196# Name : foldable()
1197# Purpose : determine whether a tree can be folded
1198# Parameters: $target => path to a directory
1199# Returns : path to the parent dir iff the tree can be safely folded
1200# Throws : n/a
1201# Comments : the path returned is relative to the parent of $target,
1202# : that is, it can be used as the source for a replacement symlink
1203#============================================================================
1204sub foldable {
1205 my $self = shift;
1206 my ($target) = @_;
1207
1208 debug(3, "--- Is $target foldable?");
1209 if ($self->{'no-folding'}) {
1210 debug(3, "--- no because --no-folding enabled");
1211 return '';
1212 }
1213
1214 opendir my $DIR, $target
1215 or error(qq{Cannot read directory "$target" ($!)\n});
1216 my @listing = readdir $DIR;
1217 closedir $DIR;
1218
1219 my $parent = '';
1220 NODE:
1221 for my $node (@listing) {
1222
1223 next NODE if $node eq '.';
1224 next NODE if $node eq '..';
1225
1226 my $path = join_paths($target, $node);
1227
1228 # Skip nodes scheduled for removal
1229 next NODE if not $self->is_a_node($path);
1230
1231 # If it's not a link then we can't fold its parent
1232 return '' if not $self->is_a_link($path);
1233
1234 # Where is the link pointing?
1235 my $source = $self->read_a_link($path);
1236 if (not $source) {
1237 error("Could not read link $path");
1238 }
1239 if ($parent eq '') {
1240 $parent = parent($source)
1241 }
1242 elsif ($parent ne parent($source)) {
1243 return '';
1244 }
1245 }
1246 return '' if not $parent;
1247
1248 # If we get here then all nodes inside $target are links, and those links
1249 # point to nodes inside the same directory.
1250
1251 # chop of leading '..' to get the path to the common parent directory
1252 # relative to the parent of our $target
1253 $parent =~ s{\A\.\./}{};
1254
1255 # If the resulting path is owned by stow, we can fold it
1256 if ($self->path_owned_by_package($target, $parent)) {
1257 debug(3, "--- $target is foldable");
1258 return $parent;
1259 }
1260 else {
1261 return '';
1262 }
1263}
1264
1265#===== METHOD ===============================================================
1266# Name : fold_tree()
1267# Purpose : fold the given tree
1268# Parameters: $source => link to the folded tree source
1269# : $target => directory that we will replace with a link to $source
1270# Returns : n/a
1271# Throws : none
1272# Comments : only called iff foldable() is true so we can remove some checks
1273#============================================================================
1274sub fold_tree {
1275 my $self = shift;
1276 my ($target, $source) = @_;
1277
1278 debug(3, "--- Folding tree: $target => $source");
1279
1280 opendir my $DIR, $target
1281 or error(qq{Cannot read directory "$target" ($!)\n});
1282 my @listing = readdir $DIR;
1283 closedir $DIR;
1284
1285 NODE:
1286 for my $node (@listing) {
1287 next NODE if $node eq '.';
1288 next NODE if $node eq '..';
1289 next NODE if not $self->is_a_node(join_paths($target, $node));
1290 $self->do_unlink(join_paths($target, $node));
1291 }
1292 $self->do_rmdir($target);
1293 $self->do_link($source, $target);
1294 return;
1295}
1296
1297
1298#===== METHOD ===============================================================
1299# Name : conflict()
1300# Purpose : handle conflicts in stow operations
1301# Parameters: $package => the package involved with the conflicting operation
1302# : $message => a description of the conflict
1303# Returns : n/a
1304# Throws : none
1305# Comments : none
1306#============================================================================
1307sub conflict {
1308 my $self = shift;
1309 my ($action, $package, $message) = @_;
1310
1311 debug(2, "CONFLICT when ${action}ing $package: $message");
1312 $self->{conflicts}{$action}{$package} ||= [];
1313 push @{ $self->{conflicts}{$action}{$package} }, $message;
1314 $self->{conflict_count}++;
1315
1316 return;
1317}
1318
1319=head2 get_conflicts()
1320
1321Returns a nested hash of all potential conflicts discovered: the keys
1322are actions ('stow' or 'unstow'), and the values are hashrefs whose
1323keys are stow package names and whose values are conflict
1324descriptions, e.g.:
1325
1326 (
1327 stow => {
1328 perl => [
1329 "existing target is not owned by stow: bin/a2p"
1330 "existing target is neither a link nor a directory: bin/perl"
1331 ]
1332 }
1333 )
1334
1335=cut
1336
1337sub get_conflicts {
1338 my $self = shift;
1339 return %{ $self->{conflicts} };
1340}
1341
1342=head2 get_conflict_count()
1343
1344Returns the number of conflicts found.
1345
1346=cut
1347
1348sub get_conflict_count {
1349 my $self = shift;
1350 return $self->{conflict_count};
1351}
1352
1353=head2 get_tasks()
1354
1355Returns a list of all symlink/directory creation/removal tasks.
1356
1357=cut
1358
1359sub get_tasks {
1360 my $self = shift;
1361 return @{ $self->{tasks} };
1362}
1363
1364=head2 get_action_count()
1365
1366Returns the number of actions planned for this Stow instance.
1367
1368=cut
1369
1370sub get_action_count {
1371 my $self = shift;
1372 return $self->{action_count};
1373}
1374
1375#===== METHOD ================================================================
1376# Name : ignore
1377# Purpose : determine if the given path matches a regex in our ignore list
1378# Parameters: $stow_path => the stow directory containing the package
1379# : $package => the package containing the path
1380# : $target => the path to check against the ignore list
1381# : relative to its package directory
1382# Returns : true iff the path should be ignored
1383# Throws : no exceptions
1384# Comments : none
1385#=============================================================================
1386sub ignore {
1387 my $self = shift;
1388 my ($stow_path, $package, $target) = @_;
1389
1390 internal_error(__PACKAGE__ . "::ignore() called with empty target")
1391 unless length $target;
1392
1393 for my $suffix (@{ $self->{ignore} }) {
1394 if ($target =~ m/$suffix/) {
1395 debug(4, " Ignoring path $target due to --ignore=$suffix");
1396 return 1;
1397 }
1398 }
1399
1400 my $package_dir = join_paths($stow_path, $package);
1401 my ($path_regexp, $segment_regexp) =
1402 $self->get_ignore_regexps($package_dir);
1403 debug(5, " Ignore list regexp for paths: " .
1404 (defined $path_regexp ? "/$path_regexp/" : "none"));
1405 debug(5, " Ignore list regexp for segments: " .
1406 (defined $segment_regexp ? "/$segment_regexp/" : "none"));
1407
1408 if (defined $path_regexp and "/$target" =~ $path_regexp) {
1409 debug(4, " Ignoring path /$target");
1410 return 1;
1411 }
1412
1413 (my $basename = $target) =~ s!.+/!!;
1414 if (defined $segment_regexp and $basename =~ $segment_regexp) {
1415 debug(4, " Ignoring path segment $basename");
1416 return 1;
1417 }
1418
1419 debug(5, " Not ignoring $target");
1420 return 0;
1421}
1422
1423sub get_ignore_regexps {
1424 my $self = shift;
1425 my ($dir) = @_;
1426
1427 # N.B. the local and global stow ignore files have to have different
1428 # names so that:
1429 # 1. the global one can be a symlink to within a stow
1430 # package, managed by stow itself, and
1431 # 2. the local ones can be ignored via hardcoded logic in
1432 # GlobsToRegexp(), so that they always stay within their stow packages.
1433
1434 my $local_stow_ignore = join_paths($dir, $LOCAL_IGNORE_FILE);
1435 my $global_stow_ignore = join_paths($ENV{HOME}, $GLOBAL_IGNORE_FILE);
1436
1437 for my $file ($local_stow_ignore, $global_stow_ignore) {
1438 if (-e $file) {
1439 debug(5, " Using ignore file: $file");
1440 return $self->get_ignore_regexps_from_file($file);
1441 }
1442 else {
1443 debug(5, " $file didn't exist");
1444 }
1445 }
1446
1447 debug(4, " Using built-in ignore list");
1448 return @default_global_ignore_regexps;
1449}
1450
1451my %ignore_file_regexps;
1452
1453sub get_ignore_regexps_from_file {
1454 my $self = shift;
1455 my ($file) = @_;
1456
1457 if (exists $ignore_file_regexps{$file}) {
1458 debug(4, " Using memoized regexps from $file");
1459 return @{ $ignore_file_regexps{$file} };
1460 }
1461
1462 if (! open(REGEXPS, $file)) {
1463 debug(4, " Failed to open $file: $!");
1464 return undef;
1465 }
1466
1467 my @regexps = $self->get_ignore_regexps_from_fh(\*REGEXPS);
1468 close(REGEXPS);
1469
1470 $ignore_file_regexps{$file} = [ @regexps ];
1471 return @regexps;
1472}
1473
1474=head2 invalidate_memoized_regexp($file)
1475
1476For efficiency of performance, regular expressions are compiled from
1477each ignore list file the first time it is used by the Stow process,
1478and then memoized for future use. If you expect the contents of these
1479files to change during a single run, you will need to invalidate the
1480memoized value from this cache. This method allows you to do that.
1481
1482=cut
1483
1484sub invalidate_memoized_regexp {
1485 my $self = shift;
1486 my ($file) = @_;
1487 if (exists $ignore_file_regexps{$file}) {
1488 debug(4, " Invalidated memoized regexp for $file");
1489 delete $ignore_file_regexps{$file};
1490 }
1491 else {
1492 debug(2, " WARNING: no memoized regexp for $file to invalidate");
1493 }
1494}
1495
1496sub get_ignore_regexps_from_fh {
1497 my $self = shift;
1498 my ($fh) = @_;
1499 my %regexps;
1500 while (<$fh>) {
1501 chomp;
1502 s/^\s+//;
1503 s/\s+$//;
1504 next if /^#/ or length($_) == 0;
1505 s/\s+#.+//; # strip comments to right of pattern
1506 s/\\#/#/g;
1507 $regexps{$_}++;
1508 }
1509
1510 # Local ignore lists should *always* stay within the stow directory,
1511 # because this is the only place stow looks for them.
1512 $regexps{"^/\Q$LOCAL_IGNORE_FILE\E\$"}++;
1513
1514 return $self->compile_ignore_regexps(%regexps);
1515}
1516
1517sub compile_ignore_regexps {
1518 my $self = shift;
1519 my (%regexps) = @_;
1520
1521 my @segment_regexps;
1522 my @path_regexps;
1523 for my $regexp (keys %regexps) {
1524 if (index($regexp, '/') < 0) {
1525 # No / found in regexp, so use it for matching against basename
1526 push @segment_regexps, $regexp;
1527 }
1528 else {
1529 # / found in regexp, so use it for matching against full path
1530 push @path_regexps, $regexp;
1531 }
1532 }
1533
1534 my $segment_regexp = join '|', @segment_regexps;
1535 my $path_regexp = join '|', @path_regexps;
1536 $segment_regexp = @segment_regexps ?
1537 $self->compile_regexp("^($segment_regexp)\$") : undef;
1538 $path_regexp = @path_regexps ?
1539 $self->compile_regexp("(^|/)($path_regexp)(/|\$)") : undef;
1540
1541 return ($path_regexp, $segment_regexp);
1542}
1543
1544sub compile_regexp {
1545 my $self = shift;
1546 my ($regexp) = @_;
1547 my $compiled = eval { qr/$regexp/ };
1548 die "Failed to compile regexp: $@\n" if $@;
1549 return $compiled;
1550}
1551
1552sub get_default_global_ignore_regexps {
1553 my $class = shift;
1554 # Bootstrap issue - first time we stow, we will be stowing
1555 # .cvsignore so it might not exist in ~ yet, or if it does, it could
1556 # be an old version missing the entries we need. So we make sure
1557 # they are there by hardcoding some crucial entries.
1558
1559 my %regexps;
1560
1561 $regexps{"RCS"}++;
1562 $regexps{".+,v"}++;
1563 $regexps{"CVS"}++;
1564 $regexps{"\.\#.+"}++;
1565 $regexps{"\.cvsignore"}++;
1566 $regexps{"\.svn"}++;
1567 $regexps{"_darcs"}++;
1568 $regexps{"\.hg"}++;
1569 $regexps{"\.git"}++;
1570 $regexps{"\.gitignore"}++;
1571 $regexps{".+~"}++;
1572 $regexps{"\#.*\#"}++;
1573 $regexps{"^/README.*"}++;
1574 $regexps{"^/LICENSE.*"}++;
1575 $regexps{"^/COPYING"}++;
1576
1577 # Local ignore lists should *always* stay within the stow directory,
1578 # because this is the only place stow looks for them.
1579 $regexps{"^/\Q$LOCAL_IGNORE_FILE\E\$"}++;
1580
1581 return $class->compile_ignore_regexps(%regexps);
1582}
1583
1584#===== METHOD ================================================================
1585# Name : defer
1586# Purpose : determine if the given path matches a regex in our defer list
1587# Parameters: $path
1588# Returns : Boolean
1589# Throws : no exceptions
1590# Comments : none
1591#=============================================================================
1592sub defer {
1593 my $self = shift;
1594 my ($path) = @_;
1595
1596 for my $prefix (@{ $self->{defer} }) {
1597 return 1 if $path =~ m/$prefix/;
1598 }
1599 return 0;
1600}
1601
1602#===== METHOD ================================================================
1603# Name : override
1604# Purpose : determine if the given path matches a regex in our override list
1605# Parameters: $path
1606# Returns : Boolean
1607# Throws : no exceptions
1608# Comments : none
1609#=============================================================================
1610sub override {
1611 my $self = shift;
1612 my ($path) = @_;
1613
1614 for my $regex (@{ $self->{override} }) {
1615 return 1 if $path =~ m/$regex/;
1616 }
1617 return 0;
1618}
1619
1620##############################################################################
1621#
1622# The following code provides the abstractions that allow us to defer operating
1623# on the filesystem until after all potential conflcits have been assessed.
1624#
1625##############################################################################
1626
1627#===== METHOD ===============================================================
1628# Name : process_tasks()
1629# Purpose : process each task in the tasks list
1630# Parameters: none
1631# Returns : n/a
1632# Throws : fatal error if tasks list is corrupted or a task fails
1633# Comments : none
1634#============================================================================
1635sub process_tasks {
1636 my $self = shift;
1637
1638 debug(2, "Processing tasks...");
1639
1640 # Strip out all tasks with a skip action
1641 $self->{tasks} = [ grep { $_->{action} ne 'skip' } @{ $self->{tasks} } ];
1642
1643 if (not @{ $self->{tasks} }) {
1644 return;
1645 }
1646
1647 $self->within_target_do(sub {
1648 for my $task (@{ $self->{tasks} }) {
1649 $self->process_task($task);
1650 }
1651 });
1652
1653 debug(2, "Processing tasks... done");
1654}
1655
1656#===== METHOD ===============================================================
1657# Name : process_task()
1658# Purpose : process a single task
1659# Parameters: $task => the task to process
1660# Returns : n/a
1661# Throws : fatal error if task fails
1662# Comments : Must run from within target directory.
1663# : Task involve either creating or deleting dirs and symlinks
1664# : an action is set to 'skip' if it is found to be redundant
1665#============================================================================
1666sub process_task {
1667 my $self = shift;
1668 my ($task) = @_;
1669
1670 if ($task->{action} eq 'create') {
1671 if ($task->{type} eq 'dir') {
1672 mkdir($task->{path}, 0777)
1673 or error("Could not create directory: $task->{path} ($!)");
1674 return;
1675 }
1676 elsif ($task->{type} eq 'link') {
1677 symlink $task->{source}, $task->{path}
1678 or error(
1679 "Could not create symlink: %s => %s ($!)",
1680 $task->{path},
1681 $task->{source}
1682 );
1683 return;
1684 }
1685 }
1686 elsif ($task->{action} eq 'remove') {
1687 if ($task->{type} eq 'dir') {
1688 rmdir $task->{path}
1689 or error("Could not remove directory: $task->{path} ($!)");
1690 return;
1691 }
1692 elsif ($task->{type} eq 'link') {
1693 unlink $task->{path}
1694 or error("Could not remove link: $task->{path} ($!)");
1695 return;
1696 }
1697 }
1698 elsif ($task->{action} eq 'move') {
1699 if ($task->{type} eq 'file') {
1700 # rename() not good enough, since the stow directory
1701 # might be on a different filesystem to the target.
1702 move $task->{path}, $task->{dest}
1703 or error("Could not move $task->{path} -> $task->{dest} ($!)");
1704 return;
1705 }
1706 }
1707
1708 # Should never happen.
1709 internal_error("bad task action: $task->{action}");
1710}
1711
1712#===== METHOD ===============================================================
1713# Name : link_task_action()
1714# Purpose : finds the link task action for the given path, if there is one
1715# Parameters: $path
1716# Returns : 'remove', 'create', or '' if there is no action
1717# Throws : a fatal exception if an invalid action is found
1718# Comments : none
1719#============================================================================
1720sub link_task_action {
1721 my $self = shift;
1722 my ($path) = @_;
1723
1724 if (! exists $self->{link_task_for}{$path}) {
1725 debug(4, " link_task_action($path): no task");
1726 return '';
1727 }
1728
1729 my $action = $self->{link_task_for}{$path}->{action};
1730 internal_error("bad task action: $action")
1731 unless $action eq 'remove' or $action eq 'create';
1732
1733 debug(4, " link_task_action($path): link task exists with action $action");
1734 return $action;
1735}
1736
1737#===== METHOD ===============================================================
1738# Name : dir_task_action()
1739# Purpose : finds the dir task action for the given path, if there is one
1740# Parameters: $path
1741# Returns : 'remove', 'create', or '' if there is no action
1742# Throws : a fatal exception if an invalid action is found
1743# Comments : none
1744#============================================================================
1745sub dir_task_action {
1746 my $self = shift;
1747 my ($path) = @_;
1748
1749 if (! exists $self->{dir_task_for}{$path}) {
1750 debug(4, " dir_task_action($path): no task");
1751 return '';
1752 }
1753
1754 my $action = $self->{dir_task_for}{$path}->{action};
1755 internal_error("bad task action: $action")
1756 unless $action eq 'remove' or $action eq 'create';
1757
1758 debug(4, " dir_task_action($path): dir task exists with action $action");
1759 return $action;
1760}
1761
1762#===== METHOD ===============================================================
1763# Name : parent_link_scheduled_for_removal()
1764# Purpose : determine whether the given path or any parent thereof
1765# : is a link scheduled for removal
1766# Parameters: $path
1767# Returns : Boolean
1768# Throws : none
1769# Comments : none
1770#============================================================================
1771sub parent_link_scheduled_for_removal {
1772 my $self = shift;
1773 my ($path) = @_;
1774
1775 my $prefix = '';
1776 for my $part (split m{/+}, $path) {
1777 $prefix = join_paths($prefix, $part);
1778 debug(4, " parent_link_scheduled_for_removal($path): prefix $prefix");
1779 if (exists $self->{link_task_for}{$prefix} and
1780 $self->{link_task_for}{$prefix}->{action} eq 'remove') {
1781 debug(4, " parent_link_scheduled_for_removal($path): link scheduled for removal");
1782 return 1;
1783 }
1784 }
1785
1786 debug(4, " parent_link_scheduled_for_removal($path): returning false");
1787 return 0;
1788}
1789
1790#===== METHOD ===============================================================
1791# Name : is_a_link()
1792# Purpose : determine if the given path is a current or planned link
1793# Parameters: $path
1794# Returns : Boolean
1795# Throws : none
1796# Comments : returns false if an existing link is scheduled for removal
1797# : and true if a non-existent link is scheduled for creation
1798#============================================================================
1799sub is_a_link {
1800 my $self = shift;
1801 my ($path) = @_;
1802 debug(4, " is_a_link($path)");
1803
1804 if (my $action = $self->link_task_action($path)) {
1805 if ($action eq 'remove') {
1806 debug(4, " is_a_link($path): returning 0 (remove action found)");
1807 return 0;
1808 }
1809 elsif ($action eq 'create') {
1810 debug(4, " is_a_link($path): returning 1 (create action found)");
1811 return 1;
1812 }
1813 }
1814
1815 if (-l $path) {
1816 # Check if any of its parent are links scheduled for removal
1817 # (need this for edge case during unfolding)
1818 debug(4, " is_a_link($path): is a real link");
1819 return $self->parent_link_scheduled_for_removal($path) ? 0 : 1;
1820 }
1821
1822 debug(4, " is_a_link($path): returning 0");
1823 return 0;
1824}
1825
1826#===== METHOD ===============================================================
1827# Name : is_a_dir()
1828# Purpose : determine if the given path is a current or planned directory
1829# Parameters: $path
1830# Returns : Boolean
1831# Throws : none
1832# Comments : returns false if an existing directory is scheduled for removal
1833# : and true if a non-existent directory is scheduled for creation
1834# : we also need to be sure we are not just following a link
1835#============================================================================
1836sub is_a_dir {
1837 my $self = shift;
1838 my ($path) = @_;
1839 debug(4, " is_a_dir($path)");
1840
1841 if (my $action = $self->dir_task_action($path)) {
1842 if ($action eq 'remove') {
1843 return 0;
1844 }
1845 elsif ($action eq 'create') {
1846 return 1;
1847 }
1848 }
1849
1850 return 0 if $self->parent_link_scheduled_for_removal($path);
1851
1852 if (-d $path) {
1853 debug(4, " is_a_dir($path): real dir");
1854 return 1;
1855 }
1856
1857 debug(4, " is_a_dir($path): returning false");
1858 return 0;
1859}
1860
1861#===== METHOD ===============================================================
1862# Name : is_a_node()
1863# Purpose : determine whether the given path is a current or planned node
1864# Parameters: $path
1865# Returns : Boolean
1866# Throws : none
1867# Comments : returns false if an existing node is scheduled for removal
1868# : true if a non-existent node is scheduled for creation
1869# : we also need to be sure we are not just following a link
1870#============================================================================
1871sub is_a_node {
1872 my $self = shift;
1873 my ($path) = @_;
1874 debug(4, " is_a_node($path)");
1875
1876 my $laction = $self->link_task_action($path);
1877 my $daction = $self->dir_task_action($path);
1878
1879 if ($laction eq 'remove') {
1880 if ($daction eq 'remove') {
1881 internal_error("removing link and dir: $path");
1882 return 0;
1883 }
1884 elsif ($daction eq 'create') {
1885 # Assume that we're unfolding $path, and that the link
1886 # removal action is earlier than the dir creation action
1887 # in the task queue. FIXME: is this a safe assumption?
1888 return 1;
1889 }
1890 else { # no dir action
1891 return 0;
1892 }
1893 }
1894 elsif ($laction eq 'create') {
1895 if ($daction eq 'remove') {
1896 # Assume that we're folding $path, and that the dir
1897 # removal action is earlier than the link creation action
1898 # in the task queue. FIXME: is this a safe assumption?
1899 return 1;
1900 }
1901 elsif ($daction eq 'create') {
1902 internal_error("creating link and dir: $path");
1903 return 1;
1904 }
1905 else { # no dir action
1906 return 1;
1907 }
1908 }
1909 else {
1910 # No link action
1911 if ($daction eq 'remove') {
1912 return 0;
1913 }
1914 elsif ($daction eq 'create') {
1915 return 1;
1916 }
1917 else { # no dir action
1918 # fall through to below
1919 }
1920 }
1921
1922 return 0 if $self->parent_link_scheduled_for_removal($path);
1923
1924 if (-e $path) {
1925 debug(4, " is_a_node($path): really exists");
1926 return 1;
1927 }
1928
1929 debug(4, " is_a_node($path): returning false");
1930 return 0;
1931}
1932
1933#===== METHOD ===============================================================
1934# Name : read_a_link()
1935# Purpose : return the source of a current or planned link
1936# Parameters: $path => path to the link target
1937# Returns : a string
1938# Throws : fatal exception if the given path is not a current or planned
1939# : link
1940# Comments : none
1941#============================================================================
1942sub read_a_link {
1943 my $self = shift;
1944 my ($path) = @_;
1945
1946 if (my $action = $self->link_task_action($path)) {
1947 debug(4, " read_a_link($path): task exists with action $action");
1948
1949 if ($action eq 'create') {
1950 return $self->{link_task_for}{$path}->{source};
1951 }
1952 elsif ($action eq 'remove') {
1953 internal_error(
1954 "read_a_link() passed a path that is scheduled for removal: $path"
1955 );
1956 }
1957 }
1958 elsif (-l $path) {
1959 debug(4, " read_a_link($path): real link");
1960 my $target = readlink $path or error("Could not read link: $path ($!)");
1961 return $target;
1962 }
1963 internal_error("read_a_link() passed a non link path: $path\n");
1964}
1965
1966#===== METHOD ===============================================================
1967# Name : do_link()
1968# Purpose : wrap 'link' operation for later processing
1969# Parameters: $oldfile => the existing file to link to
1970# : $newfile => the file to link
1971# Returns : n/a
1972# Throws : error if this clashes with an existing planned operation
1973# Comments : cleans up operations that undo previous operations
1974#============================================================================
1975sub do_link {
1976 my $self = shift;
1977 my ($oldfile, $newfile) = @_;
1978
1979 if (exists $self->{dir_task_for}{$newfile}) {
1980 my $task_ref = $self->{dir_task_for}{$newfile};
1981
1982 if ($task_ref->{action} eq 'create') {
1983 if ($task_ref->{type} eq 'dir') {
1984 internal_error(
1985 "new link (%s => %s) clashes with planned new directory",
1986 $newfile,
1987 $oldfile,
1988 );
1989 }
1990 }
1991 elsif ($task_ref->{action} eq 'remove') {
1992 # We may need to remove a directory before creating a link so continue.
1993 }
1994 else {
1995 internal_error("bad task action: $task_ref->{action}");
1996 }
1997 }
1998
1999 if (exists $self->{link_task_for}{$newfile}) {
2000 my $task_ref = $self->{link_task_for}{$newfile};
2001
2002 if ($task_ref->{action} eq 'create') {
2003 if ($task_ref->{source} ne $oldfile) {
2004 internal_error(
2005 "new link clashes with planned new link: %s => %s",
2006 $task_ref->{path},
2007 $task_ref->{source},
2008 )
2009 }
2010 else {
2011 debug(1, "LINK: $newfile => $oldfile (duplicates previous action)");
2012 return;
2013 }
2014 }
2015 elsif ($task_ref->{action} eq 'remove') {
2016 if ($task_ref->{source} eq $oldfile) {
2017 # No need to remove a link we are going to recreate
2018 debug(1, "LINK: $newfile => $oldfile (reverts previous action)");
2019 $self->{link_task_for}{$newfile}->{action} = 'skip';
2020 delete $self->{link_task_for}{$newfile};
2021 return;
2022 }
2023 # We may need to remove a link to replace it so continue
2024 }
2025 else {
2026 internal_error("bad task action: $task_ref->{action}");
2027 }
2028 }
2029
2030 # Creating a new link
2031 debug(1, "LINK: $newfile => $oldfile");
2032 my $task = {
2033 action => 'create',
2034 type => 'link',
2035 path => $newfile,
2036 source => $oldfile,
2037 };
2038 push @{ $self->{tasks} }, $task;
2039 $self->{link_task_for}{$newfile} = $task;
2040
2041 return;
2042}
2043
2044#===== METHOD ===============================================================
2045# Name : do_unlink()
2046# Purpose : wrap 'unlink' operation for later processing
2047# Parameters: $file => the file to unlink
2048# Returns : n/a
2049# Throws : error if this clashes with an existing planned operation
2050# Comments : will remove an existing planned link
2051#============================================================================
2052sub do_unlink {
2053 my $self = shift;
2054 my ($file) = @_;
2055
2056 if (exists $self->{link_task_for}{$file}) {
2057 my $task_ref = $self->{link_task_for}{$file};
2058 if ($task_ref->{action} eq 'remove') {
2059 debug(1, "UNLINK: $file (duplicates previous action)");
2060 return;
2061 }
2062 elsif ($task_ref->{action} eq 'create') {
2063 # Do need to create a link then remove it
2064 debug(1, "UNLINK: $file (reverts previous action)");
2065 $self->{link_task_for}{$file}->{action} = 'skip';
2066 delete $self->{link_task_for}{$file};
2067 return;
2068 }
2069 else {
2070 internal_error("bad task action: $task_ref->{action}");
2071 }
2072 }
2073
2074 if (exists $self->{dir_task_for}{$file} and $self->{dir_task_for}{$file} eq 'create') {
2075 internal_error(
2076 "new unlink operation clashes with planned operation: %s dir %s",
2077 $self->{dir_task_for}{$file}->{action},
2078 $file
2079 );
2080 }
2081
2082 # Remove the link
2083 debug(1, "UNLINK: $file");
2084
2085 my $source = readlink $file or error("could not readlink $file ($!)");
2086
2087 my $task = {
2088 action => 'remove',
2089 type => 'link',
2090 path => $file,
2091 source => $source,
2092 };
2093 push @{ $self->{tasks} }, $task;
2094 $self->{link_task_for}{$file} = $task;
2095
2096 return;
2097}
2098
2099#===== METHOD ===============================================================
2100# Name : do_mkdir()
2101# Purpose : wrap 'mkdir' operation
2102# Parameters: $dir => the directory to remove
2103# Returns : n/a
2104# Throws : fatal exception if operation fails
2105# Comments : outputs a message if 'verbose' option is set
2106# : does not perform operation if 'simulate' option is set
2107# Comments : cleans up operations that undo previous operations
2108#============================================================================
2109sub do_mkdir {
2110 my $self = shift;
2111 my ($dir) = @_;
2112
2113 if (exists $self->{link_task_for}{$dir}) {
2114 my $task_ref = $self->{link_task_for}{$dir};
2115
2116 if ($task_ref->{action} eq 'create') {
2117 internal_error(
2118 "new dir clashes with planned new link (%s => %s)",
2119 $task_ref->{path},
2120 $task_ref->{source},
2121 );
2122 }
2123 elsif ($task_ref->{action} eq 'remove') {
2124 # May need to remove a link before creating a directory so continue
2125 }
2126 else {
2127 internal_error("bad task action: $task_ref->{action}");
2128 }
2129 }
2130
2131 if (exists $self->{dir_task_for}{$dir}) {
2132 my $task_ref = $self->{dir_task_for}{$dir};
2133
2134 if ($task_ref->{action} eq 'create') {
2135 debug(1, "MKDIR: $dir (duplicates previous action)");
2136 return;
2137 }
2138 elsif ($task_ref->{action} eq 'remove') {
2139 debug(1, "MKDIR: $dir (reverts previous action)");
2140 $self->{dir_task_for}{$dir}->{action} = 'skip';
2141 delete $self->{dir_task_for}{$dir};
2142 return;
2143 }
2144 else {
2145 internal_error("bad task action: $task_ref->{action}");
2146 }
2147 }
2148
2149 debug(1, "MKDIR: $dir");
2150 my $task = {
2151 action => 'create',
2152 type => 'dir',
2153 path => $dir,
2154 source => undef,
2155 };
2156 push @{ $self->{tasks} }, $task;
2157 $self->{dir_task_for}{$dir} = $task;
2158
2159 return;
2160}
2161
2162#===== METHOD ===============================================================
2163# Name : do_rmdir()
2164# Purpose : wrap 'rmdir' operation
2165# Parameters: $dir => the directory to remove
2166# Returns : n/a
2167# Throws : fatal exception if operation fails
2168# Comments : outputs a message if 'verbose' option is set
2169# : does not perform operation if 'simulate' option is set
2170#============================================================================
2171sub do_rmdir {
2172 my $self = shift;
2173 my ($dir) = @_;
2174
2175 if (exists $self->{link_task_for}{$dir}) {
2176 my $task_ref = $self->{link_task_for}{$dir};
2177 internal_error(
2178 "rmdir clashes with planned operation: %s link %s => %s",
2179 $task_ref->{action},
2180 $task_ref->{path},
2181 $task_ref->{source}
2182 );
2183 }
2184
2185 if (exists $self->{dir_task_for}{$dir}) {
2186 my $task_ref = $self->{link_task_for}{$dir};
2187
2188 if ($task_ref->{action} eq 'remove') {
2189 debug(1, "RMDIR $dir (duplicates previous action)");
2190 return;
2191 }
2192 elsif ($task_ref->{action} eq 'create') {
2193 debug(1, "MKDIR $dir (reverts previous action)");
2194 $self->{link_task_for}{$dir}->{action} = 'skip';
2195 delete $self->{link_task_for}{$dir};
2196 return;
2197 }
2198 else {
2199 internal_error("bad task action: $task_ref->{action}");
2200 }
2201 }
2202
2203 debug(1, "RMDIR $dir");
2204 my $task = {
2205 action => 'remove',
2206 type => 'dir',
2207 path => $dir,
2208 source => '',
2209 };
2210 push @{ $self->{tasks} }, $task;
2211 $self->{dir_task_for}{$dir} = $task;
2212
2213 return;
2214}
2215
2216#===== METHOD ===============================================================
2217# Name : do_mv()
2218# Purpose : wrap 'move' operation for later processing
2219# Parameters: $src => the file to move
2220# : $dst => the path to move it to
2221# Returns : n/a
2222# Throws : error if this clashes with an existing planned operation
2223# Comments : alters contents of package installation image in stow dir
2224#============================================================================
2225sub do_mv {
2226 my $self = shift;
2227 my ($src, $dst) = @_;
2228
2229 if (exists $self->{link_task_for}{$src}) {
2230 # I don't *think* this should ever happen, but I'm not
2231 # 100% sure.
2232 my $task_ref = $self->{link_task_for}{$src};
2233 internal_error(
2234 "do_mv: pre-existing link task for $src; action: %s, source: %s",
2235 $task_ref->{action}, $task_ref->{source}
2236 );
2237 }
2238 elsif (exists $self->{dir_task_for}{$src}) {
2239 my $task_ref = $self->{dir_task_for}{$src};
2240 internal_error(
2241 "do_mv: pre-existing dir task for %s?! action: %s",
2242 $src, $task_ref->{action}
2243 );
2244 }
2245
2246 # Remove the link
2247 debug(1, "MV: $src -> $dst");
2248
2249 my $task = {
2250 action => 'move',
2251 type => 'file',
2252 path => $src,
2253 dest => $dst,
2254 };
2255 push @{ $self->{tasks} }, $task;
2256
2257 # FIXME: do we need this for anything?
2258 #$self->{mv_task_for}{$file} = $task;
2259
2260 return;
2261}
2262
2263
2264#############################################################################
2265#
2266# End of methods; subroutines follow.
2267# FIXME: Ideally these should be in a separate module.
2268
2269
2270#===== PRIVATE SUBROUTINE ===================================================
2271# Name : internal_error()
2272# Purpose : output internal error message in a consistent form and die
2273# Parameters: $message => error message to output
2274# Returns : n/a
2275# Throws : n/a
2276# Comments : none
2277#============================================================================
2278sub internal_error {
2279 my ($format, @args) = @_;
2280 my $error = sprintf($format, @args);
2281 my $stacktrace = Carp::longmess();
2282 die <<EOF;
2283
2284$ProgramName: INTERNAL ERROR: $error$stacktrace
2285
2286This _is_ a bug. Please submit a bug report so we can fix it! :-)
2287See http://www.gnu.org/software/stow/ for how to do this.
2288EOF
2289}
2290
2291=head1 BUGS
2292
2293=head1 SEE ALSO
2294
2295=cut
2296
22971;
2298
2299}
2 2300
3use FindBin; 2301use FindBin;
4use lib "$FindBin::Bin"; 2302use lib "$FindBin::Bin";
@@ -418,14 +2716,19 @@ use POSIX qw(getcwd);
418use Getopt::Long; 2716use Getopt::Long;
419 2717
420 2718
421use Stow;
422use Stow qw(parent error);
423
424my $ProgramName = $0; 2719my $ProgramName = $0;
425$ProgramName =~ s{.*/}{}; 2720$ProgramName =~ s{.*/}{};
426 2721
427main() unless caller(); 2722main() unless caller();
428 2723
2724sub parent {
2725 my @path = @_;
2726 my $path = join '/', @_;
2727 my @elts = split m{/+}, $path;
2728 pop @elts;
2729 return join '/', @elts;
2730}
2731
429sub main { 2732sub main {
430 my ($options, $pkgs_to_unstow, $pkgs_to_stow) = process_options(); 2733 my ($options, $pkgs_to_unstow, $pkgs_to_stow) = process_options();
431 2734
@@ -568,7 +2871,7 @@ sub check_packages {
568 for my $package (@$pkgs_to_stow, @$pkgs_to_unstow) { 2871 for my $package (@$pkgs_to_stow, @$pkgs_to_unstow) {
569 $package =~ s{/+$}{}; # delete trailing slashes 2872 $package =~ s{/+$}{}; # delete trailing slashes
570 if ($package =~ m{/}) { 2873 if ($package =~ m{/}) {
571 error("Slashes are not permitted in package names"); 2874 Stow->error("Slashes are not permitted in package names");
572 } 2875 }
573 } 2876 }
574} 2877}