#!/usr/bin/perl -w
# real Perl code begins here
#
# Adapted from Apple's uninstall-devtools.pl (Version 7 for Xcode Tools 1.2)
#
# BSD License: c.f. <http://www.opensource.org/licenses/bsd-license>
#

use strict;
use File::Basename;

use vars qw ($do_nothing $print_donothing_removals $receipts_dir $verbose $noisy_warnings);
use vars qw ($suppress_spin $spin_counter $spin_state $spin_slower_downer);
use vars qw (%exception_list $gen_dirs @gen_files @rmfiles @rmdirs @rmpkg);

#----------------------------------------------------------------------------------------

$do_nothing = 0;
$print_donothing_removals = 1;
$verbose = 1;
$noisy_warnings = 0;

# One of rm -rf in this script uses $receipts_dir -- change with care.
$receipts_dir = "/Library/Receipts";

%exception_list = (
#  '/usr/bin/aklog', '',
);

$gen_dirs = { };
#$gen_dirs->{"/"}->{"Library"}->{"OpenAFS"}->{"Tools"} = {};

@gen_files = (
    "/var/db/openafs/etc/cacheinfo",
    "/var/db/openafs/etc/ThisCell",
    "/var/db/openafs/etc/config/afsd.options",
    "/var/db/openafs/etc/config/afs.conf",
    "/var/db/openafs/etc/CellServDB.save",
    "/var/db/openafs/etc/CellServDB.master.last",
    "/var/db/openafs/etc/CellServDB",
    "/var/db/openafs/etc/config/settings.plist",
);

#----------------------------------------------------------------------------------------

$| = 1;
#if ($do_nothing == 0 && $< != 0)
#  {
#    die "ERROR: Must be run with root permissions--prefix command with 'sudo'.\n";
#  }

sub main
  {
    # commandline args: 
    #     0: dir of packages to remove
    #     1: flag indicating whether to keep package receipts
    #     2: flag indicating whether to supress spin indicator

    if (!@ARGV) {
	use FindBin qw($Bin);
	@ARGV = ("$Bin/..", 0, 0);
    }
    $suppress_spin = defined ($ARGV[2]) && $ARGV[2];

    $spin_counter = 0;
    $spin_state = 0;
    spin_rate_slow ();

    pre_print ();
    print "Uninstalling OpenAFS package:\n\n";

    remove_generated_files ();
    remove_main_packages ();
    remove_generated_directories ();

    if ($do_nothing == 0) {
    my @rmcmd = ('osascript', '-e', "do shell script \"/bin/rm -f @rmfiles; /bin/rmdir @rmdirs; /bin/rm -rf @rmpkg\" with administrator privileges");
    system @rmcmd;
    my $retcode = $? >> 8;
    if ($retcode != 0) {
           print_warning ("Warning:  There may have been a problem uninstalling\n");
    }
    }

    pre_print ();
    print "\nFinished uninstalling.\n";
  }

sub remove_main_packages
  {
#    opendir (DIR, $ARGV[0]) or die "ERROR: package directory $ARGV[0] not found\n";
#    my @pkglist = grep /\.pkg$/, readdir DIR;
#    closedir DIR;
     my @pkglist = ("org.openafs.OpenAFS-debug",
		    "org.openafs.OpenAFS",
		    "OpenAFS",
		    "OpenAFS-debug-extension",
		    );
    
    foreach (@pkglist)
      {
        s/\.pkg$//;
        my $pkgname = $_;
	my $bompath = undef;
	my $infopath = undef;
        my $pkgname_plist_newpath = "/var/db/receipts/$pkgname.pkg.plist";
	    my $pkgname_bom_newpath = "/var/db/receipts/$pkgname.pkg.bom";
        my $pkgname_plist_fullpath = "/Library/Receipts/$pkgname.pkg/Contents/Info.plist";
        my $pkgname_bom_fullpath = "/Library/Receipts/$pkgname.pkg/Contents/Archive.bom";

	if (-f $pkgname_plist_newpath && -r $pkgname_plist_newpath && -f $pkgname_bom_newpath && -r $pkgname_bom_newpath) {
	    print_verbose ("Removing package $pkgname.pkg\n");
	    $bompath = $pkgname_bom_newpath;
	    $infopath = $pkgname_plist_newpath;
	} else {
	    if (-f $pkgname_plist_fullpath && -r $pkgname_plist_fullpath && -f $pkgname_bom_fullpath && -r $pkgname_bom_fullpath) {
		print_verbose ("Removing package $pkgname.pkg\n");
		$bompath = $pkgname_bom_fullpath;
		$infopath = $pkgname_plist_fullpath;
	    } else {
		next;
	    }
	}

        next if (!defined ($bompath));
        
        my $bomroot = "";
        open (INFO, "$infopath") or next;
        while (<INFO>) {
            m/IFPkgFlagDefaultLocation/ or next;
            $bomroot = <INFO>;
        }
        close (INFO);

        $bomroot =~ />(.*)</;
	$bomroot = $1;
        $bomroot =~ s/^\/$//;

        spin_rate_slow ();

        open (LSBOM, "/usr/bin/lsbom -l -f -p f '$bompath' |") or next;
        while (<LSBOM>)
          {
            chomp;
            m#^\.(/.*)$#;
            next if (!defined ($1) || $1 eq "");
            my $filename = $bomroot . $1;

            remove_a_file ($filename);
          }
        close (LSBOM);

        my $rooth = { };

        open (LSBOM, "/usr/bin/lsbom -d -p f '$bompath' |") or next;
        while (<LSBOM>)
          {
            chomp;
            m#^\.(/.*)$#;
            next if (!defined ($1) || $1 eq "");
            my $directory = $bomroot . $1;
            if (-d $directory)
              {
                $rooth = add_directory_to_tree ($directory, $rooth);
              }
            else
              {
                if ($noisy_warnings)
                  {
                    print_warning ("Warning: \"$directory\" listed in BOM but not present on system.\n");
                  }
              }
          }
        close (LSBOM);

        spin_rate_fast ();
        remove_empty_directories ($rooth, "/");

	if (-d "$receipts_dir/$pkgname.pkg" ) {
	    remove_package_receipts ("$pkgname.pkg") if (!defined ($ARGV[1]) || !$ARGV[1]) ;
	} else {
	    unshift(@rmfiles, "$bompath");
	    unshift(@rmfiles, "$infopath");
       	}
      }
  }

sub remove_generated_files
  {
   foreach (@gen_files)
     {
       remove_a_file ($_);
     }
  }

sub remove_generated_directories
  {
    remove_empty_directories ($gen_dirs, "/");
  }

sub add_directory_to_tree
  {
    my $dir = shift;
    my $rooth = shift;
    my $p = $rooth;

    my @pathcomp = split /\//, $dir;

    progress_point ();
    foreach (@pathcomp)
      {
        my $cur_name = $_;
        if ($cur_name eq "" || !defined ($cur_name))
          {
            $cur_name = "/";
          }
        if (!defined ($p->{"$cur_name"}))
          {
            $p->{$cur_name} = { };
          }
        $p = $p->{$cur_name};
      }
    return $rooth;
  }

sub remove_empty_directories
  {
    my $rooth = shift;
    my $path = shift;
    my $children = (scalar (keys %{$rooth}));
    my $dirs_remain = 0;

    if ($children > 0)
      {
        foreach my $dirname (sort keys %{$rooth})
          {
            my $printpath;
            $printpath = "$path/$dirname";
            $printpath =~ s#^/*#/#;
            remove_empty_directories ($rooth->{$dirname}, "$printpath");
            $dirs_remain = 1 if (-d "$printpath");
          } 
      }

     if ($dirs_remain == 0)
       {
         maybe_remove_ds_store ("$path");
       }

     remove_a_dir ("$path");
  }

sub remove_a_file
  {
    my $fn = shift;
    my $dirname = dirname ($fn);
    my $basename = basename ($fn);
    my $ufs_rsrc_file = "$dirname/._$basename";

    progress_point ();
    return if (!defined ($fn) || $fn eq "");

    # Leave any files that are shared between packages alone.
    if (defined($exception_list{$fn}))
      {
        if ($noisy_warnings)
          {
            print_warning ("Warning: file \"$fn\" intentionally not removed, even though it's in the BOM.\n");
          }
        return;
      }

    if (! -f $fn && ! -l $fn)
      {
        if ($noisy_warnings)
          {
            print_warning ("Warning: file \"$fn\" present in BOM but not found on disc.\n");
          }
        return;
      }

    if ($do_nothing == 1) 
      {
        print_donothing ("rm $fn\n");
        print_donothing ("rm $ufs_rsrc_file\n") if ( -f $ufs_rsrc_file);
      }
    else
      {
	  unshift(@rmfiles, "$fn");
	  unshift(@rmfiles, "$ufs_rsrc_file") if ( -f $ufs_rsrc_file);
      }
  }

sub remove_a_dir
  {
    my $dir = shift;

    progress_point ();
    return if (!defined ($dir) || $dir eq "" || $dir eq "/" || $dir eq "/usr");
    if (! -d $dir)
      {
        if ($noisy_warnings)
          {
            print_warning ("Warning: directory \"$dir\" present in BOM but not found on disc.\n");
          }
        return;
      }

    if ($do_nothing == 1) 
      {
        print_donothing ("rmdir $dir\n");
      }
    else
      {
        push(@rmdirs, "$dir");
      }
  }

sub remove_package_receipts
  {
    my $pkgname = shift;
    $pkgname =~ s#/##g;  # There shouldn't be any path seps in the pkg name...
    return if (!defined ($pkgname) || $pkgname eq "" 
               || $pkgname eq "." || $pkgname eq "..");

    my $pkgdir = "$receipts_dir/$pkgname";
    return if (!defined ($pkgdir) || $pkgdir eq "" || ! -d $pkgdir);

    push(@rmpkg, "$pkgdir");
  }


sub maybe_remove_ds_store
  {
    my $path = shift;
    my $filecount = 0;
    return if (!defined ($path) || $path eq "" || $path eq "/" || $path eq "/usr");
    return if (! -f "$path/.DS_Store");

    open (LS, "/bin/ls -a '$path' |");
    while (<LS>)
      {
        chomp;
        next if (m#^\.$# || m#^\.\.$#);
        $filecount++;
      }
    close (LS);

    if ($filecount == 1 && -f "$path/.DS_Store")
      {
        remove_a_file ("$path/.DS_Store");
      }
  }

sub print_donothing
  {
    my $msg = shift;
    return if ($print_donothing_removals != 1);
    pre_print ();
    print $msg;
  }

sub print_verbose
  {
    my $msg = shift;
    return if ($verbose != 1);
    pre_print ();
    print $msg;
  }

sub print_warning
  {
    my $msg = shift;
    pre_print ();
    print STDERR $msg;
  }

sub print_error
  {
    my $msg = shift;
    pre_print ();
    print STDERR $msg;
  }

sub pre_print 
  {
    print " " unless ($suppress_spin);
  }

sub spin_rate_slow
  {
    $spin_slower_downer = 150;
  }

sub spin_rate_fast
  {
    $spin_slower_downer = 75;
  }

sub progress_point
  {
    return if ($suppress_spin);
    $spin_counter++;
    if (($spin_counter % $spin_slower_downer) == 0)
      {
        my $spin_chars = "|/-\\";
        my $c = substr ($spin_chars, $spin_state % 4, 1);
        $spin_state++;
        print "[7m$c[m";
      }
  }

main ();

#----------------------------------------------------------------------------------------
