MOON
Server: Apache
System: Linux server1.studioinfinity.com.br 2.6.32-954.3.5.lve1.4.90.el6.x86_64 #1 SMP Tue Feb 21 12:26:30 UTC 2023 x86_64
User: artinside (517)
PHP: 7.4.33
Disabled: exec,passthru,shell_exec,system
Upload Files
File: //usr/local/lib/perl5/site_perl/5.8.8/Cpanel/CacheFile.pm
package Cpanel::CacheFile;

use strict;

#use warnings;

use Fcntl      ();
use File::Path ();

our $version = 0.307;

my $the_logger;
my $the_locker;

# Simplifies having both classes in the module use the same package.
my $pkg = __PACKAGE__;

# -----------------------------------------------------------------------------
# Policy code: The following allows the calling code to supply two objects that
# specify the behavior for logging and file locking. This approach was applied
# to allow these policies to be changed without requiring the overhead of the
# default implementations.

# The default logging code is simple enough that I am including it inline
# instead of making a separate class file. All methods are forwarded to the
# CORE C<die> and C<warn> functions.
{

    package DefaultLogger;

    sub new {
        my ($class) = @_;
        return bless {}, $class;
    }

    sub throw {
        my $self = shift;
        die @_;
    }

    sub warn {
        my $self = shift;
        CORE::warn @_;
        return;
    }

    sub info {
        my $self = shift;
        CORE::warn @_;
        return;
    }

    sub notify {
        my $self = shift;
        return;
    }
}

sub _throw {
    my $self = shift;
    _get_logger()->throw(@_);
}

sub _warn {
    my $self = shift;
    _get_logger()->warn(@_);
}

sub _notify {
    my $self = shift;
    _get_logger()->notify(@_);
}

# We never use _info, so remove it

sub _get_logger {
    unless ( defined $the_logger ) {
        $the_logger = DefaultLogger->new();
    }
    return $the_logger;
}

sub _get_locker {
    unless ( defined $the_locker ) {
        eval "use Cpanel::CacheFile::FileLocker;";    ## no critic (ProhibitStringyEval)
        $pkg->_throw(@_) if $@;
        $the_locker = Cpanel::CacheFile::FileLocker->new( { logger => _get_logger() } );
    }
    return $the_locker;
}

my $are_policies_set = 0;

#
# This method allows changing the policies for logging and locking.
sub import {
    my $class = shift;
    die 'Not an even number of arguments to the $pkg module' if @_ % 2;
    die 'Policies already set elsewhere' if $are_policies_set;
    return 1 unless @_;    # Don't set the policies flag.

    while (@_) {
        my ( $policy, $object ) = splice( @_, 0, 2 );
        next unless defined $object;
        if ( '-logger' eq $policy ) {
            unless ( ref $object ) {
                eval "use $object;";    ## no critic (ProhibitStringyEval)
                die $@ if $@;

                # convert module into an object.
                $object = $object->new;
            }
            die 'Supplied logger object does not support the correct interface.'
              unless _valid_logger($object);
            $the_logger = $object;
        }
        elsif ( '-filelock' eq $policy ) {
            unless ( ref $object ) {
                eval "use $object;";    ## no critic (ProhibitStringyEval)
                die $@ if $@;

                # convert module into an object.
                $object = $object->new;
            }
            die 'Supplied filelock object does not support the correct interface.'
              unless _valid_file_locker($object);
            $the_locker = $object;
        }
        else {
            die "Unrecognized policy '$policy'";
        }
    }
    $are_policies_set = 1;
    return 1;
}

{

    #
    # Nested class to handle locking cleanly.
    #
    # Locks the file on creation, throwing on failure. Unlocks the file when
    # the object is destroyed.
    {

        package Cpanel::CacheFile::Guard;

        sub new {
            my ( $class, $args_ref ) = @_;
            $pkg->throw('Args parameter must be a hash reference.') unless 'HASH' eq ref $args_ref;
            $pkg->throw('No CacheFile.') unless exists $args_ref->{cache};

            my $self = bless { cache_file => $args_ref->{cache} }, $class;

            $self->_lock();

            return $self;
        }

        sub DESTROY {
            my ($self) = @_;

            # make certain that an exception here cannot escape and won't effect
            #   exceptions currently in progress.
            local $@;
            eval { $self->_unlock(); };
            return;
        }

        sub _lock {
            my $self       = shift;
            my $cache_file = $self->{cache_file};

            my $filename = $cache_file->{file_name};
            $self->{lock_file} = $cache_file->{locker}->file_lock($filename);
            $cache_file->throw("Unable to acquire file lock for '$filename'.") unless $self->{lock_file};
            return;
        }

        sub _unlock {
            my $self       = shift;
            my $cache_file = $self->{cache_file};
            return unless $self->{lock_file};

            if ( $cache_file->{file_handle} ) {

                # TODO probably need to check for failure, but then what do I do?
                eval {
                    local $SIG{'ALRM'} = sub { die "flock 8 timeout\n"; };
                    my $orig_alarm = alarm $cache_file->{flock_timeout};
                    flock $cache_file->{file_handle}, 8;
                    alarm $orig_alarm;
                };
                close $cache_file->{file_handle};
                $cache_file->{file_handle} = undef;
            }
            $cache_file->{locker}->file_unlock( $self->{lock_file} );
            $self->{lock_file} = undef;
            return;
        }

        sub call_unlocked {
            my ( $self, $code ) = @_;
            my $cache_file = $self->{cache_file};
            $cache_file->throw('Cannot nest call_unlocked calls.') unless defined $self->{lock_file};
            $cache_file->throw('Missing coderef to call_unlocked.') unless 'CODE' eq ref $code;

            # unlock for the duration of the code execution
            $self->_unlock();
            eval { $code->(); };
            my $ex = $@;

            # relock even if exception.
            $self->_lock();

            # probably should resync if necessary.
            $cache_file->_resynch($self);

            $pkg->_throw($ex) if $ex;

            return;
        }

        sub _open {
            my ( $self, $mode ) = @_;
            my $cache_file = $self->{cache_file};
            $cache_file->throw('Cannot open cache inside a call_unlocked call.') unless defined $self->{lock_file};

            open my $fh, $mode, $cache_file->{file_name}
              or $cache_file->throw("Unable to open cache file '$cache_file->{file_name}': $!");
            eval {
                local $SIG{'ALRM'} = sub { die "flock 2 timeout\n"; };
                my $orig_alarm = alarm $cache_file->{flock_timeout};
                flock $fh, 2;
                alarm $orig_alarm;
            };
            if ($@) {
                close($fh);
                if ( $@ eq "flock 2 timeout\n" ) {
                    $cache_file->throw('Guard timed out trying to open cache file.');
                }
                else {
                    $cache_file->throw($@);
                }
            }
            $cache_file->{file_handle} = $fh;
        }

        sub update_file {
            my ($self) = @_;
            my $cache_file = $self->{cache_file};
            $cache_file->throw('Cannot update_file inside a call_unlocked call.') unless defined $self->{lock_file};

            if ( !$cache_file->{file_handle} ) {
                if ( -e $cache_file->{file_name} ) {
                    $self->_open('+<');
                }
                else {
                    sysopen( my $fh, $cache_file->{file_name}, &Fcntl::O_CREAT | &Fcntl::O_EXCL | &Fcntl::O_RDWR )
                      or $cache_file->throw("Cannot create cache file '$cache_file->{file_name}': $!");
                    $cache_file->{file_handle} = $fh;
                }
            }
            seek( $cache_file->{file_handle}, 0, 0 );
            truncate( $cache_file->{file_handle}, 0 )
              or $cache_file->throw("Unable to truncate the cache: $!");

            $cache_file->{data_object}->save_to_cache( $cache_file->{file_handle} );
            $cache_file->{file_mtime} = ( stat( $cache_file->{file_handle} ) )[9];

            # Make certain we are at end of file.
            seek( $cache_file->{file_handle}, 0, 2 )
              or $cache_file->throw("Unable to go to end of file: $!");
            $cache_file->{file_size} = tell( $cache_file->{file_handle} );
            return;
        }

    }

    # Back to CacheFile

    sub new {
        my ( $class, $args_ref ) = @_;
        my $self = bless {}, $class;
        if ( exists $args_ref->{logger} && _valid_logger( $args_ref->{logger} ) ) {
            $self->{logger} = $args_ref->{logger};
        }
        else {
            $self->{logger} = $pkg->_get_logger();
            if ( exists $args_ref->{logger} ) {
                $self->throw('Supplied logger does not support required methods.');
            }
        }
        if ( exists $args_ref->{locker} && _valid_file_locker( $args_ref->{locker} ) ) {
            $self->{locker} = $args_ref->{locker};
        }
        else {
            $self->{locker} = $pkg->_get_locker();
            if ( exists $args_ref->{locker} ) {
                $self->throw('Supplied locker does not support required methods.');
            }
        }
        $self->throw('No cache filename supplied.') unless exists $args_ref->{cache_file};
        $self->throw('No data object supplied.')    unless exists $args_ref->{data_obj};
        my $data_obj = $args_ref->{data_obj};
        $self->throw('Data object does not have required interface.')
          unless eval { $data_obj->can('load_from_cache') }
              and eval { $data_obj->can('save_to_cache') };

        my ( $dirname, $file ) = ( $args_ref->{cache_file} =~ m{^(.*)/([^/]*)$}g );
        $dirname =~ s{[^/]+/\.\./}{/}g;    # resolve parent references
        $dirname =~ s{[^/]+/\.\.$}{};
        $dirname =~ s{/\./}{/}g;           # resolve self references
        $dirname =~ s{/\.$}{};
        if ( !-d $dirname ) {
            File::Path::mkpath($dirname)
              or $self->throw("Unable to create Cache directory ('$dirname').");
        }
        $self->{file_name} = "$dirname/$file";

        $self->{data_object}   = $data_obj;
        $self->{file_mtime}    = -1;
        $self->{file_size}     = -1;
        $self->{file_handle}   = undef;
        $self->{flock_timeout} = $args_ref->{timeout} || 60;

        $self->synch();

        return $self;
    }

    #
    # Return true if the supplied logger object implements the correct
    # interface, false otherwise.
    sub _valid_logger {
        my ($logger) = @_;

        foreach my $method (qw/throw warn info notify/) {
            return unless eval { $logger->can($method) };
        }

        return 1;
    }

    #
    # Return true if the supplied file locker object implements the correct
    # interface, false otherwise.
    sub _valid_file_locker {
        my ($locker) = @_;

        foreach my $method (qw/file_lock file_unlock/) {
            return unless eval { $locker->can($method) };
        }

        return 1;
    }

    sub synch {
        my ($self) = @_;

        my $guard;

        if ( !-e $self->{file_name} or -z _ ) {
            $guard = Cpanel::CacheFile::Guard->new( { cache => $self } );

            # File doesn't exist or is empty, initialize it.
            $guard->update_file();
        }
        else {
            $guard = $self->_resynch($guard);
        }

        # if not assigned anywhere, let the guard die.
        return unless defined wantarray;

        $guard ||= Cpanel::CacheFile::Guard->new( { cache => $self } );

        # Otherwise return it.
        return $guard;
    }

    sub _resynch {
        my ( $self, $guard ) = @_;

        my ( $mtime, $size ) = ( stat( $self->{file_name} ) )[ 9, 7 ];
        if ( $self->{file_mtime} < $mtime || $self->{file_size} != $size ) {

            # File is newer or a different size
            $guard ||= Cpanel::CacheFile::Guard->new( { cache => $self } );
            $guard->_open('+<');
            $self->{data_object}->load_from_cache( $self->{file_handle} );
            ( $self->{file_mtime}, $self->{file_size} ) = ( stat( $self->{file_handle} ) )[ 9, 7 ];
        }

        return $guard;
    }

    sub get_logger { return $_[0]->{logger}; }

    sub throw {
        my $self = shift;
        return $self->{logger}->throw(@_);
    }

    sub warn {
        my $self = shift;
        return $self->{logger}->warn(@_);
    }

    sub info {
        my $self = shift;
        return $self->{logger}->info(@_);
    }
}

1;