+#! /usr/bin/perl
+
+use strict;
+use warnings;
+
+use Getopt::Std;
+use Cwd;
+
+use constant FILENAME=>".cvslinks";
+use constant ENTRIES=>"CVS/Entries";
+
+
+our($opt_u,$opt_c,$opt_d,$opt_r,$opt_R,$opt_l,$opt_v);
+getopts "ucdrRlv";
+
+die "-u (update), -c (commit) or -R (delete) required" if !$opt_u && !$opt_c && !$opt_d;
+die "-r (recursive)/-R (recursive w/o CVS) and -l (local) are conflicting"
+ if ($opt_r || $opt_R) && $opt_l;
+
+$opt_r=1 if !$opt_r && !$opt_R && !$opt_l; # default
+
+body();
+exit 0;
+
+sub body
+{
+ do_u() if $opt_u;
+ do_c() if $opt_c;
+ do_d() if $opt_d;
+ do_r() if $opt_r;
+ do_R() if $opt_R;
+}
+
+sub verbose
+{
+my($msg)=@_;
+
+ return if !$opt_v;
+ print cwd().": $msg\n";
+}
+
+sub do_d
+{
+ local *D;
+ opendir D,"." or die "Cannot open directory \".\": $!";
+ for (readdir D) {
+ next if ! -l;
+ verbose "Deleting local link $_";
+ unlink;
+ }
+ closedir D;
+}
+
+sub do_u
+{
+ local *L;
+ open L,FILENAME or die "File ".FILENAME." cannot be opened: $!";
+ do_d();
+ verbose "Creating links from ".FILENAME;
+ while (<L>) {
+ chomp;
+ next if /^$/; # empty-file (->empty-dir) stub line
+ /^(.+)\t(.+)$/ or warn "Unrecognized line: $_";
+ verbose "Creating link $1";
+ symlink $2,$1 or warn "symlink(\"$2\"->\"$1\"): $!";
+ }
+ close L;
+}
+
+sub do_c
+{
+ local(*L,*D);
+ opendir D,"." or die "Cannot open directory \".\": $!";
+ open L,">".FILENAME or die "File ".FILENAME." cannot be created: $!";
+ verbose "Storing links to ".FILENAME;
+ print L "\n"; # empty-file (->empty-dir) stub line
+ for (readdir D) {
+ next if ! -l;
+ my $target=readlink or die "Cannot read link $_: $!";
+ verbose "Storing link $_";
+ print L "$_\t$target\n";
+ }
+ closedir D;
+}
+
+sub descent
+{
+my($dir)=@_;
+
+ if (!chdir $dir) {
+ warn "Cannot chdir to $dir: $!";
+ return;
+ }
+ verbose "Descented to child directory";
+ body();
+ chdir ".." or die "Cannot return back to ..: $!";
+ verbose "Back in parent directory";
+}
+
+sub do_r
+{
+ local *C;
+ if (!open C,ENTRIES) {
+ warn "Cannot open file \"".ENTRIES."\": $!";
+ return;
+ }
+ while (<C>) {
+ chomp;
+ next if !m#^D/([^/]*)/#;
+ descent($1);
+ }
+ close C;
+}
+
+sub do_R
+{
+ local *D;
+ opendir D,"." or die "Cannot open directory \".\": $!";
+ for (readdir D) {
+ # Beware of symlinked-directories !
+ next if -l || ! -d || $_ eq "." || $_ eq "..";
+ descent($_);
+ }
+ closedir D;
+}