<commit_before>package Tapper::Installer::Precondition::Exec;

use strict;
use warnings;

use Method::Signatures;
use Moose;
use IO::Handle; # needed to set pipe nonblocking
extends 'Tapper::Installer::Precondition';


=head1 NAME

Tapper::Installer::Precondition::Exec - Execute a program inside the installed system

=head1 SYNOPSIS

 use Tapper::Installer::Precondition::Exec;

=head1 FUNCTIONS

=cut


=head2 install

This function executes a program inside the installed system. This supersedes
the postinstall script facility of the package precondition and makes this
feature available to all other preconditions.

@param hash reference - contains all information about the program

@return success - 0
@return error   - error string

=cut

method install ($exec)
{
        my $filename = $exec->{filename};
        my @options;
        @options = @{$exec->{options}} if $exec->{options};

	$self->log->debug("executing $filename with options ",join (" ",@options));

        my $fullpath = $self->cfg->{paths}{base_dir}.$filename;
        return("$filename is not an executable") if not -x $fullpath;
        
	pipe (my $read, my $write);
	return ("Can't open pipe:$!") if not (defined $read and defined $write);
	
	# we need to fork for chroot
	my $pid = fork();
	return "fork failed: $!" if not defined $pid;
	
	# hello child
	if ($pid == 0) {
                $ENV{TAPPER_TESTRUN}         = $self->cfg->{test_run};
                $ENV{TAPPER_SERVER}          = $self->cfg->{mcp_host};
                $ENV{TAPPER_REPORT_SERVER}   = $self->cfg->{report_server};
                $ENV{TAPPER_REPORT_API_PORT} = $self->cfg->{report_api_port};
                $ENV{TAPPER_REPORT_PORT}     = $self->cfg->{report_port};
                $ENV{TAPPER_HOSTNAME}        = $self->cfg->{hostname};

                close $read;
		# chroot to execute script inside the future root file system
                my ($error, $output) = $self->log_and_exec("mount -o bind /dev/ ".$self->cfg->{paths}{base_dir}."/dev");
                ($error, $output)    = $self->log_and_exec("mount -t sysfs sys ".$self->cfg->{paths}{base_dir}."/sys");
                ($error, $output)    = $self->log_and_exec("mount -t proc proc ".$self->cfg->{paths}{base_dir}."/proc");
		chroot $self->cfg->{paths}{base_dir};
		chdir ("/");
                ($error, $output)=$self->log_and_exec($filename,@options);
                print( $write $output, "\n") if $output;
                close $write;
                exit $error;
	} else {
                close $write;
                my $select = new IO::Select( $read );
                my ($error, $output);
        MSG_FROM_CHILD:
                while (my @ready = $select->can_read()){
                        my $tmpout = <$read>;   # only read can be in @ready, since no other FH is in $select
                        last MSG_FROM_CHILD if not $tmpout;
                        $output.=$tmpout;
                }
                if ($output) {
                        my $outfile = $filename;
                        $outfile =~ s/[^A-Za-z_-]/_/g;
                        $self->file_save($output,$outfile);
                }
                ($error, $output)=$self->log_and_exec("umount ".$self->cfg->{paths}{base_dir}."/dev");
                ($error, $output)=$self->log_and_exec("umount ".$self->cfg->{paths}{base_dir}."/sys");
                ($error, $output)=$self->log_and_exec("umount ".$self->cfg->{paths}{base_dir}."/proc");
                waitpid($pid,0);
                if ($?) {
                        return("executing $filename failed");
                }
		return(0);
	}
}
;<commit_msg>Refactoring for cleaner code<commit_after>package Tapper::Installer::Precondition::Exec;

use strict;
use warnings;

use Method::Signatures;
use Moose;
use IO::Handle; # needed to set pipe nonblocking
use IO::Select;

extends 'Tapper::Installer::Precondition';


=head1 NAME

Tapper::Installer::Precondition::Exec - Execute a program inside the installed system

=head1 SYNOPSIS

 use Tapper::Installer::Precondition::Exec;

=head1 FUNCTIONS

=cut

=head2 set_env_variables

Set environment variables for executed command/program.

=cut

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

        $ENV{TAPPER_TESTRUN}         = $self->cfg->{test_run};
        $ENV{TAPPER_SERVER}          = $self->cfg->{mcp_host};
        $ENV{TAPPER_REPORT_SERVER}   = $self->cfg->{report_server};
        $ENV{TAPPER_REPORT_API_PORT} = $self->cfg->{report_api_port};
        $ENV{TAPPER_REPORT_PORT}     = $self->cfg->{report_port};
        $ENV{TAPPER_HOSTNAME}        = $self->cfg->{hostname};
        return;
}

=head2 install

This function executes a program inside the installed system. This supersedes
the postinstall script facility of the package precondition and makes this
feature available to all other preconditions.

@param hash reference - contains all information about the program

@return success - 0
@return error   - error string

=cut

sub install
{
        my $filename = $exec->{filename};
        my @options;
        @options = @{$exec->{options}} if $exec->{options};

	$self->log->debug("executing $filename with options ",join (" ",@options));

        my $fullpath = $self->cfg->{paths}{base_dir}.$filename;
        return("$filename is not an executable") if not -x $fullpath;
        
	pipe (my $read, my $write);
	return ("Can't open pipe:$!") if not (defined $read and defined $write);

	# we need to fork for chroot
	my $pid = fork();
	return "fork failed: $!" if not defined $pid;

	# hello child
	if ($pid == 0) {
                $self->set_env_variables;

                close $read;
		# chroot to execute script inside the future root file system
                my ($error, $output) = $self->log_and_exec("mount -o bind /dev/ ".$self->cfg->{paths}{base_dir}."/dev");
                ($error, $output)    = $self->log_and_exec("mount -t sysfs sys ".$self->cfg->{paths}{base_dir}."/sys");
                ($error, $output)    = $self->log_and_exec("mount -t proc proc ".$self->cfg->{paths}{base_dir}."/proc");
		chroot $self->cfg->{paths}{base_dir};
		chdir ("/");
                ($error, $output)=$self->log_and_exec($filename,@options);
                print( $write $output, "\n") if $output;
                close $write;
                exit $error;
	} else {
                close $write;
                my $select = IO::Select->new( $read );
                my ($error, $output);
        MSG_FROM_CHILD:
                while (my @ready = $select->can_read()){
                        my $tmpout = <$read>;   # only read can be in @ready, since no other FH is in $select
                        last MSG_FROM_CHILD if not $tmpout;
                        $output.=$tmpout;
                }
                if ($output) {
                        my $outfile = $filename;
                        $outfile =~ s/[^A-Za-z_-]/_/g;
                        $self->file_save($output,$outfile);
                }
                ($error, $output)=$self->log_and_exec("umount ".$self->cfg->{paths}{base_dir}."/dev");
                ($error, $output)=$self->log_and_exec("umount ".$self->cfg->{paths}{base_dir}."/sys");
                ($error, $output)=$self->log_and_exec("umount ".$self->cfg->{paths}{base_dir}."/proc");
                waitpid($pid,0);
                if ($?) {
                        return("executing $filename failed");
                }
		return(0);
	}
}

{'fraction_non_alphanumeric': 0.15250134480903713, 'fraction_numerical': 0.00228617536309844, 'mean_word_length': 2.250437062937063, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 91, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3941628', 'n_tokens_mistral': 2373, 'n_tokens_neox': 2243, 'n_words': 562}