You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
764 lines
23 KiB
764 lines
23 KiB
package ksb::Util 0.20; |
|
|
|
# Useful utilities, which are exported into the calling module's namespace by default. |
|
|
|
use 5.014; # Needed for state keyword |
|
use strict; |
|
use warnings; |
|
|
|
use Carp qw(cluck); |
|
use Scalar::Util qw(blessed); |
|
use File::Path qw(make_path); |
|
use File::Find; |
|
use Cwd qw(getcwd); |
|
use Errno qw(:POSIX); |
|
use Digest::MD5; |
|
use HTTP::Tiny; |
|
|
|
use ksb::Debug; |
|
use ksb::Version qw(scriptVersion); |
|
use ksb::BuildException; |
|
|
|
use Exporter qw(import); # Use Exporter's import method |
|
our @EXPORT = qw(list_has assert_isa assert_in any unique_items |
|
croak_runtime croak_internal had_an_exception make_exception |
|
download_file absPathToExecutable |
|
fileDigestMD5 log_command disable_locale_message_translation |
|
split_quoted_on_whitespace safe_unlink safe_system p_chdir |
|
pretend_open safe_rmtree get_list_digest is_dir_empty |
|
super_mkdir filter_program_output prettify_seconds); |
|
|
|
# Function to work around a Perl language limitation. |
|
# First parameter is a reference to the list to search. ALWAYS. |
|
# Second parameter is the value to search for. |
|
# Returns true if the value is in the list |
|
sub list_has |
|
{ |
|
my ($listRef, $value) = @_; |
|
my @list = @{$listRef}; |
|
|
|
return scalar grep { "$_" eq "$value" } (@list); |
|
} |
|
|
|
# Subroutine to return the path to the given executable based on the |
|
# current PATH. e.g. if you pass make you could get '/usr/bin/make'. If |
|
# the executable is not found undef is returned. |
|
# |
|
# This assumes that the module environment has already been updated since |
|
# binpath doesn't exactly correspond to $ENV{'PATH'}. |
|
sub absPathToExecutable |
|
{ |
|
my $prog = shift; |
|
my @paths = split(/:/, $ENV{'PATH'}); |
|
|
|
# If it starts with a / the path is already absolute. |
|
return $prog if $prog =~ /^\//; |
|
|
|
for my $path (@paths) |
|
{ |
|
return "$path/$prog" if (-x "$path/$prog"); |
|
} |
|
|
|
return undef; |
|
} |
|
|
|
# Returns a Perl object worth "die"ing for. (i.e. can be given to the die |
|
# function and handled appropriately later with an eval). The returned |
|
# reference will be an instance of ksb::BuildException. The actual exception |
|
# type is passed in as the first parameter (as a string), and can be retrieved |
|
# from the object later using the 'exception_type' key, and the message is |
|
# returned as 'message' |
|
# |
|
# First parameter: Exception type. Recommended are one of: Config, Internal |
|
# (for logic errors), Runtime (other runtime errors which are not logic |
|
# bugs in kdesrc-build), or just leave blank for 'Exception'. |
|
# Second parameter: Message to show to user |
|
# Return: Reference to the exception object suitable for giving to "die" |
|
sub make_exception |
|
{ |
|
my $exception_type = shift // 'Exception'; |
|
my $message = shift; |
|
my $levels = shift // 0; # Allow for more levels to be removed from bt |
|
|
|
# Remove this subroutine from the backtrace |
|
local $Carp::CarpLevel = 1 + $levels; |
|
|
|
$message = Carp::cluck($message) if $exception_type eq 'Internal'; |
|
return ksb::BuildException->new($exception_type, $message); |
|
} |
|
|
|
# Helper function to return $@ if $@ is a ksb::BuildException. |
|
# |
|
# This function assumes that an eval block had just been used in order to set or |
|
# clear $@ as appropriate. |
|
sub had_an_exception |
|
{ |
|
if ($@ && ref $@ && $@->isa('ksb::BuildException')) { |
|
return $@; |
|
} |
|
|
|
return; |
|
} |
|
|
|
# Should be used for "runtime errors" (i.e. unrecoverable runtime problems that |
|
# don't indicate a bug in the program itself). |
|
sub croak_runtime |
|
{ |
|
die (make_exception('Runtime', $_[0], 1)); |
|
} |
|
|
|
# Should be used for "logic errors" (i.e. impossibilities in program state, things |
|
# that shouldn't be possible no matter what input is fed at runtime) |
|
sub croak_internal |
|
{ |
|
die (make_exception('Internal', $_[0], 1)); |
|
} |
|
|
|
# Throws an exception if the first parameter is not an object at all, or if |
|
# it is not an object of the type given by the second parameter (which |
|
# should be a string of the class name. There is no return value; |
|
sub assert_isa |
|
{ |
|
my ($obj, $class) = @_; |
|
|
|
if (!blessed($obj) || !$obj->isa($class)) { |
|
croak_internal("$obj is not of type $class, but of type " . ref($obj)); |
|
} |
|
|
|
return $obj; |
|
} |
|
|
|
# Throws an exception if the first parameter is not included in the |
|
# provided list of possible alternatives. The list of alternatives must |
|
# be passed as a reference, as the second parameter. |
|
sub assert_in |
|
{ |
|
my ($val, $listRef) = @_; |
|
|
|
if (!list_has($listRef, $val)) { |
|
croak_runtime("$val is not a permissible value for its argument"); |
|
} |
|
|
|
return $val; |
|
} |
|
|
|
# Subroutine to unlink the given symlink if global-pretend isn't set. |
|
sub safe_unlink |
|
{ |
|
if (pretending()) |
|
{ |
|
pretend ("\tWould have unlinked ", shift, "."); |
|
return 1; # Return true |
|
} |
|
|
|
return unlink (shift); |
|
} |
|
|
|
# Subroutine to execute the system call on the given list if the pretend |
|
# global option is not set. |
|
# |
|
# Returns the shell error code, so 0 means success, non-zero means failure. |
|
sub safe_system(@) |
|
{ |
|
if (!pretending()) |
|
{ |
|
whisper ("\tExecuting g['", join("' '", @_), "'"); |
|
return system (@_) >> 8; |
|
} |
|
|
|
pretend ("\tWould have run g['" . join("' '", @_) . "'"); |
|
return 0; # Return true |
|
} |
|
|
|
# Is exactly like "chdir", but it will also print out a message saying that |
|
# we're switching to the directory when debugging. |
|
sub p_chdir($) |
|
{ |
|
my $dir = shift; |
|
debug ("\tcd g[$dir]\n"); |
|
|
|
chdir ($dir) or do { |
|
return 1 if pretending(); |
|
croak_runtime("Could not change to directory $dir: $!"); |
|
}; |
|
} |
|
|
|
# Helper subroutine to create a directory, including any parent |
|
# directories that may also need created. |
|
# Throws an exception on failure. See File::Path. |
|
sub super_mkdir |
|
{ |
|
my $pathname = shift; |
|
state %createdPaths; |
|
|
|
if (pretending()) { |
|
if (!exists $createdPaths{$pathname} && ! -e $pathname) { |
|
pretend ("\tWould have created g[$pathname]"); |
|
} |
|
|
|
$createdPaths{$pathname} = 1; |
|
return 1; |
|
} |
|
else { |
|
make_path($pathname); |
|
return (-e $pathname) ? 1 : 0; |
|
} |
|
} |
|
|
|
# Calculates the MD5 digest of a file already on-disk. The digest is |
|
# returned as a hex string digest as from Digest::MD5::md5_hex |
|
# |
|
# First parameter: File name to read |
|
# Return value: hex string MD5 digest of file. |
|
# An exception is thrown if an error occurs reading the file. |
|
sub fileDigestMD5 |
|
{ |
|
my $fileName = shift; |
|
my $md5 = Digest::MD5->new; |
|
|
|
open my $file, '<', $fileName or croak_runtime( |
|
"Unable to open $fileName: $!"); |
|
binmode($file); |
|
|
|
$md5->addfile($file); |
|
return $md5->hexdigest(); |
|
} |
|
|
|
# This function is intended to disable the message translation catalog |
|
# settings in the program environment, so that any child processes executed |
|
# will have their output untranslated (and therefore scrapeable). |
|
# |
|
# As such this should only be called for a forked child about to exec as |
|
# there is no easy way to undo this within the process. |
|
sub disable_locale_message_translation |
|
{ |
|
# Ensure that program output is untranslated by setting 'C' locale. |
|
# We're really trying to affect the LC_MESSAGES locale category, but |
|
# LC_ALL is a catch-all for that (so needs to be unset if set). |
|
# |
|
# Note that the ONLY SUPPORTED way to pass file names, command-line |
|
# args, etc. to commands is under the UTF-8 encoding at this point, as |
|
# that is the only sane way for this en_US-based developer to handle |
|
# the task. Patches (likely using Encode::Locale) are accepted. :P |
|
|
|
$ENV{'LC_MESSAGES'} = 'C'; |
|
if ($ENV{'LC_ALL'}) { |
|
$ENV{'LANG'} = $ENV{'LC_ALL'}; # This is lower-priority "catch all" |
|
delete $ENV{'LC_ALL'}; |
|
} |
|
} |
|
|
|
# Returns an array of lines output from a program. Use this only if you |
|
# expect that the output will be short. |
|
# |
|
# Since there is no way to disambiguate no output from an error, this |
|
# function will call die on error, wrap in eval if this bugs you. |
|
# |
|
# First parameter is subroutine reference to use as a filter (this sub will |
|
# be passed a line at a time and should return true if the line should be |
|
# returned). If no filtering is desired pass 'undef'. |
|
# |
|
# Second parameter is the program to run (either full path or something |
|
# accessible in $PATH). |
|
# |
|
# All remaining arguments are passed to the program. |
|
# |
|
# Return value is an array of lines that were accepted by the filter. |
|
sub filter_program_output |
|
{ |
|
my ($filterRef, $program, @args) = @_; |
|
$filterRef //= sub { return 1 }; # Default to all lines |
|
|
|
debug ("Slurping '$program' '", join("' '", @args), "'"); |
|
|
|
# Check early for whether an executable exists since otherwise |
|
# it is possible for our fork-open below to "succeed" (i.e. fork() |
|
# happens OK) and then fail when it gets to the exec(2) syscall. |
|
if (!absPathToExecutable($program)) { |
|
croak_runtime("Can't find $program in PATH!"); |
|
} |
|
|
|
my $execFailedError = "\t - kdesrc-build - exec failed!\n"; |
|
my $pid = open(my $childOutput, '-|'); |
|
croak_internal("Can't fork: $!") if ! defined($pid); |
|
|
|
if ($pid) { |
|
# parent |
|
my @lines = grep { &$filterRef; } (<$childOutput>); |
|
close $childOutput or do { |
|
# $! indicates a rather grievous error |
|
croak_internal("Unable to open pipe to read $program output: $!") if $!; |
|
|
|
# we can pass serious errors back to ourselves too. |
|
my $exitCode = $? >> 8; |
|
if ($exitCode == 99 && @lines >= 1 && $lines[0] eq $execFailedError) { |
|
croak_runtime("Failed to exec $program, is it installed?"); |
|
} |
|
|
|
# other errors might still be serious but don't need a backtrace |
|
if (pretending()) { |
|
whisper ("$program gave error exit code $exitCode"); |
|
} else { |
|
warning ("$program gave error exit code $exitCode"); |
|
} |
|
}; |
|
|
|
return @lines; |
|
} |
|
else { |
|
disable_locale_message_translation(); |
|
|
|
# We don't want stderr output on tty. |
|
open (STDERR, '>', '/dev/null') or close (STDERR); |
|
|
|
exec { $program } ($program, @args) or do { |
|
# Send a message back to parent |
|
print $execFailedError; |
|
exit 99; # Helper proc, so don't use finish(), just die |
|
}; |
|
} |
|
} |
|
|
|
# Subroutine to return a string suitable for displaying an elapsed time, |
|
# (like a stopwatch) would. The first parameter is the number of seconds |
|
# elapsed. |
|
sub prettify_seconds |
|
{ |
|
my $elapsed = $_[0]; |
|
my $str = ""; |
|
my ($days,$hours,$minutes,$seconds,$fraction); |
|
|
|
$fraction = int (100 * ($elapsed - int $elapsed)); |
|
$elapsed = int $elapsed; |
|
|
|
$seconds = $elapsed % 60; |
|
$elapsed = int $elapsed / 60; |
|
|
|
$minutes = $elapsed % 60; |
|
$elapsed = int $elapsed / 60; |
|
|
|
$hours = $elapsed % 24; |
|
$elapsed = int $elapsed / 24; |
|
|
|
$days = $elapsed; |
|
|
|
$seconds = "$seconds.$fraction" if $fraction; |
|
|
|
my @str_list; |
|
|
|
for (qw(days hours minutes seconds)) |
|
{ |
|
# Use a symbolic reference without needing to disable strict refs. |
|
# I couldn't disable it even if I wanted to because these variables |
|
# aren't global or localized global variables. |
|
my $value = eval "return \$$_;"; |
|
my $text = $_; |
|
$text =~ s/s$// if $value == 1; # Make singular |
|
|
|
push @str_list, "$value $text" if $value or $_ eq 'seconds'; |
|
} |
|
|
|
# Add 'and ' in front of last element if there was more than one. |
|
push @str_list, ("and " . pop @str_list) if (scalar @str_list > 1); |
|
|
|
$str = join (", ", @str_list); |
|
|
|
return $str; |
|
} |
|
|
|
# Subroutine to mark a file as being the error log for a module. This also |
|
# creates a symlink in the module log directory for easy viewing. |
|
# First parameter is the module in question. |
|
# Second parameter is the filename in the log directory of the error log. |
|
sub _setErrorLogfile |
|
{ |
|
my $module = assert_isa(shift, 'ksb::Module'); |
|
my $logfile = shift; |
|
|
|
return unless $logfile; |
|
|
|
my $logdir = $module->getLogDir(); |
|
|
|
$module->setOption('#error-log-file', "$logdir/$logfile"); |
|
debug ("Logfile for $module is $logfile"); |
|
|
|
# Setup symlink in the module log directory pointing to the appropriate |
|
# file. Make sure to remove it first if it already exists. |
|
unlink("$logdir/error.log") if -l "$logdir/error.log"; |
|
|
|
if(-e "$logdir/error.log") |
|
{ |
|
# Maybe it was a regular file? |
|
error ("r[b[ * Unable to create symlink to error log file]"); |
|
return; |
|
} |
|
|
|
symlink "$logfile", "$logdir/error.log"; |
|
} |
|
|
|
|
|
# Subroutine to run a command, optionally filtering on the output of the child |
|
# command. |
|
# |
|
# First parameter is the module object being built (for logging purposes |
|
# and such). |
|
# Second parameter is the name of the log file to use (relative to the log |
|
# directory). |
|
# Third parameter is a reference to an array with the command and its |
|
# arguments. i.e. ['command', 'arg1', 'arg2'] |
|
# |
|
# After the required three parameters you can pass a hash reference of |
|
# optional features: |
|
# 'callback' => a reference to a subroutine to have each line |
|
# of child output passed to. This output is not supposed to be printed |
|
# to the screen by the subroutine, normally the output is only logged. |
|
# However this is useful for e.g. munging out the progress of the build. |
|
# USEFUL: When there is no more output from the child, the callback will be |
|
# called with an undef string. (Not just empty, it is also undefined). |
|
# |
|
# 'no_translate' => any true value will cause a flag to be set to request |
|
# the executed child process to not translate (for locale purposes) its |
|
# output, so that it can be screen-scraped. |
|
# |
|
# The return value is the shell return code, so 0 is success, and non-zero is |
|
# failure. |
|
# |
|
# NOTE: This function has a special feature. If the command passed into the |
|
# argument reference is 'kdesrc-build', then log_command will, when it |
|
# forks, execute the subroutine named by the second parameter rather than |
|
# executing a child process. The subroutine should include the full package |
|
# name as well (otherwise the package containing log_command's implementation |
|
# is used). The remaining arguments in the list are passed to the |
|
# subroutine that is called. |
|
sub log_command |
|
{ |
|
my ($module, $filename, $argRef, $optionsRef) = @_; |
|
assert_isa($module, 'ksb::Module'); |
|
my @command = @{$argRef}; |
|
|
|
$optionsRef //= { }; |
|
my $callbackRef = $optionsRef->{'callback'}; |
|
|
|
debug ("log_command(): Module $module, Command: ", join(' ', @command)); |
|
|
|
if (pretending()) |
|
{ |
|
pretend ("\tWould have run g['" . join ("' '", @command) . "'"); |
|
return 0; |
|
} |
|
|
|
# Do this before we fork so we can see errors |
|
my $logpath = $module->getLogPath("$filename.log"); |
|
|
|
# Fork a child, with its stdout connected to CHILD. |
|
my $pid = open(CHILD, '-|'); |
|
if ($pid) |
|
{ |
|
# Parent |
|
if (!$callbackRef && debugging()) { |
|
# If no other callback given, pass to debug() if debug-mode is on. |
|
while (<CHILD>) { |
|
print ($_) if $_; |
|
} |
|
} |
|
|
|
if ($callbackRef) { |
|
&{$callbackRef}($_) while (<CHILD>); |
|
|
|
# Let callback know there is no more output. |
|
&{$callbackRef}(undef); |
|
} |
|
|
|
# This implicitly does a waitpid() as well |
|
close CHILD or do { |
|
if ($! == 0) { |
|
_setErrorLogfile($module, "$filename.log"); |
|
return $?; |
|
} |
|
|
|
return 1; |
|
}; |
|
|
|
return 0; |
|
} |
|
else |
|
{ |
|
# Child. Note here that we need to avoid running our exit cleanup |
|
# handlers in here. For that we need POSIX::_exit. |
|
|
|
# Apply altered environment variables. |
|
$module->buildContext()->commitEnvironmentChanges(); |
|
|
|
$SIG{PIPE} = "IGNORE"; |
|
$SIG{INT} = sub { |
|
close (STDOUT); # This should be a pipe |
|
close (STDERR); |
|
POSIX::_exit(EINTR); |
|
}; |
|
|
|
# Redirect STDIN to /dev/null so that the handle is open but fails when |
|
# being read from (to avoid waiting forever for e.g. a password prompt |
|
# that the user can't see. |
|
|
|
open (STDIN, '<', "/dev/null") unless exists $ENV{'KDESRC_BUILD_USE_TTY'}; |
|
if ($callbackRef || debugging()) { |
|
open (STDOUT, "|tee $logpath") or do { |
|
error ("Error opening pipe to tee command."); |
|
# Don't abort, hopefully STDOUT still works. |
|
}; |
|
} |
|
else { |
|
open (STDOUT, '>', $logpath) or do { |
|
error ("Error $! opening log to $logpath!"); |
|
}; |
|
} |
|
|
|
# Make sure we log everything. |
|
open (STDERR, ">&STDOUT"); |
|
|
|
# Call internal function, name given by $command[1] |
|
if ($command[0] eq 'kdesrc-build') |
|
{ |
|
# No colors! |
|
ksb::Debug::setColorfulOutput(0); |
|
debug ("Calling $command[1]"); |
|
|
|
my $cmd = $command[1]; |
|
splice (@command, 0, 2); # Remove first two elements. |
|
|
|
no strict 'refs'; # Disable restriction on symbolic subroutines. |
|
if (! &{$cmd}(@command)) # Call sub |
|
{ |
|
POSIX::_exit (EINVAL); |
|
} |
|
|
|
POSIX::_exit (0); # Exit child process successfully. |
|
} |
|
|
|
# Don't leave empty output files, give an indication of the particular |
|
# command run. Use print to go to stdout. |
|
say "# kdesrc-build running: '", join("' '", @command), "'"; |
|
say "# from directory: ", getcwd(); |
|
|
|
# If a callback is set assume no translation can be permitted. |
|
disable_locale_message_translation() if $optionsRef->{'no_translate'}; |
|
|
|
# External command. |
|
exec (@command) or do { |
|
my $cmd_string = join(' ', @command); |
|
error (<<EOF); |
|
r[b[Unable to execute "$cmd_string"]! |
|
$! |
|
|
|
Please check your binpath setting (it controls the PATH used by kdesrc-build). |
|
Currently it is set to g[$ENV{PATH}]. |
|
EOF |
|
# Don't use return, this is the child still! |
|
POSIX::_exit (1); |
|
}; |
|
} |
|
} |
|
|
|
# This subroutine acts like split(' ', $_) except that double-quoted strings |
|
# are not split in the process. |
|
# |
|
# First parameter: String to split on whitespace. |
|
# Return value: A list of the individual words and quoted values in the string. |
|
# The quotes themselves are not returned. |
|
sub split_quoted_on_whitespace |
|
{ |
|
use Text::ParseWords qw(parse_line); |
|
my $line = shift; |
|
|
|
# Remove leading/trailing whitespace |
|
$line =~ s/^\s+//; |
|
$line =~ s/\s+$//; |
|
|
|
# 0 means not to keep delimiters or quotes |
|
return parse_line('\s+', 0, $line); |
|
} |
|
|
|
# This subroutine downloads the file pointed to by the URL given in the |
|
# first parameter, saving to the given filename. (FILENAME, not |
|
# directory). HTTP and HTTPS are supported, using Perl's built-in HTTP::Tiny (and for |
|
# HTTPS, IO::Socket::SSL must also be installed) |
|
# |
|
# First parameter: URL of link to download (i.e. https://kdesrc-build.kde.org/foo.tbz2) |
|
# Second parameter: Filename to save as (i.e. $ENV{HOME}/blah.tbz2) |
|
# Third parameter: URL of a proxy to use (undef or empty means proxy as set in environment) |
|
# Return value is 0 for failure, non-zero for success. |
|
sub download_file |
|
{ |
|
my ($url, $filename, $proxy) = @_; |
|
|
|
my $scriptVersion = scriptVersion(); |
|
my %opts = ( |
|
# Trailing space adds lib version info |
|
agent => "kdesrc-build/$scriptVersion ", |
|
timeout => 30, |
|
); |
|
|
|
if ($proxy) { |
|
whisper ("Using proxy $proxy for HTTP downloads"); |
|
$opts{proxy} = $proxy; |
|
} |
|
|
|
my $http_client = HTTP::Tiny->new(%opts); |
|
|
|
whisper ("Downloading g[$filename] from g[$url]"); |
|
my $response = $http_client->mirror($url, $filename); |
|
|
|
return 1 if $response->{success}; |
|
|
|
$response->{reason} .= " $response->{content}" if $response->{status} == 599; |
|
error ("Failed to download y[b[$url] to b[$filename]"); |
|
error ("Result was: y[b[$response->{status} $response->{reason}]"); |
|
|
|
return 0; |
|
} |
|
|
|
# Function: pretend_open |
|
# |
|
# Opens the given file and returns a filehandle to it if the file actually |
|
# exists or the script is not in pretend mode. If the script is in pretend mode |
|
# and the file is not already present then an open filehandle to an empty |
|
# string is returned. |
|
# |
|
# Parameters: |
|
# filename - Path to the file to open. |
|
# default - String to use if the file doesn't exist in pretend mode |
|
# |
|
# Returns: |
|
# filehandle on success (supports readline() and eof()), can return boolean |
|
# false if there is an error opening an existing file (or if the file doesn't |
|
# exist when not in pretend mode) |
|
sub pretend_open |
|
{ |
|
my $path = shift; |
|
my $defaultText = shift // ''; |
|
my $fh; |
|
|
|
if (pretending() && ! -e $path) { |
|
open $fh, '<', \$defaultText or return; |
|
} |
|
else { |
|
open $fh, '<', $path or return; |
|
} |
|
|
|
return $fh; |
|
} |
|
|
|
# Returns true if the given sub returns true for any item in the given listref. |
|
sub any(&@) |
|
{ |
|
my ($subRef, $listRef) = @_; |
|
($subRef->($_) && return 1) foreach @{$listRef}; |
|
return 0; |
|
} |
|
|
|
# Returns unique items of the list. Order not guaranteed. |
|
sub unique_items |
|
{ |
|
# See perlfaq4 |
|
my %seen; |
|
my @results = grep { ! $seen{$_}++; } @_; |
|
return @results; |
|
} |
|
|
|
# Subroutine to delete a directory and all files and subdirectories within. |
|
# Does nothing in pretend mode. An analog to "rm -rf" from Linux. |
|
# Requires File::Find module. |
|
# |
|
# First parameter: Path to delete |
|
# Returns boolean true on success, boolean false for failure. |
|
sub safe_rmtree |
|
{ |
|
my $path = shift; |
|
|
|
# Pretty user-visible path |
|
my $user_path = $path; |
|
$user_path =~ s/^$ENV{HOME}/~/; |
|
|
|
my $delete_file_or_dir = sub { |
|
# $_ is the filename/dirname. |
|
return if $_ eq '.' or $_ eq '..'; |
|
if (-f $_ || -l $_) |
|
{ |
|
unlink ($_) or croak_runtime("Unable to delete $File::Find::name: $!"); |
|
} |
|
elsif (-d $_) |
|
{ |
|
rmdir ($File::Find::name) or |
|
croak_runtime("Unable to remove directory $File::Find::name: $!"); |
|
} |
|
}; |
|
|
|
if (pretending()) |
|
{ |
|
pretend ("Would have removed all files/folders in $user_path"); |
|
return 1; |
|
} |
|
|
|
# Error out because we probably have a logic error even though it would |
|
# delete just fine. |
|
if (not -d $path) |
|
{ |
|
error ("Cannot recursively remove $user_path, as it is not a directory."); |
|
return 0; |
|
} |
|
|
|
eval { |
|
$@ = ''; |
|
finddepth( # finddepth does a postorder traversal. |
|
{ |
|
wanted => $delete_file_or_dir, |
|
no_chdir => 1, # We'll end up deleting directories, so prevent this. |
|
}, $path); |
|
}; |
|
|
|
if ($@) |
|
{ |
|
error ("Unable to remove directory $user_path: $@"); |
|
return 0; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
# Returns a hash digest of the given options in the list. The return value is |
|
# base64-encoded at this time. |
|
# |
|
# Note: Don't be dumb and pass data that depends on execution state as the |
|
# returned hash is almost certainly not useful for whatever you're doing with |
|
# it. (i.e. passing a reference to a list is not helpful, pass the list itself) |
|
# |
|
# Parameters: List of scalar values to hash. |
|
# Return value: base64-encoded hash value. |
|
sub get_list_digest |
|
{ |
|
use Digest::MD5 "md5_base64"; # Included standard with Perl 5.8 |
|
|
|
return md5_base64(@_); |
|
} |
|
|
|
# Utility function to see if a directory path is empty or not |
|
sub is_dir_empty |
|
{ |
|
my $dir = shift; |
|
|
|
opendir my $dirh, $dir or return; |
|
|
|
# while-readdir needs Perl 5.12 |
|
while (readdir $dirh) { |
|
next if ($_ eq '.' || $_ eq '..'); |
|
|
|
closedir ($dirh); |
|
return; # not empty |
|
} |
|
|
|
closedir ($dirh); |
|
return 1; |
|
} |
|
|
|
1; |
|
|
|
|