The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use File::Path;
use File::Basename;
use Getopt::Std;

getopts( 'iru:', \my %opt );
my $d = dirname(__FILE__) . '/extra';

bringup()  if defined $opt{i};
teardown() if defined $opt{r};

# Setup structure:

# {base}/         (0755, 0, 0);
#   1/             (0777, u, g)
#     a             (0600, u, g)
#     b             (0400, u, g)
#   2/             (0700, u, g)
#     a             (0066, 0, 0)
#     b             (0400, 0, 0)
#     c             (0000, u, g)
#   3/             (0700, u, g)
#     a             (0400, 0, 0)
#     b             (0400, u, g)
#     M/            (0700, u, g)
#       xx           (0400, u, g)
#       yy           (0400, u, g)
#     N => M/
#     S/            (0000, 0, 0)
#     T/            (0000, 0, 0)
#     U/            (0000, 0, 0)
#     V/            (0700, u, g)
#   4 => 3/
#   5/             (0200, 0, 0)
#     xx            (0700, 0, 0)
#     yy            (0700, 0, 0)

sub bringup {
  die 'Must be root to bringup test' if $< != 0;
  die 'Must provide uid of the user running tests as non-root'
    if not defined $opt{u};

  my ($uid, $gid) = $opt{u} =~ /\D/
      ? (getpwnam($opt{u}))[2,3]
      : (getpwuid($opt{u}))[2,3]
  ;

  create_dir($d, 0755);

  # directory EXTRA/1 could be deleted by a
  # non-privileged account, including one file belonging to root.
  create_dir ( $d . '/1',   0777, $uid, $gid );
  create_file( $d . '/1/a', 0600, $uid, $gid );
  create_file( $d . '/1/b', 0400, $uid, $gid );

  # contents of EXTRA/2 can be removed by a
  # non-privileged account.
  create_dir ( $d . '/2',   0700, $uid, $gid );
  create_file( $d . '/2/a', 0066, $<,   $(   );
  create_file( $d . '/2/b', 0400, $<,   $(   );
  create_file( $d . '/2/c', 0000, $uid, $gid );

  # directory EXTRA/3 contains sundry files
  create_dir(  $d . '/3',      0700, $uid, $gid );
  create_file( $d . '/3/a',    0400, $<,   $(   );
  create_file( $d . '/3/b',    0400, $uid, $gid );

  # directory EXTRA/4 is a symlink to EXTRA/3
  symlink './3', $d . '/4' or die "symlink: $!";

  create_dir(    $d . '/3/M',    0700, $uid, $gid );
  create_file(   $d . '/3/M/xx', 0400, $uid, $gid );
  create_file(   $d . '/3/M/yy', 0400, $uid, $gid );
  create_dir(    $d . '/3/S',    0000, $<,   $( );
  create_dir(    $d . '/3/T',    0000, $<,   $( );
  create_dir(    $d . '/3/U',    0000, $<,   $( );
  create_dir(    $d . '/3/V',    0700, $uid, $gid );
  symlink './M', $d . '/3/N' or die "symlink: $!";

  # inaccessible child dir
  create_dir (   $d . '/5',    0700, $<,   $( );
  create_file(   $d . '/5/xx', 0700, $<,   $( );
  create_file(   $d . '/5/yy', 0700, $<,   $( );
  chmod( 0200,   $d . '/5' );
}

sub teardown {
  die 'Must be root to teardown test' if $< != 0;
  rmtree($d);
}

sub create_dir {
    my $dir  = shift;
    my $mask = shift;
    my $uid  = shift;
    my $gid  = shift;
    if (!-d $dir) {
        mkdir $dir, $mask or die "mkdir $dir: $!\n";
    }
    if (defined $uid and defined $gid) {
        chown $uid, $gid, $dir
            or die "failed to chown dir $dir to ($uid,$gid)\n"
    }
}

sub create_file {
    my $file = shift;
    my $mask = shift;
    my $uid  = shift;
    my $gid  = shift;
    open OUT, "> $file" or die "Cannot open $file for output: $!\n";
    print OUT <<EOM;
Test file for module File::Path
If you can read this, feel free to delete this file.
EOM
    close OUT;
    if ($uid and defined $gid) {
        chown $uid, $gid, $file
            or die "failed to chown $file to ($uid,$gid)\n"
    }
    if (defined $mask) {
        chmod $mask, $file
            or die "failed to chmod $file to $mask: $!\n";
    }
}