#!/usr/bin/perl
# I know lowercase names are reserved for pragma's, but other programs do this
# such as perlbrew and dzil. It makes loading this program for testing very easy
# with C<use lib 'bin'; require fetchware; fetchware->import();>, and it
# bypasses a limitation in dzil regarding creating the POD properly.

package fetchware;
our $VERSION = '1.016'; # VERSION: generated by DZP::OurPkgVersion
# ABSTRACT: Fetchware is a package manager for source code distributions.
use strict;
use warnings;

# Enable Perl 6 knockoffs, and use 5.10.1, because smartmatching and other
# things in 5.10 were changed in 5.10.1+.
use 5.010001;

# Must change umask to prevent group and other from having writer permissions to
# files created by fetchware, because it's stupid to have fetchware potentially
# create files that it can't read back in, because they fail fetchware's
# security checks. It's also a potential security risk.
umask 0022;

# Use Getopt::Long for options parsing beyond fetchware's simple commands such
# as install, new, uninstall, help, and so on.
use Getopt::Long qw(:config bundling pass_through);
###BUGALERT### This breaks App::Fetchware's encapsulation, and screws up its API
#fix this bug by extracting the fetchwarefile without relying on start() having
#already created the temp directory!!!!!!
use App::Fetchware qw(parse_directory_listing);
use App::Fetchware::Config qw(config __clear_CONFIG config_replace);
use App::Fetchware::Util qw(:UTIL);
use Test::Fetchware 'create_test_fetchwarefile';
use App::Fetchware::Fetchwarefile;
use Archive::Tar;
use File::Copy qw(mv cp);
use File::Spec::Functions qw(curdir catdir catfile catpath tmpdir splitpath
    splitdir rel2abs abs2rel updir file_name_is_absolute);
use Cwd 'cwd';
use File::Path qw(make_path remove_tree);
use Term::UI;
use Term::ReadLine;
use Perl::OSType 'is_os_type';
use File::HomeDir;
use File::Find 'find';
use File::Temp 'tempfile';
use Fcntl qw(SEEK_SET);
use Path::Class;
use Text::Wrap 'wrap';
use Data::Dumper;
use Fcntl ':flock';
use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
use Sub::Mage;
use URI::Split qw(uri_split uri_join);
use Scalar::Util 'blessed';
use Module::Load 'load';

# Setup exports, which are only meant to ease testing.
use Exporter 'import';
our %EXPORT_TAGS  = (
    TESTING => [qw(
        parse_fetchwarefile    
        create_fetchware_package
        fetchware_database_path
        determine_fetchware_package_path
        extract_fetchwarefile
        copy_fpkg_to_fpkg_database
        cmd_install
        cmd_uninstall
        cmd_look
        cmd_list
        cmd_upgrade
        cmd_upgrade_all
        cmd_new
        cmd_clean
        run
    )]
);
our @EXPORT_OK = @{$EXPORT_TAGS{TESTING}};

our $verbose = 0;
our $quiet = 0;
our $dry_run = 0;

# Be a modulino, so I can "use fetchware" in my test suite, so I can test
# bin/fetchware normally like any other perl module.
###BUGALERT## Add a test suite for run(), and also one that directly calls
#bin/fetchware to test its command line options.
run() unless caller();

sub run {
    # Set up a %SIG handler for CTRL-C or CTRL-Z on Windows.
    # And a %SIG handler for QUIT, which is CTRL-\
    #
    # Define a $parent_pid, so I can compare it to $$ (the current pid) to
    # see if I'm the child or the parent inside the sig handler to act
    # accordingly.
    my $parent_pid = $$;
    #
    # Be sure to prepend the first message that's printed with a newline to
    # ensure that it's printed on a brand new fresh line.
    @SIG{qw(INT TERM QUIT)} = sub {
        my $sig = shift;
        # Avoid a silly race condition where both the parent and the child both
        # try to run this code at the same time resulting in the one closing the
        # file and deleting the tempdir() before the other one resulting in
        # strange undefined warnings.
        #
        if ($parent_pid == $$) {
            msg <<EOM;
\nSignal [$sig] received. Cleaning up Fetchware.
EOM
            vmsg <<EOM;
Any temporary files that fetchware may have created will be deleted by
File::Temp's END block.
EOM

            cleanup_tempdir();
        }

        # Exit failure, because fetchware failed to properly install your
        # software while it was running, because the signal it received forced
        # it to exit prematurely making it questionable if fetchware succeeded
        # in properly and completely completing the actions you specified on the
        # command line and/or in a Fetchwarefile.
        exit 1;
    };

    vmsg 'Parsing command line options using Getopt::Long';

    GetOptions(
        # $VERSION is managed by dzil; therefore, I use eval to access it at
        # run time instead of compile time, so that I can test fetchware without
        # running dzil test.
        'version|V' => sub { eval 'say "Fetchware version $fetchware::VERSION"; '; exit 0},
        'help|h|?' => \&cmd_help,
        'verbose|v' => \$verbose,
        'quiet|q' => \$quiet,
        # Expose File::Temp's KEEP_ALL flag, and an easy feature to implement
        # that lets users easily ensure the tempdir stays around when needed.
        'keep-temp|K' => \$File::Temp::KEEP_ALL,
        ###BUGALERT### dry-run functionality is *not* implemented!!!
        #'dry-run|d' => \$dry_run,
    );


    # Getopt::Long is *only* used to determine dash and double dash style options
    # such as -v, --verbose, --help, -h, -?, etc....
    #
    # Below the first argument to fetchware is used to determine what fetchware
    # does.  If nothing is specified then help is printed.
    ###BUGALERT### Add a loop around @ARGV to support multiple Fetchwarefiles
    #or fetchware packages ending in .fpkg.
    eval { # Trap any fatal errors.
        vmsg 'Entering main eval{} block to trap errors.';
        ###BUGALERT### Should trapped exceptions with this eval cause fetchware
        #to cd to $original_cwd and then exit, so that the File::Temp's END
        #block can delete fetchware's source dir???
        # Or fetchware could print the path of this source dir and close, and
        # tell the user that they can clean it up with fetchware clean??
        # Also, add cmdline options to control what to do when this happens???
        vmsg 'Determining which command to run based on command line options.';
        my $command;
        @ARGV ? ($command = shift @ARGV) : ($command = '');
        if ($command eq 'install') {
            cmd_install(@ARGV);
        } elsif ($command eq 'uninstall') {
            cmd_uninstall(@ARGV);
        } elsif ($command eq 'new') {
            cmd_new(@ARGV);
        } elsif ($command eq 'upgrade') {
            cmd_upgrade(@ARGV);
        } elsif ($command eq 'upgrade-all') {
            cmd_upgrade_all(@ARGV);
        } elsif ($command eq 'list') {
            cmd_list(@ARGV);
        } elsif ($command eq 'look') {
            cmd_look(@ARGV);
        } elsif ($command eq 'clean') {
            cmd_clean(@ARGV);
        } elsif ($command eq 'help') {
            cmd_help(@ARGV);
        } else {
            cmd_help(@ARGV);
        }
        # Exit success, because if any of the main subroutines run into any
        # problems they die() exceptions, which get caught in eval above, and
        # warn()ed below, and fetchware exits 1 for failure.
        vmsg 'Fetchware ran successfully! Exiting with status of 0 for success!';
        exit 0;
    };
    # If a fatal error was thrown print it to STDERR and exit indicating failure.
    if ($@) {
        # Set File::Temp's $KEEP_ALL so user can troubleshoot what happend
        # without having to bother to use --keep-all.
        $File::Temp::KEEP_ALL = 1;
        msg <<EOM;
Fetchware threw an exception! Exiting with an exit status of 1 for failure.
Fetchware failed inside directory [@{[cwd()]}].
EOM
        warn $@;
        exit 1;
    }
}





###BUGALERT### cmd_install() does *not* actually do this. Consider implementing
#it.
#If no filename was
#provided or the filename doesn't exist then, cmd_install() calls new() to create
#and install a new fetchware package.


sub cmd_install {
    # These variables must be shared back to the parent from the child using
    # pipe_{write,read}_newline().
    my $P_build_path;
    ###BUGALERT### After verifying basic functionality of cmd_install wrap
    #subroutine contents in a for my $filename (pop @ARGV) loop to try to
    #install all given arguments that arn't command line options as parsed by
    #GetOpt::Long.
    ### Add this loop in run(), so there is just one loop to test.
    my $filename = shift;
    
    my $output;
    if (defined($filename) and -e $filename) {

    msg "Starting fetchware install to install [$filename]";

    # If a fpkg extract out the Fetchwarefile into a scalar, and if not a
    # fetchware package to go ahead and open for reading only the Fetchware
    # right now while we're perhaps still root, so we can be sure we can
    # still access it.
    my $fetchwarefile;
    if ($filename =~ /\.fpkg$/) {
        $fetchwarefile = extract_fetchwarefile($filename);
        vmsg <<EOM;
Extracting out Fetchwarefile from [$filename] to [$$fetchwarefile]
EOM
    } else {
        my $fh = safe_open($filename, <<EOD);
fetchware: Fetchware failed to open the filename you specified to fetchware
install [$filename]. The OS error was [$!].
EOD
        vmsg "Opened file [$filename] for slurping.";
        # Add a \ to turn the slurped scalar into a scalar ref for calling
        # parse_fetchwarefile() properly.
        $fetchwarefile = \do {local $/; <$fh>};
        vmsg  "Slurped [$filename] into fetchware: [$$fetchwarefile]";
    }

    # Must parse the Fetchwarefile in the parent, so that the parent has access
    # to the imported subroutines and modified fetchware configuration (%CONFIG)
    # just as the child does.
    parse_fetchwarefile($fetchwarefile);
    vmsg "Parsed Fetchwarefile [$$fetchwarefile].";


    # start() runs as root before the fork, because it uses
    # App::Fetchware::Util's create_tempdir() to create a $temp_dir. This
    # subroutine uses a variable to store an open filehandle to a
    # "fetchware.sem" semaphore file. This filehandle must stay open and locked
    # using flock, because otherwise a "fetchware clean" run could delete the
    # temporary directory out from under fetchware. Therefore, the parent must
    # open this semaphore, because the child if it runs start() will close this
    # file handle when it exits causing cleanup_tempdir() to freak out when
    # end() is called.
    my $temp_dir = start();

        # Drop privs, so only install() and  end() are called with root perms
        $output = drop_privs(
        sub {
            my $write_pipe = shift;

            # Run the App::Fetchware API subroutines to do everything to install
            # the program, but be mindful of drop_privs() requiring this coderef
            # to use write_dropprivs_pipe() to communicate needed changes back to
            # the parent process, for example, $P_build_path--the parent needs to
            # chdir() to that directory before it tries to execute install().

            ###BUGALERT### install installs no matter if the program is already
            #installed!!! Change this to parse out the package from the
            #download_urlif possible, compare with the one in the fetchware
            #package database, and call exit right here if the current version
            #is already installed unless of course --force is used!!!
            my $download_url = lookup();

            my $package_path = download($temp_dir, $download_url);

            ###BUGALERT### Add support for caching the key files gpg creates to
            #the fetchwarefile, and for actually using them later on inside the
            #fpkg.
            verify($download_url, $package_path);

            $P_build_path = unarchive($package_path);

            build($P_build_path);

            # Tell the parent, root, process the values of the variables the
            # child calculated in this coderef, and write them across this pipe
            # back to the parent
            write_dropprivs_pipe($write_pipe, $P_build_path);
        }, config('user')
        ); # End drop_privs().

        # Read from the pipe the child, the drop_privs()ed process, writes to to
        # read the necessary values that correspond to the variables that the
        # child must communicate back to the parent, so the parent can continue
        # processing as though no fork()ing or priv dropping took place.
        ($P_build_path)
            = read_dropprivs_pipe($output);

        my $installed_fetchware_package_path;
        if (not config('no_install')) {
            install($P_build_path);

            vmsg "Creating Fetchware package from [@{[cwd()]}].";
            my $fetchware_package_path
                =
                create_fetchware_package($fetchwarefile, cwd());
            vmsg "Created fetchware package at [$fetchware_package_path].";

            vmsg 'Installing created fetchware package to fetchware database.';
            $installed_fetchware_package_path
                = copy_fpkg_to_fpkg_database($fetchware_package_path);
            vmsg <<EOM;
Installed created fetchware package to [$installed_fetchware_package_path]
EOM
        }

        end();

        # Return the path of the created and installed fetchware package.
        return $installed_fetchware_package_path;
    } else {
        ###BUGALERT### Replace with warn for proposed for loop above to work???
        die <<EOD;
fetchware: You called fetchware install incorrectly. You must also specify
either a Fetchwarefile or a fetchware package that ends with [.fpkg].
EOD
    }
}




sub cmd_uninstall {

    my $uninstall_package_path = shift;

    msg "Uninstalling specified package [$uninstall_package_path]";

    my $fetchware_package_path
        = determine_fetchware_package_path($uninstall_package_path);
    vmsg <<EOM;
Determined the path of this package in the fetchware database to be
[$fetchware_package_path].
EOM

    # Extract out the $fetchwarefile from the $fetchware_package_path.
    my $fetchwarefile;
    if ($fetchware_package_path =~ /\.fpkg$/) {
        $fetchwarefile
            =
            extract_fetchwarefile($fetchware_package_path, cwd());
        vmsg <<EOM;
Extracting out Fetchwarefile from [$fetchware_package_path] to [$$fetchwarefile]
EOM
    } else {
        die <<EOD;
fetchware: The option you provided to uninstall is not a currently installed
fetchware package. Please rerun uninstall after determining the proper name for
the already installed fetchware package. To see a list of already installed
fetchware packages please try fetchware's list command: fetchware list
EOD
    }

    # Must parse the Fetchwarefile in the parent, so that the parent has access
    # to the imported subroutines and modified fetchware configuration (%CONFIG)
    # just as the child does.
    parse_fetchwarefile($fetchwarefile);
    vmsg "Parsed Fetchwarefile [$$fetchwarefile].";

    # start() runs as root before the fork, because it uses
    # App::Fetchware::Util's create_tempdir() to create a $temp_dir. This
    # subroutine uses a variable to store an open filehandle to a
    # "fetchware.sem" semaphore file. This filehandle must stay open and locked
    # using flock, because otherwise a "fetchware clean" run could delete the
    # temporary directory out from under fetchware. Therefore, the parent must
    # open this semaphore, because the child if it runs start() will close this
    # file handle when it exits causing cleanup_tempdir() to freak out when
    # end() is called.
    my $temp_dir = start();

    # "Download" the package using File::Copy's cp().
    my $package_path;
    if (cp($fetchware_package_path, $temp_dir)) {
        # Determine the output file that cp() used.
        ###BUGALERT### Open the file for cp(), and provide cp() with a
        #filehandle to write the data to to ensure the filename is exactly
        #what it needs to be.
        $package_path = catfile($temp_dir,
            file($fetchware_package_path)->basename());
    } else {
        die <<EOD;
fetchware: Fetchware failed to copy the file [$fetchware_package_path] to the
destination directory [$temp_dir]. OS error [$!].
EOD
    }


    vmsg "Copied installed package to temporary directory at [$package_path]";

    my $build_path = unarchive($package_path);

    uninstall($build_path);

    end();

    vmsg 'Uninstalling fetchware package from fetchware database.';
    uninstall_fetchware_package_from_database($fetchware_package_path);

    msg "Uninstalled fetchware package [$uninstall_package_path].";
    # Return the name of the uninstalled package's full path fetchware's
    # database.
    return $fetchware_package_path;
}



###BUGALERT### Move cmd_new() before install()?????
###BUGALERT### Print out fetchware's assumptions it makes about what FTP & hTTP
#lookup_url's look like, versionstring's assumptions, timestamp's assumptions,
#verify's assumptions, and so on. If not here in new() at least do it in the
#POD documentation.
###BUGALERT### Support ~/.Fetchwarefile, or whatever File::HomeDir wants it to
#be. Test if ~/.Fetchwarefile exists, if it does do nothing, but if it does not
#exist then prompt the user to fill one out!!!

############BUGALERT########################BUGALERT##################
############BUGALERT########################BUGALERT##################
###BUGALERT### Modify analyze_lookup_listing() to print the directory listing
#for the user to peruse, and have the user choose what program they want to
#install from the listing. Then use that as the basis for the filter option.
#httpd-2.4.1.tar.bz2 would simply be m/(\w+?)[.-_\d]+?/ And $1 is the filter
#option. If the match fails to the existing manual crap.
############BUGALERT########################BUGALERT##################
############BUGALERT########################BUGALERT##################

###BUGALERT### Add a command line option to fetchware new that allows users to
#only edit a blank file (Have helpful comments about what options are must
#haves), and then have fetchware ask the user if they would like to then install
#that new Fetchwarefile.
###BUGALERT### Rename lookup_url to main_mirror or master_mirror or author_mirror
#or something to better implicitly explain what it is.
###BUGALERT### Add the check to see if there is a MIRRORS file or some similar
#regex, and if so parse it, and auto add it to the list of mirrors? Is this
#really feasible?


sub cmd_new {

    # These are the variables that the child must share back with the parent.
    my $program_name = shift; # The child might change or define it again.

    my $term = Term::ReadLine->new('Fetchware new');

    # Must ask user what App::Fetchware extension they are going to create a
    # Fetchwarefile for, so I can load that extension's new() and new_install()
    # API subroutines, because *no* API subroutines are available until
    # parse_fetchwarefile() is called in cmd_install(), and I can't call
    # parse_fetchwarefile() before the user has answered the questons to
    # actually create a Fetchwarefile.
    my $fetchware_extension = $term->get_reply(
        print_me => <<EOP,
Unless you're using a Fetchware extension, press enter to continue along in the
creation of your new Fetchwarefile. If you are using a Fetchware extension,
please enter its name without the 'App::FetchwareX::' prefix.
EOP
        prompt => q{Unless you're using a Fetchware extension press enter to use default?},
        default => 'App::Fetchware',
    );

    ###Security Note### Whatever string the user supplies will be given
    #to Module::Load's load() subroutine, and then forwarded on to
    #Perl's require() function, which parses and executes it as far as
    #loading it goes, and then whatever new() and new_install()
    #subroutines will be imported in the current package, and later one
    #executed below new() with dropped privs, and new_install() as root
    #if fun as root. You may consider this a security hole as it is
    #never a good idea to execute user specified code, but considering
    #later on cmd_new() via new() will ask the user if they want to edit
    #the Fetchwarefile, where they can put whatever Perl code in it they
    #want to, and then ask_to_install_now_to_test_fetchwarefile() will
    #actually then run that Fetchwarefile, so that is also a security
    #hole. However none of this  really is a security hole, because the
    #user could create a Perl program that does whatever bad stuff that
    #they could use this to mess with Fetchware for.
    ###Do basic security checking anyway.
    die <<EOD if $fetchware_extension =~ m!\.\./|/|.pl|.pm!;
fetchware: The Fetchware extension you provided has characters that are not
allowed in fetchware extensions such as [../ , / , .pl , or .pm]. Please remove
these characters, and try again.
EOD
    if (grep { $fetchware_extension eq $_ }
        qw(App::Fetchware Fetchware fetchware default Default)
    ) {
        load 'App::Fetchware', qw(new new_install);
    } else {
        # Prepend the App::FetchwareX:: prefix for all fetchware
        # extensions.
        $fetchware_extension = "App::FetchwareX::$fetchware_extension";

        load $fetchware_extension, qw(new new_install);
    }

    

    # Drop privs, so only install() is called with root permissions
    my $output = drop_privs( sub {
        my $write_pipe = shift;


        my @pipe_args = new($term, $program_name);

        # Tell the parent, root, process the values of the variables the
        # child calculated in this coderef, and write them across this pipe
        # back to the parent
        write_dropprivs_pipe($write_pipe, @pipe_args);
    }, config('user'),
    # cmd_new() does not want or need the directory that drop_privs() creates
    # for the child so that the child can write files inside the parent's
    # temporary directory that was created with start().
    SkipTempDirCreation => 1
    ); # End drop_privs() back to root now!


    # Read from the pipe the child, the drop_privs()ed process, writes to to
    # read the necessary values that correspond to the variables that the
    # child must communicate back to the parent, so the parent can continue
    # processing as though no fork()ing or priv dropping took place.
    my (@pipe_args) = read_dropprivs_pipe($output);

    # Use our own term in the parent process. Writing the Term::ReadLine object
    # across the pipe does not work, so just make your own in the parent
    # process.
    $term = Term::ReadLine->new('Fetchware new');

    # Call App::Fetchware's or an App::Fetchware extension's new_install() to
    # install the previously generated Fetchwarefile, or whatever the
    # extension's new_install() does in addition to or instead of.
    return new_install($term, @pipe_args);
}






###BUGALERT### Add a config sub for a command to run after upgrade.
#C<after_upgrade_commands> that will allow you to restart apache or whatever
#after you've upgraded it, so that the newest version is running after you
#upgrade, because otherwise the currently running version won't have whatever
#suecurity fixes that might have been in the previous release.
sub cmd_upgrade {
    my $upgrade_name = shift;

    my ($P_upgrade,
        $P_build_path,
        $P_download_path,
        $P_fetchware_package_path);

    ###BUGALERT### the or --force cmdline option will skip the checking of
    #version numbers, and the one lookup() says to download will be installed
    #regardless.
    msg "Upgrading installed fetchware package [$upgrade_name].";

    $P_fetchware_package_path = determine_fetchware_package_path($upgrade_name);
    vmsg <<EOM;
Determined already installed fetchware package's path to be [$P_fetchware_package_path].
EOM

    # Parse out the Fetchwarefile from the fetchware package stored in the
    # fetchware database directory.
    my $fetchwarefile;
    if ($P_fetchware_package_path =~ /\.fpkg$/) {
        $fetchwarefile
            = extract_fetchwarefile($P_fetchware_package_path);            
        vmsg "Extracted Fetchwarefile temporarily into [$fetchwarefile]";
    } else {
        die <<EOD;
fetchware: fetchware upgrade failed to extract the Fetchwarefile from the
fetchware package that should be stored in fetchware's database.
EOD
    }

    # Must parse the Fetchwarefile in the parent, so that the parent has access
    # to the imported subroutines and modified fetchware configuration (%CONFIG)
    # just as the child does.
    parse_fetchwarefile($fetchwarefile);
    vmsg "Parsed Fetchwarefile [$$fetchwarefile].";

    # start() runs as root before the fork, because it uses
    # App::Fetchware::Util's create_tempdir() to create a $temp_dir. This
    # subroutine uses a variable to store an open filehandle to a
    # "fetchware.sem" semaphore file. This filehandle must stay open and locked
    # using flock, because otherwise a "fetchware clean" run could delete the
    # temporary directory out from under fetchware. Therefore, the parent must
    # open this semaphore, because the child if it runs start() will close this
    # file handle when it exits causing cleanup_tempdir() to freak out when
    # end() is called.
    my $temp_dir = start();

    # Drop privs, so only install() is called with root permissions
    my $output = drop_privs(
    sub {
        my $write_pipe = shift;

        ###BUGALERT### Have lookup() replace the timestamp of what we should
        #download too to make upgrade() be able to use the lookup_by_timestamp
        #algorithm too, which is a better default anyway.
        $P_download_path = lookup();

        # Call upgrade() to determine if the currently available version
        # ($P_download_path) is newer than the currenlty installed version
        # ($P_fetchware_package_path).
        my $P_upgrade = upgrade($P_download_path, $P_fetchware_package_path);

        if ($P_upgrade) {
            msg 'New version available upgrading now.';

            my $package_path = download($temp_dir, $P_download_path);

            ###BUGALERT### Add support for caching the key files gpg creates to the
            #fetchwarefile, and for actually using them later on inside the fpkg.
            verify($P_download_path, $package_path);

            $P_build_path = unarchive($package_path);
            build($P_build_path);
        } else {
            # If a new version is not available, then the child should do
            # nothing, and let the parent call end() to clean up below.

            # Set $P_build_path to something that will fail, and give a decent
            # error message just in case.
            $P_build_path = 'Build Path not set because upgrade not needed.';
        }

        # Tell the parent, root, process the values of the variables the
        # child calculated in this coderef, and write them across this pipe
        # back to the parent
        write_dropprivs_pipe($write_pipe,
            $P_upgrade,
            $P_build_path,
            $P_download_path,
            $P_fetchware_package_path
        );
    }, config('user')
    ); # End drop_privs()

    # Read from the pipe the child, the drop_privs()ed process, writes to to
    # read the necessary values that correspond to the variables that the
    # child must communicate back to the parent, so the parent can continue
    # processing as though no fork()ing or priv dropping took place.
    ($P_upgrade,
    $P_build_path,
    $P_download_path,
    $P_fetchware_package_path) = read_dropprivs_pipe($output);

    # Test if a new version is available again due to drop_priv() ending
    # half way through this if statement.
    if ($P_upgrade) {
        install($P_build_path);

        my $updated_fetchware_package_path
            =
            create_fetchware_package($fetchwarefile, cwd());
        vmsg <<EOM;
Created a new fetchware package for the newly installed upgraded fetchware
package [$updated_fetchware_package_path].
EOM

        uninstall_fetchware_package_from_database($P_fetchware_package_path);
        vmsg 'Uninstalled the old fetchware package from the fetchware database.';

        my $installed_fetchware_package_path
            = copy_fpkg_to_fpkg_database($updated_fetchware_package_path);
        vmsg <<EOM;
Installed new fetchware package to fetchware package database
[$installed_fetchware_package_path].
EOM

        end();

        # Return the path of the created and installed fetchware package.
        return $installed_fetchware_package_path;
    } else {

        # I only need the basename.
        my $download_path_basename = file($P_download_path)->basename();
        my $upgrade_name_basename =
            file( $P_fetchware_package_path)->basename();

        # Strip trailing garbage to normalize their names, so that they can be
        # compared to each other.
        ###BUGALERT### This comparision is quite fragile. Figure out a better way to
        #do this!!!
        $upgrade_name_basename =~ s/\.fpkg$//;
        $download_path_basename
            =~ s/(\.(?:zip|tgz|tbz|txz|fpkg)|(?:\.tar\.(gz|bz2|xz|Z)?))$//;

        msg <<EOM;
The latest version [$download_path_basename] is the same as the currently
installed version [$upgrade_name_basename]. So no upgrade is needed. 
EOM
        # Clean up temp dir.
        end();

        # Return success! An upgrade isn't needed, because the latest version
        # has been installed.
        return 'No upgrade needed.';
    }
}



sub cmd_upgrade_all {
    # Does *not* drop_privs(), because it calls cmd_upgrade(), which does, and
    # it does not make any real sense to do it in cmd_upgrade_all(), because all
    # it does is glob the fetchware_database_path(), and pass each element
    # of that list to cmd_upgrade() to do the actual upgrading.
    die <<EOD if @_;
fetchware: fetchware's upgrade-all command takes no arguments. Instead, it
simply loops through fetchware's package database, and upgrades all already
installed fetchware packages. Please rerun fetchware upgrade-all without any
arguments to upgrade all already installed packages, or run fetchware help for
usage instructions.
EOD

    msg 'Upgrading all installed fetchware packages.';

    my $fetchware_db_glob = catfile(fetchware_database_path(), '*');

    my @upgraded_packages;
    for my $fetchware_package (glob $fetchware_db_glob) {
        vmsg 'Looping over list of installed fetchware packages.';
        ###BUGALERT### subize the 2 lines below, because I do this more than
        #once.
        # Strip each member of the fetchwarefile database down to just its name
        # without any path garbage or fetchware package file extension, because
        # cmd_upgrade() only accepts arguments of this format, and I do not want
        # users to be able to provide a fetchware package as an argument to
        # the fetchware upgrade command. I only want it capable of looking them
        # up from its database.
        $fetchware_package = file($fetchware_package)->basename();
        $fetchware_package =~ s/\.fpkg$//;
        ###BUGALERT### Spit out a warning for anything in
        #fetchware_database_path() that does not end .fpkg, which should be
        #here.
        vmsg "Upgrading installed fetchware package [$fetchware_package]";
        push @upgraded_packages, cmd_upgrade($fetchware_package);
    }

    ###BUGALERT### push the fetchware pacakge name and its cmd_upgrade() return
    #value into a hash, and then return it or msg() it, to tell the user what
    #was upgraded and what was not.
    # Return 'No upgrade needed.' only if every package that was upgraded
    # returned 'No upgrade needed.'.
    if ( (grep { $_ eq 'No upgrade needed.'}
            @upgraded_packages) eq @upgraded_packages) {
        msg 'No packages need to be upgraded.';
        return 'No upgrade needed.';
    # Return a list of all packages that are not 'No upgrade needed.', which
    # should not be returned.
    } else {
        my @upgraded_packages = grep { $_ ne 'No upgrade needed.' }
            @upgraded_packages;
        msg 'Packages were upgraded to newer versions:';
        msg Dumper(\@upgraded_packages);
        return @upgraded_packages;
    }
}




###BUGALERT### Fix the bug that prevents look from check for an installed
#package first, then a filename or fetchwarefile.
sub cmd_look {
    my $filename = shift;

    my $P_look_path;

    my $fetchwarefile;
    if ($filename =~ /\.fpkg$/) {
        $fetchwarefile = extract_fetchwarefile($filename);
        vmsg <<EOM;
Extracting out Fetchwarefile from [$filename] to [$$fetchwarefile]
EOM
    } else {
        my $fh = safe_open($filename, <<EOD);
fetchware: Fetchware failed to open the filename you specified to fetchware
install [$filename]. The OS error was [$!].
EOD
        vmsg "Opened file [$filename] for slurping.";
        # Add a \ to turn the slurped scalar into a scalar ref for calling
        # parse_fetchwarefile() properly.
        $fetchwarefile = \do {local $/; <$fh>};
        vmsg  "Slurped [$filename] into fetchware: [$$fetchwarefile]";
    }

    # Must parse the Fetchwarefile in the parent, so that the parent has access
    # to the imported subroutines and modified fetchware configuration (%CONFIG)
    # just as the child does.
    parse_fetchwarefile($fetchwarefile);
    vmsg "Parsed Fetchwarefile [$$fetchwarefile].";

    # start() runs as root before the fork, because it uses
    # App::Fetchware::Util's create_tempdir() to create a $temp_dir. This
    # subroutine uses a variable to store an open filehandle to a
    # "fetchware.sem" semaphore file. This filehandle must stay open and locked
    # using flock, because otherwise a "fetchware clean" run could delete the
    # temporary directory out from under fetchware. Therefore, the parent must
    # open this semaphore, because the child if it runs start() will close this
    # file handle when it exits causing cleanup_tempdir() to freak out when
    # end() is called.
    #
    # Call start() with an option to have it keep the temp dir, and not
    # have File::Temp clean it up with an END handler.
    my $temp_dir = start(KeepTempDir => 1);

    # Drop privs to match up with cmd_install()'s behavior.
    my $output = drop_privs(
    sub {
        my $write_pipe = shift;

        msg 'Downloading and unarchiving specified distribution.';

        my $download_url = lookup();

        my $package_path = download(cwd(), $download_url);

        ###BUGALERT### Add support for caching the key files gpg creates to
        #the fetchwarefile, and for actually using them later on inside the
        #fpkg.
        verify($download_url, $package_path);

        my $build_path = unarchive($package_path);

        # end() is *not* run, because the point of look is to lookup,
        # download, and unarchive, and then actually "look" at the files,
        # and running end() would delete them.

        # Compose the $P_look_path. A simple catfile($temp_dir, $build_path)
        # should work, but don't forget about drop_privs() extra temporary
        # directory when run as root! To avoid this problem of the $P_look_path
        # being wrong when run as root due to the extra temporary directory;
        # instead, of a simple catfile(...) replace the last file portion of
        # $package_path, with $build_path.
        $P_look_path = catfile(dir($package_path)->parent(), $build_path);
        msg <<EOM;
Your package's contents are at [$P_look_path]. Please run [fetchware clean] to
delete these files and any other files fetchware may have left behind when you
are finished looking inside this package.
EOM

        # Tell the parent, root, process the values of the variables the
        # child calculated in this coderef, and write them across this pipe
        # back to the parent
        write_dropprivs_pipe($write_pipe, $P_look_path);

        # Does not need to execute anything as root, because cmd_look() does not
        # install anything or even call end(), because the suer is supposed to
        # look at its output in the tempdir it prints out.
    }, config('user')
    ); # End drop_privs()

    # Read from the pipe the child, the drop_privs()ed process, writes to to
    # read the necessary values that correspond to the variables that the
    # child must communicate back to the parent, so the parent can continue
    # processing as though no fork()ing or priv dropping took place.
    ($P_look_path)
        = read_dropprivs_pipe($output);

    return $P_look_path;
}



sub cmd_list {
    my @installed_packages = glob catfile(fetchware_database_path(), '*');
    
    if (@installed_packages == 0) {
        msg 'No fetchware packages are currently installed.';
        return;
    }

    msg 'Listing all currently installed packages:';
    for my $fetchware_package (@installed_packages) {
        # Clean up $fetchware_package.
        $fetchware_package = file($fetchware_package)->basename();
        $fetchware_package =~ s/\.fpkg$//;

        msg $fetchware_package;
    }
}




###BUGALERT### It could parse all installed Fetchwarefile's to obtain a listing
#of all temp_dirs that are used, and clean them as well!!!!
###BUGALERT### Use --force to parse all temp_dir's in installed packages, and
#clean them too?? Let it receive an arg to a dir to clean of fetchware crap???
sub cmd_clean {
    # If user specified no specific directories to clean, then clean the default
    # system tmpdir().
    my @fetchware_temp_dirs = scalar @_ ? @_ : tmpdir();

    my @globbed_fetchware_temp_dirs;

    # Build a list of fetchware temporary directories across tmpdir() and any
    # user provided paths on the command line.
    for my $fetchware_temp_dir (@fetchware_temp_dirs) {
        # What the user specified or tmpdir() must be a directory.
        die <<EOD if not -d $fetchware_temp_dir;
fetchware: The specified directory [$fetchware_temp_dir] is not a directory or
does not exist. Please only specify directories that exist, and ones you have
read and write permission in. OS error [$!].
EOD

        # Store all of the fetchware-* temp dirs in @globbed_fetchware_temp_dirs
        # for later processing.
        for my $fetchware_file_or_dir (
            glob(catfile($fetchware_temp_dir, 'fetchware-*')),
            glob(catfile($fetchware_temp_dir, 'Fetchwarefile-*'))
        ) {
            # If it's a directory add it to the queue of directories to delete
            # below.
            if (-d $fetchware_file_or_dir) {
                push @globbed_fetchware_temp_dirs, $fetchware_file_or_dir;
            # If it's just a file just delete right away.
            } else {
                ###BUGALERT### Should I check if the current user has perms to
                #delete the file before deleting it? What about root? Should
                #root delete all files found even for other users? I'll go with
                #the Unix default of just doing the operation, and dealing with
                #the error message you receive to avoid the complexity of
                #checking perms. Furthermore, what about Unix ACLs and Windows'
                #ACL style perms? It's not worth dealing with that hassel.
                unlink $fetchware_file_or_dir or die <<EOD;
fetchware: Failed to unlink file [$fetchware_file_or_dir]. OS error [$!].
EOD
                    vmsg <<EOM;
fetchware clean found and deleted file [$fetchware_file_or_dir].
EOM
            }
        }
    }

    msg "fetchware clean found no fetchware temporary directories to clean"
        if @globbed_fetchware_temp_dirs < 1;

    # Holds the number of directories that had errors when they were
    # deleted.
    my $num_remove_tree_errors = 0;
    # Number of directories remove_tree removed successfully.
    my $num_remove_tree_successes = 0;


    # Loop over fetchware temp dirs, and delete the ones that are not locked.
    for my $temp_dir (@globbed_fetchware_temp_dirs) {
        # Try to lock the 'fetchware.sem' semaphore lock file

        # I annoying must open the file before I can see if I can lock it or
        # not.
        my $sem_lock_file = catfile($temp_dir, 'fetchware.sem');
        my $fh_sem;
        if (open $fh_sem, '>', $sem_lock_file) {
            vmsg "Successfully created [fetchware.sem] semaphore lock file.";
        } else {
            # Test if the lockfile has the same owner uid as this running perl
            # process, and if they differ skip deleting this one, because we
            # lack the perms to do it anyway.
            if ($> != (stat($sem_lock_file))[4]) {
                msg "Skipping file [$sem_lock_file], because a different user created it.";
                next;
            } else {
                die <<EOD;
App-Fetchware-Util: Failed to create [$sem_lock_file] semaphore lock file! This
should not happen, because fetchware is creating this file in a brand new
directory that only fetchware should be accessing. You simply shouldn't see this
error unless some one is messing with fetchware, or perphaps there actually is a
bug? I don't know, but this just shouldn't happen. It's so hard to trigger it to
happen, it can't easily be tested in fetchware's test suite. OS error [$!].
EOD
            }
        }
        # Now flock 'fetchware.sem.' This should
        # Use LOCK_NB so flock won't stupidly wait forever and ever until 
        # he lock becomes available.
        # If flock fails, don't die! Instead, just skip deleting this
        # fetchware temporary directory, and go on to the next one.
        unless (flock $fh_sem, LOCK_EX | LOCK_NB) {
            # Flock failed, something else has the lock, print message, and skip
            # this directory, and go on to the next one.
            msg <<EOM;
[$temp_dir] locked by another fetchware process. Skipping.
EOM
            next;
        }

        # Delete the whole $tempdir. Use error and result for File::Path's
        # experimental error handling, and set safe to true to avoid borking the
        # filesystem. This might be run as root, so it really could screw up
        # your filesystem big time! So set safe to true to avoid doing so.
        remove_tree($temp_dir, {
            error => \my $err,
            result => \my $res,
            safe => 1} );

        # Parse remove_tree()'s insane error handling system. It's expirimental,
        # but it's been experimental forever, so I can't see it changing.
        if (@$err) {
            $num_remove_tree_errors++;
            for my $diag (@$err) {
                my ($file, $message) = %$diag;
                if ($file eq '') {
                    warn "general error: $message\n";
                } else {
                    warn "problem unlinking $file: $message\n";
                }
            }
        } else {
            vmsg "No errors encountered during removal of [$temp_dir]\n";
        }

        if (@$res) {
            # Keep track of each successfully removed directory.
            $num_remove_tree_successes++;
            vmsg "unlinked [$_]" for @$res;
        }
    }

    # Summarize success or failure for user, so he doesn't have to dig
    # through a bunch of error messages to see if it worked right.
    msg <<EOM if $num_remove_tree_errors > 0;
fetchware clean had [$num_remove_tree_errors] directories give errors.
EOM
    msg <<EOM if $num_remove_tree_successes > 0;
fetchware clean successfully deleted [$num_remove_tree_successes] directories. 
EOM

}



sub cmd_help {
	print <<'HELP';
fetchware is a package manager for source code distributions. It gives you the
ability to install, uninstall, and even upgrade your source code distributions
just like you can with your binary packages using yum, apt-get, or slackpkg.

To create a new package just use fetchware's "new" command such as:
	$ fetchware new
And then answer the questions as best you can while fetchware takes your
answers and creates a Fetchwarefile for you. If your program's needs seem to
exceed the ability of fetchware's q&a configuration see perldoc App::Fetchware
for instructions on manual Fetchwarefile configuration.

USAGE:
    fetchware new|install|uninstall|upgrade|upgrade-all|list|look|clean|help
        --help|-h|-?|--version|-V|--verbose|-v|--quiet|-q]
        package-name

COMMANDS:
    new - creates a new Fetchwarefile for use with fetchware.
    install - installs a fetchware package, which is a .tar.gz ending with
        .fpkg, which includes the source code distribution unmodified,
        but with an included Fetchwarefile. See perldoc fetchware.
    uninstall - uninstalls a fetchware package.
    upgrade - upgrades a fetchware package if a newer version is available.
    upgrade-all - upgrades *all* installed fetchware packages.
    list - lists all installed fetchware packages.
    look - downloads and unarchives a fetchware package for viewing.
    clean - deletes any left over messes caused by fetchware in your tempdir.
    help - prints this help message
OPTIONS:
    --help|-h|-? - prints this help message.
    --version|-V - prints a version message.
    --verbose|-v - prints additional logging information.
    --quiet|-q - prints *no* logging invormation. Determine success or
        failure with fetchware's exit status. 0 = success. Non-zero = failure.

For more information see perldoc fetchware and perldoc App::Fetchware.
HELP

    ###BUGALERT###Consider actually adding dry run functionality.
    #--dry-run|-d - turns on dry run functionality causing fetchware to not
    #actually download or install or create any packages.
	exit 0;
}







sub parse_fetchwarefile {
    my $fetchwarefile = shift;

    # Arg $fetchwarefile must be a SCALAR ref.
    die <<EOD unless ref($fetchwarefile) eq 'SCALAR';
fetchware: parse_fetchwarefile() was called with the wrong arguments. It only
accepts and scalar references of the text of your fetchwarefile.
EOD

    # Ensure the $fetchwarefile has a use App::Fetchware somewhere in it. And be
    # sure to support fetchware extensions such as App::FetchwareX::HTMLPageSync.
    die <<EOD unless $$fetchwarefile =~ /^\s*use\s+App::FetchwareX?(::)?/m;
fetchware: The fetchwarefile you provided did not have a [use App::Fetchware]
line in it. This line is required, because it is an important part of how
fetchware uses Perl for its configuration file. Your fetchware file was.
[$$fetchwarefile]
EOD

    # Do the potentially evil eval. No Safe compartment or use ops is used. This
    # is one gigantic security hole; however, it is also how fetchware works :)
    #
    # safe_open() is used to ensure that the file the user provides is "safe" to
    # use, and is the limit of fetchware's safety features.
    eval $$fetchwarefile;

    die <<EOD if $@;
fetchware: run-time error. fetchware failed to execute the Fetchwarefile
[$$fetchwarefile] you provieded on the command line or that was packaged
with your Fetchware package (*.fpkg). The error was [$@].
EOD


    # Ensure that the specified App::Fetchware implementation exports the proper
    # subroutines.
    my %api_subs = (
        start => 1,
        lookup => 1,
        download => 1,
        verify => 1,
        unarchive => 1,
        build => 1,
        install => 1,
        uninstall => 1,
        upgrade => 1,
        check_syntax => 1,
        end => 1,
    );

    # Determine if all of the @api_subs are in sublist, the list of all subs in
    # the current package.
    # Code adapted from Perl Cookbook pg. 129.
    my (%union, %intersection);
    for my $element (keys %api_subs, sublist()) {
        $union{$element}++ && $intersection{$element}++;
    }

    # Compares the number of %intersection's to the number of %api_subs, and if
    # they're *not* equal throw an exception, so the user knows which API subs
    # are not set up right.
    if ( (grep {exists $api_subs{$_} and exists $intersection{$_}
                and $api_subs{$_} eq $intersection{$_}}
                keys %api_subs) != scalar keys %api_subs) {
        my @missing_api_subs;
        for my $api_sub (keys %api_subs) {
            if (not exists $intersection{$api_sub}
                or not defined $intersection{$api_sub}
                or ($intersection{$api_sub} == 0)
            ) {
                push @missing_api_subs, $api_sub;
            }
        }
        die <<EOD;
fetchware: The App::Fetchware module you choose in your fetchwarefile does not
properly export the necessary subroutines fetchware needs it to. These include:
start(), lookup(), download(), verify, unarchive(), build(), install(),
uninstall(), and end().
The missing subroutines are [@missing_api_subs].
EOD
    }

    # Call App::Fetchware's check_syntax() (or a App::Fetchware extension's).
    check_syntax();

    return 'Evaled config file successfully';
}



sub create_fetchware_package {
    my ($fetchwarefile,
        $unarchived_package_path,
        $dir_for_new_fpkg) = @_;

    # chdir() to my cwd's parent directory, because my cwd is currently on linux
    # /tmp/fetchware-kd883ejfe/program-1.2, and I need the program-1.2 part to
    # be in the archive's @file_list. This needs to happen even when dropping
    # privs, because drop_privs() chdir()'s before it forks putting both parent
    # and child in the same directory.
    my $previous_cwd = cwd();
    my $new_dir = dir(cwd())->parent();
    chdir($new_dir) or die <<EOD;
fetchware: run-time error. Fetchware failed to change it's working
directory to
[$new_dir] from [$previous_cwd]. The os error was [$!].
EOD

    # Turn something like /tmp/fetchware-djdjkd8382/package-1.2/Fetchware (with
    # the "Fetchwarefile" filename only sometimes being there) into just
    # "package-1.2"
    my $pc = dir($unarchived_package_path);
    my $last_dir = $pc->dir_list(-1, 1);
    my $fetchware_package_name = "$last_dir.fpkg";

    # The dir the new fpkg goes in is the current working directory, or a user
    # provided alternate path to store it in.
    $dir_for_new_fpkg //= cwd();
    # Calculate the full absolute path of the fetchware package I create below.
    my $fetchware_package_full_path
        =
        catfile($dir_for_new_fpkg, $fetchware_package_name);

    # Determine @file_list, because Archive::Tar does not just automatically
    # include *all* files like bin/tar does.
    my @file_list;
    find(sub {
            push @file_list, $File::Find::name;
        }, $unarchived_package_path);

    # Convert absolute filenames into relative filenames, because Archive::Tar
    # will use the exact filenames that you provide, so I need to remove the
    # unneeded machine specific paths from the paths that will be stored in the
    # fetchware package.
    $_ = abs2rel($_) for @file_list;

    my $tar = Archive::Tar->new();

    # Add the $fetchwarefile to the new fetchware package.
    # 
    # Create a Archive::Tar::File object to represent the Fetchwarefile without
    # bothering to write it to disk, or use the Fetchwarefile, which may or may
    # not already be on the disk.
    #
    # Be sure to deref $fetchwarefile, becauses it's passed in as a ref.
    my $tar_fetchwarefile
        =
        Archive::Tar::File->new(data => './Fetchwarefile', $$fetchwarefile)
            or die <<EOD;
fetchware: Failed to create a Archive::Tar::File object to represent your
Fetchwarefile
[$fetchwarefile] 
Archive::Tar error [@{[Archive::Tar->error()]}].
EOD

    $tar->add_files($tar_fetchwarefile) or die <<EOD;
fetchware: Failed to add your Fetchwarefile to fetchware's internal Archive::Tar
object. Archive::Tar error [@{[Archive::Tar->error()]}].
EOD

    # Add all of the other files to the Fetchware package.
    $tar->add_files(@file_list) or die <<EOD;
fetchware: Failed to add all of your program's files to fetchware's internal
Archvie::Tar object. Archive::Tar error [@{[Archive::Tar->error()]}].
EOD

    $tar->write($fetchware_package_full_path, COMPRESS_GZIP) or die <<EOD;
fetchware: Failed to write Archive::Tar's in-memeory tar file to disk.
Archive::Tar error [@{[Archive::Tar->error()]}].
EOD

    # chdir() back to original directory.
    chdir($previous_cwd) or die <<EOD;
fetchware: run-time error. Fetchware failed to change its working directory from
[@{[cwd()]}] to [$previous_cwd]. The os error was [$!].
EOD

    # Return a fullpath version of $fetchware_package_name.
    return $fetchware_package_full_path;
}



sub fetchware_database_path {
    # If user specifically specifies their own fetchware database path in their
    # fetchwarefile use it instead of the default one.
    my $fetchware_database_path;
    if (defined config('fetchware_db_path')) {
        $fetchware_database_path = config('fetchware_db_path');
    } elsif (defined $ENV{FETCHWARE_DATABASE_PATH}) {
        $fetchware_database_path = $ENV{FETCHWARE_DATABASE_PATH};
    } elsif (is_os_type('Unix', $^O)) {
        # If we're effectively root use a "system" directory.
        if ($> == 0) {
            # Fetchware is modeled slightly after Slackware's package manager,
            # which keeps its package database under /var/log/packages.
            $fetchware_database_path = '/var/log/fetchware';
        # else use a "user" directory.
        } else {
            $fetchware_database_path
                =
                File::HomeDir->my_dist_data('fetchware', { create => 1 });
        }
    } elsif ($^O eq "MSWin32") {
        # Load main Windows module to use to see if we're Administrator or not.
        BEGIN {
            if ($^O eq "MSWin32")
            {
                require Win32;
                Win32->import();  # assuming you would not be passing arguments to "use Module"
            }
        }
        if (Win32::IsAdminUser()) {
            # Is this an appropriate default?
            $fetchware_database_path = 'C:\Fetchware'; 
        } else {
            $fetchware_database_path
                =
                File::HomeDir->my_dist_data('fetchware' , { create => 1 });
        }
    # Fall back on File::HomeDir's recommendation if not "Unix" or windows.
    ###BUGALERT### Is this appropriate for Mac OSX???? /Fetchware perhaps?????
    } else {
         $fetchware_database_path
            =
            File::HomeDir->my_dist_data('fetchware', { create => 1 });
    }
    vmsg <<EOM;
Determined fetchware database path to be: [$fetchware_database_path]
EOM
    return $fetchware_database_path;
}



sub determine_fetchware_package_path {
    my $fetchware_package = shift;
my ($package, $filename, $line) = caller;
    my $fetchware_db_glob = catfile(fetchware_database_path(), '*');

    my @fetchware_package_filenames
        =
        grep /$fetchware_package/, glob $fetchware_db_glob;

    die <<EOD if @fetchware_package_filenames == 0;
fetchware: Fetchware failed to determine the fetchware package that is
associated with the argument that you provided to fetchware
[$fetchware_package]. In this case, fetchware only allows arguments for
fetchware packages that have already been installed. Please run fetchware list
to obtain a list of installed packages to choose from.
EOD

    ###BUGALERT### Use Term::UI, and output a numbered list for the user to
    #choose from using a prompt, and then rerun upgrade with that argument.
    if (@fetchware_package_filenames > 1) {
        # Print beginning of message to STDERR.
        warn <<EOW;
fetchware: Too many installed packages match the argument you provided to the
upgrade command. Your argument was [$fetchware_package], and the multiple
results it returned were:
EOW

        # Print modified array values to STDERR.
        for (@fetchware_package_filenames) {
            warn file($_)->basename(), "\n";
        }

        # Print closing of message to STDERR.
        die <<EOD;
Choose which package from the list above you want to upgrade, and rerun
fetchware upgrade using it as the argument for the package you want to upgrade.
EOD
    }

    # Return the first and only result.
    return $fetchware_package_filenames[0];
}



sub extract_fetchwarefile {
    my ($fetchware_package_path) = @_;

    # safe_open() the fetchware package path, which ends with .fpkg, but it
    # actually a .tar.gz.
    my $fh = safe_open($fetchware_package_path, <<EOD);
fetchware: run-time error. fetchware failed to open the Fetchwarefile you
specified on the command line [$fetchware_package_path]. Please check
permissions and try again. See perldoc App::Fetchware. OS error [$!].
EOD

    # Create a temporary file to write the ungzipped file to.
    my ($output_fh, $gunzipped_path) = tempfile("fetchware-$$-XXXXXXXXXXX",
        TMPDIR => 1, UNLINK => 1); 

    gunzip($fh => $output_fh) or die <<EOD;
fetchware: IO::Uncompress::Gunzip::gunzip failed to un gzip
[$fetchware_package_path]. Gunzip's error [$GunzipError].
EOD

    my $tar = Archive::Tar->new();

    # seek the $output_fh back to its beginning, so tar can reuse it.
    seek $output_fh, 0, SEEK_SET;

    # read in the same output filehandle that gunzip() wrote the uncompressed tar
    # file to. This prevents any race conditions, and other users from messing
    # with our version of the open file.
    $tar->read($output_fh) or die <<EOD;
fetchware: Archive::Tar failed to read in the gunziped file [$gunzipped_path]
that was previously gziped as [$fetchware_package_path].
Archive::Tar error [@{[Archive::Tar->error()]}].
EOD

    my $fetchwarefile = $tar->get_content('./Fetchwarefile')
        or die <<EOD;
fetchware: run-time error. fetchware failed to extract your fetchware package's
Fetchwarefile from the argument you specified on the command line [@ARGV].
Archive::Tar error [@{[$tar->error()]}]. Please see perldoc App::Fetchware.
EOD


    # Return a scalar ref of the $fetchwarefile that makes up the Fetchwarefile.
    # Do not
    return \$fetchwarefile;
}



sub copy_fpkg_to_fpkg_database {
    my $fetchware_package_path = shift;

    my $fetchware_db_path = fetchware_database_path();

    unless (-e $fetchware_db_path) {
        # Just use make_path() from File::Path to avoid having to check if
        # directories that contain the fetchware db directory have been created
        # or not. I doubt /var and /var/log won't exist on *nix systems, but
        # they probably don't on Mac OSX, which is kinda *nix.
        make_path($fetchware_db_path) or die <<EOD;
fetchware: run-time error. fetchware failed to create the directory that it
needs to store its database of installed packages in [$fetchware_db_path].
Library function error [$@].
EOD
    }
    cp($fetchware_package_path, $fetchware_db_path) or die <<EOD;
fetchware: run-time error. fetchware failed to copy the specified
fetchware package path [$fetchware_package_path] to [$fetchware_db_path]. Please
see perldoc App::Fetchware.
EOD
    
    # Return the full path to the fetchware package that has been copied.
    my $fetchware_package_path_basename
        = dir($fetchware_package_path)->basename();
    return catfile($fetchware_db_path, $fetchware_package_path_basename);
}



sub uninstall_fetchware_package_from_database {
    my $uninstall_package_name = shift;

    # Don't make preexisting absolute paths absolute again.
    $uninstall_package_name
        =
        catfile(fetchware_database_path(), $uninstall_package_name)
            unless file_name_is_absolute($uninstall_package_name);

    unlink $uninstall_package_name
        or die <<EOD;
fetchware: Fetchware successfully uninstalled the fetchware package you
requested [$uninstall_package_name], but it failed to also delete the
corresponding fetchware package from its database Os error [$!].
EOD
}



1;

=pod

=head1 NAME

fetchware - Fetchware is a package manager for source code distributions.

=head1 VERSION

version 1.016

=head1 SYNOPSIS

=head2 Manpage synopsis.

    fetchware [-v | --verbose] [-q | --quiet] [-h | -? | --help]
              [-V | --version] <command> [<filenames | paths | Fetchwarfiles>]

=head2 L<Create a new fetchware package.|/new>

    fetchware new <name of program>

    ... Read the printed explanations...

    ... And answer the questions fetchware asks you appropriately and then press
    enter.

=head2 L<Install a new fetchware package.|/install>

    fetchware install name-of-program.Fetchwarefile

    # And you can use a .fpkg fetchware package instead of a Fetchwarefile if
    # you have one.
    fetchware install name-of-program.fpkg

=head2 L<Upgrade a specific fetchware package.|/upgrade>

    fetchware upgrade <name of already installed program>

    # Use fetchware list to see a list of already installed programs.
    fetchware list

=head2 L<Upgrade B<all> installed fetchware packages.|/upgrade-all>

    fetchware upgrade-all

=head2 L<Uninstall an installed fetchware package.|/uninstall>

    # Requires a "uninstall" make target, or customization of its Fetchwarefile
    # to specify what specific C<uninstall_commands> will uninstall this package.
    fetchware uninstall <name of already installed program>

=head2 L<List all installed fetchware packages.|/list>

    fetchware list 

    # Pipe to grep if you want to search for something specific.
    fetchware list | grep <something specific>

=head2 L<"Look" inside a fetchware package.|/look>

    fetchware look <name-of-program.fpkg> | <name-of-program.Fetchwarefile>

=head2 Put this in your /etc/cron.daily to make fetchware check for updates every night

    #!/bin/sh
    # Update all already installed fetchware packages.
    fetchware upgrade-all

=head2 Or use crontab -e to put this in a user crontab if you don't want to fetchware system wide

    # Check for updates using fetchware every night at 2:30AM.
    # Minute   Hour   Day of Month     Month          Day of Week     Command    
    # (0-59)  (0-23)     (1-31)  (1-12 or Jan-Dec) (0-6 or Sun-Sat)
        30      2          *              *               *           fetchware upgrade-all

=head1 MOTIVATION

While sysadmining I liked to install my own compiled from source versions of
popular programs like Apache, MySQL, or Perl without threading. However, doing
so means that you have to manually recompile everytime a new security hole comes
out, which is annoyingly frequent for Apache. So, fetchware was created to bring
the power of package management to source code distributions.

=head1 DESCRIPTION

Fetchware is a package manager for source code distributions. It takes advantage
of the fact that coincidentially I<most> source code distributions follow the same
conventions. Most use FTP and HTTP mirrors. Most use AutoTools or at least just
a few commands that you execute in sequence to configure, build, and install the
program.

Fetchware harnesses these conventions to create a powerful and flexible package
manager for source code distributions. It includes a simple, powerful, and
flexible configuration syntax stored in files called C<Fetchwarefile>s. These
C<Fetchwarefile>s specify the required mandatory configuration options,
C<program>, C<lookup>, C<mirror>, and a method of verifying your program. And
they also specify any additional optional configuration options.

To create a new Fetchwarefile to install a source code distribution use the
L<fetchware new|/new> command. It will ask you a bunch of questions, and based
on your answers and fetchware's assumptions fetchware will automagically create
a new Fetchwarefile for you. Then it will ask if you would like fetchware to
install it for you.

If your source code distribution exceeds fetchware's new command's capabilities,
then see the section L<MANUALLY CREATING A App::Fetchware FETCHWAREFILE> in
L<App::Fetchware>. It details how to create a Fetchwarefile manually in a text
editor of your choice.

Fetchware's commands are described next followed by its options. Following that
is the section L<HOW FETCHWARE WORKS>, which describes in some detail how
Fetchware does its magic, and documents how it all fits together.

See L<App::Fetchware> for more information on fetchware's Fetchwarefile syntax:

=over

=item *

L<App::Fetchware/"MANUALLY CREATING A App::Fetchware FETCHWAREFILE"> - Describes
how to create a appropriate Fetchwarefile manually using a text editor. This
can be skipped. You should try fetchware's L<new command|/new> first.

=item *

L<App::Fetchware/"USING YOUR App::Fetchwarefile WITH FETCHWARE"> - Shows how to
use your newly created fetchwarefile with fetchware.

=item *

L<App::Fetchware/"App::Fetcwhare'S FETCHWAREFILE CONFIGURATION OPTIONS"> - Details
all of fetchware's configuration options that you can use to further customize
your Fetchwarefile.

=item *

L<App::Fetchware/"FURTHER CUSTOMIZING YOUR FETCHWAREFILE"> - Shows you how to use
embed Perl inside your Fetchwarefile to change fetchware's behavior as needed
to make fetchware work with programs that use different conventions and
assumptions that fetchware makes.

=item *

L<App::Fetchware/"EXAMPLE FETCHWAREFILES"> - Details how to customize a
Fetchwarefile for popular programs such as Apache, Nginx, PHP, MariaDB, and
Postgres.

=item *

L<App::Fetchware/"CREATING A FETCHWARE EXTENSION"> - Details how to replace the
module that implements fetchware's behavior, App::Fetchware, with a completely
different module implementing completely different behavior. These fetchware
extensions can even be shared with everyone else on CPAN. See
L<App::FetchwareX::HTMLPageSync> for an example.

=back

=head1 COMMANDS

Each command maps to one operation a package manager can do. C<install>,
C<upgrade>, and C<uninstall>. There is also C<new> to create new Fetchwarefiles
without bothering with a text editor. And fetchware's way of upgrading all
packages with C<upgrade-all>. Fetchware can also list its installed packages
with C<list>. And C<look> is similar to Perl's original CPAN client's look
command that downloads and unarchives the package, so you can "look" at it.
The C<clean> command deletes any unused, leftover temporary files and
directories Fetchware has unintentionally left in your system's temporary
directory.

=head2 new

    fetchware new <name of program>

C<new> asks you a bunch of questions, and uses the answers you provide in
addition to the contents of the directory listing fetchware downloads based on
the C<lookup_url> you give fetchware, to create a Fetchwarefile for you with all
the mandatory options filled in. It also gives you the opportunity to add any
additional options that you may want to use. C<new> also gives you a chance to
edit the Fetchwarefile it created for you manually in your editor. Set the
C<EDITOR> environment variable to pick which editor to use, or leave it empty,
and fetchware will ask you what editor you would like to use.

C<new> finishes by asking if you would like fetchware to go ahead and install
the Fetchwarefile it has just created for you. If you say yes, then fetchware
will install it, or if you say no, fetchware will skip installing it for you,
and print out the path to the Fetchwarefile it just created for you.

You can install that Fetchwarefile later with:

    fetchware install path/to/your/some-program.Fetchwarefile

For more details about fetchware's configuration files Fetchwarefiles see
L<App::Fetchware/CREATING A App::Fetchware FETCHWAREFILE>

=head2 install

    fetchware install <path to program.Fetchwarefile>

    fetchware install <path to program.fpkg>

C<install> parses the given Fetchwarefile or uses the embeded Fetchwarefile
inside the fetchware package you specify. Then C<install> I<install>s your
program as you specified in your Fetchwarefile. 

By default executes the commands:

=over

=item 1. C<./configure>

=item 2. C<make>

=item 3. C<make install>

=back

You can use the Fetchwarefile configuration options C<make_options> and
C<configure_options> to customize how your program is build and installed.
C<make_options> specifies command line options that are added before C<make> is
run each time by Fetchware. And C<configure_options> specifies options to the
first AutoTools command, C<./configure> that customizes how your program is
built and installed.

    ...
    prefix '/usr/local';
    make_options '-j 4';
    configure_options '--enable-mpm --enable-so';

Or, you can use Fetchwarefile's more generic configuraton options. You cannot
use both C<build_options> and any of C<prefix>, C<make_options>,
C<configure_options> at the same time.  C<build_commands> specifies alternate
commands to build the program replacing C<./configure> and C<make>, and you can
also specify the C<install_commands> to replace C<make install> with some other
command or commands that install your program.

    ...
    # build_commands and install_commands Fetchwarefile example.
    build_commands './Configure', 'make';

    install_commands 'make test', 'make install';

    ...

See L<App::Fetchware/"App::Fetchware'S FETCHWAREFILE CONFIGURATION OPTIONS> for
more details on these configuration options.

=head2 upgrade

    fetchware upgrade <already installed fetchware package>

C<upgrade> only upgrades already installed fetchware packages. You cannot
upgrade a Fetchwarefile only an already installed fetchware package. To see a
list of already installed fetchware packages run C<fetchware list>, or pipe it
through L<grep(1)>

    fetchware list | grep <keyword>

=head2 upgrade-all

    fetchware upgrade-all

C<upgrade-all> takes no arguments. Instead, it loops over the list of installed
programs as shown in C<fetchware list> and runs C<upgrade> on each one to upgrade all
currently installed programs.

=head2 uninstall

C<uninstall> removes all components of a currently installed program.
Afterwards, that program won't show up in a C<fetchware list> anymore.

=over

=item B<WARNING>

C<uninstall> is only capable of uninstalling programs that maintain a
C<uninstall> make target. For example, C<ctags> has a C<make uninstall>, while
Apache does not; therefore, without a prefix, fetchware can uninstall ctags, but
it cannot uninstall Apache.

The easiest way to be able to uninstall a program you install with fetchware
that does not have a C<make uninstall> is to use the C<prefix> configuration
option to use a separate prefix that everything is installed into this
directory. Then you could specify a custom C<uninstall_commands> that would
delete everything in that directory:

    # Set prefix so apache can be easily uninstalled.
    prefix '/usr/local/apache';

    # Set uninstall_commands to delete everything in the prefix directory when
    # apache is uninstalled.
    uninstall_commands 'rm -r /usr/local/apache';

Then when you uninstall apache, fetchware deletes its associated files, which
may include your Web site's Web files, so back them up before hand if you need to
keep them.

The other way around this limitation is to use one of the following programs
that use a cool C<LD_PRELOAD> trick to watch what files C<make install> or its
equivelent copy, and where they are copied to. Then these files are put into
some sort of vendor-specific package such as apt-get or rpm.

=over

=item L<checkinstall|http://www.debian-administration.org/articles/147>

Run like C<checkinstall make install> will detect what files are copied where
during installation, and will create a slackware, debian, or redhat package
based on this information.

=item L<paco|http://paco.sourceforge.net/>

Provides very similar functionality to fetchware, but lacks fetchware's lookup
and verify mechanisms. Includes its own package management functionality.

=back

=back

As far as fetchware one day supporting some sort of hack like checkinstall or
paco use, I'm against it. I'd prefer everyone just adding a C<make uninstall> to
their Makefiles. But it is on my todo list, and I may add similar functionality
in the future, but I'll make no promises. Until then consider using the
C<prefix> and C<uninstall_commands> hack.

=head2 list

    fetchware list

    fetchware list | grep <what are you looking for?>

C<list> just prints out the names of all fetchware packages that have been
installed. It takes no arguments, and currently does not support listing only
packages that match a certain criteria. However, you can just pipe it to
L<grep(1)> to using a regex to limit which packages you're looking for.

=head2 look

    fetchware look <package name>

C<look> looks up the specified program using your C<lookup_url>, downloads it,
verifies it, and unarchives it. Then it prints out the location of the
unarchived program, so you can take a look at its code, or install it manually
if you would like to.

=head2 clean

    fetchware clean

C<clean> deletes all fetchware temporary files and directories to clean up your
system temporary directory.

You can also specify one or more arguments to C<fetchware clean> to specify what
directories you want fetchware to search for fetchware's left over temp files to
clean up.

=head2 help

Prints out a brief screen full of help messages reminding you of fetchware's
command-line syntax.

=head1 OPTIONS

Fetchware's configuration file options are detailed below.

Most of its options are stored in its configuration file. If none of these
options suite what you need fetchware to do, consider using its Fetchwarefile
to meet your needs. See
L<App::Fetchware/"App::Fetchware'S FETCHWAREFILE CONFIGURATION OPTIONS>

=head2 -v or --verbose

    fetchware -v install <some-program.Fetchwarefile>

Fetchware's -v or --verbose option turns on verbose logging, which prints to
STDOUT additional information regarding what fetchware is doing and how
fetchware does it.

If you have any problems with your Fetchwarefile, then you could turn on verbose
mode to have fetchware log additional messages to STDOUT to aid in debugging
your Fetchwarefile.

=head2 -q or --quite

    fetchware -q upgrade <some-program>

The -q or --quite option tells fetchware to B<not> log anything at all.
Fetchware will even prevent any commands it runs from printing output to your
terminal's STDOUT to avoid cluttering up your screen.

Any warnings or error messages are still printed to STDERR.

To determine if fetchware succeeded or failed you can test its exit status:

    fetchware -q upgrade <some-program>

    echo $?
    0

Fetchware exits 0 for success and non-zero for failure.

=head2 -V or --version

Prints out a short message and says what version of fetchware is running.

=head2 -h or -? or --help

Prints out a brief screen full of help messages reminding you of fetchware's
command-line syntax.

=head1 HOW FETCHWARE WORKS

Fetchware works by having fetchware, the C<bin/fetchware> file and
App::Fetchware Perl package, do all of the "package manager" stuff: 

=over

=item *

Creating fetchware packages (create_fetchware_package())

=item *

Copying fetchware packages to the fetchware database
(copy_fpkg_to_fpkg_database())

=item *

Creating and managing the fetchware database
(determine_fetchware_package_path(), extract_fetchwarefile(),
and fetchware_database_path())

=item *

uninstalling installed packages from the fetchware database
(uninstall_fetchware_package_from_database())

=back

Fetchware I<delegates> all of the specifics on how to install, upgrade, and
uninstall the fetchware packages that fetchware manages to App::Fetchware or a
App::Fetchware extension:

=over

=item *

Implement Fetchware's new command's Q&A wizard interface (new() and new_install())

=item *

Checking Fetchwarefile's high-level syntax before execution (check_syntax())

=item *

Lookup to see if a new version is available (lookup())

=item *

Downloading the archive (download())

=item *

Verifying that the downloaded file is the same one the author uploaded (verify())

=item *

Unarchiving the package (unarchive())

=item *

Building and installing it (build() and install())

=item *

Uninstalling any already installed fetchware package (uninstall())

=item *

Determining if a newer version is available (upgrade())

=item *

Some before and after hooks (start() and end()).

=back

=head2 How fetchware's commands work

Fetchware's commands work by using fetchware's API, described in the section
L<INTERNAL LIBRARY SUBROUTINES>, to manage the package manager stuff. And
fetchware I<delegates> the heavy lifting of the steps needed to install,
upgrade, and uninstall fetchware packages to L<App::Fetchware> or a
L<App::Fetchware extension|App::Fetchware/"CREATING A FETCHWARE EXTENSION">.

=over

=item new

C<new> just asks the user a bunch of questions, and gives them an opportunity to
answer questions. Then it uses your answers to generate a Fetchwarefile for you,
so that you don't have to mess with creating one manually in a text editor.

=item install

Fetchware's install runs whatever fetchware API subroutines it needs to use, see
the section L<INTERNAL LIBRARY SUBROUTINES> for more. Then, install() will parse
a user provided Fetchwarefile or a Fetchwarefile fetchware finds in a fetchware
package. The act of parsing the Fetchwarefile will import the App::Fetchware API
subroutines into fetchware's namespace. This gives fetchware access to
App::Fetchwares API or whatever extension may have been used. Then, the API
subroutines are run providing whatever arguments they need and storing whatever
their important return values may be in a variable to probably later be given to
a later API subroutine as an argument.

=item upgrade

Cleverly reusues the same API subroutines that install uses, but in the middle
of all that uses the upgrade() API subroutine to determine if a newer version is
available. The upgrade() API subroutine allows Fetchware extensions to modify
how Fetcwhare determines if a new version is available to support using git or
something else to determine this.

=item uninstall

Uninstall parses the Fetcwharefile of the installed pacakges you specified. Then
it runs whatever C<uninstall_commands> you specified or the default,
C<make uninstall> if you specified none. Then the installed package is deleted
from the fetchware database.

=item list

List just globs all files in the fetchware database directory as returned by
fetchware_database_path(), and prints them to STDOUT. It does not let you
specify a Perl regex, or a keyword or anything yet, because I'm currently unsure
about the security ramifications of doing so. This feature may be added in the
future.

=item look

look just does the first part of install(). It parses whatever Fetchwarefile it
gets passed to it, then it does the start(), lookup(), download(), verify(), and
unarchive() parts of install(). Then look prints the path of this directory, and
exits.

=item clean

Clean just deletes all fetchware temp files and directories in the system
temp_dir. These files and directories all start with C<fetchware-*> or
C<Fetchwarefile-*>.

=item help

Just prints a simple, short, concise help message.

=back

=head2 How fetchware interfaces with App::Fetchware

Fetchware interfaces with App::Fetchware using the parse_fetchwarefile() API
subroutine. This subroutine simply eval()'s your Fetchwarefile and traps any
errors, and then rethrows that exception adding a helpful message about what
happened in addition to passing along the original problem from Perl.

The act of eval()ing your Fetchwarefile causes Perl to parse and execute as it
would any other Perl program. Only because its inside an eval any subroutines
that are imported are imported in the the caller of eval()'s package. In this
case fetchware.

Fetchware takes advantage of this by requiring all Fetchwarefile's to have a
C<use App::Fetchware...;> line. This line is what imports the default imports of
App::Fetchware into fetchware, which include App::Fetchware's API subroutines.

=head2 How fetchware intefaces with a fetchware extension

As explained above parse_fetchwarefile() eval()'s your Fetchwarefile, and this
causes Perl to parse and execute it. And any imports are imported into the
caller's package, which is fetchware.

That's how fetchware receives App::Fetchware's API subroutines, and it is also
how fetchware receives a fetchware extensions API subroutines, the fetchware
extension is simply use()d inside your Fetchwarefile instead of the default one
of App::Fetchware. Instead of:

    use App::Fetchware;

You would write:

    use App::FetchwareX::HTMLPageSync;

To use the fetchware extension HTMLPageSync.

=head1 INTERNAL SUBROUTINES IMPLEMENTING FETCHWARE COMMANDS

Below are all of subroutines that implement fetchware's main command line
options such as C<fetchware install> or C<fetchware new> and so on. These main
subroutines are called based on the options you pass to fetchware from the
command line.

=head2 cmd_install()

    my $installed_fetchware_package_path = cmd_install($filename|@ARGV)

cmd_install() implements fetchware's install command, which installs a package
based on the specified Fetchwarefile or fetchware package.

=head2 cmd_uninstall()

    my $uninstall_package_path = cmd_uninstall($uninstall_package_path|@ARGV);

Uninstalls the given package. Note the given package does B<not> have to be an
exact match, but it does have to be unique if you have two versions of the same
software installed such as httpd-2.2 and httpd-2.4. In that case you'd have to
specify the version number as well.

=over

=item LIMITATION

cmd_uninstall() unlike cmd_install() does not accept Fetchwarefiles as an
argument to uninstall a fetchware package! Instead, you must provide the name
and perhaps the name and version number of an already installed software
package. For a list of such package names just run C<fetchware list> to list all
installed fetchware packages.

=back

=over

=item NOTICE

cmd_uninstall() does B<not> call drop_privs() to drop privileges, because it
needs root privileges to copy the installed fetchware package from the system
level fetchware package database, and it needs root to actually be able to
delete files in system level directories.

=back

=head2 cmd_new()

    my $fetchware_package_path = cmd_new($program_name);

cmd_new() implements fetchware's new command. See
L<App::Fetchware/CREATING A App::Fetchware FETCHWAREFILE> for detailed
documentation for the specifics of the new command. This chunk of POD is about
its implementation. cmd_new() calls the new() and new_install() App::Fetchware
API subroutines, which in turn call a bunch of helper subroutines that implement
the algorithm fetchware uses to build new Fetchwarefiles automagically for the
user. The algorithm is dead stupid:

=over

=item 1. Ask for lookup_url & download it.

=item 2. Analyze the contents of the output from the lookup_url.

=item 3. Build the Fetchwarefile according to the output.

=item 4. Ask other questions as needed.

=back

cmd_new() uses Term::UI, which in turn uses Term::ReadLine to implement the
character based question and anwser wizard interface.

cmd_new() also asks the user if they would like fetchware to build and install
their new program based on their newly created Fetchwarefile. If they answer
yes, it builds and installs it, and if not, cmd_new() returns the path to the
created Fetchwarefile for them.

=head2 cmd_upgrade()

    my $installed_fetchware_package_path = cmd_upgrade($upgrade_name);
    'No upgrade needed.' = cmd_upgrade($upgrade_name);

Subroutine implementing Fetchware's upgrade command. This subroutine and command
upgrade one and only one package that must be specified on the command line as
well.

=head2 cmd_upgrade_all()

    my @upgraded_packages = cmd_upgrade_all();
    'No upgrade needed.' = cmd_upgrade_all();

Implements the C<fetchware upgrade-all> command, which upgrades all installed
packages simply by looping over the fetchware database and running cmd_upgrade()
on each one.

Returns a list of the packages that were upgraded or the string
'No upgrade needed.' if no packages were upgraded.

=head2 cmd_look()

    my $look_path = cmd_look($filename);

Looks up the latest version of the specified Fetchwarefile or fetchware package,
and downloads, verifies, and unarchives the specified source code distribution,
and then prints out the location of this archive.

=over

=item LIMITATION

cmd_look() unarchive's the desired source code distribution into the same sort
of temporary directory that fetchware itself uses during regular installs or
upgrades. This cannot be changed, but after fetchware creates this directory it
outputs its path, so that you can cd to it, and do whatever you need to it. You
could also move it to where you want it to be as well. Remember to delete the
fetchware-$PID-randomeletters style directory that it was stored in, or just run
fetchware clean when you are finished working with it.

=back

=head2 cmd_list()

    cmd_list();

Lists B<all> of the packages fetchware has stored in its
fetchware_database_path().

=over

=item LIMITATION

There is no ability to limit this listing with a
regex currently, so just pipe it to grep for now. Obviously in the future this
ability could be added, but I'm currently unclear about its security
ramifications. So for now, I'll hold out until I study what ack does.

=back

=head2 cmd_clean()

    cmd_clean(@ARGV);

cmd_clean() implements fetchware's clean command, which deletes any left over
fetchware temporary directories from your system's temorary directory. It
cleverly uses locking to ensure that cmd_clean() does B<not> delete a temporary
directory that is still being used by a running fetchware process.

cmd_clean() also deletes any temporary files that Fetchware uses that are
regular files not directories. These start with either C<fetchware-*> or
C<Fetchwarefile-*> for Fetchwarefiles cmd_new() creates for the user.

flock() is used along with C<LOCK_{EX,NB}> from L<Fcntl>. C<LOCK_EX> gets an
exclusive lock (only current process who got lock can access the file, and
C<LOCK_NB>, which does a non-blocking attempt to get a lock returning success at
getting the lock or not getting the lock immediately. flock() is used on a semaphore
file called C<fetchware.sem> it is a useless empty file, that is only used for
locking each fetchware temporary directory.

flock() is used, because if the fetchware process using the lock closes the
file or the process dies, exits itself, or is killed even sith C<SIGKILL>, the
lock is released automatically by the OS and/or system libraries.

cmd_clean() simply attempts to get a lock, and if it does it deletes that
particular fetchware temporary directory. If it fails to get the exclusive lock,
then it probably means that that fetchware temporary directory is still being
used by another fetchware process, so that directory is skipped.

create_tempdir() and cleanup_tempdir() create and lock the fetchware semaphore
lock file, and close and unlock it as they are executed by start() and end().

cmd_clean() via @ARGV, which run() calls it with, takes the arguments it
receives as paths to whatever temporary directories it should clean.

=head2 cmd_help()

    cmd_help();

Prints a help message to C<STDOUT> listing usage, all command options, and
examples.

And then C<exit()>s with an exit status of 0 indicating success.

=head1 INTERNAL LIBRARY SUBROUTINES

Below are the helper subroutines used by install(), uninstall(), new(), and so
on.

=head2 parse_fetchwarefile()

    'Evaled config file successfully' = parse_fetchwarefile(\$fetchwarefile);

Eval's the \$fetchwarefile to effectively "parse" it.

The only checking for the $fetchwarefile it does is that it is a scalar ref, and
that it has at least one line beginning with C<use App::Fetchware>.

It also checks to see that the eval of the provided $fetchwarefile actually
winds up importing all of fetchware's API subroutines into fetchware's namespace.

Then it runs check_syntax() to check the $fetchwarefile's syntax. Typically this
only involves running config() a bunch of times to check that configuration
options that don't belong together arn't used together.

Returns true on success and dies with an error message if it fails.

=head2 create_fetchware_package()

    # Most uses should just use this.
    my $fetchware_package_full_path
        =
        create_fetchware_package($fetchwarefile, $unarchived_package_path);


    # But some uses in test suites thanks to safe_open() need to be able to
    # specify where they should write the new fetchware package's path to.
    my $fetchware_package_full_path
        =
        create_fetchware_package($fetchwarefile,
            $unarchived_package_path
            $path_to_new_fpkg);

Creates a fetchware package, ending in .fpkg, using $unarchived_package_path, as
the directory to archive. Also, adds the C<Fetchwarefile> stored in the
scalar $fetchwarefile argument to the fethware package that is created.

You can specify an optional $path_to_new_fpkg, which will be a directory where
create_fetchware_package() will write the new fetchware package to.

Returns the full pathname to the fetchware package that was created.

=head2 fetchware_database_path()

    my $fetchware_database_path = fetchware_database_path();

Returns the correct path for the fetchware package database based on operating
system and if super user or not.

Also, supports user customizable fetchware database paths via the
C<FETCHWARE_DATABASE_PATH> environment variable, and the
C<fetchware_database_path> Fetchwarefile configuration file. If both are
specified C<fetchware_database_path> is prefered over
C<FETCHWARE_DATABASE_PATH>.

=head2 determine_fetchware_package_path()

    my $fetchware_package_filename = determine_fetchware_package_path($fetchware_package);

Looks up the $fetchware_package in C<fetchware_database_path()>, and returns the
full path to that given $fetchware_package.

=over 
=item NOTE
determine_fetchware_package_path() could potentially come up with more than one
result if you have multiple versions of apache or other similarly named packages
installed at the same time. If this happens an exception is thrown asking the
user to specify a more specific name to query the fetchware database with.

=back

=head2 extract_fetchwarefile()

    my $fetchwarefile = extract_fetchwarefile($fetchware_package_path);

Extracts out the Fetchwarefile of the provided fetchware package as specified by
$fetchware_package_path, and returns the content of the Fetchwarefile as a
scalar reference. Throws an exception if it it fails.

=head2 copy_fpkg_to_fpkg_database()

    my $fetchware_package_path = copy_fpkg_to_fpkg_database($fetchwarefile_path);

Installs (just copies) the specified fetchware package to the fetchware
database, which is /var/log/fetchware on UNIX, C:\FETCHWARE on Windows with
root or Administrator. All others are whatever L<File::HomeDir> says. For Unix
or Unix-like systems such as linux, L<File::HomeDir> will put your own user
fetchware database independent of the system-wide one in C</var/log/fetchware>
in C<~/.local/share/Perl/dist/fetchware/>. This correctly follows some sort of
standard. XDG or FreeDesktop perhaps?

Creates the directory the fetchware database is stored in if it does not already
exist.

Returns the full path of the copied fetchware package.

=head2 uninstall_fetchware_package_from_database()

    uninstall_fetchware_package_from_database($uninstall_package_name);

Deletes the specified $uninstall_package_name from the fetchware package
database. Throws an exception on error.

=head1 THE FETCHWARE PACKAGE

Like other package managers, fetchware has its own package format:

=over

=item *

It ends with a C<.fpkg> file extension.

=item *

The package path, the location of the unarchived downloaded program, is simply
archived again using L<Archive::Tar>, and compressed with gzip.

=item *

But before the package path is archived the currently used Fetchwarefile is
copied into the current directory, so that it is included with your fetchware
package:

    ./Fetchwarefile
    httpd-2.2.x
    httpd-2.2.x/README
    httpd-2.2.x/INSTALL
    ....

=back

This simple package format was chosen instead of using a native package format
such as a Microsoft C<.msi> package, Slackware format, rpm format, C<.deb>
format, and so on. Thanks to distros like Gentoo and Arch, there are even more
formats now. Also, each version of BSD has its own package format, and each
version of commerical UNIX has its own package format too. ...It was easier to
create a new format, then deal with all of the existing ones.

This custom package format is unique, bare bones, and retains all of the power
that installing the software from source manaully gives you.

=over

=item *

Simple, and retains backward compatibility with manual installation.

=item *

The package format includes the source code, so it can be recompiled if you
move the fetchware package to an architecture different than the one it was
compiled on.

=item *

You can specify whatever configure and build options you want, so you're not
stuck with whatever your distro's package maintainer has chosen.

=back

=head1 FAQ

=head2 How does fetchware's database work?

The design of fetchware's database was copied after Slackware's package database
design. In Slackware each package is a file in C</var/log/packages>, an
example: C</var/log/packages/coreutils-8.14-x86_64_slack13.37>. And inside that
file is a list of files, whoose names are the locations of all of the files that
this Slackware package installed. This format is really simple and flexible.

Fetchware's database is simply the directory C</var/log/fetchware> (on Unix when
run as root), or whatever File::HomeDir recommends. When packages are installed
the final version of that package that ends with C<.fpkg> is copied to your
fetchware database path. So after you install apache your fetchware database
will look like:

    ls /var/log/fetchware
    httpd-2.4.3.fpkg

It's not a real database or anything cool like that. It is simply a directory
containting a list of fetchware packages that have been installed. However, this
directory is managed by fetchware, and should not be messed with unless you are
sure of what you are doing.

=head2 What exactly is a fetchware package?

A fetchware package is a gziped tar archive with its file extension changed to
C<.fpkg>. This archive consists of the package that was downloaded in addition
to your Fetchwarefile. For example.

    tar tvf httpd-2.4.3.fpkg
    ./Fetchwarefile
    httpd-2.4.3/README
    httpd-2.4.3/...
    ...

See the section L<THE FETCHWARE PACKAGE> to see all of the cool things you can
do with them.

=head1 ERRORS

As with the rest of Fetchware, fetchware does not return any
error codes; instead, all errors are die()'d if it's fetchware's
error, or croak()'d if its the caller's fault.

=head1 CAVEATS

=over

=item WINDOWS COMPATIBILITY

Fetchware was written on Linux and tested by its author B<only> on Linux.
However, it should work on popular Unixes without any changes. But it has B<not>
been ported or tested on Windows yet, so it may work, or parts of it may work,
but some might not. However, I have used File::Spec and Path::Class to support
path and file manipulation accross all Perl-supported platorms, so that code
should work on Windows. I intend to add Windows support, and add tests for Windows
in the future, but for now it is unsupported, but may work. This is likely to
improve in the future.

=back

=head1 SEE ALSO

L<pkgsrc|http://www.pkgsrc.org/>, L<paco|http://paco.sourceforge.net/>,
L<porg|http://porg.sourceforge.net/>, L<slackbuilds|http://slackbuilds.org/>,
L<sbopkg|http://sbopkg.org/>, L<fpm|https://github.com/jordansissel/fpm>,
L<checkinstall|http://asic-linux.com.mx/~izto/checkinstall/>

=head1 AUTHOR

David Yingling <deeelwy@gmail.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2016 by David Yingling.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut

__END__











###BUGALERT###Should I implement dryrun functionality???
#=head2 -d or --dryrun
#
#Just prints out what fetchware will do when you run fetchware. No external
#commands are run, and fetchware itself doesn't read or create any files
#including any temporary directories.
#
####BUGALERT### dryrun functionality is not implemnted.
## Should be easy to implement in run_prog(), and create a sub like
## skip_all_unless_release_testing() to test for -d.

###BUGALERT###What about -f and --force too??????
#=head2 -f or --force
#
####BUGALERT### Do I want or need --force???
#



###BUGALERT###Actually add better examples of these awesome features and more
#details.
##TODO##These unique qualities give fetchware unique abilites that other package
##TODO##managers don't have.
##TODO##
##TODO##=head2 Deploying with Fetchware Packages
##TODO##
##TODO##Just like how you can create a repository for custom rpms or C<.deb>s, you can
##TODO##do the same with fetchware packages, which brings with it the full power of your
##TODO##software's build environment, and fetchware's Fetchwarefile's support for
##TODO##embeding Perl giving you great flexibility.
##TODO##
##TODO#####BUGALERT### Actually give a useful example of this!
##TODO##
##TODO##=head2 Cross-platform Deployment with Fetchware Packages
##TODO##
##TODO##Unlike other package formats, fetchware's is cross-platform, and supports the
##TODO##same platforms, the software's build environment does. You can take advantage of
##TODO##this to deploy your software as a fetchware package across whatever number of
##TODO##architectures your software's build system supports.
##TODO##
##TODO##Note, it will have to be built on each platform when the package is installed.
##TODO##
##TODO#####BUGALERT### Give more details about this!
##TODO##
##TODO##=head2 Deploying to Systems without a Build Environment
##TODO##
##TODO#####BUGALERT### This doesn't actually work now!!!
##TODO##
##TODO##Fetchware is flexible enough, using the C<no_rebuild> and C<no_lookup>
##TODO##configuration options, to be configured and compiled on one computer, and then
##TODO##B<only> installed on any additional servers that install that fetchware package.
##TODO##
##TODO##So, instead of needing gcc, devel versions of libraries, and make, the servers
##TODO##you deploy your fetchware package on compile using these options will only need
##TODO##make installed instead of an entire build environment (Actually, they'll need
##TODO##whatever commands your Fetchwarefile's C<install_commands> uses, which is
##TODO##C<make install> by default.
##TODO##
##TODO#####BUGALERT### Actually implemente this cool idea, and fix docs to say that to
##TODO###actually lookup and actually build the package use the --force option, which
##TODO###also needs to be implemented.









###BUGALERT### Actually implement croak or more likely confess() support!!!