#############################################################################
## Name:        Compartment.pm
## Purpose:     Safe::World::Compartment -> Based in the Safe module.
## Author:      Graciliano M. P.
## Modified by:
## Created:     04/12/2003
## RCS-ID:      
## Copyright:   (c) 2003 Graciliano M. P.
## Licence:     This program is free software; you can redistribute it and/or
##              modify it under the same terms as Perl itself
#############################################################################

package Safe::World::Compartment ;

use strict qw(vars) ;

no warnings ;

##########
# SCOPES #
##########

  use vars qw($Safe_World_EVALX) ;

  *Safe_World_EVALX = \$Safe::World::EVALX ;

######### *** Don't declare any lexicals above this point ***

sub reval {
  my $__EVALCODE__ = $_[1] ;
  no strict ;

  $Safe_World_EVALX += 2 ;

  return Opcode::_safe_call_sv(
    $_[0]->{Root},
    $_[0]->{Mask},
    eval("package ". $_[0]->{Root} ."; sub { \@_=(); my \$EVALX = $Safe_World_EVALX; eval \$__EVALCODE__; }")
  );
}

#############################################################################

use vars qw($VERSION @ISA) ;

$VERSION = '0.02' ;

use Opcode 1.01, qw(
  opset opset_to_ops opmask_add
  empty_opset full_opset invert_opset verify_opset
  opdesc opcodes opmask define_optag opset_to_hex
);

*ops_to_opset = \&opset ;   # Temporary alias for old Penguins
*Opcode_safe_pkg_prep = \&Opcode::_safe_pkg_prep ;

my $default_share = ['*_'] ;

my $SCALAR_R ; tie( $SCALAR_R , 'Safe::World::Compartment::SCALAR_R') ;

#############################################################################

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

  $obj->{Root} = $root ;

  return undef if !defined($root) ;

  $obj->permit_only(':default') ;
  $obj->share_from('main', $default_share) ;
  
  {
    ## (See Safe::World::Compartment::SCALAR_R at the end of this file).
    ## Set the tied $^R to fix behavior:
    my $tmp = $_ ;
    $_ = \$SCALAR_R ;
    $obj->reval('*^R = $_') ;
    $_ = $tmp ;
    $^R = undef ; ## Ensure that is reseted.
  }
  
  Opcode_safe_pkg_prep($root) if($Opcode::VERSION > 1.04);
  
  return $obj;
}

sub deny {
  my $obj = shift;
  $obj->{Mask} |= opset(@_);
}
sub deny_only {
  my $obj = shift;
  $obj->{Mask} = opset(@_);
}

sub permit {
  my $obj = shift;
  $obj->{Mask} &= invert_opset opset(@_);
}

sub permit_only {
  my $obj = shift;
  $obj->{Mask} = invert_opset opset(@_);
}

sub share_from {
  my $obj = shift;
  my $pkg = shift;
  my $vars = shift;

  my $root = $obj->{Root} ;

  return undef if ref($vars) ne 'ARRAY' ;
  
  no strict 'refs';
  
  return undef unless keys %{"$pkg\::"} ;

  my $REF ;

  my $arg;
  foreach $arg (@$vars) {
    next unless( $arg =~ /^[\$\@%*&]?\w[\w:]*$/ || $arg =~ /^\$\W\w?$/ ) ;

    my ($var, $type);
    $type = $1 if ($var = $arg) =~ s/^(\W)// ;

    *{$root."::$var"} = (!$type) ?
      \&{$pkg."::$var"} : ($type eq '&') ?
        \&{$pkg."::$var"} : ($type eq '$') ?
          \${$pkg."::$var"} : ($type eq '@') ?
            \@{$pkg."::$var"} : ($type eq '%') ?
              \%{$pkg."::$var"} : ($type eq '*') ?
                \*{$pkg."::$var"} : undef ;
  }

  return 1 ;
}

######################################
# SAFE::WORLD::COMPARTMENT::SCALAR_R # TIE SCALAR FOR $^R
######################################

# The predefined variable $^R doesn't work like normal variables,
# that to be global lives in the main:: package. $^R doesn't exists
# at main::, soo $main::^R doesn't exists and we can't share it with
# the World compartment. $^R actually points to the last scalar returned
# by the code executed in the RE, soo $^R will point to different SCALARs
# during the RE, and if we change by hand the scalar reference of *^R it
# will be overwrited during the RE.
#
# To fix that I have used a closure in the
# FETCH and STORE methods of the TIESCALAR, and set the scalar of the
# GLOB reference inside the compartment (*^R) with the tied scalar.
# Soo, if an RE compiled inside the compartment make some reference to $^R
# it will see the external $^R through the TIED SCALAR.
# 

package Safe::World::Compartment::SCALAR_R ;

sub TIESCALAR {
  my $class = shift ;
  my $ref = shift ;
  return bless( \$ref , __PACKAGE__ ) ;
}

sub STORE {
  my $this = shift ;
  $^R = $_[0] ;
  return $^R ;
}

sub FETCH {
  my $this = shift ;
  return $^R ;
}

sub UNTIE {}
sub DESTROY {}

#######
# END #
#######

1;