# Copyright 1999-2017. Parallels IP Holdings GmbH. All Rights Reserved. package Storage::Bundle; use strict; use warnings; 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) = @_; if ($self->{srcdir}) { 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}); my @groups = split(' ', `id -G $self->{sysuser}`); if (scalar(@groups) == 1) { push @groups, $groups[0]; } $EGID = join(' ', @groups); POSIX::setgid($gid); POSIX::setuid($uid); } my $cmd_handle = gensym(); open($cmd_handle, "$cmd |"); my $buf; while (read($cmd_handle, $buf, 65536) > 0) { print $to_parent $buf; } 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;