# ex:ts=8 sw=4: # $OpenBSD: Add.pm,v 1.97 2009/11/16 14:42:18 espie Exp $ # # Copyright (c) 2003-2007 Marc Espie # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. use strict; use warnings; package OpenBSD::Add; use OpenBSD::Error; use OpenBSD::PackageInfo; use OpenBSD::ArcCheck; use OpenBSD::Paths; use File::Copy; sub manpages_index { my ($state) = @_; return unless defined $state->{mandirs}; my $destdir = $state->{destdir}; require OpenBSD::Makewhatis; while (my ($k, $v) = each %{$state->{mandirs}}) { my @l = map { $destdir.$_ } @$v; if ($state->{not}) { $state->say("Merging manpages in $destdir$k: ", join(@l)) if $state->{verbose}; } else { try { OpenBSD::Makewhatis::merge($destdir.$k, \@l); } catchall { $state->errsay("Error in makewhatis: $_"); }; } } } sub register_installation { my $plist = shift; return if $main::not; my $dest = installed_info($plist->pkgname); mkdir($dest); $plist->copy_info($dest); $plist->set_infodir($dest); $plist->to_installation; } sub validate_plist { my ($plist, $state) = @_; $plist->prepare_for_addition($state, $plist->pkgname); } sub record_partial_installation { my ($plist, $state, $h) = @_; use OpenBSD::PackingElement; my $n = $plist->make_shallow_copy($h); my $borked = borked_package($plist->pkgname); $n->set_pkgname($borked); # last file may have not copied correctly my $last = $n->{state}->{lastfile}; if (defined $last && defined($last->{d})) { my $old = $last->{d}; my $lastname = $last->realname($state); $last->{d} = $last->compute_digest($lastname, $old); if (!$old->equals($last->{d})) { $state->say("Adjusting ", $old->keyword, " for $lastname from ", $old->stringize, " to ", $last->{d}->stringize); } } register_installation($n); return $borked; } sub perform_installation { my ($handle, $state) = @_; my $totsize = $handle->{totsize}; $state->{archive} = $handle->{location}; my $donesize = 0; $state->{end_faked} = 0; if (!defined $handle->{partial}) { $handle->{partial} = {}; } $state->{partial} = $handle->{partial}; $handle->{plist}->install_and_progress($state, \$donesize, $totsize); $handle->{location}->finish_and_close; } my $user_tagged = {}; sub extract_pkgname { my $pkgname = shift; $pkgname =~ s/^.*\///; $pkgname =~ s/\.tgz$//; return $pkgname; } sub tweak_package_status { my ($pkgname, $state) = @_; $pkgname = extract_pkgname($pkgname); return 0 unless is_installed($pkgname); return 0 unless $user_tagged->{$pkgname}; my $plist = OpenBSD::PackingList->from_installation($pkgname); if ($plist->has('manual-installation') && $state->{automatic}) { delete $plist->{'manual-installation'}; $plist->to_installation; return 1; } elsif (!$plist->has('manual-installation') && !$state->{automatic}) { OpenBSD::PackingElement::ManualInstallation->add($plist); $plist->to_installation; return 1; } return 0; } sub tweak_plist_status { my ($plist, $state) = @_; my $pkgname = $plist->pkgname; return 0 unless $user_tagged->{$pkgname}; if (!$plist->has('manual-installation') && !$state->{automatic}) { OpenBSD::PackingElement::ManualInstallation->add($plist); } } sub tag_user_packages { for my $set (@_) { for my $n ($set->newer_names) { $user_tagged->{OpenBSD::PackageName::url2pkgname($n)} = 1; } } } # used by newuser/newgroup to deal with options. package OpenBSD::PackingElement; use OpenBSD::Error; my ($uidcache, $gidcache); sub prepare_for_addition { } sub install_and_progress { my ($self, $state, $donesize, $totsize) = @_; unless ($state->{do_faked} && $state->{end_faked}) { $self->install($state); } if ($state->{interrupted}) { die "Interrupted"; } $self->mark_progress($state->progress, $donesize, $totsize); } sub install { my ($self, $state) = @_; $state->{partial}->{$self} = 1; } sub copy_info { } sub set_modes { my ($self, $name) = @_; if (defined $self->{owner} || defined $self->{group}) { require OpenBSD::IdCache; if (!defined $uidcache) { $uidcache = OpenBSD::UidCache->new; $gidcache = OpenBSD::GidCache->new; } my ($uid, $gid); if (-l $name) { ($uid, $gid) = (lstat $name)[4,5]; } else { ($uid, $gid) = (stat $name)[4,5]; } if (defined $self->{owner}) { $uid = $uidcache->lookup($self->{owner}, $uid); } if (defined $self->{group}) { $gid = $gidcache->lookup($self->{group}, $gid); } chown $uid, $gid, $name; } if (defined $self->{mode}) { my $v = $self->{mode}; if ($v =~ m/^\d+$/o) { chmod oct($v), $name; } else { System(OpenBSD::Paths->chmod, $self->{mode}, $name); } } } package OpenBSD::PackingElement::ExtraInfo; use OpenBSD::Error; sub prepare_for_addition { my ($self, $state, $pkgname) = @_; if ($state->{cdrom_only} && $self->{cdrom} ne 'yes') { $state->errsay("Package $pkgname is not for cdrom."); $state->{problems}++; } if ($state->{ftp_only} && $self->{ftp} ne 'yes') { $state->errsay("Package $pkgname is not for ftp."); $state->{problems}++; } } package OpenBSD::PackingElement::NewAuth; use OpenBSD::Error; sub add_entry { shift; # get rid of self my $l = shift; while (@_ >= 2) { my $f = shift; my $v = shift; next if !defined $v or $v eq ''; if ($v =~ m/^\!(.*)$/o) { push(@$l, $f, $1); } else { push(@$l, $f, $v); } } } sub prepare_for_addition { my ($self, $state, $pkgname) = @_; my $ok = $self->check; if (defined $ok) { if ($ok == 0) { $state->errsay($self->type, " ", $self->name, " does not match"); $state->{problems}++; } } $self->{okay} = $ok; } sub install { my ($self, $state) = @_; $self->SUPER::install($state); my $auth = $self->name; $state->say("adding ", $self->type, " ", $auth) if $state->{verbose}; return if $state->{not}; return if defined $self->{okay}; my $l=[]; push(@$l, "-v") if $state->{very_verbose}; $self->build_args($l); $state->vsystem($self->command,, @$l, $auth); } package OpenBSD::PackingElement::NewUser; sub command { OpenBSD::Paths->useradd } sub build_args { my ($self, $l) = @_; $self->add_entry($l, '-u', $self->{uid}, '-g', $self->{group}, '-L', $self->{class}, '-c', $self->{comment}, '-d', $self->{home}, '-s', $self->{shell}); } package OpenBSD::PackingElement::NewGroup; sub command { OpenBSD::Paths->groupadd } sub build_args { my ($self, $l) = @_; $self->add_entry($l, '-g', $self->{gid}); } package OpenBSD::PackingElement::Sysctl; use OpenBSD::Error; sub install { my ($self, $state) = @_; my $name = $self->name; $self->SUPER::install($state); open(my $pipe, '-|', OpenBSD::Paths->sysctl, '-n', $name); my $actual = <$pipe>; chomp $actual; if ($self->{mode} eq '=' && $actual eq $self->{value}) { return; } if ($self->{mode} eq '>=' && $actual >= $self->{value}) { return; } if ($state->{not}) { $state->say("sysctl -w $name != ". $self->{value}); return; } $state->vsystem(OpenBSD::Paths->sysctl, $name.'='.$self->{value}); } package OpenBSD::PackingElement::DirBase; sub prepare_for_addition { my ($self, $state, $pkgname) = @_; return unless $self->{noshadow}; $state->{noshadow}->{$state->{destdir}.$self->fullname} = 1; } package OpenBSD::PackingElement::FileBase; use OpenBSD::Error; use File::Basename; use File::Path; sub prepare_for_addition { my ($self, $state, $pkgname) = @_; my $fname = $state->{destdir}.$self->fullname; # check for collisions with existing stuff if ($state->vstat->exists($fname)) { push(@{$state->{colliding}}, $self); $state->{problems}++; return; } my $s = $state->vstat->add($fname, $self->{size}, \$pkgname); return unless defined $s; if ($s->{ro}) { $s->report_ro($state, $fname); } if ($s->avail < 0) { $s->report_overflow($state, $fname); } } sub install { my ($self, $state) = @_; $self->SUPER::install($state); my $fullname = $self->fullname; my $destdir = $state->{destdir}; if ($state->{replacing}) { if ($state->{not}) { $state->say("moving tempfile -> $destdir$fullname") if $state->{very_verbose}; return; } File::Path::mkpath(dirname($destdir.$fullname)); if (defined $self->{link}) { link($destdir.$self->{link}, $destdir.$fullname); } elsif (defined $self->{symlink}) { symlink($self->{symlink}, $destdir.$fullname); } else { rename($self->{tempname}, $destdir.$fullname) or Fatal "Can't move ", $self->{tempname}, " to $fullname: $!"; $state->say("moving ", $self->{tempname}, " -> $destdir$fullname") if $state->{very_verbose}; undef $self->{tempname}; } } else { my $file = $self->prepare_to_extract($state); $state->say("extracting $destdir$fullname") if $state->{very_verbose}; if ($state->{not}) { $state->{archive}->skip; return; } else { $file->create; $self->may_check_digest($file, $state); } } $self->set_modes($destdir.$fullname); } sub prepare_to_extract { my ($self, $state) = @_; my $fullname = $self->fullname; my $destdir = $state->{destdir}; my $file=$state->{archive}->next; if (!defined $file) { Fatal "Error: truncated archive\n"; } $file->{cwd} = $self->cwd; if (!$file->check_name($self)) { Fatal "Error: archive does not match ", $file->name, "!=", $self->name, "\n"; } if (defined $self->{symlink} || $file->isSymLink) { unless (defined $self->{symlink} && $file->isSymLink) { Fatal "Error: bogus symlink ", $self->name, "\n"; } if (!$file->check_linkname($self->{symlink})) { Fatal "Error: archive sl does not match ", $file->{linkname}, "!=", $self->{symlink}, "\n"; } } elsif (defined $self->{link} || $file->isHardLink) { unless (defined $self->{link} && $file->isHardLink) { Fatal "Error: bogus hardlink ", $self->name, "\n"; } if (!$file->check_linkname($self->{link})) { Fatal "Error: archive hl does not match ", $file->{linkname}, "!=", $self->{link}, "!!!\n"; } } if (!$file->verify_modes($self)) { Fatal "Can't continue\n"; } $file->set_name($fullname); $file->{destdir} = $destdir; # faked installation are VERY weird if (defined $self->{symlink} && $state->{do_faked}) { $file->{linkname} = $destdir.$file->{linkname}; } return $file; } package OpenBSD::PackingElement::EndFake; sub install { my ($self, $state) = @_; $self->SUPER::install($state); $state->{end_faked} = 1; } package OpenBSD::PackingElement::Sample; use OpenBSD::Error; use File::Copy; sub prepare_for_addition { my ($self, $state, $pkgname) = @_; if (!defined $self->{copyfrom}) { Fatal "\@sample element does not reference a valid file\n"; } my $fname = $state->{destdir}.$self->fullname; # If file already exists, we won't change it if ($state->vstat->exists($fname)) { return; } my $size = $self->{copyfrom}->{size}; my $s = $state->vstat->add($fname, $size, \$pkgname); return unless defined $s; if ($s->{ro}) { $s->report_ro($state, $fname); } if ($s->avail < 0) { $s->report_overflow($state, $fname); } } sub install { my ($self, $state) = @_; $self->SUPER::install($state); my $destdir = $state->{destdir}; my $filename = $destdir.$self->fullname; my $orig = $self->{copyfrom}; my $origname = $destdir.$orig->fullname; if (-e $filename) { if ($state->{verbose}) { $state->say("The existing file $filename has NOT been changed"); if (defined $orig->{d}) { # XXX assume this would be the same type of file my $d = $self->compute_digest($filename, $orig->{d}); if ($d->equals($orig->{d})) { $state->say("(but it seems to match the sample file $origname)"); } else { $state->say("It does NOT match the sample file $origname"); $state->say("You may wish to update it manually"); } } } } else { if ($state->{not}) { $state->say("The file $filename would be installed from $origname"); } else { if (!copy($origname, $filename)) { $state->errsay("File $filename could not be installed:\n\t$!"); } $self->set_modes($filename); if ($state->{verbose}) { $state->say("installed $filename from $origname"); } } } } package OpenBSD::PackingElement::Sampledir; sub install { &OpenBSD::PackingElement::Dir::install; } package OpenBSD::PackingElement::Mandir; sub install { my ($self, $state) = @_; $self->SUPER::install($state); $state->log("You may wish to add ", $self->fullname, " to /etc/man.conf\n"); } package OpenBSD::PackingElement::Manpage; sub install { my ($self, $state) = @_; $self->SUPER::install($state); $self->register_manpage($state) unless $state->{not}; } package OpenBSD::PackingElement::InfoFile; use File::Basename; use OpenBSD::Error; sub install { my ($self, $state) = @_; $self->SUPER::install($state); return if $state->{not}; my $fullname = $state->{destdir}.$self->fullname; $state->vsystem(OpenBSD::Paths->install_info, "--info-dir=".dirname($fullname), $fullname); } package OpenBSD::PackingElement::Shell; sub install { my ($self, $state) = @_; $self->SUPER::install($state); return if $state->{not}; my $fullname = $self->fullname; my $destdir = $state->{destdir}; # go append to /etc/shells if needed open(my $shells, '<', $destdir.OpenBSD::Paths->shells) or return; my $_; while(<$shells>) { s/^\#.*//o; return if $_ =~ m/^\Q$fullname\E\s*$/; } close($shells); open(my $shells2, '>>', $destdir.OpenBSD::Paths->shells) or return; print $shells2 $fullname, "\n"; close $shells2; $state->say("Shell $fullname appended to $destdir", OpenBSD::Paths->shells); } package OpenBSD::PackingElement::Dir; sub install { my ($self, $state) = @_; $self->SUPER::install($state); my $fullname = $self->fullname; my $destdir = $state->{destdir}; $state->say("new directory ", $destdir, $fullname) if $state->{very_verbose}; return if $state->{not}; File::Path::mkpath($destdir.$fullname); $self->set_modes($destdir.$fullname); } package OpenBSD::PackingElement::Exec; use OpenBSD::Error; sub install { my ($self, $state) = @_; $self->SUPER::install($state); if ($self->should_run($state)) { $self->run($state); } } sub should_run() { 1 } package OpenBSD::PackingElement::ExecAdd; sub should_run { my ($self, $state) = @_; return !$state->{replacing}; } package OpenBSD::PackingElement::ExecUpdate; sub should_run { my ($self, $state) = @_; return $state->{replacing}; } package OpenBSD::PackingElement::Lib; sub install { my ($self, $state) = @_; $self->SUPER::install($state); return if $state->{do_faked}; $self->mark_ldconfig_directory($state->{destdir}); } package OpenBSD::PackingElement::SpecialFile; use OpenBSD::PackageInfo; use OpenBSD::Error; sub prepare_for_addition { my ($self, $state, $pkgname) = @_; my $fname = installed_info($pkgname).$self->name; my $cname = $self->fullname; my $size = $self->{size}; if (!defined $size) { $size = (stat $cname)[7]; } if ($self->exec_on_add) { my $s2 = $state->vstat->stat($cname); if (defined $s2 && $s2->{noexec}) { $s2->report_noexec($state, $cname); } } my $s = $state->vstat->add($fname, $self->{size}, \$pkgname); return unless defined $s; if ($s->{ro}) { $s->report_ro($state, $fname); } if ($s->{noexec} && $self->exec_on_delete) { $s->report_noexec($state, $fname); } if ($s->avail < 0) { $s->report_overflow($state, $fname); } } sub copy_info { my ($self, $dest) = @_; require File::Copy; File::Copy::move($self->fullname, $dest) or print STDERR "Problem while moving ", $self->fullname, " into $dest: $!\n"; } package OpenBSD::PackingElement::FINSTALL; sub install { my ($self, $state) = @_; $self->run($state, 'PRE-INSTALL'); } package OpenBSD::PackingElement::FCONTENTS; sub copy_info { } 1;