#! /usr/bin/perl -w

# Copyright (c) 2002-2005 Apple Computer, Inc

# Remove the receipts of a Developer Tools installation so a subsequent
# installation of an older developer tools release will work correctly.

# Version 17 Xcode Tools 2.1

### This script will remove

#  1. The package receipts for Dec2001DevToolsExtra, 
#     Apr2002DevToolsExtra, Aug2002DevTools10.2Update, Oct2002nibtool,
#     Dec2002DevToolsExtras, June2003DevToolsExtras, 2005-06XcodeExtras.
#  2. The version.plist files in vmutils.framework,
#     AppleScriptKit.framework from Apr2002DevToolsExtra and
#     Graphite.framework from the June2003DevToolsExtras.
#  3. All files and receipts for all other Dev* packages.
#  4. A few odds and ends that aren't explicitly listed in
#     the BOMs (post-install symlinks, help index files,
#     gdb cached symfiles).

use strict;
use File::Basename;

use vars qw ($do_nothing $print_donothing_removals $receipts_dir $verbose $noisy_warnings $debug);
use vars qw ($suppress_spin $spin_counter $spin_state $spin_slower_downer);
use vars qw (%exception_list);

# The only setting that most people would ever set is $do_nothing = 1,
# which will make the script print a list of files/directories/receipts
# it would remove, instead of doing anything.

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

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

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

# This is a list of files that are in dev tools packages but also in other
# packages. Don't remove these.
# EOCocoa.h is installed by server pieces.

# TO DO:
# Next time around, I am going to store the duplicated package instead of the null string,
# and then look for that receipt. If I don't find it, then I will remove the file. But
# that is going to have to wait.
#
# Note that WebObjectsRuntime has duplicated content with ApplicationsServer. Rather than account for all
# of the files, I am just not going to remove the content of WebObjectsRuntime if ApplicationsServer is there.

%exception_list = (
  '/Library/Documentation/Acknowledgements.rtf', '',
  '/System/Library/Frameworks/JavaEOCocoa.framework/Versions/A/Headers/EOCocoa.h', '',
  '/System/Library/Frameworks/JavaEOCocoa.framework/Versions/A/Headers/EOInitializer.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Headers/JDWP.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Headers/JDWPCommands.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Headers/jni.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Headers/jni_md.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Headers/jvmdi.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Headers/jvmpi.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Libraries/libdt_local.jnilib', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.3.1/Libraries/libdt_socket.jnilib', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.4.1/Headers/AWTCocoaComponent.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.4.1/Headers/JDWP.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.4.1/Headers/JDWPCommands.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.4.1/Headers/jawt.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.4.1/Headers/jawt_md.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.4.1/Headers/jni.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.4.1/Headers/jni_md.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.4.1/Headers/jvmdi.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.4.1/Headers/jvmpi.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.4.1/Headers/typedefs_md.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/1.4.1/Home/src.jar', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/AWTCocoaComponent.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/JDWP.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/JDWPCommands.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/JavaVM.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/NSJavaConfiguration.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/NSJavaVirtualMachine.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/jawt.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/jawt_md.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/jni.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/jni_md.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/jvmdi.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/jvmpi.h', '',
  './System/Library/Frameworks/JavaVM.framework/Versions/A/Resources/MacOS/JavaApplicationStub', '',
  '/usr/bin/tar', '',
  '/usr/bin/gnutar', '',
  '/usr/share/man/man1/tar.1', '',
  '/usr/share/man/man1/gnutar.1', '',
  '/usr/share/locale/pt_BR/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/da/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/ja/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/pt/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/sv/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/de/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/fr/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/cs/LC_MESSAGES/tar.mo', '',
  '/usr/libexec/gnurmt', '',
  '/usr/share/locale/nl/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/it/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/ko/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/ru/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/no/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/id/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/es/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/tr/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/pl/LC_MESSAGES/tar.mo', '',
  '/usr/share/locale/et/LC_MESSAGES/tar.mo', '',
  '/usr/share/info/tar.info', '',
  '/usr/share/info/tar.info-1', '',
  '/usr/share/info/tar.info-2', '',
  '/usr/share/info/tar.info-3', '',
  '/usr/share/info/tar.info-4', '',
  '/usr/share/info/tar.info-5', '',
  '/usr/share/info/tar.info-6', '',
  '/usr/share/info/tar.info-7', '',
  '/usr/share/info/tar.info-8', '',
);


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

sub main
  {
    remove_generated_files ();
    remove_a_couple_plist_files ();
    remove_main_packages ();
    remove_2005_06_package ();
    remove_apr_2004_package ();
    remove_dec_2003_package ();
    remove_june_2003_package ();
    remove_dec_2002_package ();
    remove_oct_2002_package ();
    remove_aug_2002_internal_package ();
    remove_aug_2002_package ();
    remove_apr_2002_package ();
    remove_dec_2001_package ();
    remove_2005_06AutomatorUpdate_package ();
    remove_generated_directories ();
    chud_removal_cleanup ();

    pre_print ();
    print "Finished uninstalling.\n";
    print "IMPORTANT:  If you are going to install a previous version of the\n";
    print "Developer Tools, be sure to restart the machine after installing.\n";
  }

sub remove_main_packages
  {

   # We changed the package name to 'DeveloperTools' rather than 'DevTools',
   # so try removing them both.

    my @pkglist = ('DevTools',
		   'DeveloperTools',
		   'DevSDK', 
		   'DevDocumentation',
           'DevExamples', 
           'BSDSDK',
		   'X11SDK',
           'DevPBWO',
		   'MacOSX10.1',
		   'MacOSX10.2',
		   'MacOSX10.2.8',
		   'MacOSX10.3',
		   'MacOSX10.3.2',
		   'MacOSX10.3.9',
		   'MacOSX10.4',
		   'gcc2.95.2',
		   'gcc3.1',
		   'gcc3.3',
		   'gcc3.5',
		   'gcc4.0',
		   'JavaApplicationServersDev',
		   'ApplicationsServerDev',
		   'Dec2002gccUpdater',
		   'August2003gccUpdater',
		   'xcodeupdate1.0.1',
		   'XcodeUpdate1.0.2',
		   'XcodeUpdate1.1',
		   'Dec2003gccLongBranchTools',
		   'April2004LibgmallocPreview',
		   'FireWireSDK',
		   'BluetoothSDK',
		   'JavaSDK',
		   'OpenGLSDK',
		   'QuickTimeSDK',
		   'WebKitSDK',
		   'CHUD',
		   'CoreAudioSDK',
		   'Java14Documentation',
		   'Java14Tools',
		   'Java15Documentation',
		   'Java15Tools',
		   'WebObjectsDevelopment',
		   'WebObjectsDocumentation',
		   'WebObjectsExamples',
		   'WebObjectsRuntime');
    
    foreach (@pkglist)
      {
        my $pkgname = $_;
        my $pkgname_fullpath = "$receipts_dir/$pkgname.pkg";
        my $pkgname_bom_fullpath = undef;

        print_verbose ("REMOVING PACKAGE $pkgname.pkg\n");

        next if (! -d "$pkgname_fullpath" );

	# WebObjectsRuntime and ApplciationsServer have shared content. Rather than try to keep
	# an up-to-date list of file that are shared, remove only the receipt for WebObjectsRuntime
	# if ApplicationsServer is present.
	
	if ( $pkgname eq "WebObjectsRuntime" && -d "$receipts_dir/ApplicationsServer.pkg")
	  {
		print_verbose ("REMOVING PACKAGE WebObjectsRuntime.pkg if present\n");
		remove_package_receipts ( "WebObjectsRuntime.pkg" );
		next;
	  }
	  
        foreach my $f ("$pkgname_fullpath/Contents/Resources/$pkgname.bom",
                       "$pkgname_fullpath/Contents/Archive.bom",
                       "$pkgname_fullpath/Contents/Resources/Archive.bom") {
            if (-f $f && -r $f) {
                $pkgname_bom_fullpath = $f;
                last;
            }
        }

        next if (!defined ($pkgname_bom_fullpath));

#
# First, get list of files in BOM, remove them
#
        spin_rate_slow ();

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

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

#
# Second, get list of directories in BOM, remove them.
#
# Directories are only removed if they're empty.  Directories are only
# empty when all their subdirectories are removed.  So we need to build
# an internal representation of the directory hierarchy and remove it
# depth-first.
#
        my $rooth = { };

        open (LSBOM, "/usr/bin/lsbom -d -p f '$pkgname_bom_fullpath' |") or next;
        while (<LSBOM>)
          {
            chomp;
            m#^\.(/.*)$#;
            my $directory = $1;
            next if (!defined ($directory) || $directory eq "");
            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, "/");

#
# Third, remove package receipts from the system.
#
        remove_package_receipts ("$pkgname.pkg");
      }
  }

sub remove_2005_06_package
  {
    print_verbose ("REMOVING PACKAGE 2005-06XcodeExtras.pkg if present\n");
    remove_package_receipts ("2005-06XcodeExtras.pkg");
  }

sub remove_apr_2004_package
  {
    print_verbose ("REMOVING PACKAGE Apr2004XcodeToolsExtras.pkg if present\n");
    remove_package_receipts ("Apr2004XcodeToolsExtras.pkg");
  }
    

sub remove_dec_2003_package
  {
    print_verbose ("REMOVING PACKAGE Dec2003DevToolsExtras.pkg if present\n");
    remove_package_receipts ("Dec2003DevToolsExtras.pkg");
  }

sub remove_june_2003_package
  {
    print_verbose ("REMOVING PACKAGE June2003DevToolsExtras.pkg if present\n");
    remove_package_receipts ("June2003DevToolsExtras.pkg");
  }

sub remove_dec_2002_package
  {
    print_verbose ("REMOVING PACKAGE Dec2002DevToolsExtras.pkg if present\n");
    remove_package_receipts ("Dec2002DevToolsExtras.pkg");
  }

sub remove_oct_2002_package
  {
    print_verbose ("REMOVING PACKAGE Oct2002nibtool.pkg if present\n");
    remove_package_receipts ("Oct2002nibtool.pkg");
  }

sub remove_aug_2002_internal_package
  {
#    print_verbose ("REMOVING PACKAGE Internal August 2002 Dev Tools 10.2 Update.pkg if present\n");
    remove_package_receipts ("Internal August 2002 Dev Tools 10.2 Update.pkg");
  }

sub remove_aug_2002_package
  {
    print_verbose ("REMOVING PACKAGE Aug2002DevTools10.2Update.pkg if present\n");
    remove_package_receipts ("Aug2002DevTools10.2Update.pkg");
  }

sub remove_apr_2002_package
  {
    print_verbose ("REMOVING PACKAGE Apr2002DevToolsExtras.pkg if present\n");
    remove_package_receipts ("Apr2002DevToolsExtras.pkg");
  }

sub remove_dec_2001_package
  {
    print_verbose ("REMOVING PACKAGE Dec2001DevToolsExtras.pkg if present\n");
    remove_package_receipts ("Dec2001DevToolsExtras.pkg");
  }

sub remove_2005_06AutomatorUpdate_package
  {
    print_verbose ("REMOVING PACKAGE 2005-06AutomatorUpdate.pkg if present\n");
    remove_package_receipts ("2005-06AutomatorUpdate.pkg");
  }
  
# The CHUD tools install a kext. If we don't force a refresh of the kext cache, 
# the system might panic when we reboot. To force a refresh, we make the 
# directory newer than the cache.

sub chud_removal_cleanup
  {
      my $extensions = '/System/Library/Extensions';
      my @files_to_remove = 
      (

    	"$extensions/CHUDProf.kext/Contents/Info.plist",
	"$extensions/CHUDProf.kext/Contents/MacOS/CHUDProf_Universal",
	"$extensions/CHUDProf.kext/Contents/version.plist",
	"$extensions/CHUDUtils.kext/Contents/Info.plist",
	"$extensions/CHUDUtils.kext/Contents/MacOS/CHUDUtils_Universal",
	"$extensions/CHUDUtils.kext/Contents/version.plist",
      );
      foreach (@files_to_remove)
	{
	  remove_a_file ($_);
	}
      my $rooth = { };
      $rooth->{"/"}->{"System"}->{"Library"}->{"Extensions"}->{"CHUDProf.kext"}->{"Contents"}->{"MacOS"} = {};
      $rooth->{"/"}->{"System"}->{"Library"}->{"Extensions"}->{"CHUDUtils.kext"}->{"Contents"}->{"MacOS"} = {};

      remove_empty_directories ($rooth, "/");

  
    if ($do_nothing == 1) 
      {
        print_donothing ("touch /System/Library/Extensions\n");
      }
    else
      {
	system("touch /System/Library/Extensions");
      }
  }
  
# If we're uninstalling the June2003 tools release, remove the version.plist
# files for these frameworks.

sub remove_a_couple_plist_files
  {
    my @files_to_remove = 
      (
        "/System/Library/PrivateFrameworks/Graphite.framework/Versions/A/Resources/version.plist",
        "/System/Library/PrivateFrameworks/vmutils.framework/Versions/A/Resources/version.plist",
        "/System/Library/Frameworks/AppleScriptKit.framework/Versions/A/Resources/version.plist"
      );

# We want to leave these two frameworks on the disk,
# but we want to remove the plists so installation of an older
# devtools release won't get a version mismatch from the installer.

   return if ( ! -d "$receipts_dir/June2003DevToolsExtras.pkg");

   foreach (@files_to_remove)
     {
       remove_a_file ($_);
     }
  }

sub remove_generated_files
  {
    
    my $develdoc ='/Developer/Documentation/Help/Developer Help Viewer';
    my $astlinks = '/Developer/Applications/AppleScript Studio';
    my $frameworks = '/System/Library/Frameworks';
    my $privateframeworks = '/System/Library/PrivateFrameworks';

    my @files_to_remove = 
      (
        "$develdoc/pbHelpIndex.cstore/control",
        "$develdoc/pbHelpIndex.cstore/strings",
        "$develdoc/AppleRefList",
        "$astlinks/About AppleScript Studio",
        "$astlinks/Documentation",
        "$astlinks/Documentation (HTML)",
        "$astlinks/Documentation (PDF)",
        "$astlinks/Examples",
        "$astlinks/Watson Tutorial Project",
        "$astlinks/Mail Search Tutorial Project",
        "$frameworks/AppKit.framework/Versions/C/Headers/AppKit-gcc3.p",
        "$frameworks/ApplicationServices.framework/Versions/A/Headers/ApplicationServices-gcc3.p",
        "$frameworks/Carbon.framework/Versions/A/Headers/Carbon-gcc3.p",
        "$frameworks/Cocoa.framework/Versions/A/Headers/Cocoa-gcc3.p",
        "$frameworks/CoreServices.framework/Versions/A/Headers/CoreServices-gcc3.p",
        "$frameworks/Foundation.framework/Versions/C/Headers/Foundation-gcc3.p",
        "/usr/include/libc-gcc3.p",
        "/usr/include/unistd-gcc3.p",
	"/usr/include/mach/mach-gcc3.p",
        "/Applications/AppleScript/AppleScript Studio",
        "/Developer/Documentation/Cocoa/Reference/NIAccess",
        "/Developer/Documentation/Cocoa/Reference/NIInterface",
#
# The following two aren't generated files, but they are a part of the
# June2003DevToolsExtras package that we specifically want to remove from
# the system.
#
	"$privateframeworks/ZeroLink.framework/Versions/A/Resources/libobjc.A.dylib",
	"$privateframeworks/ZeroLink.framework/Versions/A/Resources/libobjc.dylib",
      );
   foreach (@files_to_remove)
     {
       remove_a_file ($_);
     }

   remove_cached_syms ();
   remove_java14_reference_docs();
   remove_java15_reference_docs();
  }

# These two directories contain files created by a post-install script
# or by the PB help viewer the first time it runs.  Remove them explicitly.

sub remove_generated_directories
  {
    my $rooth = { };

    $rooth->{"/"}->{"Developer"}->{"Applications"}->{"AppleScript Studio"} = {};
    $rooth->{"/"}->{"Developer"}->{"Documentation"}->{"Help"}->{"Developer Help Viewer"}->{"pbHelpIndex.cstore"} = {};
    $rooth->{"/"}->{"System"}->{"Library"}->{"Frameworks"}->{"AppKit.framework"}->{"Versions"}->{"C"}->{"Headers"} = {};
    $rooth->{"/"}->{"System"}->{"Library"}->{"Frameworks"}->{"ApplicationServices.framework"}->{"Versions"}->{"A"}->{"Headers"} = {};
    $rooth->{"/"}->{"System"}->{"Library"}->{"Frameworks"}->{"Carbon.framework"}->{"Versions"}->{"A"}->{"Headers"} = {};
    $rooth->{"/"}->{"System"}->{"Library"}->{"Frameworks"}->{"Cocoa.framework"}->{"Versions"}->{"A"}->{"Headers"} = {};
    $rooth->{"/"}->{"System"}->{"Library"}->{"Frameworks"}->{"CoreServices.framework"}->{"Versions"}->{"A"}->{"Headers"} = {};
    $rooth->{"/"}->{"System"}->{"Library"}->{"Frameworks"}->{"Foundation.framework"}->{"Versions"}->{"C"}->{"Headers"} = {};
    $rooth->{"/"}->{"usr"}->{"libexec"}->{"gdb"} = {};
    remove_empty_directories ($rooth, "/");
  }

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 print_tree
  {
    my $rooth = shift;
    my $path = shift;
    my $p = $rooth;

    if (defined ($p))
      {
        foreach my $dirname (sort keys %{$p})
          {
            my $printpath;
            $printpath = "$path/$dirname";
            $printpath =~ s#^/*#/#;
            print_debug ("$printpath\n");
            print_tree ($p->{$dirname}, "$printpath");
          }
      }
  }

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 we've removed all the children, and there's a .DS_Store file, remove
# it if it's last file sitting around.
     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 all of /usr/share/terminfo alone.
# Leave PackageMaker.app along
# Leave any files that are shared between packages alone.
    if (($fn =~ m#^/usr/share/terminfo/#) || defined($exception_list{$fn}) || ($fn =~ m#PackageMaker.app#))
      {
        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\" not found on disc.  And I was so looking forward to removing it.\n");
          }
        return;
      }

    if ($do_nothing == 1) 
      {
        print_donothing ("rm $fn\n");
        print_donothing ("rm $ufs_rsrc_file\n") if ( -f $ufs_rsrc_file);
      }
    else
      {
        if ((unlink $fn) != 1)
          {
            print_error ("ERROR: Encountered error while trying to remove \"$fn\"\n");
          }
        if (-f $ufs_rsrc_file && ((unlink ($ufs_rsrc_file)) != 1))
          {
            print_error ("ERROR: Encountered error while trying to remove \"$ufs_rsrc_file\"\n");
          }
      }
  }

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\" not found on disc.  And I was so looking forward to removing it.\n");
          }
        return;
      }

    if ($do_nothing == 1) 
      {
        print_donothing ("rmdir $dir\n");
      }
    else
      {
        rmdir $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);

    my @rmcmd = ('/bin/rm', '-rf', "$pkgdir");
    if ($do_nothing == 1)
      {
        print_donothing ("rm -rf $pkgdir\n");
      }
    else
      {
        system @rmcmd;
        my $retcode = $? >> 8;
        if ($retcode != 0)
          {
            print_warning ("Warning:  There may have been a problem removing \"$pkgdir\"\n");
          }
      }
  }

sub remove_cached_syms
  {
    my @rmcmd = ('/bin/rm', '-rf', "/usr/libexec/gdb/symfiles");
    if ($do_nothing == 1)
      {
        print_donothing ("rm -rf /usr/libexec/gdb/symfiles\n");
      }
    else
      {
        system @rmcmd;
        my $retcode = $? >> 8;
        if ($retcode != 0)
          {
            print_warning ("Warning:  There may have been a problem removing \"/usr/libexec/gdb/symfiles\"\n");
          }
      }
  }

sub remove_java14_reference_docs
  {
    my @rmcmd = ('/bin/rm', '-rf', "/System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Resources/Documentation/Reference", "/Developer/ADC Reference Library/documentation/Java/Reference/1.4.2");

    if ($do_nothing == 1)
      {
        print_donothing ("rm -rf /System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Resources/Documentation/Reference\n");
      }
    else
      {
        system @rmcmd;
        my $retcode = $? >> 8;
        if ($retcode != 0)
          {
            print_warning ("Warning:  There may have been a problem removing \"/System/Library/Frameworks/JavaVM.framework/Versions/1.4.2/Resources/Documentation/Reference\"\n");
          }
      }
  }

sub remove_java15_reference_docs
  {
    my @rmcmd = ('/bin/rm', '-rf', "/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Resources/Documentation/Reference");

    if ($do_nothing == 1)
      {
        print_donothing ("rm -rf /System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Resources/Documentation/Reference\n");
      }
    else
      {
        system @rmcmd;
        my $retcode = $? >> 8;
        if ($retcode != 0)
          {
            print_warning ("Warning:  There may have been a problem removing \"/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Resources/Documentation/Reference\"\n");
          }
      }
  }

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;
    pre_print ();
    return if ($print_donothing_removals != 1);
    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 print_debug
  {
    my $msg = shift;
    pre_print ();
    print $msg;
  }

sub pre_print 
  {
    print " " if ($suppress_spin != 0);
  }

sub spin_rate_slow
  {
    $spin_slower_downer = 150;
  }

sub spin_rate_fast
  {
    $spin_slower_downer = 75;
  }

sub progress_point
  {
    return if ($suppress_spin != 0);
    $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 ();
