# ex:ts=8 sw=4: # $OpenBSD: Add.pm,v 1.50 2007/02/24 18:45:11 espie Exp $ # # Copyright (c) 2003-2004 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 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}) { print "Merging manpages in $destdir$k: ", join(@l), "\n" if $state->{verbose}; } else { try { OpenBSD::Makewhatis::merge($destdir.$k, \@l); } catchall { print STDERR "Error in makewhatis: $_\n"; }; } } } sub register_installation { my ($dir, $dest, $plist) = @_; return if $main::not; mkdir($dest); for my $i (info_names()) { copy($dir.$i, $dest); } $plist->to_installation(); } sub validate_plist($$) { my ($plist, $state) = @_; my $destdir = $state->{destdir}; my $problems = 0; my $pkgname = $plist->pkgname(); my $totsize = 0; my $colliding = []; $plist->visit('validate', $state, \$problems, $colliding, \$totsize, $plist->pkgname()); if (@$colliding > 0) { require OpenBSD::CollisionReport; OpenBSD::CollisionReport::collision_report($colliding, $state); } if (defined $state->{overflow}) { OpenBSD::Vstat::tally(); } Fatal "fatal issues in installing $pkgname" if $problems; $totsize = 1 if $totsize == 0; return $totsize; } sub borked_installation { my ($plist, $dir, $not, @msg) = @_; Fatal @msg if $not; use OpenBSD::PackingElement; my $borked = borked_package($plist->pkgname()); # fix packing list for pkg_delete $plist->{items} = $plist->{done}; # last file may have not copied correctly my $last = $plist->{items}->[@{$plist->{items}}-1]; if ($last->IsFile() && defined($last->{md5})) { require OpenBSD::md5; my $old = $last->{md5}; my $lastname; if (defined $last->{tempname}) { $lastname = $last->{tempname}; } else { $lastname = $last->fullname(); } $last->{md5} = OpenBSD::md5::fromfile($lastname); if ($old ne $last->{md5}) { print "Adjusting md5 for $lastname from ", unpack('H*', $old), " to ", unpack('H*', $last->{md5}), "\n"; } } OpenBSD::PackingElement::Cwd->add($plist, '.'); my $pkgname = $plist->pkgname(); $plist->{name}->{name} = $borked; $plist->{pkgdep} = []; my $dest = installed_info($borked); register_installation($dir, $dest, $plist); Fatal @msg, ", partial installation recorded as $borked"; } # used by newuser/newgroup to deal with options. package OpenBSD::PackingElement; use OpenBSD::Error; my ($uidcache, $gidcache); sub validate { } sub install { } 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+$/) { chmod oct($v), $name; } else { System('chmod', $self->{mode}, $name); } } } package OpenBSD::PackingElement::ExtraInfo; use OpenBSD::Error; sub validate { my ($self, $state, $problems, $colliding, $totsize, $pkgname) = @_; if ($state->{cdrom_only} && $self->{cdrom} ne 'yes') { Warn "Package $pkgname is not for cdrom.\n"; $$problems++; } if ($state->{ftp_only} && $self->{ftp} ne 'yes') { Warn "Package $pkgname is not for ftp.\n"; $$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/^\!/) { push(@$l, $f, $'); } else { push(@$l, $f, $v); } } } sub validate { my ($self, $state, $problems, $colliding, $totsize, $pkgname) = @_; my $ok = $self->check(); if (defined $ok) { if ($ok == 0) { Warn $self->type(), " ", $self->{name}, " does not match\n"; $$problems++; } } $self->{okay} = $ok; } sub install { my ($self, $state) = @_; my $auth = $self->{name}; print "adding ", $self->type(), " $auth\n" 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); VSystem($state->{very_verbose}, $self->command(),, @$l, $auth); } package OpenBSD::PackingElement::NewUser; sub command { '/usr/sbin/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 { '/usr/sbin/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}; open(my $pipe, '-|', '/sbin/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}) { print "sysctl -w $name != ". $self->{value}, "\n"; return; } VSystem($state->{very_verbose}, '/sbin/sysctl', $name.'='.$self->{value}); } package OpenBSD::PackingElement::FileBase; use OpenBSD::Error; use File::Basename; use File::Path; sub validate { my ($self, $state, $problems, $colliding, $totsize, $pkgname) = @_; my $fname = $state->{destdir}.$self->fullname(); # check for collisions with existing stuff if (OpenBSD::Vstat::vexists($fname)) { push(@$colliding, $self); $$problems++; return; } $$totsize += $self->{size} if defined $self->{size}; my $s = OpenBSD::Vstat::add($fname, $self->{size}, \$pkgname); return unless defined $s; if ($s->{ro}) { if ($state->{very_verbose} or ++($s->{problems}) < 4) { Warn "Error: ", $s->{dev}, " is read-only ($fname)\n"; } elsif ($s->{problems} == 4) { Warn "Error: ... more files can't be written to ", $s->{dev}, "\n"; } $$problems++; } if ($state->{forced}->{kitchensink} && $state->{not}) { return; } if ($s->avail() < 0) { if ($state->{very_verbose} or ++($s->{problems}) < 4) { Warn "Error: ", $s->{dev}, " is not large enough ($fname)\n"; } elsif ($s->{problems} == 4) { Warn "Error: ... more files do not fit on ", $s->{dev}, "\n"; } $state->{overflow} = 1; $$problems++; } } sub install { my ($self, $state) = @_; my $fullname = $self->fullname(); my $destdir = $state->{destdir}; if ($state->{replacing}) { if ($state->{not}) { print "moving tempfile -> $destdir$fullname\n" if $state->{very_verbose}; return; } File::Path::mkpath(dirname($destdir.$fullname)); if (defined $self->{link}) { link($destdir.$self->{link}, $destdir.$fullname); delete $self->{zap}; } elsif (defined $self->{symlink}) { symlink($self->{symlink}, $destdir.$fullname); delete $self->{zap}; } else { rename($self->{tempname}, $destdir.$fullname) or Fatal "Can't move ", $self->{tempname}, " to $fullname: $!"; print "moving ", $self->{tempname}, " -> $destdir$fullname\n" if $state->{very_verbose}; undef $self->{tempname}; } } else { my $file = $self->prepare_to_extract($state); print "extracting $destdir$fullname\n" if $state->{very_verbose}; if ($state->{not}) { $state->{archive}->skip(); return; } else { $file->create(); } } $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"; } } $file->{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) = @_; $state->{end_faked} = 1; } package OpenBSD::PackingElement::Sample; use OpenBSD::Error; use File::Copy; sub validate { my ($self, $state, $problems, $colliding, $totsize, $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 (OpenBSD::Vstat::vexists($fname)) { return; } my $size = $self->{copyfrom}->{size}; $$totsize += $size if defined $size; my $s = OpenBSD::Vstat::add($fname, $size, \$pkgname); return unless defined $s; if ($s->{ro}) { if ($state->{very_verbose} or ++($s->{problems}) < 4) { Warn "Error: ", $s->{dev}, " is read-only ($fname)\n"; } elsif ($s->{problems} == 4) { Warn "Error: ... more files can't be written to ", $s->{dev}, "\n"; } $$problems++; } if ($state->{forced}->{kitchensink} && $state->{not}) { return; } if ($s->avail() < 0) { if ($state->{very_verbose} or ++($s->{problems}) < 4) { Warn "Error: ", $s->{dev}, " is not large enough ($fname)\n"; } elsif ($s->{problems} == 4) { Warn "Error: ... more files do not fit on ", $s->{dev}, "\n"; } $state->{overflow} = 1; $$problems++; } } sub install { my ($self, $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}) { print "The existing file $filename has NOT been changed\n"; if (defined $orig->{md5}) { require OpenBSD::md5; my $md5 = OpenBSD::md5::fromfile($filename); if ($md5 eq $orig->{md5}) { print "(but it seems to match the sample file $origname)\n"; } else { print "It does NOT match the sample file $origname\n"; print "You may wish to update it manually\n"; } } } } else { if ($state->{not}) { print "The file $filename would be installed from $origname\n"; } else { if (!copy($origname, $filename)) { Warn "File $filename could not be installed:\n\t$!\n"; } $self->set_modes($filename); if ($state->{verbose}) { print "installed $filename from $origname\n"; } } } } package OpenBSD::PackingElement::Sampledir; sub install { &OpenBSD::PackingElement::Dir::install; } package OpenBSD::PackingElement::Mandir; sub install { my ($self, $state) = @_; $self->SUPER::install($state); $state->print("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(); VSystem($state->{very_verbose}, "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.'/etc/shells') or return; local $_; while(<$shells>) { s/^\#.*//; return if $_ =~ m/^\Q$fullname\E\s*$/; } close($shells); open(my $shells2, '>>', $destdir.'/etc/shells') or return; print $shells2 $fullname, "\n"; close $shells2; print "Shell $fullname appended to $destdir/etc/shells\n"; } package OpenBSD::PackingElement::Dir; sub install { my ($self, $state) = @_; my $fullname = $self->fullname(); my $destdir = $state->{destdir}; print "new directory ", $destdir, $fullname, "\n" 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->run($state); } 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 validate { my ($self, $state, $problems, $colliding, $totsize, $pkgname) = @_; my $fname = installed_info($pkgname).$self->{name}; my $cname = $state->{dir}.'/'.$self->{name}; my $size = $self->{size}; if (!defined $size) { $size = (stat $cname)[7]; } if ($self->exec_on_add()) { my $s2 = OpenBSD::Vstat::filestat($cname); if (defined $s2 && $s2->{noexec}) { Warn "Error: ", $s2->{dev}, " is noexec ($cname)\n"; $$problems++; } } my $s = OpenBSD::Vstat::add($fname, $self->{size}, \$pkgname); return unless defined $s; if ($s->{ro}) { Warn "Error: ", $s->{dev}, " is read-only ($fname)\n"; $$problems++; } if ($s->{noexec} && $self->exec_on_delete()) { Warn "Error: ", $s->{dev}, " is noexec ($fname)\n"; $$problems++; } if ($state->{forced}->{kitchensink} && $state->{not}) { return; } if ($s->avail() < 0) { Warn "Error: ", $s->{dev}, " is not large enough ($fname)\n"; $state->{overflow} = 1; $$problems++; } } 1;