# Copyright 1999-2014. Parallels IP Holdings GmbH. All Rights Reserved.
package Storage::Bundle;

use strict;
use POSIX;
use English;
use Logging;
use Symbol;

sub new {
  my $self = {};
  bless($self, shift);
  return $self if $self->_init(@_);
}

#
# Common options:
#
# 'user' - absence implies 'root'
#

sub _init {
  my ($self, %options) = @_;

  $self->{user} = $options{user} if defined $options{user};
  $self->{sysuser} = $options{sysuser} if defined $options{sysuser};
  $self->{srcdir} = $options{directory} if $options{directory};

  return 1;
}

#
# Returns the file handle object producing the bundle' data
#

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

  chdir $self->{srcdir};

  my $cmd = $self->commandLine();

  if ($self->isLogAllowed()) {
    Logging::debug("Executing bundle producer: '" . $cmd . "' in " . $self->{srcdir});
  }

  if ($self->{user} || $self->{sysuser}) {
    my $from_child = gensym();
    my $to_parent = gensym();
    pipe($from_child, $to_parent);

    my $pid = fork();
    if ($pid) {
      close($to_parent);
      $self->{pid} = $pid;
      $self->{channel} = $from_child;

      return $from_child;
    } else {
      close($from_child);

      $ENV{'LANG'} = 'C'; # To have non-localized error messages which may be parsed.
      local $SIG{'PIPE'} = 'DEFAULT' if !$SIG{'PIPE'} or $SIG{'PIPE'} eq 'IGNORE';

      if ($self->{sysuser}) {
        my ($name, $pass, $uid, $gid) = POSIX::getpwnam($self->{sysuser});
        $EGID = `id -G $self->{sysuser}`;
        POSIX::setgid($gid);
        POSIX::setuid($uid);
      }

      my $cmd_handle = gensym();
      open($cmd_handle, "$cmd |");
      while (<$cmd_handle>) {
        print $to_parent $_;
      }
      close($cmd_handle);
      close($to_parent);

      POSIX::_exit($? >> 8);
    }
  } else {
    local $SIG{'PIPE'} = 'DEFAULT' if !$SIG{'PIPE'} or $SIG{'PIPE'} eq 'IGNORE';

    my $cmd_handle = gensym();

    open($cmd_handle, "$cmd |");
    $self->{channel} = $cmd_handle;
    return $cmd_handle;
  }
}

sub isLogAllowed {
  my ($self) = @_;
  return 1;
}

#
# Cleans up all leftovers after run()
#

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

  close($self->{channel}) if exists $self->{channel};
  waitpid($self->{pid}, 0) if exists $self->{pid};
  my $exit_code = 0;
  if (exists $self->{channel} || exists $self->{pid}) {
    $exit_code = $? >> 8;
  }
  if ($exit_code) {
     Logging::debug("Bundle producer exit code: $exit_code");
  }
  return $exit_code;
}

sub filterStderr {
  my ($self, $stderr) = @_;
  return $stderr;
}

# -- Factory --

#
# Additional options:
#
# 'directory' - directory to archive - mandatory
# 'follow_symlinks' - archive the files symlink pointed to, not the symlinks
# 'include' - list of files to include in archive.
# 'exclude' - list of files to exclude from archive.
#

sub createTarBundle {
  require Storage::TarBundle;
  return Storage::TarBundle->new(@_);
}

#
# Additional options: see Db::Connection::getConnection()
#

sub createDbBundle {
  my %options = @_;

  if ($options{'type'} eq 'postgresql') {
	require Storage::PostgresqlDbBundle;
	return Storage::PostgresqlDbBundle->new(@_);
  }
  else {
	require Storage::DbBundle;
	return Storage::DbBundle->new(@_);
  }
}

1;
