diff --git a/SRC/snap/Commands.pm b/SRC/snap/Commands.pm index 76b6cd1..fb54da4 100644 --- a/SRC/snap/Commands.pm +++ b/SRC/snap/Commands.pm @@ -92,6 +92,10 @@ my $commands = { . "/file system" ] }, + provides => { + options => [], + brief => 'List packages that provide a file' + }, purge => { options => [ '', @@ -241,10 +245,14 @@ sub commandhelp { my $help = $commands->{$command}{'help'}; print "snap $command @{$commands->{$command}{'options'}}\n\n"; - print "$commands->{$command}{'brief'}\n\n"; + print "$commands->{$command}{'brief'}\n"; for ( my $i = 0; $i <= $#{$options}; $i++ ) { - print " $options->[$i]$help->[$i]\n"; + print "\n $options->[$i]$help->[$i]"; + } + + if ( @$options ) { + print "\n"; } } diff --git a/SRC/snap/Makefile b/SRC/snap/Makefile index 4a3d6d4..e54b09b 100644 --- a/SRC/snap/Makefile +++ b/SRC/snap/Makefile @@ -1,11 +1,10 @@ dirs: - install -d -v -m 755 $(DESTDIR)/etc/snap.d + install -d -v -m 755 $(DESTDIR)/etc/snap.d/templates install -d -v -m 755 $(DESTDIR)/usr/bin install -d -v -m 755 $(DESTDIR)/usr/share/snap install -d -v -m 755 $(DESTDIR)/usr/lib/perl5/vendor_perl/5.24.0/Snap files: - install -v -m 644 core.spt $(DESTDIR)/etc/snap.d/core.spt install -v -m 755 snap $(DESTDIR)/usr/bin/snap install -v -m 755 snapinstall $(DESTDIR)/usr/bin/snapinstall install -v -m 644 Makefile.skel \ @@ -20,5 +19,9 @@ files: $(DESTDIR)/usr/lib/perl5/vendor_perl/5.24.0/Snap/Sources.pm install -v -m 644 Snap.pm \ $(DESTDIR)/usr/lib/perl5/vendor_perl/5.24.0/Snap.pm + @find templates -type d -exec install {} -d -v -m 755 \ + $(DESTDIR)/etc/snap.d/templates/{} \; && \ + find templates -type f -exec install {} -v -m 644 \ + $(DESTDIR)/etc/snap.d/templates/{} \; install: dirs files diff --git a/SRC/snap/Package.pm b/SRC/snap/Package.pm index a8f0080..86885d0 100644 --- a/SRC/snap/Package.pm +++ b/SRC/snap/Package.pm @@ -7,7 +7,6 @@ use Fcntl; use IPC::Open3; use IO::Select; use Cwd 'abs_path'; -use Data::Dumper; use parent 'Snap'; @@ -177,6 +176,15 @@ sub conflicts { next; } + ### FIXME ################################## + # + # Not quite sure why "$installed" is used + # here... $package would be more consistant + # + # Should this be changed? + # + ############################################ + my $installed = $sources->{'installed'}{$pkgname}; foreach my $file ( @{$installed->{'files'}} ) { @@ -193,18 +201,16 @@ sub conflicts { if ( keys( %$conflicts ) ) { print STDERR "\nPackage $self->{'name'} conflicts with the" - . " following packages:\n\n"; + . " following packages:\n"; foreach my $pkgname ( sort { $conflicts->{$a}{'name'} cmp $conflicts->{$b}{'name'} } keys( %$conflicts ) ) { - print STDERR "[$pkgname]\n"; + print STDERR "\n[$pkgname]\n"; foreach my $file ( sort { $a cmp $b } @{$conflicts->{$pkgname}} ) { print " * $file\n"; } - - print "\n"; } Snap->error( -1, "Exiting due to conflicts" ); @@ -246,7 +252,7 @@ sub depends { Snap->error( -1, "$self->{'name'}" . "=$self->{'version'}:" . " A package cannot be" - . " dependant on itself" ); + . " dependent on itself" ); } if ( $selflist->{$name} && ( ! $req || @@ -379,6 +385,11 @@ sub files { my $self = shift; my $opts = shift; my $manifestfile = Snap->INSTDIR . "/$self->{'name'}/manifest"; + + if ( ref( $self->{'files'} ) eq 'ARRAY' && @{$self->{'files'}} ) { + return; + } + $self->{'files'} = []; if ( $self->{'path'} && -f $self->{'path'} ) { @@ -602,7 +613,19 @@ sub install { Snap->lock(); - $ENV{'VERSION'} = $self->{'version'}; + #################################################### + # + # Here we iterate through all installed packages + # and add their file lists. This will later be + # checked against for conflicts. + # + #################################################### + + foreach my $pkgname ( sort { $sources->{'installed'}{$a}{'name'} cmp + $sources->{'installed'}{$b}{'name'} } + keys( %{$sources->{'installed'}} ) ) { + $sources->{'installed'}{$pkgname}->files( { quiet => 1 } ); + } if ( ! -f $self->{'path'} ) { Snap->error( -1, "install(): $self->{'path'}:" @@ -725,6 +748,20 @@ sub install { } } + ########################################################### + # + # Here we attempt to symlink any modules that are installed + # via package. The intention is for a particular snaplinux + # release to maintain the kernel ABI throughout the + # lifecycle so that modules installed via package will + # continue to work without requiring updates. + # + ########################################################### + + if ( $self->iskern || $self->ismodule ) { + $self->linkmodules( $sources ); + } + $self->usher( 'postinst' ); open( AR, "ar p $self->{'path'} manifest|" ) || @@ -805,6 +842,241 @@ sub installed { return( 0 ); } +############################################################ +# +# This determines if the package is a kernel package and +# sets $self->{'iskern'} to 1. This allows snap to treat +# kernel packages in a special way - specifically allowing +# snap to symlink any compatible kernel modules so that +# this new kernel can use them. +# +############################################################ + +sub iskern { + my $self = shift; + + if ( $self->{'iskern'} ) { + return( 1 ); + } + + foreach my $file ( @{$self->{'files'}} ) { + if ( substr( $file, 0, 13 ) eq 'boot/vmlinuz-' ) { + $self->{'iskern'} = 1; + + return( 1 ); + } + } + + return(); + } + +############################################################ +# +# This determines if the package is a module package and +# sets $self->{'ismodule'} to 1. This allows snap to treat +# modules in a special way - modules installed via package +# should be placed in /lib/modules/PKGNAME and snap will +# symlink to the files in the directories of all compatible +# kernels. +# +############################################################ + +sub ismodule { + my $self = shift; + + if ( $self->{'ismodule'} ) { + return( 1 ); + } + + foreach my $file ( @{$self->{'files'}} ) { + if ( $file =~ /^lib\/modules\/$self->{'name'}\/.*\.ko$/ ) { + $self->{'ismodule'} = 1; + + return( 1 ); + } + } + + return(); + } + +############################################################ +# +# This is the bit that does the module symlinking in the +# kernel module directories. This is only done with kernels +# and modules that are installed via packages. +# +############################################################ + +sub linkmodules { + my $self = shift; + my $sources = shift; + my $modules = []; + my $kernels = []; + my $badsymvers = []; + my $missingsym = []; + + if ( $self->iskern() ) { + + ############################################ + # + # If the package is a kernel we will need + # to iterate through all packages that + # provide kernel modules and link all .ko + # files in the module tree for this version. + # + ############################################ + + push( @$kernels, $self ); + + foreach my $pkgname ( keys( %{$sources->{'installed'}} ) ) { + my $package = $sources->{'installed'}{$pkgname}; + + if ( $package->ismodule() ) { + foreach my $file ( @{$package->{'files'}} ) { + if ( $file =~ /^lib\/modules.*\.ko$/ ) { + push( @$modules, $file ); + } + } + } + } + } + elsif ( $self->ismodule() ) { + + ############################################ + # + # If the package is a module we will only + # symlink the .ko files from this package. + # + ############################################ + + foreach my $pkgname ( keys( %{$sources->{'installed'}} ) ) { + my $package = $sources->{'installed'}{$pkgname}; + + if ( $package->iskern() ) { + push( @$kernels, $package ); + } + } + + foreach my $file ( @{$self->{'files'}} ) { + if ( $file =~ /^lib\/modules.*\.ko$/ ) { + push( @$modules, $file ); + } + } + } + + foreach my $kernel ( @$kernels ) { + my $moddir = Snap->TARGET + . "/lib/modules/$kernel->{'version'}/snap"; + my $pid; + my $sel; + my $stdout; + my $stderr; + my $stat; + + if ( @$modules && ! -d $moddir ) { + mkdir( $moddir, 0755 ) || Snap->error( int( $! ), + "linkmodules(): $!" ); + } + + foreach my $module ( @$modules ) { + ( my $filename = $module ) =~ s/.*\///; + + if ( -l "$moddir/$filename" ) { + unlink( "$moddir/$filename" ); + } + + symlink( "/$module", "$moddir/$filename" ) || + Snap->error( int( $! ), "symlink(): $!" ); + } + + eval { + $pid = open3( \*CHLDIN, \*CHLDOUT, \*CHLDERR, + "/sbin/depmod -b " . Snap->TARGET + . " -aeE " . Snap->TARGET . "/lib/modules/" + . "$kernel->{'version'}/Module.symvers" + . " -e $kernel->{'version'}" ); + } || Snap->error( int( $! ), "open3():" + . " /sbin/depmod: $!" ); + + close( CHLDIN ); + + $sel = IO::Select->new(); + $sel->add( *CHLDOUT, *CHLDERR ); + + while ( my @fhs = $sel->can_read ) { + foreach my $fh ( @fhs ) { + if ( eof( $fh ) ) { + $sel->remove( $fh ); + + next; + } + + if ( fileno( $fh ) == fileno( *CHLDOUT ) ) { + $stdout .= <$fh>; + } + elsif ( fileno( $fh ) == fileno( *CHLDERR ) ) { + $stderr .= <$fh>; + } + } + } + + close( CHLDOUT ); + close( CHLDERR ); + close( USHER ); + + waitpid( $pid, 0 ); + $stat = $? >> 8; + + if ( $stat ) { + Snap->error( $stat, "depmod failed for kernel" + . " $kernel->{'version'}: $stderr" ); + } + + if ( $stderr ) { + foreach my $line ( split( /\n/, $stderr ) ) { + my $modfile; + my $modname; + my $msg; + + if ( $line =~ + /^depmod: WARNING: (\/(.*)\.ko) (.*)/ ) { + $modfile = $1; + $modname = $2; + $msg = $3; + } + else { + next; + } + + if ( ! -l $modfile ) { + next; + } + elsif ( $msg =~ /disagrees about version/ ) { + push( @$badsymvers, $modname ); + } + elsif ( $msg =~ /needs unknown symbol/ ) { + push( @$missingsym, $modname ); + } + + unlink( $1 ) || Snap->error( int( $! ), + "unlink(): $!" ); + } + } + + if ( @$badsymvers ) { + Snap->error( 0, "Incorrect symbol version for the" + . " following modules with kernel" + . " $kernel->{'version'}: " + . join( ' ', @$badsymvers ) ); + } + if ( @$missingsym ) { + Snap->error( 0, "Missing symbols for the following" + . " modules with kernel $kernel->{'version'}: " + . join( ' ', @$badsymvers ) ); + } + } + } + sub printbrief { my $self = shift; @@ -834,6 +1106,8 @@ sub printself { sub remove { my $self = shift; + my $sources = shift; + my $snapbase = ''; my $pkgdir = Snap->INSTDIR . "/$self->{'name'}"; my $snapinfo = "$pkgdir/snapinfo"; my $manifest = "$pkgdir/manifest"; @@ -844,22 +1118,97 @@ sub remove { $self->files( { quiet => 1, all => 1 } ); + #################################################### + # + # Here we're putting the snap-base package object + # into $snapbase if present. This will be used to + # make sure we don't unlink any directories that + # are part of snap-base. + # + #################################################### + + if ( $sources->{'installed'}{'snap-base'} ) { + $snapbase = $sources->{'installed'}{'snap-base'}; + $snapbase->files( { quiet => 1, all => 1 } ); + } + print "Removing $self->{'name'}=$self->{'version'}\n"; $self->usher( 'prerm' ); print "\e[?25l\r"; - foreach ( @{$self->{'files'}} ) { - if ( -f Snap->TARGET . "/$_" ) { - unlink( Snap->TARGET . "/$_" ) || Snap->error( - int( $! ), "unlink(): " . Snap->TARGET - . "/$_: $!" ); + #################################################### + # + # This sort is used to order the files from the + # deepest parts of the directory tree to the most + # shallow. This makes it easy to be sure we've + # deleted all the files in a directory before + # attempting to delete that directory. + # + # Of course, we also test that the directory is in + # fact empty first before deleting it. We also skip + # over any directories that are part of snap-base. + # + #################################################### - print "\e[K$_\r"; + foreach my $file ( sort { ( $b =~ tr/\/// ) <=> + ( $a =~ tr/\/// ) }( @{$self->{'files'}} ) ) { + ( my $filename = $file ) =~ s/.*\///; + my $fullpath = Snap->TARGET . "/$file"; + my $skip = 0; - $cnt++; + if ( $snapbase ) { + foreach my $basefile ( @{$snapbase->{'files'}} ) { + if ( $file eq $basefile ) { + $skip++; + + last; + } + } } + + if ( $skip || ( -d $fullpath && + ! Snap->dirempty( $fullpath ) ) ) { + next; + } + + if ( -f $fullpath || -l $fullpath ) { + unlink( $fullpath ) || Snap->error( int( $! ), + "unlink(): $fullpath: $!" ); + } + elsif ( -d $fullpath ) { + rmdir( $fullpath ) || Snap->error( int( $! ), + "rmdir(): $fullpath: $!" ); + } + else { + next; + } + + print "\e[K$filename\r"; + + $cnt++; + } + + print "\e[K$cnt files removed\e[?25h\n"; + + #################################################### + # + # This will allow us to unlink modules when either + # a module is removed. + # + #################################################### + + if ( $self->iskern() || $self->ismodule() ) { + foreach my $pkgname ( sort { + $sources->{'installed'}{$a}{'name'} cmp + $sources->{'installed'}{$b}{'name'} } + keys( %{$sources->{'installed'}} ) ) { + $sources->{'installed'}{$pkgname}->files( + { quiet => 1 } ); + } + + $self->unlinkmodules( $sources ); } $self->usher( 'postrm' ); @@ -874,20 +1223,48 @@ sub remove { . " $usher: $!" ); } - print "\e[K$cnt files removed\e[?25h\n"; + delete( $sources->{'installed'}{$self->{'name'}} ) || + Snap->error( int( $! ), "remove(): $!" ); Snap->unlock(); print "Finished removing $self->{'name'}\n"; } +### revdeps() ############################################## +# +# This sub looks through all installed packages to +# discover any which may depend on $self. This is used +# both in cases where one simply wants to know which +# packages depend on $self and in cases where $self is +# being upgraded. +# +# Regarding $opts->{'noreq'} - this tells us that it is +# unnecessary to attempt to verify that the package which +# depends on $self is satisfied with the version of $self. +# This is used in cases where we only wish to know what +# currently installed packages depend on $self. If we're +# doing an upgrade of $self we would want to check to see +# if any packages that depend on $self would need to be +# updated as well, so 'noreq' is not used in those cases. +# +############################################################ + sub revdeps { my $self = shift; my $sources = shift; my $revdeps = shift; my $opts = shift; - foreach my $pkgname ( keys( %{$sources->{'installed'}} ) ) { + #################################################### + # + # Here we begin to iterate through all installed + # packages and check if they depend on $self. + # + #################################################### + + foreach my $pkgname ( sort{ $a cmp $b } + keys( %{$sources->{'installed'}} ) ) { if ( $self->{'name'} eq $pkgname ) { next; } @@ -904,6 +1281,7 @@ sub revdeps { } elsif ( $opts->{'noreq'} && ! grep( $_->{'name'} eq $package->{'name'}, @$revdeps ) ) { + $package->depends( $sources, $revdeps ); $package->revdeps( $sources, $revdeps ); push( @$revdeps, $package ); @@ -943,6 +1321,7 @@ sub revdeps { } if ( ! $chgver ) { + $newpkg->depends( $sources, $revdeps ); $newpkg->revdeps( $sources, $revdeps ); push( @$revdeps, $newpkg ); @@ -956,6 +1335,58 @@ sub revdeps { . " $self->{'name'}=$self->{'version'}\n" ); } } + + } + +sub unlinkmodules { + my $self = shift; + my $sources = shift; + my $modules = []; + my $kernels = []; + + if ( $self->iskern() ) { + foreach my $pkgname ( keys( %{$sources->{'installed'}} ) ) { + my $package = $sources->{'installed'}{$pkgname}; + + if ( $package->ismodule() ) { + foreach my $file ( @{$package->{'files'}} ) { + if ( $file =~ /^lib\/modules.*\.ko$/ ) { + push( @$modules, $file ); + } + } + } + } + } + elsif ( $self->ismodule() ) { + foreach my $pkgname ( keys( %{$sources->{'installed'}} ) ) { + my $package = $sources->{'installed'}{$pkgname}; + + if ( $package->iskern() ) { + push( @$kernels, $package ); + } + } + + foreach my $file ( @{$self->{'files'}} ) { + if ( $file =~ /^lib\/modules.*\.ko$/ ) { + push( @$modules, $file ); + } + } + } + + foreach my $kernel ( @$kernels ) { + my $moddir = Snap->TARGET + . "/lib/modules/$kernel->{'version'}/snap"; + + foreach my $module ( @$modules ) { + ( my $filename = $module ) =~ s/.*\///; + + if ( -l "$moddir/$filename" ) { + unlink( "$moddir/$filename" ) || + Snap->error( int( $! ), + "unlinkmodules(): $!" ); + } + } + } } sub usher { @@ -967,6 +1398,15 @@ sub usher { my $stderr; my $stat; + #################################################### + # + # Here we expose the package version as an + # environment variable for the usher script to use + # + #################################################### + + $ENV{'VERSION'} = $self->{'version'}; + if ( $action eq 'preinst' ) { my $cnt = 0; @@ -1140,4 +1580,34 @@ sub source { } } +### verify() ############################################### +# +# This verifies the sha hash for all files in an installed +# package. +# +############################################################ + +sub verify { + my $self = shift; + my $opts = shift; + my $verified = []; + my $failed = []; + + $self->files( { quiet => 1, verbose => 1, all => 1 } ); + + foreach ( sort { $a->[2] cmp $b->[2] }( @{$self->{'files'}} ) ) { + my ( $sha, $perms, $file ) = @$_; + my $fullpath = Snap->TARGET . "/$file"; + my $shasum = Snap->sha( $fullpath ); + + if ( substr( $perms, 0, 1 ) eq '-' && $sha ne $shasum ) { + Snap->error( 0, "verify(): $fullpath has incorrect" + . " sha: $sha - $shasum" ); + } + elsif ( $opts->{'verbose'} ) { + print "$fullpath - OK\n"; + } + } + } + 1; diff --git a/SRC/snap/Snap.pm b/SRC/snap/Snap.pm index 13a8d8b..8c4b7bc 100644 --- a/SRC/snap/Snap.pm +++ b/SRC/snap/Snap.pm @@ -10,9 +10,8 @@ use Snap::Sources; use Fcntl qw( :flock ); use IPC::Open3; use IO::Socket::INET; -use Digest::SHA qw( sha256_hex ); +use Digest::SHA qw( sha1_hex sha256_hex ); use POSIX; -use Data::Dumper; use parent 'Exporter'; our @EXPORT = qw( @@ -21,6 +20,7 @@ our @EXPORT = qw( genpkg httpget human + kernver list listfiles virtfs @@ -30,6 +30,7 @@ our @EXPORT = qw( sha sha256 target + templates termsize vercmp ); @@ -89,7 +90,7 @@ use constant VERFILE => eval { } }; use constant { - VERSION => '0.12', + VERSION => '0.13', SNAPDIR => TARGET . '/var/lib/snap', PKGDIR => TARGET . '/var/lib/snap/packages', INSTDIR => TARGET . '/var/lib/snap/installed', @@ -107,6 +108,16 @@ use constant SNAPVER => eval { }; use constant LOCKFILE => TARGET . '/.snap'; +############################################################ +# +# Verify TARGET is a valid directory +# +############################################################ + +if ( TARGET && ! -d TARGET ) { + Snap->error( -1, TARGET . ": invalid target directory" ); + } + ############################################################ # # Set the process name @@ -185,6 +196,36 @@ sub chkyes { } } +### dirempty() ############################################# +# +# A simple test for an empty directory. The default $empty +# is 1 (meaning there are no files in the directory), and +# $empty is set to 0 if any files are found. +# +############################################################ + +sub dirempty { + my $class = shift; + my $dir = shift; + my $empty = 1; + + if ( ! -d $dir ) { + Snap->error( -1, "dirempty(): $dir: Invalid directory" ); + } + + opendir( my $dh, $dir ) || Snap->error( int( $! ), "dirempty(): $!" ); + + while ( readdir( $dh ) ) { + if ( $_ ne '.' && $_ ne '..' ) { + $empty = 0; + + last; + } + } + + return( $empty ); + } + ### error() ################################################ # # All errors should be sent here. This sub takes a status @@ -237,7 +278,7 @@ sub error { ############################################################ sub genpkg{ - my $pkgname = shift; + my $pkgname = shift || Snap->error( -1, "genpkg(): pkgname missing" ); my $skelfile = '/usr/share/snap/Makefile.skel'; my $snapreadme = "This is the directory where the manifest, snapinfo,\n" . "and files.tar.gz files will be created. It is also\n" @@ -477,6 +518,10 @@ sub issnap { return( 0 ); } +sub kernver { + return( ( uname() )[2] ); + } + sub list { my $listpackages = shift; my $packages = {}; @@ -588,13 +633,6 @@ sub readconf { my $line = 0; my $type; - if ( $conffile =~ /\.spt$/ ) { - $type = 'template'; - } - elsif ( $conffile =~ /\.conf$/ ) { - $type = 'config'; - } - open( my $fh, "<", $conffile ) || Snap->error( int( $! ), "open(): $conffile: $!\n" ); @@ -612,6 +650,7 @@ sub readconf { while ( my $file = readdir( $dh ) ) { if ( -f "$dir/$file" ) { + readconf( "$dir/$file", $data ); } } @@ -623,20 +662,13 @@ sub readconf { elsif ( $_ =~ /\s*\[(\S+)\]\s*/ ) { $section = $1; - if ( $section eq 'sources' ) { + if ( $section eq 'sources' && ! $data->{$section} ) { $data->{$section} = []; } } elsif ( $section eq 'sources' ) { push( @{$data->{$section}}, $_ ); } - elsif ( $section && $type eq 'template' ) { - if ( ! $data->{'templates'}{$section} ) { - $data->{'templates'}{$section} = []; - } - - push( @{$data->{'templates'}{$section}}, $_ ); - } elsif ( $_ =~ /(\S+)\s*=\s*(.*)$/ ) { $data->{$section}{$1} = $2; } @@ -710,7 +742,14 @@ sub setup { } } +### sha() ############################################### +# +# This sub returns a hex sha hash of a supplied file +# +############################################################ + sub sha { + my $class = shift; my $file = shift; my $digest = eval { Digest::SHA->new( 1 )->addfile( $file ); @@ -735,6 +774,53 @@ sub sha256 { return( $digest->hexdigest ); } +sub templates { + my $conf = shift; + my $templatedir = $conf->{'snapinstall'}{'templatedir'}; + my $unsorted = []; + my $templates = {}; + + opendir( my $dh, $templatedir ) || + Snap->error( int( $! ), "templates(): $!" ); + + while ( my $template = readdir( $dh ) ) { + if ( $template eq '.' || $template eq '..' ) { + next; + } + elsif ( ! -f "$templatedir/$template/packages" ) { + Snap->error( 0, "Template '$template' has" + . " no packages" ); + + next; + } + + $templates->{$template}{'packages'} = []; + + open( my $fh, "$templatedir/$template/packages" ) || + Snap->error( int( $! ), "templates(): $!" ); + + while ( readline( $fh ) ) { + chomp(); + + if ( $_ =~ /^\s*#/ || $_ =~ /^$/ ) { + next; + } + + push( @{$templates->{$template}{'packages'}}, $_ ); + } + + close( $fh ); + } + + closedir( $dh ); + + foreach ( sort { $a cmp $b }( @$unsorted ) ) { + push( @$templates, $_ ); + } + + return( $templates ); + } + sub termsize { my $row = 24; my $col = 80; @@ -776,7 +862,7 @@ sub termsize { waitpid( $pid, 0 ); $stat = $? >> 8; - if ( $stdout =~ /(\d+)\s+(\d+)/ ) { + if ( $stdout && $stdout =~ /(\d+)\s+(\d+)/ ) { $row = $1; $col = $2; } diff --git a/SRC/snap/Sources.pm b/SRC/snap/Sources.pm index 266a848..49a77be 100644 --- a/SRC/snap/Sources.pm +++ b/SRC/snap/Sources.pm @@ -4,7 +4,6 @@ use strict; use warnings; use Compress::Zlib; -use Data::Dumper; use parent 'Snap'; diff --git a/SRC/snap/snap b/SRC/snap/snap index 5d6605e..dbe1a11 100755 --- a/SRC/snap/snap +++ b/SRC/snap/snap @@ -4,7 +4,6 @@ use strict; use warnings; use Snap; -use Data::Dumper; my $command = shift( @ARGV ) || ''; my $conf = readconf(); @@ -55,6 +54,10 @@ elsif ( $command eq 'files' ) { } } elsif ( $command eq 'genpkg' ) { + if ( ! @ARGV ) { + Snap->error( -1, "Failed to provide package name" ); + } + foreach my $arg ( @ARGV ) { genpkg( $arg ); } @@ -173,21 +176,10 @@ elsif ( $command eq 'install' ) { print "Ignoring dependencies for $package->{'name'}\n"; } - push( @$packages, $package ); - } - - #################################################### - # - # Here we iterate through all installed packages - # and add their file lists. This will later be - # checked against for conflicts. - # - #################################################### - - foreach my $pkgname ( sort { $sources->{'installed'}{$a}{'name'} cmp - $sources->{'installed'}{$b}{'name'} } - keys( %{$sources->{'installed'}} ) ) { - $sources->{'installed'}{$pkgname}->files( { quiet => 1 } ); + if ( ! grep( $_->{'name'} eq $package->{'name'}, + @$packages ) ) { + push( @$packages, $package ); + } } for ( my $i = 0; $i <= $#$packages; $i++ ) { @@ -357,6 +349,26 @@ elsif ( $command eq 'list' ) { print "No installed packages found matching '@ARGV'\n"; } } +elsif ( $command eq 'provides' ) { + ( my $string = $ARGV[0] ) =~ s/^\/*/\//; + my $len = length( $string ); + + $sources->readpkgs(); + + foreach my $pkgname ( sort( keys( %{$sources->{'installed'}} ) ) ) { + my $package = $sources->{'installed'}{$pkgname}; + + $package->files( { quiet => 1 } ); + + foreach my $file ( @{$package->{'files'}} ) { + if ( substr( "/$file", -$len, $len ) eq "$string" ) { + print "$pkgname\n"; + + last; + } + } + } + } elsif ( $command eq 'purge' ) { print "Not yet implemented\n"; @@ -436,7 +448,10 @@ elsif ( $command eq 'reinstall' ) { $package->files( $opts ); - push( @$packages, $package ); + if ( ! grep( $_->{'name'} eq $package->{'name'}, + @$packages ) ) { + push( @$packages, $package ); + } } foreach my $package ( sort { $a->{'name'} cmp $b->{'name'} } @@ -473,7 +488,7 @@ elsif ( $command eq 'reinstall' ) { print "\n"; - $package->install(); + $package->install( $sources ); } virtfs( 'umount' ); @@ -505,6 +520,7 @@ elsif ( $command eq 'remove' ) { my $bytes = 0; my $cnt = 0; my $termsize = Snap->termsize(); + my $virtfs = 0; $sources->readpkgs(); @@ -572,6 +588,10 @@ elsif ( $command eq 'remove' ) { $cnt = 0; foreach my $package ( @$packages ) { + if ( ! $virtfs ) { + $virtfs = virtfs( 'mount' ); + } + if ( $cnt ) { print "\n"; } @@ -580,6 +600,10 @@ elsif ( $command eq 'remove' ) { $cnt++; } + + if ( $virtfs ) { + virtfs( 'umount' ); + } } elsif ( $command eq 'revdep' ) { my $revdeps = []; @@ -697,14 +721,22 @@ elsif ( $command eq 'upgrade' ) { exit( -1 ); } elsif ( $command eq 'verify' ) { - print "Not yet implemented\n"; + my $opts = { + verbose => eval { + for ( my $i = 0; $i <= $#ARGV; $i++ ) { + if ( $ARGV[$i] eq '-v' ) { + splice( @ARGV, $i, 1 ); - exit( -1 ); + return( 1 ); + } + } + } + }; foreach my $arg ( @ARGV ) { my $package = Snap::Package->new( $arg ); - print Dumper( $package ); + $package->verify( $opts ); } } elsif ( $command eq 'version' ) { diff --git a/SRC/snap/snapinstall b/SRC/snap/snapinstall old mode 100644 new mode 100755 index cce3562..66961c3 --- a/SRC/snap/snapinstall +++ b/SRC/snap/snapinstall @@ -4,41 +4,23 @@ use strict; use warnings; use Snap; -use Data::Dumper; setup(); my $conf = readconf(); +my $templates = templates( $conf ); my $sources = Snap::Sources->new( $conf->{'sources'} ); - -my $container = eval { - for ( my $i = 0; $i <= $#ARGV; $i++ ) { - if ( $ARGV[$i] eq '-c' || $ARGV[$i] eq '--container' ) { - splice ( @ARGV, $i, 1 ); - - return( 1 ); - } - } - }; -my $opts = { - repo => 'core', - quiet => 1 - }; -my $corepkgs; -my $packages; -my $virtfs = 0; -my $prepkgs = {}; -my @prelist = qw( - ); my @packages = (); +my $template; if ( ! Snap->TARGET ) { Snap->error( -1, 'A target must be specified with -t' ); } +$template = $ARGV[0]; $sources->readpkgs(); -foreach my $pkgname ( @{$conf->{'templates'}{'core'}} ) { +foreach my $pkgname ( @{$templates->{$template}{'packages'}} ) { my $package = $sources->search( { name => $pkgname, quiet => 1 } ); if ( $package->{'status'} && $package->{'status'} eq 'installed' ) { @@ -51,6 +33,9 @@ foreach my $pkgname ( @{$conf->{'templates'}{'core'}} ) { if ( -f "/var/lib/snap/packages/$filename" ) { $package->{'path'} = "/var/lib/snap/packages/$filename"; } + elsif ( -f Snap->PKGDIR . "/$filename" ) { + $package->{'path'} = Snap->PKGDIR . "/$filename"; + } elsif ( ! -f Snap->PKGDIR . "/$filename" ) { Snap->httpget( $package->{'path'}, Snap->PKGDIR . "/$filename", 0644 ); @@ -67,7 +52,7 @@ foreach my $pkgname ( @{$conf->{'templates'}{'core'}} ) { } foreach my $package ( @packages ) { - $package->files( $opts ); + $package->files( { quiet => 1 } ); print "\n"; @@ -78,85 +63,82 @@ if ( ! @packages ) { print "Nothing to do\n"; } else { + my $rootfs = "$conf->{'snapinstall'}{'templatedir'}/$template/rootfs"; + my $postinst = "$conf->{'snapinstall'}{'templatedir'}" + . "/$template/postinst"; + my $pid; + my $stat; + + if ( ! Snap->dirempty( $rootfs ) ) { + if ( $pid = fork() ) { + waitpid( $pid, 0 ); + $stat = $? >> 8; + } + else { + exec( "cp -a '$rootfs'/* " . Snap->TARGET ); + } + + if ( $stat ) { + Snap->error( $stat, "Failed to copy rootfs" ); + } + } + print "Setting root password\n"; - if ( $> ) { - exec( "fakeroot fakechroot /usr/sbin/chroot " - . Snap->TARGET . " passwd root" ); + virtfs( 'mount' ); + + if ( $pid = fork() ) { + waitpid( $pid, 0 ); + $stat = $? >> 8; } else { - exec ( "chroot " . Snap->TARGET . " passwd root" ); + if ( $> ) { + exec( "fakeroot fakechroot /usr/sbin/chroot " + . Snap->TARGET . " passwd root" ); + } + else { + exec ( "chroot " . Snap->TARGET . " passwd root" ); + } + } + + virtfs( 'umount' ); + + if ( $stat ) { + Snap->error( $stat, "Failed to set password" ); + } + + if ( -f $postinst ) { + my $tmpscript = "/var/lib/snap/postinst"; + + open( my $fh, '<', $postinst ) || Snap->error( int( $! ), + "Failed to open $postinst" ); + open( my $wh, '>', Snap->TARGET . "/$tmpscript" ) || + Snap->error( int( $! ), "Failed to open $tmpscript" ); + + while ( <$fh> ) { + print $wh $_; + } + + close( $wh ); + close( $fh ); + + if ( $pid = fork() ) { + waitpid( $pid, 0 ); + $stat = $? >> 8; + } + else { + if ( $> ) { + exec( "fakeroot fakechroot /usr/sbin/chroot " + . Snap->TARGET . " $tmpscript" ); + } + else { + exec ( "chroot " . Snap->TARGET + . " $tmpscript" ); + } + } + + if ( $stat ) { + Snap->error( $stat, "Failed to execute $tmpscript" ); + } } } - -exit; - -#$sources->readpkgs(); -#$corepkgs = $sources->search( $opts ); - -#for ( my $i = 0; $i <= $#$corepkgs; $i++ ) { -# if ( ! $opts->{'nodeps'} ) { -# print "Resolving dependencies for" -# . " $corepkgs->[$i]{'name'}\n"; -# -# $corepkgs->[$i]->depends( $sources, $packages ); -# } -# else { -# print "Ignoring dependencies for" -# . " $corepkgs->[$i]{'name'}\n"; -# } -# -# push( @$packages, $corepkgs->[$i] ); -# } -# -#for ( my $i = 0; $i <= $#$packages; $i++ ) { -# if ( $packages->[$i]{'path'} =~ /https*:\/\// ) { -# ( my $filename = $packages->[$i]{'path'} ) =~ s/.*\///; -# -# if ( ! -f Snap->PKGDIR . "/$filename" ) { -# Snap->httpget( $packages->[$i]{'path'}, -# Snap->PKGDIR . "/$filename", 0644 ); -# } -# -# $packages->[$i]{'path'} = Snap->PKGDIR . "/$filename"; -# } -# -# -# if ( grep( $_ eq $packages->[$i]{'name'}, @$prelist ) ) { -# $prepkgs->{$packages->[$i]{'name'}} = $packages->[$i]; -# -# splice( @$packages, $i, 1 ); -# $i--; -# } -# } - -#foreach my $package ( @prelist ) { -# print "\n"; -# -# $prepkgs->{$package}->install(); -# } -# -#foreach my $package ( @$packages ) { -# if ( ! $virtfs ) { -# $virtfs = virtfs( 'mount' ); -# } -# -# print "\n"; -# -# $package->install(); -# } -# -#if ( $virtfs ) { -# my $pid; -# -# if ( $pid = fork() ) { -# waitpid( $pid, 0 ); -# } -# else { -# exec( "chroot " . Snap->TARGET . " passwd" ); -# } -# } -# -#virtfs( 'umount' ); -# -#print "\n"; diff --git a/SRC/snap/core.spt b/SRC/snap/templates/container/packages similarity index 96% rename from SRC/snap/core.spt rename to SRC/snap/templates/container/packages index c5b6ec3..3e37d1f 100644 --- a/SRC/snap/core.spt +++ b/SRC/snap/templates/container/packages @@ -3,12 +3,11 @@ # This file is required for snapinstall to function, so don't delete it! # -[core] snap-base dash texinfo -coreutils glibc +coreutils libacl libattr libcap @@ -41,7 +40,6 @@ libstdc++ libzfs linux-firmware man-db -mkinitramfs mpfr net-tools procps-ng diff --git a/SRC/snap/templates/container/rootfs/etc/inittab b/SRC/snap/templates/container/rootfs/etc/inittab new file mode 100644 index 0000000..7732638 --- /dev/null +++ b/SRC/snap/templates/container/rootfs/etc/inittab @@ -0,0 +1,37 @@ +# Default runlevel +id:3:initdefault: + +# This is the set of scripts that prepare the system prior to entering runlevels +si::sysinit:/etc/init.d/rc S + +# /etc/init.d/rc executes the S and K scripts when runlevel is changed +# +# Runlevel 0 is halt. +# Runlevel 1 is single-user. +# Runlevel 2 is multi-user without networking. +# Runlevel 3 is multi-user with networking. +# Runlevel 4 is not used by default. +# Runlevel 5 is multi-user with GUI. +# Runlevel 6 is reboot. + +l0:0:wait:/etc/init.d/rc 0 +l1:1:wait:/etc/init.d/rc 1 +l2:2:wait:/etc/init.d/rc 2 +l3:3:wait:/etc/init.d/rc 3 +l4:4:wait:/etc/init.d/rc 4 +l5:5:wait:/etc/init.d/rc 5 +l6:6:wait:/etc/init.d/rc 6 + +# This allows ctrl-alt-del to reboot the system +ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now + +# Single user mode +su:S1:respawn:/sbin/sulogin + +# The following spawns agetty on tty1-6 +# +# Format: +# id:runlevels:action:process +# +# More information is available with 'man inittab' +1:2345:respawn:/sbin/agetty --noclear --nohints tty1 38400 diff --git a/SRC/snap/templates/container/rootfs/etc/network.conf b/SRC/snap/templates/container/rootfs/etc/network.conf new file mode 100755 index 0000000..70b112a --- /dev/null +++ b/SRC/snap/templates/container/rootfs/etc/network.conf @@ -0,0 +1,7 @@ +auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet dhcp + +source-directory network.d diff --git a/SRC/snap/templates/server/packages b/SRC/snap/templates/server/packages new file mode 100644 index 0000000..9d03f33 --- /dev/null +++ b/SRC/snap/templates/server/packages @@ -0,0 +1,61 @@ +# +# These packages are the bare minimum necessary for a functional system. +# This file is required for snapinstall to function, so don't delete it! +# + +snap-base +dash +texinfo +glibc +coreutils +libacl +libattr +libcap +ncurses +readline +tzdata +perl +binutils +bzip2 +cpio +dhclient +e2fsprogs +ex +findutils +gawk +gdbm +gmp +grep +groff +grub +gzip +iana-etc +iftools +inetutils +iproute2 +kmod +less +libgcc +libpipeline +libstdc++ +libzfs +linux-firmware +man-db +mkinitramfs +mpfr +net-tools +openssh-client +openssh-server +procps-ng +psmisc +sed +shadow +tar +snap +initscripts +sysklogd +sysvinit +tar +util-linux +xz +zlib diff --git a/SRC/snap/templates/server/postinst b/SRC/snap/templates/server/postinst new file mode 100644 index 0000000..46c4040 --- /dev/null +++ b/SRC/snap/templates/server/postinst @@ -0,0 +1,29 @@ +#!/bin/sh + +### PLACEHOLDER ### +# This script needs to provide things like network setup perhaps + +# This code should help with setting up the network interfaces +# +#my $dir = '/sys/class/net'; +#my $devs = {}; +# +#opendir( my $dh, $dir ) || die( $! ); +# +#while ( readdir( $dh ) ) { +# my $link = readlink( "$dir/$_" ) || next; +# +# if ( $link =~ /virtual/ ) { +# next; +# } +# +# open( my $fh, "$dir/$_/address" ) || die( $! ); +# $devs->{$_}{'mac'} = <$fh>; +# close( $fh ); +# +# chomp( $devs->{$_}{'mac'} ); +# } +# +#foreach my $dev ( sort( keys( %$devs ) ) ) { +# print "$dev - $devs->{$dev}{'mac'}\n"; +# } diff --git a/SRC/snap/templates/server/rootfs/etc/snap.conf b/SRC/snap/templates/server/rootfs/etc/snap.conf new file mode 100644 index 0000000..4f020df --- /dev/null +++ b/SRC/snap/templates/server/rootfs/etc/snap.conf @@ -0,0 +1,8 @@ +[sources] +default = http://packages.snaplinux.org/ core dev main server + +[snapinstall] +templatedir = /etc/snap.d/templates + +include /etc/snap.d +