Local modifications to ClusterLabs/Anvil by Alteeve
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.
 
 
 
 
 
 

991 lines
30 KiB

package AN::Tools::Storage;
#
# This module contains methods used to handle storage related tasks
#
use strict;
use warnings;
use Data::Dumper;
use Scalar::Util qw(weaken isweak);
our $VERSION = "3.0.0";
my $THIS_FILE = "Storage.pm";
### Methods;
# change_mode
# change_owner
# copy_file
# find
# make_directory
# read_config
# read_file
# read_mode
# search_directories
# write_file
=pod
=encoding utf8
=head1 NAME
AN::Tools::Storage
Provides all methods related to storage on a system.
=head1 SYNOPSIS
use AN::Tools;
# Get a common object handle on all AN::Tools modules.
my $an = AN::Tools->new();
# Access to methods using '$an->Storage->X'.
#
# Example using 'find()';
my $foo_path = $an->Storage->find({file => "foo"});
=head1 METHODS
Methods in this module;
=cut
sub new
{
my $class = shift;
my $self = {
SEARCH_DIRECTORIES => \@INC,
};
bless $self, $class;
return ($self);
}
# Get a handle on the AN::Tools object. I know that technically that is a sibling module, but it makes more
# sense in this case to think of it as a parent.
sub parent
{
my $self = shift;
my $parent = shift;
$self->{HANDLE}{TOOLS} = $parent if $parent;
# Defend against memory leads. See Scalar::Util'.
if (not isweak($self->{HANDLE}{TOOLS}))
{
weaken($self->{HANDLE}{TOOLS});;
}
return ($self->{HANDLE}{TOOLS});
}
#############################################################################################################
# Public methods #
#############################################################################################################
=head2 change_mode
This changes the mode of a file or directory.
$an->Storage->change_mode({target => "/tmp/foo", mode => "0644"});
If it fails to write the file, an alert will be logged.
Parameters;
=head3 target (required)
This is the file or directory to change the mode on.
=head3 mode (required)
This is the numeric mode to set on the file. It expects four digits to cover the sticky bit, but will work with three digits.
=cut
sub change_mode
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
my $target = defined $parameter->{target} ? $parameter->{target} : "";
my $mode = defined $parameter->{mode} ? $parameter->{mode} : "";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
target => $target,
mode => $mode,
}});
my $error = 0;
if (not $target)
{
# No target...
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->change_mode()", parameter => "target" }});
$error = 1;
}
if (not $mode)
{
# No mode...
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->change_mode()", parameter => "mode" }});
$error = 1;
}
elsif (($mode !~ /^\d\d\d$/) && ($mode !~ /^\d\d\d\d$/))
{
# Invalid mode
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0038", variables => { mode => $mode }});
$error = 1;
}
if (not $error)
{
my $shell_call = $an->data->{path}{exe}{'chmod'}." $mode $target";
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, key => "log_0011", variables => { shell_call => $shell_call }});
open (my $file_handle, $shell_call." 2>&1 |") or $an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0014", variables => { shell_call => $shell_call, error => $! }});
while(<$file_handle>)
{
chomp;
my $line = $_;
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, key => "log_0017", variables => { line => $line }});
}
close $file_handle;
}
return(0);
}
=head2 change_owner
This changes the owner and/or group of a file or directory.
$an->Storage->change_owner({target => "/tmp/foo", mode => "0644"});
If it fails to write the file, an alert will be logged and 'C<< 1 >>' will be returned. Otherwise, 'C<< 0 >>' will be returned.
Parameters;
=head3 target (required)
This is the file or directory to change the mode on.
=head3 group (optional)
This is the group name or UID to set the target to.
=head3 user (optional)
This is the user name or UID to set the target to.
=cut
sub change_owner
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
my $target = defined $parameter->{target} ? $parameter->{target} : "";
my $group = defined $parameter->{group} ? $parameter->{group} : "";
my $user = defined $parameter->{user} ? $parameter->{user} : "";
my $debug = 3;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
target => $target,
group => $group,
user => $user,
}});
# Make sure the user and group and just one digit or word.
$user =~ s/^(\S+)\s.*$/$1/;
$group =~ s/^(\S+)\s.*$/$1/;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
group => $group,
user => $user,
}});
my $string = "";
my $error = 0;
if (not $target)
{
# No target...
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->change_owner()", parameter => "target" }});
$error = 1;
}
if (not -e $target)
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0051", variables => {target => $target }});
$error = 1;
}
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { user => $user }});
if ($user ne "")
{
$string = $user;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { string => $string }});
}
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { group => $group }});
if ($group ne "")
{
$string .= ":".$group;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { string => $string }});
}
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { error => $error, string => $string }});
if ((not $error) && ($string ne ""))
{
my $shell_call = $an->data->{path}{exe}{'chown'}." $string $target";
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0011", variables => { shell_call => $shell_call }});
open (my $file_handle, $shell_call." 2>&1 |") or $an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0014", variables => { shell_call => $shell_call, error => $! }});
while(<$file_handle>)
{
chomp;
my $line = $_;
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0017", variables => { line => $line }});
}
close $file_handle;
}
return($error);
}
=head2 copy_file
This copies a file, with a few additional checks like creating the target directory if it doesn't exist, aborting if the file has already been backed up before, etc.
# Example
$an->Storage->copy_file({source => "/some/file", target => "/another/directory/file"});
Parameters;
=head3 overwrite (optional)
If this is set to 'C<< 1 >>', and if the target file exists, it will be replaced.
If this is not passed and the target exists, this module will return 'C<< 3 >>'.
=head3 source (required)
This is the source file. If it isn't specified, 'C<< 1 >>' will be returned. If it doesn't exist, this method will return 'C<< 4 >>'.
=head3 target (required)
This is the target *B<file>*, not the directory to put it in. The target file name can be different from the source file name.
if this is not specified, 'C<< 2 >>' will be returned.
=cut
sub copy_file
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
my $overwrite = defined $parameter->{overwrite} ? $parameter->{overwrite} : 0;
my $source = defined $parameter->{source} ? $parameter->{source} : "";
my $target = defined $parameter->{target} ? $parameter->{target} : "";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
overwrite => $overwrite,
source => $source,
target => $target,
}});
if (not $source)
{
# No source passed.
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->copy_file()", parameter => "source" }});
return(1);
}
elsif (not -e $source)
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0052", variables => { source => $source }});
return(4);
}
if (not $target)
{
# No target passed.
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->copy_file()", parameter => "target" }});
return(2);
}
# If the target exists, abort
if ((-e $target) && (not $overwrite))
{
# This isn't an error.
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0046", variables => {
source => $source,
target => $target,
}});
return(3);
}
# Make sure the target directory exists and create it, if not.
my ($directory, $file) = ($target =~ /^(.*)\/(.*)$/);
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
directory => $directory,
file => $file,
}});
if (not -e $directory)
{
$an->Storage->make_directory({
directory => $directory,
group => $(, # Real UID
user => $<, # Real GID
mode => "0750",
});
}
# Now backup the file.
my $output = $an->System->call({shell_call => $an->data->{path}{exe}{'cp'}." -af $source $target"});
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { output => $output }});
return(0);
}
=head2 find
This searches for the given file on the system. It will search in the directories returned by C<< $an->Storage->search_directories() >>.
Example to search for 'C<< foo >>';
$an->Storage->find({file => "foo"});
Same, but error out if the file isn't found.
$an->Storage->find({
file => "foo",
fatal => 1,
});
If it fails to find the file and C<< fatal >> isn't set to 'C<< 1 >>', 'C<< 0 >>' is returned.
Parameters;
=head3 file (required)
This is the name of the file to search for.
=cut
sub find
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
# WARNING: Don't call Log from here! It causes it to abort
my $debug = 0;
my $file = defined $parameter->{file} ? $parameter->{file} : "";
print $THIS_FILE." ".__LINE__."; [ Debug] - file: [$file]\n" if $debug;
# Each full path and file name will be stored here before the test.
my $full_path = "#!not_found!#";
if ($file)
{
foreach my $directory (@{$an->Storage->search_directories()})
{
# If "directory" is ".", expand it.
print $THIS_FILE." ".__LINE__."; [ Debug] - >> directory: [$directory]\n" if $debug;
if (($directory eq ".") && ($ENV{PWD}))
{
$directory = $ENV{PWD};
print $THIS_FILE." ".__LINE__."; [ Debug] - << directory: [$directory]\n" if $debug;
}
# Put together the initial path
my $test_path = $directory."/".$file;
print $THIS_FILE." ".__LINE__."; [ Debug] - >> test_path: [$test_path]\n" if $debug;
# Clear double-delimiters.
$test_path =~ s/\/+/\//g;
print $THIS_FILE." ".__LINE__."; [ Debug] - << test_path: [$test_path]\n" if $debug;
if (-f $test_path)
{
# Found it!
$full_path = $test_path;
print $THIS_FILE." ".__LINE__."; [ Debug] - >> full_path: [$full_path]\n" if $debug;
last;
}
}
print $THIS_FILE." ".__LINE__."; [ Debug] - << full_path: [$full_path]\n" if $debug;
}
# Return
print $THIS_FILE." ".__LINE__."; [ Debug] - full_path: [$full_path]\n" if $debug;
return ($full_path);
}
=head2 make_directory
This creates a directory (and any parent directories).
$an->Storage->make_directory({directory => "/foo/bar/baz", owner => "me", grou[ => "me", group => 755});
If it fails to create the directory, an alert will be logged.
Parameters;
=head3 directory (required)
This is the name of the directory to create.
=head3 group (optional)
This is the group name or group ID to set the ownership of the directory to.
=head3 mode (optional)
This is the numeric mode to set on the file. It expects four digits to cover the sticky bit, but will work with three digits.
=head3 user (optional)
This is the user name or user ID to set the ownership of the directory to.
=cut
sub make_directory
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
my $directory = defined $parameter->{directory} ? $parameter->{directory} : "";
my $group = defined $parameter->{group} ? $parameter->{group} : "";
my $mode = defined $parameter->{mode} ? $parameter->{mode} : "";
my $user = defined $parameter->{user} ? $parameter->{user} : "";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
directory => $directory,
group => $group,
mode => $mode,
user => $user,
}});
# Make sure the user and group and just one digit or word.
$user =~ s/^(\S+)\s.*$/$1/;
$group =~ s/^(\S+)\s.*$/$1/;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
group => $group,
user => $user,
}});
# Break the directories apart.
my $working_directory = "";
foreach my $this_directory (split/\//, $directory)
{
next if not $this_directory;
$working_directory .= "/$this_directory";
$working_directory =~ s/\/\//\//g;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { working_directory => $working_directory }});
if (not -e $working_directory)
{
# Directory doesn't exist, so create it.
my $shell_call = $an->data->{path}{exe}{'mkdir'}." ".$working_directory;
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, key => "log_0011", variables => { shell_call => $shell_call }});
open (my $file_handle, $shell_call." 2>&1 |") or $an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0014", variables => { shell_call => $shell_call, error => $! }});
while(<$file_handle>)
{
chomp;
my $line = $_;
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, key => "log_0017", variables => { line => $line }});
}
close $file_handle;
if ($mode)
{
$an->Storage->change_mode({target => $working_directory, mode => $mode});
}
if (($user) or ($group))
{
$an->Storage->change_owner({target => $working_directory, user => $user, group => $group});
}
}
}
return(0);
}
=head2 read_config
This method is used to read 'AN::Tools' style configuration files. These configuration files are in the format:
# This is a comment for the 'a::b::c' variable
a::b::c = x
A configuration file can be read in like this;
$an->Storage->read_config({file => "test.conf"});
In this example, the file 'C<< test.conf >>' will be searched for in the directories returned by 'C<< $an->Storage->search_directories >>'.
Any line starting with '#' is a comment and is ignored. Preceding white spaces are allowed and also ignored.
Any line in the format 'x = y' is treated as a variable / value pair, split on the first 'C<< = >>'. Whitespaces on either side of the 'C<< = >>' are removed and ignored. However, anything after the first non-whitespace character is treated as data an unmolested. This includes addition 'C<< = >>' characters, white spaces and so on. The exception is that trailing white spaces are cropped and ignored. If nothing comes after the 'C<< = >>', the variable is set to a blank string.
Successful read will return 'C<< 0 >>'. Non-0 is an error;
C<< 0 >> = OK
C<< 1 >> = Invalid or missing file name
C<< 2 >> = File not found
C<< 3 >> = File not readable
Parameters;
=head3 file (required)
This is the configuration file to read.
If the 'C<< file >>' parameter starts with 'C<< / >>', the exact path to the file is used. Otherwise, this method will search for the file in the list of directories returned by 'C<< $an->Storage->search_directories >>'. The first match is read in.
All variables are stored in the root of 'C<< $an->data >>', allowing for configuration files to override internally set variables.
For example, if you set:
$an->data->{a}{b}{c} = "1";
Then you read in a config file with:
a::b::c = x
Then 'C<< $an->data->{a}{b}{c} >>' will now contain 'C<< x >>'.
=cut
sub read_config
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
# Setup default values
my $file = defined $parameter->{file} ? $parameter->{file} : 0;
my $return_code = 0;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { file => $file }});
if (not $file)
{
# No file to read
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0032"});
$return_code = 1;
}
# If I have a file name that isn't a full path, find it.
if (($file) && ($file !~ /^\//))
{
# Find the file, if possible. If not found, we'll not alter what the user passed in and hope
# it is relative to where we are.
my $path = $an->Storage->find({ file => $file });
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { path => $path }});
if ($path ne "#!not_found!#")
{
# Update the file
$file = $path;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { file => $file }});
}
}
if ($file)
{
if (not -e $file)
{
# The file doesn't exist
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0033", variables => { file => $file }});
$return_code = 2;
}
elsif (not -r $file)
{
# The file can't be read
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0034", variables => {
file => $file,
user => getpwuid($<),
uid => $<,
}});
$return_code = 3;
}
else
{
# Read it in!
my $count = 0;
open (my $file_handle, "<$file") or die "Can't read: [$file], error was: $!\n";
while (<$file_handle>)
{
chomp;
my $line = $_;
$line =~ s/^\s+//;
$line =~ s/\s+$//;
$count++;
next if ((not $line) or ($line =~ /^#/));
next if $line !~ /=/;
my ($variable, $value) = split/=/, $line, 2;
$variable =~ s/\s+$//;
$value =~ s/^\s+//;
if (not $variable)
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0035", variables => {
file => $file,
count => $count,
line => $line,
}});
}
$an->_make_hash_reference($an->data, $variable, $value);
}
close $file_handle;
}
}
return($return_code);
}
=head2 read_file
This reads in a file and returns the contents of the file as a single string variable.
my $body = $an->Storage->read_file({file => "/tmp/foo"});
If it fails to find the file, or the file is not readable, 'C<< !!error!! >>' is returned.
Parameters;
=head3 file (required)
This is the name of the file to read.
=cut
sub read_file
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
my $body = "";
my $file = defined $parameter->{file} ? $parameter->{file} : "";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { file => $file }});
if (not $file)
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->read_file()", parameter => "file" }});
return("!!error!!");
}
elsif (not -e $file)
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0021", variables => { file => $file }});
return("!!error!!");
}
elsif (not -r $file)
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0022", variables => { file => $file }});
return("!!error!!");
}
my $shell_call = $file;
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, key => "log_0012", variables => { shell_call => $shell_call }});
open (my $file_handle, "<", $shell_call) or $an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0015", variables => { shell_call => $shell_call, error => $! }});
while(<$file_handle>)
{
chomp;
my $line = $_;
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, key => "log_0023", variables => { line => $line }});
$body .= $line."\n";
}
close $file_handle;
$body =~ s/\n$//s;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { body => $body }});
return($body);
}
=head2 read_mode
This reads a file or directory's mode (sticky-bit and ownership) and returns the mode as a four-digit string (ie: 'c<< 0644 >>', 'C<< 4755 >>', etc.
my $mode = $an->Storage->read_mode({file => "/tmp/foo"});
If it fails to find the file, or the file is not readable, 'C<< 0 >>' is returned.
Parameters;
=head3 file (required)
This is the name of the file or directory to check the mode of.
=cut
sub read_mode
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
my $debug = 1;
my $target = defined $parameter->{target} ? $parameter->{target} : "";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { target => $target }});
if (not $target)
{
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0020", variables => { method => "Storage->read_mode()", parameter => "target" }});
return(1);
}
# Read the mode and convert it to digits.
my $mode = (stat($target))[2];
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { mode => $mode }});
# Return the full mode, unless it is a directory or file. In those cases, return the last four digits.
my $say_mode = $mode;
if (-d $target)
{
# Directory - five digits
$say_mode = sprintf("%04o", $mode);
$say_mode =~ s/^\d(\d\d\d\d)$/$1/;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { say_mode => $say_mode }});
}
elsif (-f $target)
{
# File - six digits
$say_mode = sprintf("%04o", $mode);
$say_mode =~ s/^\d\d(\d\d\d\d)$/$1/;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { say_mode => $say_mode }});
}
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { mode => $mode, say_mode => $say_mode }});
return($say_mode);
}
=head2 search_directories
This method returns an array reference of directories to search within for files and directories.
Parameters;
=head3 directories (optional)
This accepts either an array reference of directories to search, or a comma-separated string of directories to search (which will be converted to an array). When passed, this sets the internal list of directories to search.
By default, it is set to all directories in C<< @INC >>, 'C<< path::directories::tools >> (our tools) and the C<< $ENV{'PATH'} >> variables, minus directories that don't actually exist. The returned array is sorted alphabetically.
=head3 initialize (optional)
If this is set, the list of directories to search will be set to 'C<< @INC >>' + 'C<< $ENV{'PATH'} >>' + 'C<< path::directories::tools >>'.
NOTE: You don't need to call this manually unless you want to reset the list. Invoking AN::Tools->new() causes this to be called automatically.
=cut
sub search_directories
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
# Set a default if nothing was passed.
my $array = defined $parameter->{directories} ? $parameter->{directories} : "";
my $initialize = defined $parameter->{initialize} ? $parameter->{initialize} : "";
# If the array is a CSV of directories, convert it now.
if ($array =~ /,/)
{
# CSV, convert to an array.
my @new_array = split/,/, $array;
$array = \@new_array;
}
elsif (($initialize) or (($array) && (ref($array) ne "ARRAY")))
{
if (not $initialize)
{
# Not initializing and an array was passed that isn't.
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "alert", key => "log_0031", variables => { array => $array }});
}
# Create a new array containing the '$ENV{'PATH'}' directories and the @INC directories.
my @new_array = split/:/, $ENV{'PATH'} if $ENV{'PATH'} =~ /:/;
foreach my $directory (@INC)
{
push @new_array, $directory;
}
# Add the tools directory
push @new_array, $an->data->{path}{directories}{tools};
$array = \@new_array;
}
# Store the new array, if set.
if (ref($array) eq "ARRAY")
{
# Dedupe and sort.
my $sorted_array = [];
my $seen_directories = {};
foreach my $directory (sort {$a cmp $b} @{$array})
{
next if not defined $directory;
# Convert '.' to $ENV{PWD}
if ($directory eq ".")
{
$directory = $ENV{PWD};
}
# Skip duplicates
next if exists $seen_directories->{$directory};
# Skip non-existent directories
next if not -d $directory;
# Record this directory.
$seen_directories->{$directory} = 1;
push @{$sorted_array}, $directory;
}
$array = $sorted_array;
$self->{SEARCH_DIRECTORIES} = $array;
}
# Debug
foreach my $directory (@{$self->{SEARCH_DIRECTORIES}})
{
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { directory => $directory }});
}
return ($self->{SEARCH_DIRECTORIES});
}
=head2 write_file
This writes out a file on the local system. It can optionally set the mode as well.
$an->Storage->write_file({file => "/tmp/foo", body => "some data", mode => 0644});
If it fails to write the file, an alert will be logged.
Parameters;
=head3 body (optional)
This is the contents of the file. If it is blank, an empty file will be created (similar to using 'C<< touch >>' on the command line).
=head3 file (required)
This is the name of the file to write.
NOTE: The file must include the full directory it will be written into.
=head3 group (optional)
This is the group name or group ID to set the ownership of the file to.
=head3 mode (optional)
This is the numeric mode to set on the file. It expects four digits to cover the sticky bit, but will work with three digits.
=head3 overwrite (optional)
Normally, if the file already exists, it won't be overwritten. Setting this to 'C<< 1 >>' will cause the file to be overwritten.
=head3 secure (optional)
If set to 'C<< 1 >>', the body is treated as containing secure data for logging purposes.
=head3 user (optional)
This is the user name or user ID to set the ownership of the file to.
=cut
sub write_file
{
my $self = shift;
my $parameter = shift;
my $an = $self->parent;
my $body = defined $parameter->{body} ? $parameter->{body} : "";
my $file = defined $parameter->{file} ? $parameter->{file} : "";
my $group = defined $parameter->{group} ? $parameter->{group} : "";
my $mode = defined $parameter->{mode} ? $parameter->{mode} : "";
my $overwrite = defined $parameter->{overwrite} ? $parameter->{overwrite} : 0;
my $secure = defined $parameter->{secure} ? $parameter->{secure} : "";
my $user = defined $parameter->{user} ? $parameter->{user} : "";
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, secure => $secure, list => {
body => $body,
file => $file,
group => $group,
mode => $mode,
overwrite => $overwrite,
secure => $secure,
user => $user,
}});
# Make sure the user and group and just one digit or word.
$user =~ s/^(\S+)\s.*$/$1/;
$group =~ s/^(\S+)\s.*$/$1/;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
group => $group,
user => $user,
}});
my $error = 0;
if ((-e $file) && (not $overwrite))
{
# Nope.
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0040", variables => { file => $file }});
$error = 1;
}
if ($file !~ /^\/\w/)
{
# Not a fully defined path, abort.
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, priority => "err", key => "log_0041", variables => { file => $file }});
$error = 1;
}
if (not $error)
{
# Break the directory off the file.
my ($directory, $file_name) = ($file =~ /^(\/.*)\/(.*)$/);
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => {
directory => $directory,
file_name => $file_name,
}});
if (not -e $directory)
{
# Don't pass the mode as the file's mode is likely not executable.
$an->Storage->make_directory({
directory => $directory,
group => $group,
user => $user,
});
}
# If 'secure' is set, the file will probably contain sensitive data so touch the file and set
# the mode before writing it.
if ($secure)
{
my $shell_call = $an->data->{path}{exe}{touch}." ".$file;
$an->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { shell_call => $shell_call }});
$an->System->call({shell_call => $shell_call});
$an->Storage->change_mode({target => $file, mode => $mode});
}
# Now write the file.
my $shell_call = $file;
$an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 3, secure => $secure, key => "log_0013", variables => { shell_call => $shell_call }});
open (my $file_handle, ">", $shell_call) or $an->Log->entry({source => $THIS_FILE, line => __LINE__, level => 0, secure => $secure, priority => "err", key => "log_0016", variables => { shell_call => $shell_call, error => $! }});
print $file_handle $body;
close $file_handle;
if ($mode)
{
$an->Storage->change_mode({target => $file, mode => $mode});
}
if (($user) or ($group))
{
$an->Storage->change_owner({target => $file, user => $user, group => $group});
}
}
return(0);
}
# =head3
#
# Private Functions;
#
# =cut
#############################################################################################################
# Private functions #
#############################################################################################################
1;