home *** CD-ROM | disk | FTP | other *** search
- package PPM::Installer;
-
- use strict;
- use Socket;
- use Data::Dumper;
- use PPM::Result qw(Error Warning Ok List);
-
- my %connections;
- sub new {
- my ($class, $name, $targ) = @_;
-
- my @ret;
- my $pkg = "PPM::Installer::$targ->{type}";
-
- # Let the type decide which subclass to use:
- eval "require $pkg";
- if (not $@) {
- no strict 'refs';
- $connections{$name} = &{"${pkg}::new"}($pkg, $targ, $name);
- $connections{$name}->init;
- @ret = $connections{$name};
- }
- else {
- @ret = (undef, "Unsupported installer type '$targ->{type}'");
- }
- @ret;
- }
-
- sub DESTROY {
- my $o = shift;
- $o->fini;
- }
-
- #=============================================================================
- # API methods -- simple fetch routines.
- #=============================================================================
- sub name {
- my $inst = shift;
- $inst->{name};
- }
-
- sub port {
- my $inst = shift;
- $inst->{port};
- }
-
- sub type {
- my $inst = shift;
- $inst->{type};
- }
-
- sub ckeys { qw(port type) }
- sub cvals {
- my $o = shift;
- map { $o->$_ } $o->ckeys;
- }
-
- #=============================================================================
- # Methods which determine which version of the API the backend is running.
- #=============================================================================
- # We remember here what methods were supported under protocol version 1.
- my %HAS = map { $_ => 1 } qw(
- query
- properties
- remove
- precious
- bundled
- dependents
- config_info
- config_keys
- config_get
- config_set
- error_str
- install
- upgrade
- pkginit
- pkgfini
- transmit
- stop
- );
-
- sub has {
- my $inst = shift;
- my $method = shift;
- return $inst->_has($method) if $inst->protocol >= 2;
- return $HAS{$method} ? 1 : 0;
- }
-
- sub _has {
- my $inst = shift;
- my $method = shift;
- my $send = $inst->encode_record("HAS", $method);
- $inst->sendmsg($send);
- my $msg = $inst->recvmsg;
- return 0 if $msg and $msg eq 'NOK';
- return 1;
- }
-
- #=============================================================================
- # API methods which probably should be overridden in subclasses.
- #=============================================================================
- sub init { }
- sub fini { }
-
- # Transmit files to the backend installer agent. This is used for remote
- # installers, which must copy the files to the remote machine in order to
- # install them. Local installers can just use the local copy.
- sub pkginit { Ok() } # creates any temporary directory needed
- sub transmit { Ok() } # transmits files (into the temp dir)
- sub pkgfini { Ok() } # removes the temporary directory
-
- #=============================================================================
- # API methods which _may_ be overridden in subclasses.
- #=============================================================================
- sub connect_to {
- my $inst = shift;
- my $addr = shift;
- my $port = shift;
- my ($iaddr, $paddr, $proto);
- $iaddr = inet_aton($addr)
- or return Error("no host: $addr");
- $paddr = sockaddr_in($port, $iaddr);
- $proto = getprotobyname('tcp');
- socket($inst->{SOCK}, PF_INET, SOCK_STREAM, $proto)
- or return Error("socket: $!");
- connect($inst->{SOCK}, $paddr)
- or return Error("connect: $!");
- select((select($inst->{SOCK}), $| = 1)[0]);
- Ok();
- }
-
- sub query {
- my $inst = shift;
- $inst->sendmsg($inst->encode_record("QUERY", @_));
- my $msg = $inst->recvmsg;
- return Error($inst->error_str) if $msg eq 'NOK';
- my@l = map { PPM::PPD->new($_) }
- map { chomp; $inst->decode_record($_) } # flattens fields
- split(/^/, $msg);
- List(@l);
- }
-
- sub properties {
- my $inst = shift;
- $inst->sendmsg($inst->encode_record("PROPERTIES", @_));
- my $recv = $inst->recvmsg;
- if ($recv eq 'NOK') {
- return Error($inst->error_str);
- }
- my ($ppd, @other) = $inst->decode_record($recv);
- return List(PPM::PPD->new($ppd), @other);
- }
-
- sub precious {
- my $inst = shift;
- $inst->sendmsg("PRECIOUS");
- List($inst->decode_record($inst->recvmsg));
- }
-
- sub bundled {
- my $inst = shift;
- $inst->sendmsg("BUNDLED");
- List($inst->decode_record($inst->recvmsg));
- }
-
- sub dependents {
- my $inst = shift;
- $inst->sendmsg($inst->encode_record("DEPENDENTS", @_));
- my $recv = $inst->recvmsg;
- return Error($inst->error_str) if $recv eq 'NOK';
- return List() if not defined (($inst->decode_record($recv))[0]);
- return List($inst->decode_record($recv));
- }
-
- sub remove {
- my $inst = shift;
- $inst->sendmsg($inst->encode_record("REMOVE", @_));
- return Ok() if $inst->recvmsg eq 'OK';
- return Error($inst->error_str);
- }
-
- sub install {
- my $inst = shift;
- $inst->sendmsg($inst->encode_record("INSTALL", @_));
- return Ok() if $inst->recvmsg eq 'OK';
- return Error($inst->error_str);
- }
-
- sub upgrade {
- my $inst = shift;
- $inst->sendmsg($inst->encode_record("UPGRADE", @_));
- return Ok() if $inst->recvmsg eq 'OK';
- return Error($inst->error_str);
- }
-
- sub config_info {
- my $inst = shift;
- $inst->sendmsg("CONFIG_INFO");
- my %info = map { chomp; $inst->decode_record } split /^/, $inst->recvmsg;
- return List(map { [$_, $info{$_}] } sort keys %info);
- }
-
- sub config_keys {
- my $inst = shift;
- $inst->sendmsg("CONFIG_KEYS");
- my %keys = map { chomp; $inst->decode_record } split /^/, $inst->recvmsg;
- return List(map { [$_, $keys{$_}] } sort keys %keys);
- }
-
- sub config_get {
- my $inst = shift;
- $inst->sendmsg($inst->encode_record("CONFIG_GET", @_));
- my $value = $inst->recvmsg;
- return Error($inst->error_str)
- if $value eq 'NOK';
- return Ok($value);
- }
-
- sub config_set {
- my $inst = shift;
- $inst->sendmsg($inst->encode_record("CONFIG_SET", @_));
- return Ok() if $inst->recvmsg eq 'OK';
- return Error($inst->error_str);
- }
-
- sub protocol {
- my $inst = shift;
- my $v = $inst->config_get("PROTOCOL");
- return $v->result if $v->ok;
- return 1; # version 1 didn't expose the PROTOCOL variable.
- }
-
- # This method was not supported in the first version of the PPM3 backend. To
- # compensate, we'll do a lousy emulation: if the languages are the same, and
- # the required version is lexically less than the PERLCORE exposed by the
- # backend, we'll allow the installation to proceed.
- sub can_install {
- my $inst = shift;
- unless ($inst->has('can_install')) {
- my ($lang, $version, $compat) = @_;
- return Ok(1) if (
- lc $lang eq lc $inst->config_get("TARGET_TYPE")->result and
- lc $version lt lc $inst->config_get("PERLCORE")->result
- );
- return Ok(0);
- }
- $inst->sendmsg($inst->encode_record("CAN_INSTALL", @_));
- my $result = $inst->recvmsg;
- return Error($inst->error_str) if $result eq 'NOK';
- return Ok($result);
- }
-
- sub error_str {
- my $inst = shift;
- $inst->sendmsg("ERROR_STR");
- $inst->recvmsg;
- }
-
- #=============================================================================
- # Non-API methods. Touch not, lest ye be smacked.
- #=============================================================================
- use constant FIELD_SEP => "\001";
- use constant FIELD_UNDEF => "\002";
- my $EOL = "\015\012";
- sub sendmsg {
- my $inst = shift;
- my $msg = shift;
- my $fd = $inst->{SOCK};
- {
- local $\ = "$EOL.$EOL";
- print $fd $msg;
- }
- }
-
- sub recvmsg {
- my $inst = shift;
- local $/ = "$EOL.$EOL";
- my $fd = $inst->{SOCK};
- chomp(my $msg = <$fd>);
- return $msg;
- }
-
- sub qmeta {
- local $_ = shift || $_;
- s{([^A-Za-z0-9])}{sprintf('\x%.2X',ord($1))}eg;
- $_;
- }
-
- sub uqmeta {
- local $_ = shift || $_;
- $_ = eval qq{qq{$_}};
- warn $@ if $@;
- $_;
- }
-
- sub encode_record {
- my $o = shift;
- my @fields = map { my $a = defined $_ ? $_ : FIELD_UNDEF; qmeta($a) } @_;
- join FIELD_SEP, @fields;
- }
-
- sub decode_record {
- my $o = shift;
- my $t = shift || $_;
- return map { $_ = &uqmeta; $_ = undef if $_ eq FIELD_UNDEF; $_ }
- split(FIELD_SEP, $t, -1);
- }
-
- 1;
-