mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-10-21 15:18:19 +00:00
883549fea2
liteimg deleted rootimg-statelite.gz before it packed a statelite ramdisk image, and wrote the new archive in place. It ignored a failed pack, so nodes could get no image or a partial one. It took no image lock. --tracker took no value, so the metainfo named the tracker 1, and it described rootimg.gz instead of the new archive. liteimg now takes the image lock and packs into the temporary directory that packimage uses. It fails when a command of the pack fails. It writes the SHA-256 checksum, and publishes it with the archive through publish_rootimg. --tracker takes the tracker URL, as it does for packimage, and the metainfo describes the new archive. Nodes keep downloading the previous archive until the new one replaces it.
1042 lines
37 KiB
Perl
1042 lines
37 KiB
Perl
package xCAT_plugin::statelite;
|
|
|
|
BEGIN
|
|
{
|
|
$::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
|
|
}
|
|
use lib "$::XCATROOT/lib/perl";
|
|
use xCAT::Table;
|
|
use Getopt::Long;
|
|
use File::Basename;
|
|
use File::Path;
|
|
use File::Copy;
|
|
use File::Find;
|
|
use Cwd;
|
|
use File::Temp;
|
|
use xCAT::Utils qw(genpassword);
|
|
use xCAT::TableUtils qw(get_site_attribute);
|
|
use xCAT::SvrUtils;
|
|
use xCAT::PasswordUtils;
|
|
use Data::Dumper;
|
|
Getopt::Long::Configure("bundling");
|
|
Getopt::Long::Configure("pass_through");
|
|
|
|
my $cmdname = "liteimg";
|
|
my $statedir = ".statelite";
|
|
my $verbose = "0";
|
|
|
|
sub handled_commands {
|
|
return {
|
|
$cmdname => "statelite"
|
|
}
|
|
}
|
|
|
|
# function to handle request. Basically, get the information
|
|
# about the image and then do the action on it. Is that vague enough?
|
|
sub process_request {
|
|
my $request = shift;
|
|
my $callback = shift;
|
|
my $doreq = shift;
|
|
|
|
#my $sitetab = xCAT::Table->new('site');
|
|
#my $ent = $sitetab->getAttribs({key=>'installdir'},['value']);
|
|
my @entries = xCAT::TableUtils->get_site_attribute("installdir");
|
|
my $t_entry = $entries[0];
|
|
my $installroot = "/install";
|
|
|
|
# get /install directory
|
|
if (defined($t_entry)) {
|
|
$installroot = $t_entry;
|
|
}
|
|
|
|
# if not defined, error out... or should we set it as default?
|
|
unless ($installroot) {
|
|
$callback->({ error => ["No installdir defined in site table"], errorcode => [1] });
|
|
return;
|
|
}
|
|
|
|
@ARGV = @{ $request->{arg} };
|
|
my $argc = scalar @ARGV;
|
|
if ($argc == 0) {
|
|
$callback->({ info => ["$cmdname -h # this help message\n$cmdname -v # version\n$cmdname -V # verbose\n$cmdname -p <profile> -a <architecture> -o <OS>\n$cmdname imagename"] });
|
|
return;
|
|
}
|
|
|
|
my $rootfstype;
|
|
my $exlistloc; # borrowed from packimage.pm
|
|
my $osver;
|
|
my $arch;
|
|
my $profile;
|
|
my $rootimg_dir;
|
|
my $exlist; # it is used when rootfstype = ramdisk
|
|
my $destdir;
|
|
my $imagename;
|
|
my $dotorrent;
|
|
|
|
GetOptions(
|
|
"rootfstype|t=s" => \$rootfstype,
|
|
"profile|p=s" => \$profile,
|
|
"arch|a=s" => \$arch,
|
|
"osver|o=s" => \$osver,
|
|
"help|h" => \$help,
|
|
"tracker=s" => \$dotorrent,
|
|
"version|v" => \$version,
|
|
"verbose|V" => \$verbose
|
|
);
|
|
|
|
if ($version) {
|
|
my $version = xCAT::Utils->Version();
|
|
$callback->({ info => [$version] });
|
|
return;
|
|
}
|
|
if ($help) {
|
|
$callback->({ info => ["$cmdname -h # this help message\n$cmdname -v # version\n$cmdname -V # verbose\n$cmdname [-p profile] [-a architecture] [-o OS]\n$cmdname imagename"] });
|
|
return;
|
|
}
|
|
|
|
# if they only gave us the image name:
|
|
if (@ARGV > 0) {
|
|
$imagename = $ARGV[0];
|
|
if ($arch or $osver or $profile) {
|
|
$callback->({ error => ["-o, -p and -a options are not allowed when a image name is specified."], errorcode => [1] });
|
|
return;
|
|
}
|
|
|
|
#load the module in memory
|
|
eval { require("$::XCATROOT/lib/perl/xCAT/Table.pm") };
|
|
if ($@) {
|
|
$callback->({ error => [$@], errorcode => [1] });
|
|
return;
|
|
}
|
|
|
|
#get the info from the osimage and linux
|
|
|
|
my $osimagetab = xCAT::Table->new('osimage', -create => 1);
|
|
if (!$osimagetab) {
|
|
|
|
# os image doesn't exist in table.
|
|
$callback->({ error => ["The osimage table cannot be opened."], errorcode => [1] });
|
|
return;
|
|
}
|
|
|
|
# open the linux image table to get more attributes... for later.
|
|
my $linuximagetab = xCAT::Table->new('linuximage', -create => 1);
|
|
if (!$linuximagetab) {
|
|
$callback->({ error => ["The linuximage table cannot be opened."], errorcode => [1] });
|
|
return;
|
|
}
|
|
|
|
# get the os, arch, and profile from the image name table.
|
|
(my $ref) = $osimagetab->getAttribs({ imagename => $imagename }, 'rootfstype', 'osvers', 'osarch', 'profile', 'provmethod');
|
|
if (!$ref) {
|
|
$callback->({ error => ["Cannot find image \'$imagename\' from the osimage table."], errorcode => [1] });
|
|
return;
|
|
}
|
|
|
|
my $provmethod = $ref->{'provmethod'};
|
|
if ($provmethod !~ /statelite/) {
|
|
$callback->({ error => ["Please make sure that osimage.provmethod is set to statelite before calling this command."], errorcode => [1] });
|
|
return;
|
|
}
|
|
|
|
$rootfstype = $ref->{'rootfstype'};
|
|
$osver = $ref->{'osvers'};
|
|
$arch = $ref->{'osarch'};
|
|
$profile = $ref->{'profile'};
|
|
|
|
# get the exlist and rootimgdir attributes
|
|
(my $ref1) = $linuximagetab->getAttribs({ imagename => $imagename }, 'exlist', 'rootimgdir');
|
|
unless ($ref1) {
|
|
$callback->({ error => [qq{Cannot find image '$imagename' from the osimage table.}], errorcode => [1] });
|
|
}
|
|
$destdir = $ref1->{'rootimgdir'};
|
|
$exlistloc = $ref1->{'exlist'};
|
|
$rootimg_dir = "$destdir/rootimg";
|
|
|
|
} # end of case where they give us osimage.
|
|
|
|
unless ($osver and $arch and $profile) {
|
|
$callback->({ error => ["osimage.osvers, osimage.osarch, and osimage.profile and must be specified for the image $imagename in the database, or you must specify -o os -p profile -a arch."], errorcode => [1] });
|
|
return;
|
|
}
|
|
|
|
unless ($destdir) {
|
|
$destdir = "$installroot/netboot/$osver/$arch/$profile";
|
|
$rootimg_dir = "$destdir/rootimg";
|
|
}
|
|
|
|
# genimage, packimage, imgimport and rmimage take this lock before they change the image.
|
|
my ($lock_error, $lock) = xCAT::Utils->acquire_lock_imageop($rootimg_dir);
|
|
if ($lock_error) {
|
|
$callback->({ error => [$lock], errorcode => [1] });
|
|
return;
|
|
}
|
|
my $oldpath = cwd();
|
|
|
|
# now we have all the info we need:
|
|
# - rootimg_dir
|
|
# - osver
|
|
# - arch
|
|
# - profile
|
|
my $rsp;
|
|
push @{ $rsp->{data} }, "Modifying $rootimg_dir ...";
|
|
xCAT::MsgUtils->message("I", $rsp, $callback);
|
|
#copy $installroot/postscripts into the image at /xcatpost
|
|
if (-e "$rootimg_dir/xcatpost") {
|
|
system("rm -rf $rootimg_dir/xcatpost");
|
|
}
|
|
|
|
system("mkdir -p $rootimg_dir/xcatpost");
|
|
system("cp -r $installroot/postscripts/* $rootimg_dir/xcatpost/");
|
|
|
|
# update rw to ro for sles
|
|
updateFstab($rootimg_dir,$profile,$arch);
|
|
|
|
#get the root password for the node
|
|
my $pass = xCAT::PasswordUtils::crypt_system_password();
|
|
if (!defined($pass)) {
|
|
$pass = 'cluster';
|
|
}
|
|
|
|
my $oldmask = umask(0077);
|
|
my $shadow;
|
|
open($shadow, "<", "$rootimg_dir/etc/shadow");
|
|
my @shadents = <$shadow>;
|
|
close($shadow);
|
|
open($shadow, ">", "$rootimg_dir/etc/shadow");
|
|
print $shadow "root:$pass:13880:0:99999:7:::\n";
|
|
foreach (@shadents) {
|
|
unless (/^root:/) {
|
|
print $shadow "$_";
|
|
}
|
|
}
|
|
close($shadow);
|
|
umask($oldmask);
|
|
|
|
my $distname = $osver;
|
|
unless (-r "$::XCATROOT/share/xcat/netboot/$distname/" or not $distname) {
|
|
chop($distname);
|
|
}
|
|
|
|
unless ($distname) {
|
|
$callback->({ error => ["Unable to find $::XCATROOT/share/xcat/netboot directory for $osver"], errorcode => [1] });
|
|
return;
|
|
}
|
|
|
|
unless ($imagename) {
|
|
|
|
#store the image in the DB
|
|
my @ret = xCAT::SvrUtils->update_tables_with_diskless_image($osver, $arch, $profile, 'statelite');
|
|
if ($ret[0]) {
|
|
$callback->({ error => [ "Error when updating the osimage tables: " . $ret[1] ] });
|
|
}
|
|
$imagename = "$osver-$arch-statelite-$profile";
|
|
|
|
$exlistloc = xCAT::SvrUtils->get_exlist_file_name("$installroot/custom/netboot/$distname", $profile, $osver, $arch);
|
|
unless ($exlistloc) {
|
|
$exlistloc = xCAT::SvrUtils->get_exlist_file_name("$::XCATROOT/share/xcat/netboot/$distname", $profile, $osver, $arch);
|
|
}
|
|
}
|
|
|
|
#sync fils configured in the synclist to the rootimage
|
|
$syncfile = xCAT::SvrUtils->getsynclistfile(undef, $osver, $arch, $profile, "netboot", $imagename);
|
|
if (defined($syncfile) && -d $rootimg_dir) {
|
|
my @filelist = split ',', $syncfile;
|
|
foreach my $synclistfile (@filelist) {
|
|
if ( -f $synclistfile) {
|
|
print "sync files from $synclistfile to the $rootimg_dir\n";
|
|
`$::XCATROOT/bin/xdcp -i $rootimg_dir -F $synclistfile`;
|
|
}
|
|
}
|
|
}
|
|
|
|
# check if the file "litefile.save" exists or not
|
|
# if it doesn't exist, then we get the synclist, and run liteMe
|
|
# if it exists, it means "liteimg" has run more than one time, we need to compare with the synclist
|
|
|
|
my @listSaved;
|
|
if (-e "$rootimg_dir/.statelite/litefile.save") {
|
|
open SAVED, "$rootimg_dir/.statelite/litefile.save";
|
|
|
|
# store all its contents to @listSaved;
|
|
while (<SAVED>) {
|
|
chomp $_;
|
|
push @listSaved, $_;
|
|
}
|
|
close SAVED;
|
|
}
|
|
|
|
my %hashSaved = ();
|
|
if (parseLiteFiles(\@listSaved, \%hashSaved)) {
|
|
$callback->({ error => ["parseLiteFiles failed for listSaved!"] });
|
|
return;
|
|
}
|
|
|
|
# now get the files for the node
|
|
my @synclist = xCAT::Utils->runcmd("ilitefile $imagename", 0, 1);
|
|
unless (@synclist) {
|
|
$callback->({ error => ["There are no files to sync for $imagename. You have to have some files read/write filled out in the synclist table."], errorcode => [1] });
|
|
return;
|
|
}
|
|
|
|
my $listNew = $synclist[0];
|
|
|
|
# for compatible reason, replace "tmpfs,rw" with "link" option in xCAT 2.5 or higher
|
|
for (@{$listNew}) {
|
|
s/tmpfs,rw/link/;
|
|
}
|
|
|
|
# the directory/file in litefile table must be the absolute path ("/***")
|
|
foreach my $entry (@$listNew) {
|
|
my @tmp = split(/\s+/, $entry);
|
|
|
|
# check the validity of the option
|
|
if ($tmp[1] !~ /^(tmpfs|persistent|localdisk|rw|ro|con|link|tmpfs,rw|link,ro|link,persistent|link,con)$/) {
|
|
$callback->({ error => [qq{ $tmp[2] has invalid option. The valid options: tmpfs persistent localdisk rw ro con link tmpfs,rw link,ro link,persistent link,con}], errorcode => [1] });
|
|
return;
|
|
}
|
|
|
|
unless ($tmp[2] =~ m{^/}) {
|
|
$callback->({ error => [qq{ $tmp[2] is not one absolute path. }], errorcode => [1] });
|
|
return;
|
|
}
|
|
if ($tmp[1] =~ m{con} and $tmp[2] =~ m{/$}) {
|
|
$callback->({ error => [qq{ $tmp[2] is directory, don't use "con" as its option }], errorcode => [1] });
|
|
return;
|
|
}
|
|
}
|
|
|
|
my %hashNew = ();
|
|
if (parseLiteFiles($listNew, \%hashNew)) {
|
|
$callback->({ error => ["parseLiteFiles failed for listNew!"] });
|
|
return;
|
|
}
|
|
|
|
# validate the options for all litefile entries
|
|
# if there is any scenario not supported, the command exits
|
|
foreach my $entry (keys %hashNew) {
|
|
my @tmp = split(/\s+/, $entry);
|
|
my $f = $tmp[1];
|
|
|
|
if ($hashNew{$entry}) {
|
|
if ($tmp[0] =~ m/ro$/ or $tmp[0] =~ m/con$/) {
|
|
$callback->({ error => [qq{the directory "$f" should not be with "ro" or "con" as its option}], errorcode => [1] });
|
|
return;
|
|
}
|
|
foreach my $child (@{ $hashNew{$entry} }) {
|
|
my @tmpc = split(/\s+/, $child);
|
|
my $fc = $tmpc[1];
|
|
if ($tmp[0] =~ m/link/) {
|
|
if ($tmpc[0] eq "link,ro") {
|
|
$callback->({ error => [qq{Based on the option of $f, $fc should not use "link,ro" as its option}], errorcode => [1] });
|
|
return;
|
|
}
|
|
if ($tmpc[0] !~ m/link/) {
|
|
$callback->({ error => [qq{Based on the option of $f, $fc can only use "link"-based options}], errorcode => [1] });
|
|
return;
|
|
}
|
|
} else {
|
|
if ($tmpc[0] =~ m/link/) {
|
|
|
|
# The /etc/mtab is a specific file which can only be handled by link option.
|
|
# It need to be existed in rootimage and during the runing of statelite,
|
|
# and after running of statelite, it need to be linked to /proc/mount
|
|
if ($tmpc[1] != "/etc/mtab") {
|
|
$callback->({ error => [qq{Based on the option of $f, $fc should not use "link"-based options}], errorcode => [1] });
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (($tmp[0] =~ m{persistent}) and ($tmpc[0] !~ m{persistent})) {
|
|
|
|
# TODO: if the parent is "persistent", the child can be ro/persistent/rw/con
|
|
$callback->({ error => ["$fc should have persistent option like $f "], errorcode => [1] });
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
# backup the file/directory before recovering the files in litefile.save
|
|
unless (-d "$rootimg_dir/.statebackup") {
|
|
if (-e "$rootimg_dir/.statebackup") {
|
|
xCAT::Utils->runcmd("rm $rootimg_dir/.statebackup", 0, 1);
|
|
}
|
|
$verbose && $callback->({ info => ["mkdir $rootimg_dir/.statebackup"] });
|
|
xCAT::Utils->runcmd("mkdir $rootimg_dir/.statebackup", 0, 1);
|
|
}
|
|
|
|
#delete useless rootimg/tmp/dracut.* files
|
|
#fix copy many dracut.* files cost too much time in liteimg
|
|
$verbose && $callback->({ info => ["removing \"$rootimg_dir/tmp/dracut.*\""] });
|
|
unlink glob "$rootimg_dir/tmp/dracut.*";
|
|
|
|
# recovery the files in litefile.save if necessary
|
|
foreach my $line (keys %hashSaved) {
|
|
my @oldentry = split(/\s+/, $line);
|
|
my $f = $oldentry[1];
|
|
|
|
my @newentries = grep /\s+$f$/, @{$listNew};
|
|
my @entry;
|
|
if (scalar @newentries == 1) {
|
|
@entry = split(/\s+/, $newentries[0]);
|
|
}
|
|
|
|
# backup the children to .statebackup
|
|
if ($hashSaved{$line}) {
|
|
my $childrenRef = $hashSaved{$line};
|
|
|
|
unless (-d "$rootimg_dir/.statebackup$f") {
|
|
xCAT::Utils->runcmd("rm -rf $rootimg_dir/.statebackup$f", 0, 1) if (-e "$rootimg_dir/.statebackup$f");
|
|
$verbose && $callback->({ info => ["mkdir $rootimg_dir/.statebackup$f"] });
|
|
xCAT::Utils->runcmd("mkdir -p $rootimg_dir/.statebackup$f");
|
|
}
|
|
foreach my $child (@{$childrenRef}) {
|
|
my @tmpc = split(/\s+/, $child);
|
|
my $name = $rootimg_dir . $tmpc[1];
|
|
|
|
if (-e $name) {
|
|
$verbose && $callback->({ info => ["cp -r -a $name $rootimg_dir/.statebackup$f"] });
|
|
xCAT::Utils->runcmd("cp -r -a $name $rootimg_dir/.statebackup$f");
|
|
}
|
|
}
|
|
}
|
|
|
|
# there's one parent directory, whose option is different from the old one
|
|
unless ($entry[1] eq $oldentry[0]) {
|
|
recoverFiles($rootimg_dir, \@oldentry, $callback);
|
|
|
|
# if its children items exist, we need to copy the backup files from .statebackup to the rootfs,
|
|
if ($hashSaved{$line}) {
|
|
$verbose && $callback->({ info => ["$f has child file/directory in the litefile table."] });
|
|
my $childrenRef = $hashSaved{$line};
|
|
foreach my $child (@{$childrenRef}) {
|
|
|
|
# recover them from .statebackup to $rootimg_dir
|
|
my @tmpc = split(/\s+/, $child);
|
|
my $name = $tmpc[1];
|
|
my @newentries = grep /\s+$name$/, @{listNew};
|
|
|
|
my $destf = $rootimg_dir . $name;
|
|
my $srcf = $rootimg_dir . "/.statebackup" . $name;
|
|
if (-e $destf) {
|
|
$verbose && $callback->({ info => ["rm -rf $destf"] });
|
|
xCAT::Utils->runcmd("rm -rf $destf", 0, 1);
|
|
}
|
|
|
|
# maybe the dir of $destf doesn't exist, so we will create one
|
|
my $dirDestf = dirname $destf;
|
|
unless (-d $dirDestf) {
|
|
$verbose && $callback->({ info => ["mkdir -p $dirDestf"] });
|
|
xCAT::Utils->runcmd("mkdir -p $dirDestf", 0, 1);
|
|
}
|
|
|
|
if (-e $srcf) {
|
|
$verbose && $callback->({ info => ["recovering from $srcf to $destf"] });
|
|
xCAT::Utils->runcmd("cp -r -a $srcf $destf", 0, 1);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
# recover the children
|
|
if ($hashSaved{$line}) {
|
|
$verbose && $callback->({ info => ["$f has child file/directory in the litefile table."] });
|
|
my $childrenRef = $hashSaved{$line};
|
|
foreach my $child (@{$childrenRef}) {
|
|
my @tmpc = split(/\s+/, $child);
|
|
my $name = $tmpc[1];
|
|
my @newentries = grep /\s+$name$/, @{$listNew};
|
|
my @entry;
|
|
|
|
if (scalar @newentries == 1) {
|
|
@entry = split(/\s+/, $newentries[0]);
|
|
}
|
|
unless ($tmpc[0] eq $entry[1]) {
|
|
recoverFiles($rootimg_dir, \@tmpc, $callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
# remove .statebackup
|
|
$verbose && $callback->({ info => ["remove .statebackup"] });
|
|
xCAT::Utils->runcmd("rm -rf $rootimg_dir/.statebackup", 0, 1);
|
|
|
|
# then store the @synclist to litefile.save
|
|
#system("cp $rootimg_dir/.statelite/litefile.save $rootimg_dir/.statelite/litefile.save1");
|
|
open SAVED, ">$rootimg_dir/.statelite/litefile.save";
|
|
foreach my $line (@{$listNew}) {
|
|
print SAVED "$line\n";
|
|
}
|
|
close SAVED;
|
|
|
|
liteMe($rootimg_dir, \%hashNew, $callback);
|
|
|
|
# now stick the rc file in:
|
|
# this is actually a pre-rc file because it gets run before the node boots up all the way.
|
|
$verbose && $callback->({ info => ["put the statelite rc file to $rootimg_dir/etc/init.d/"] });
|
|
|
|
# rh5,rh6.1 to rh6.4 use rc.statelite.ppc.redhat, otherwise use rc.statelite
|
|
if (($osver =~ m/^rh[a-zA-Z]*5/) or ($osver =~ m/^rh[a-zA-Z]*6(\.)?[1-4]$/) and $arch eq "ppc64") { # special case for redhat5/6.x on PPC64
|
|
system("cp -a $::XCATROOT/share/xcat/netboot/add-on/statelite/rc.statelite.ppc.redhat $rootimg_dir/etc/init.d/statelite");
|
|
} else {
|
|
system("cp -a $::XCATROOT/share/xcat/netboot/add-on/statelite/rc.statelite $rootimg_dir/etc/init.d/statelite");
|
|
}
|
|
|
|
# newly-introduced code for the rootfs with "ramdisk" as its type
|
|
if ($rootfstype eq "ramdisk") {
|
|
my $xcat_packimg_tmpfile = "/tmp/xcat_packimg.$$";
|
|
my $excludestr = "find . ";
|
|
my $includestr;
|
|
if ($exlistloc) {
|
|
my @excludeslist = split ',', $exlistloc;
|
|
foreach my $exlistlocname (@excludeslist) {
|
|
my $exlist;
|
|
my $excludetext;
|
|
open($exlist, "<", $exlistlocname);
|
|
system("echo -n > $xcat_packimg_tmpfile");
|
|
while (<$exlist>) {
|
|
$excludetext .= $_;
|
|
}
|
|
close($exlist);
|
|
|
|
#handle the #INLCUDE# tag recursively
|
|
my $idir = dirname($exlistlocname);
|
|
my $doneincludes = 0;
|
|
while (not $doneincludes) {
|
|
$doneincludes = 1;
|
|
if ($excludetext =~ /#INCLUDE:[^#^\n]+#/) {
|
|
$doneincludes = 0;
|
|
$excludetext =~ s/#INCLUDE:([^#^\n]+)#/include_file($1,$idir)/eg;
|
|
}
|
|
}
|
|
|
|
my @tmp = split("\n", $excludetext);
|
|
foreach (@tmp) {
|
|
chomp $_;
|
|
s/\s*#.*//; #-- remove comments
|
|
next if /^\s*$/; #-- skip empty lines
|
|
if (/^\+/) {
|
|
s/^\+//; #remove '+'
|
|
$includestr .= "-path '" . $_ . "' -o ";
|
|
} else {
|
|
s/^\-//; #remove '-' if any
|
|
$excludestr .= "'!' -path '" . $_ . "' -a ";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$excludestr =~ s/-a $//;
|
|
if ($includestr) {
|
|
$includestr =~ s/-o $//;
|
|
$includestr = "find . " . $includestr;
|
|
}
|
|
|
|
print "\nexcludestr=$excludestr\n\n includestr=$includestr\n\n"; # debug
|
|
|
|
# some rpms like atftp mount the rootimg/proc to /proc, we need to make sure rootimg/proc is free of junk
|
|
# before packaging the image
|
|
system("umount $rootimg_dir/proc");
|
|
|
|
#put the image name, uuid and timestamp into diskless image when it is packed.
|
|
$verbose && $callback->({ data => ["add image info to xcatinfo file"] });
|
|
`echo IMAGENAME="'$imagename'" > $rootimg_dir/opt/xcat/xcatinfo`;
|
|
|
|
my $uuid = `uuidgen`;
|
|
chomp $uuid;
|
|
`echo IMAGEUUID="'$uuid'" >> $rootimg_dir/opt/xcat/xcatinfo`;
|
|
|
|
my $timestamp = `date`;
|
|
chomp $timestamp;
|
|
`echo TIMESTAMP="'$timestamp'" >> $rootimg_dir/opt/xcat/xcatinfo`;
|
|
|
|
my $verb = "Packing";
|
|
$callback->({ data => ["$verb contents of $rootimg_dir ..."] });
|
|
|
|
my $compress = "gzip";
|
|
|
|
#use "pigz" as the compress tool instead of gzip if "pigz" exist
|
|
my $ispigz = system("bash -c 'type -p pigz' >/dev/null 2>&1");
|
|
if ($ispigz == 0) {
|
|
$compress = "pigz";
|
|
}
|
|
|
|
$callback->({ info => ["compress method: $compress"] });
|
|
|
|
# Nodes keep downloading the previous archive until the new one is complete and replaces it.
|
|
my ($stage, $stage_error) = xCAT::SvrUtils->rootimg_stage($destdir);
|
|
unless ($stage) {
|
|
$callback->({ error => [$stage_error], errorcode => [1] });
|
|
chdir($oldpath);
|
|
return;
|
|
}
|
|
my $archive = "$stage/rootimg-statelite.gz";
|
|
chdir("$rootimg_dir");
|
|
my @pack;
|
|
if ($exlistloc) {
|
|
push @pack, "$excludestr >> $xcat_packimg_tmpfile";
|
|
push @pack, "$includestr >> $xcat_packimg_tmpfile" if $includestr;
|
|
push @pack, "cat $xcat_packimg_tmpfile |cpio -H newc -o | $compress -c - > '$archive'";
|
|
} else {
|
|
push @pack, "find . |cpio -H newc -o | $compress -c - > '$archive'";
|
|
}
|
|
my $oldmask = umask 0077;
|
|
my $error;
|
|
foreach my $command (@pack) {
|
|
next unless system('/bin/bash', '-o', 'pipefail', '-c', $command);
|
|
$error = "liteimg failed while running: $command";
|
|
last;
|
|
}
|
|
umask $oldmask;
|
|
system("rm -f $xcat_packimg_tmpfile");
|
|
if ($dotorrent && !$error) {
|
|
my $made = chdir($stage) && !system("ctorrent -t -u $dotorrent -l 1048576 -s rootimg-statelite.gz.metainfo rootimg-statelite.gz");
|
|
$error = "ctorrent cannot create the metainfo of $destdir/rootimg-statelite.gz" unless $made;
|
|
}
|
|
$error ||= xCAT::SvrUtils->write_rootimg_checksum($archive);
|
|
$error ||= xCAT::SvrUtils->publish_rootimg($archive, "$destdir/rootimg-statelite.gz");
|
|
chdir($oldpath);
|
|
if ($error) {
|
|
$callback->({ error => [$error], errorcode => [1] });
|
|
return;
|
|
}
|
|
$callback->({ data => ["$verb contents of $rootimg_dir done."] });
|
|
}
|
|
chdir($oldpath);
|
|
|
|
|
|
}
|
|
|
|
sub liteMe {
|
|
my $rootimg_dir = shift;
|
|
my $hashNewRef = shift;
|
|
my $callback = shift;
|
|
|
|
unless (-d $rootimg_dir) {
|
|
$callback->({ error => ["no rootimage dir"], errorcode => [1] });
|
|
return;
|
|
}
|
|
|
|
unless (-d "$rootimg_dir/$statedir") {
|
|
|
|
# snapshot directory for tmpfs and persistent data.
|
|
if (-e "$rootimg_dir/$statedir") {
|
|
xCAT::Utils->runcmd("rm -rf $rootimg_dir/$statedir", 0, 1);
|
|
}
|
|
$callback->({ info => ["creating $rootimg_dir/$statedir"] });
|
|
xCAT::Utils->runcmd("mkdir -p $rootimg_dir/$statedir", 0, 1);
|
|
}
|
|
unless (-d "$rootimg_dir/$statedir/tmpfs") {
|
|
xCAT::Utils->runcmd("mkdir -p $rootimg_dir/$statedir/tmpfs", 0, 1);
|
|
}
|
|
|
|
foreach my $line (keys %{$hashNewRef}) {
|
|
liteItem($rootimg_dir, $line, 0, $callback);
|
|
if ($hashNewRef->{$line}) { # there're children
|
|
my $childrenRef = $hashNewRef->{$line};
|
|
foreach my $child (@{$childrenRef}) {
|
|
liteItem($rootimg_dir, $child, 1, $callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
$callback->({ info => ["Modifying $rootimg_dir done."] });
|
|
|
|
# end loop, synclist should now all be in place.
|
|
}
|
|
|
|
sub getRelDir {
|
|
my $f = shift;
|
|
$f = dirname($f);
|
|
if ($f eq "/") {
|
|
return ".";
|
|
}
|
|
my $l = "";
|
|
|
|
my @arr = split("/", $f);
|
|
foreach (1 .. $#arr) {
|
|
$l .= "../";
|
|
}
|
|
|
|
chop($l); # get rid of last /
|
|
return $l
|
|
}
|
|
|
|
sub include_file
|
|
{
|
|
my $file = shift;
|
|
my $idir = shift;
|
|
my @text = ();
|
|
unless ($file =~ /^\//) {
|
|
$file = $idir . "/" . $file;
|
|
}
|
|
|
|
open(INCLUDE, $file) || \
|
|
return "#INCLUDEBAD:cannot open $file#";
|
|
|
|
while (<INCLUDE>) {
|
|
chomp($_);
|
|
s/\s+$//; #remove trailing spaces
|
|
next if /^\s*$/; #-- skip empty lines
|
|
push(@text, $_);
|
|
}
|
|
|
|
close(INCLUDE);
|
|
|
|
return join("\n", @text);
|
|
}
|
|
|
|
|
|
=head3 parseLiteFiles
|
|
In the liteentry table, one directory and its sub-items (including sub-directory and entries) can co-exist;
|
|
In order to handle such a scenario, one hash is generated to show the hirarachy relationship
|
|
|
|
For example, one array with entry names is used as the input:
|
|
my @entries = (
|
|
"imagename persistent /var/",
|
|
"imagename tempfs /var/tmp/",
|
|
"imagename link /root/",
|
|
"imagename link /root/.bashrc",
|
|
"imagename link /root/test/",
|
|
"imagename link /root/second/third",
|
|
"imagename tempfs /etc/resolv.conf",
|
|
"imagename tempfs /var/run/"
|
|
);
|
|
Then, one hash will generated as:
|
|
%hashentries = {
|
|
'persistent /var/' => [
|
|
'tempfs /var/tmp/',
|
|
'tempfs /var/run/'
|
|
],
|
|
'tempfs /etc/resolv.conf' => undef,
|
|
'link /root/' => [
|
|
'link /root/.bashrc',
|
|
'link /root/test/',
|
|
'link /root/second/third"
|
|
]
|
|
};
|
|
|
|
Arguments:
|
|
one array with entrynames,
|
|
one hash to hold the entries parsed
|
|
|
|
Returns:
|
|
0 if sucucess
|
|
1 if fail
|
|
|
|
=cut
|
|
|
|
sub parseLiteFiles {
|
|
my ($flref, $dhref) = @_;
|
|
my @entries = @{$flref};
|
|
|
|
|
|
foreach (@entries) {
|
|
my $entry = $_;
|
|
my @str = split /\s+/, $entry;
|
|
shift @str; # remove the imgname in @entries
|
|
$entry = join "\t", @str;
|
|
my $file = $str[1];
|
|
chop $file if ($file =~ m{/$});
|
|
unless (exists $dhref->{"$entry"}) {
|
|
my $parent = dirname $file;
|
|
my @res;
|
|
my $found = 0;
|
|
while ($parent ne "/") {
|
|
|
|
# to see whether $parent exists in @entries or not
|
|
$parent .= "/" unless ($parent =~ m/\/$/);
|
|
@res = grep { $_ =~ m/\Q$parent\E$/ } @entries;
|
|
$found = scalar @res;
|
|
last if ($found == 1);
|
|
$parent = dirname $parent;
|
|
}
|
|
|
|
if ($found == 1) { # $parent is found in @entries
|
|
# handle $res[0];
|
|
my @tmpresentry = split /\s+/, $res[0];
|
|
shift @tmpresentry; # remove the imgname in @tmpresentry
|
|
$res[0] = join "\t", @tmpresentry;
|
|
chop $parent;
|
|
my @keys = keys %{$dhref};
|
|
my $kfound = grep { $_ =~ m/\Q$res[0]\E$/ } @keys;
|
|
if ($kfound eq 0) {
|
|
$dhref->{ $res[0] } = [];
|
|
}
|
|
push @{ $dhref->{"$res[0]"} }, $entry;
|
|
} else {
|
|
$dhref->{"$entry"} = ();
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
=head3
|
|
recoverFiles
|
|
=cut
|
|
|
|
sub recoverFiles {
|
|
my ($rootimg_dir, $oldentry, $callback) = @_;
|
|
$f = $oldentry->[1];
|
|
|
|
#$callback->({info => ["! updating $f ..."]});
|
|
|
|
if ($oldentry->[0] =~ m{^link}) {
|
|
my $target = $rootimg_dir . $f;
|
|
if (-l $target) { #not one directory
|
|
my $location = readlink $target;
|
|
|
|
# if it is not linked from tmpfs, it should have been modified by the .postinstall file
|
|
if ($location =~ /\.statelite\/tmpfs/) {
|
|
xCAT::Utils->runcmd("rm -rf $target", 0, 1);
|
|
my $default = $rootimg_dir . "/.default" . $f;
|
|
if (-e $default) {
|
|
xCAT::Utils->runcmd("cp -r -a $default $target", 0, 1);
|
|
} else { # maybe someone deletes the copy in .default directory
|
|
xCAT::Utils->runcmd("touch $target", 0, 1);
|
|
}
|
|
}
|
|
} else {
|
|
chop $target;
|
|
if (-l $target) {
|
|
my $location = readlink $target;
|
|
if ($location =~ /\.statelite\/tmpfs/) {
|
|
xCAT::Utils->runcmd("rm -rf $target", 0, 1);
|
|
my $default = $rootimg_dir . "/.default" . $f;
|
|
if (-e $default) {
|
|
xCAT::Utils->runcmd("cp -r -a $default $target", 0, 1);
|
|
} else {
|
|
xCAT::Utils->runcmd("mkdir $target", 0, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$target = $rootimg_dir . "/.statelite/tmpfs" . $f;
|
|
xCAT::Utils->runcmd("rm -rf $target", 0, 1);
|
|
} else {
|
|
|
|
# shouldn't copy back from /.default, maybe the user has replaced the file/directory in .postinstall file
|
|
my $default = $rootimg_dir . $f;
|
|
xCAT::Utils->runcmd("rm -rf $default", 0, 1); # TODO: not sure whether it's necessary right now
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
=head3
|
|
liteItem
|
|
=cut
|
|
|
|
sub liteItem {
|
|
my ($rootimg_dir, $item, $isChild, $callback) = @_;
|
|
|
|
my @entry = split(/\s+/, $item);
|
|
|
|
my $f = $entry[1]; # file name
|
|
|
|
my $rif = $rootimg_dir . $f; # the file's location in rootimg_dir
|
|
my $d = dirname($f);
|
|
|
|
if ($entry[0] =~ m/link/) {
|
|
|
|
# 1. copy original contents if they exist to .default directory
|
|
# 2. remove file
|
|
# 3. create symbolic link to .statelite
|
|
|
|
# the /etc/mtab should be handled every time even the parent /etc/ has been handled.
|
|
# if adding /etc/ to litefile, only tmpfs should be used.
|
|
if ($entry[1] eq "/etc/mtab") {
|
|
#
|
|
# In RHEL7 /etc/mtab is a symlink to /proc/self/mounts and not a regular file.
|
|
# If that's the case, then we skip forcing /etc/mtab into the .statelite path
|
|
# and symlink'ing /etc/mtab to that file. Otherwise, since the OS isn't updating
|
|
# /etc/mtab commands like "df" will not be happy.
|
|
#
|
|
my $ret = `readlink $rootimg_dir/etc/mtab`;
|
|
chomp($ret);
|
|
if ($? == 0 and ($ret eq '/proc/self/mounts')) {
|
|
$verbose && $callback->({ info => ["skipping /etc/mtab (symlink to /proc/self/mounts)"] });
|
|
next;
|
|
}
|
|
$isChild = 0;
|
|
}
|
|
|
|
if ($isChild == 0) {
|
|
|
|
#check if the file has been moved to .default by its parent or by last liteimg, if yes, then do nothing
|
|
my $ret = `readlink -m $rootimg_dir$f`;
|
|
if ($? == 0) {
|
|
if ($ret =~ /$rootimg_dir\/.default/) {
|
|
$verbose && $callback->({ info => ["do nothing for file $f"] });
|
|
next;
|
|
}
|
|
}
|
|
|
|
# copy the file to /.defaults
|
|
if (-f "$rif" or -d "$rif") {
|
|
|
|
# if its already a link then leave it alone.
|
|
unless (-l $rif) {
|
|
|
|
# mk the directory if it doesn't exist:
|
|
unless (-d "$rootimg_dir/.default$d") {
|
|
$verbose && $callback->({ info => ["mkdir -p $rootimg_dir/.default$d"] });
|
|
system("mkdir -p $rootimg_dir/.default$d");
|
|
}
|
|
|
|
# copy the file in place.
|
|
$verbose && $callback->({ info => ["cp -r -a $rif $rootimg_dir/.default$d"] });
|
|
system("cp -r -a $rif $rootimg_dir/.default$d");
|
|
|
|
# remove the real file
|
|
$verbose && $callback->({ info => ["rm -rf $rif"] });
|
|
system("rm -rf $rif");
|
|
}
|
|
} else {
|
|
|
|
# in this case the file doesn't exist in the image so we create something to it.
|
|
# here we're modifying the read/only image
|
|
|
|
unless (-d "$rootimg_dir$d") {
|
|
$verbose && $callback->({ info => ["mkdir -p $rootimg_dir$d"] });
|
|
system("mkdir -p $rootimg_dir$d");
|
|
}
|
|
|
|
unless (-d "$rootimg_dir/.default$d") {
|
|
$verbose && $callback->({ info => ["mkdir -p $rootimg_dir/.default$d"] });
|
|
system("mkdir -p $rootimg_dir/.default$d");
|
|
}
|
|
|
|
# now make touch the file:
|
|
if ($f =~ /\/$/) {
|
|
|
|
# if its a directory, make the directory in .default
|
|
$verbose && $callback->({ info => ["mkdir -p $rootimg_dir/.default$f"] });
|
|
system("mkdir -p $rootimg_dir/.default$f");
|
|
} else {
|
|
|
|
# if its just a file then link it.
|
|
$verbose && $callback->({ info => ["touch $rootimg_dir/.default$f"] });
|
|
system("touch $rootimg_dir/.default$f");
|
|
}
|
|
}
|
|
|
|
# now create the spot in tmpfs
|
|
$verbose && $callback->({ info => ["mkdir -p $rootimg_dir/$statedir/tmpfs$d"] });
|
|
system("mkdir -p $rootimg_dir/$statedir/tmpfs$d");
|
|
|
|
# now for each file, create a symbollic link to /.statelite/tmpfs/
|
|
# strip the last / if its a directory for linking, but be careful!
|
|
# doing ln -sf ../../tmp <blah>../tmp when tmp is a directory creates 50 levels of links.
|
|
# have to do:
|
|
# ln -sf ../../tmp <blah>../../tmp/ <- note the / on the end!
|
|
if ($f =~ /\/$/) {
|
|
$f =~ s/\/$//g;
|
|
}
|
|
|
|
# now do the links.
|
|
# link the .default file to the .statelite file and the real file to the .statelite file.
|
|
# ../ for tmpfs
|
|
# ../ for .statelite
|
|
# the rest are for the paths in the file.
|
|
my $l = getRelDir($f);
|
|
$verbose && $callback->({ info => ["ln -sf ../../$l/.default$f $rootimg_dir/$statedir/tmpfs$f"] });
|
|
system("ln -sfn ../../$l/.default$f $rootimg_dir/$statedir/tmpfs/$f");
|
|
|
|
$verbose && $callback->({ info => ["ln -sf $l/$statedir/tmpfs$f $rootimg_dir$f"] });
|
|
system("ln -sfn $l/$statedir/tmpfs$f $rootimg_dir$f");
|
|
|
|
} else {
|
|
|
|
# since its parent directory has been linked to .default and .statelite/tmpfs/,
|
|
# what we need to do is only to check whether it exists in .default directory
|
|
if ($f =~ m{/$}) { # one directory
|
|
unless (-d "$rootimg_dir/.default$f") {
|
|
if (-e "$rootimg_dir/.default$f") {
|
|
xCAT::Utils->runcmd("rm -rf $rootimg_dir/.default$f", 0, 1);
|
|
}
|
|
$verbose && $callback->({ info => ["mkdir -p $rootimg_dir/.default$f"] });
|
|
xCAT::Utils->runcmd("mkdir -p $rootimg_dir/.default$f", 0, 1);
|
|
}
|
|
} else { # only one file
|
|
my $fdir = dirname($f);
|
|
unless (-d "$rootimg_dir/.default$fdir") {
|
|
$verbose && $callback->({ info => ["mkdir -p $rootimg_dir/.default$fdir"] });
|
|
xCAT::Utils->runcmd("mkdir -p $rootimg_dir/.default$fdir", 0, 1);
|
|
}
|
|
unless (-e "$rootimg_dir/.default$f") {
|
|
$verbose && $callback->({ info => ["touch $rootimg_dir/.default$f"] });
|
|
xCAT::Utils->runcmd("touch $rootimg_dir/.default$f", 0, 1);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
|
|
# if no such file like $rif, create one
|
|
unless (-e "$rif") {
|
|
if ($f =~ m{/$}) {
|
|
$verbose && $callback->({ info => ["mkdir -p $rif"] });
|
|
system("mkdir -p $rif");
|
|
} else {
|
|
|
|
# check whether its directory exists or not
|
|
my $rifdir = $rootimg_dir . $d;
|
|
unless (-e $rifdir) {
|
|
$verbose && $callback->({ info => ["mkdir $rifdir"] });
|
|
mkdir($rifdir);
|
|
}
|
|
$verbose && $callback->({ info => ["touch $rif"] });
|
|
system("touch $rif");
|
|
}
|
|
}
|
|
|
|
unless (-e "$rootimg_dir/.default$d") {
|
|
$verbose && $callback->({ info => ["mkdir -p $rootimg_dir/.default$d"] });
|
|
system("mkdir -p $rootimg_dir/.default$d");
|
|
}
|
|
|
|
# copy the file to /.defaults
|
|
$verbose && $callback->({ info => ["cp -r -a $rif $rootimg_dir/.default$d"] });
|
|
system("cp -r -a $rif $rootimg_dir/.default$d");
|
|
}
|
|
}
|
|
|
|
=head3
|
|
updateFstab
|
|
=cut
|
|
|
|
sub updateFstab {
|
|
|
|
$rootimg_dir = shift;
|
|
$profile = shift;
|
|
$arch = shift;
|
|
|
|
my $rootfs_name = $profile . "_" . $arch;
|
|
|
|
my $tfstab;
|
|
open($tfstab, "<", "$rootimg_dir/etc/fstab");
|
|
my @fsdents = <$tfstab>;
|
|
close($tfstab);
|
|
|
|
open($tfstab, ">", "$rootimg_dir/etc/fstab");
|
|
foreach my $line (@fsdents) {
|
|
if ( $line =~ /^$rootfs_name/ ) {
|
|
$line =~ s/\brw\b/ro/;
|
|
}
|
|
print $tfstab $line;
|
|
}
|
|
close($tfstab);
|
|
|
|
}
|
|
|
|
|
|
1;
|