diff --git a/xCAT-server/lib/perl/xCAT/SvrUtils.pm b/xCAT-server/lib/perl/xCAT/SvrUtils.pm index 88337bab3..87c95f0df 100644 --- a/xCAT-server/lib/perl/xCAT/SvrUtils.pm +++ b/xCAT-server/lib/perl/xCAT/SvrUtils.pm @@ -2640,6 +2640,106 @@ sub rootimg_checksum_enabled { return !(defined($value) && $value =~ /^\s*(?:no|0)\s*$/ix); } +#-------------------------------------------------------------------------------------------------------- +#write_initrd_check_marker: +#description: record that an initial ramdisk checks the compressed rootimage against its checksum. +# The marker holds the SHA-256 of the initial ramdisk, so a replaced ramdisk does not match it. +#argument: +# $initrd: the path of the initial ramdisk +#return: +# on success: nothing +# on fail: the error message +#-------------------------------------------------------------------------------------------------------- +sub write_initrd_check_marker { + my (undef, $initrd) = @_; + my $digest = eval { Digest::SHA->new(256)->addfile($initrd)->hexdigest } + or return "Cannot compute the SHA-256 checksum of $initrd: $@"; + return _write_published_file("$initrd.rootimgcheck", "sha256=$digest\n"); +} + +#-------------------------------------------------------------------------------------------------------- +#initrd_checks_rootimg: +#description: tell whether genimage built an initial ramdisk that checks the compressed rootimage +#arguments: +# $initrd: the path of the initial ramdisk, or of a copy of it +# $marker: the marker that genimage wrote, by default .rootimgcheck +#return: +# true when the marker holds the SHA-256 of the initial ramdisk +#-------------------------------------------------------------------------------------------------------- +sub initrd_checks_rootimg { + my (undef, $initrd, $marker_path) = @_; + open(my $marker, '<', $marker_path // "$initrd.rootimgcheck") or return 0; + my $line = <$marker> // ''; + close($marker); + my ($recorded) = $line =~ /^sha256=([0-9a-f]{64})$/x or return 0; + my $digest = eval { Digest::SHA->new(256)->addfile($initrd)->hexdigest } or return 0; + return $digest eq $recorded; +} + +#-------------------------------------------------------------------------------------------------------- +#prepare_rootimg_checksum: +#description: write the missing checksums of an image before nodeset copies its boot files. With the check +# turned off, a checksum that cannot be written is a warning instead of an error. +#arguments (by name): +# value: the rootimgchecksum attribute of the image, or undef +# rootimgdir: the image directory +# imagename: the image name, for the messages +# warned: a hash of the image directories that already got a warning +# callback: the request callback +#return: +# nothing, or the error that keeps nodeset from setting up the node +#-------------------------------------------------------------------------------------------------------- +sub prepare_rootimg_checksum { + my ($class, %args) = @_; + my $rootimgdir = $args{rootimgdir}; + my $imagename = $args{imagename} || $rootimgdir; + + # A node whose boot entry nodeset wrote while the check was on still checks, so every archive gets a checksum. + my $error = $class->publish_missing_rootimg_checksums($rootimgdir) or return; + $error =~ s/\.$//x; + if (!$class->rootimg_checksum_enabled($args{value})) { + $args{callback}->({ warning => [ "$error. Nodes of $imagename whose boot entries still ask for the check " + . "download the root image again until nodeset runs for them." ] }) unless $args{warned}{$rootimgdir}++; + return; + } + return "The root image of $imagename has no checksum that its initial ramdisk can check: $error. " + . "Run packimage for the image, then nodeset."; +} + +#-------------------------------------------------------------------------------------------------------- +#rootimg_checksum_boot: +#description: decide how a node checks the compressed rootimage that it downloads at boot, and warn once +# for each image whose initial ramdisk has no matching marker +#arguments (by name): +# value: the rootimgchecksum attribute of the image, or undef +# rootimgdir: the image directory +# initrd: the initial ramdisk that nodes boot, as nodeset left it in TFTP +# imagename: the image name, for the messages +# warned: a hash of the image directories that already got a warning +# callback: the request callback +#return: +# the kernel argument that asks the node to check, or an empty string when the check is off +#-------------------------------------------------------------------------------------------------------- +my %initrd_checks; +sub rootimg_checksum_boot { + my ($class, %args) = @_; + return '' unless $class->rootimg_checksum_enabled($args{value}); + my ($rootimgdir, $initrd) = @args{qw(rootimgdir initrd)}; + my $imagename = $args{imagename} || $rootimgdir; + + # The copy in TFTP checks only when it matches the marker that genimage wrote in the image directory. + my $marker = "$rootimgdir/" . basename($initrd) . '.rootimgcheck'; + + # nodeset runs this for every node, so each version of the initial ramdisk and its marker is hashed once. + my @version = map { (stat($_))[ 0, 1, 7, 9 ] } ($initrd, $marker); + my $key = join(':', $initrd, map { $_ // '' } @version); + $initrd_checks{$key} //= $class->initrd_checks_rootimg($initrd, $marker) ? 1 : 0; + $args{callback}->({ warning => [ "Cannot tell whether the initial ramdisk $initrd checks the root image checksum, so " + . "nodes may boot the root image of $imagename unchecked. Run genimage for the image and then nodeset, or " + . "set its rootimgchecksum attribute to no." ] }) unless $initrd_checks{$key} || $args{warned}{$rootimgdir}++; + return 'rootimgchecksum=yes '; +} + # Returns the content of a file, or undef when it cannot be read. sub _read_published_file { my ($path) = @_; @@ -2770,6 +2870,34 @@ sub _restore_rootimg { return; } +#-------------------------------------------------------------------------------------------------------- +#publish_missing_rootimg_checksums: +#description: write the checksum of each published compressed rootimage of an image directory that has +# none. An initial ramdisk that checks the rootimage refuses an image packed before the check. +#argument: +# $dir: the image directory +#return: +# on success: nothing +# on fail: the error message +#-------------------------------------------------------------------------------------------------------- +sub publish_missing_rootimg_checksums { + my ($class, $dir) = @_; + my @formats = qw(rootimg.gz rootimg.cpio.gz rootimg.cpio.xz rootimg.tar.gz rootimg.tar.xz rootimg.sfs rootimg-statelite.gz); + my $missing = sub { grep { -f "$dir/$_" && !-e "$dir/$_.sha256" } @formats }; + my @missing = $missing->(); + return if !@missing; + + # The lock keeps packimage from replacing an archive while this checksum is computed. + my ($lock, $error) = _acquire_publish_lock($dir); + return $error unless $lock; + foreach my $file ($missing->()) { + $error = $class->write_rootimg_checksum("$dir/$file"); + last if $error; + } + _release_publish_lock($lock); + return $error; +} + # The image lock is per host, and mkdir is atomic for every host, also on NFS without locks. sub _acquire_publish_lock { my ($dir) = @_; diff --git a/xCAT-server/lib/xcat/plugins/anaconda.pm b/xCAT-server/lib/xcat/plugins/anaconda.pm index 2167e30db..917706fbb 100644 --- a/xCAT-server/lib/xcat/plugins/anaconda.pm +++ b/xCAT-server/lib/xcat/plugins/anaconda.pm @@ -394,6 +394,7 @@ sub mknetboot } my %donetftp = (); + my %rootimg_warned; my $ostab = xCAT::Table->new('nodetype'); my %oents = %{ $ostab->getNodesAttribs(\@nodes, [qw(os arch profile provmethod)]) }; my $restab = xCAT::Table->new('noderes'); @@ -426,6 +427,7 @@ sub mknetboot my $nfsrootopts; my $tftpdir; my $cfgpart; + my $rootimgchecksum; my $imagename; # set it if running of 'nodeset osimage=xxx' if ($reshash->{$node}->[0] and $reshash->{$node}->[0]->{tftpdir}) { @@ -457,10 +459,11 @@ sub mknetboot if (!$linuximagetab) { $linuximagetab = xCAT::Table->new('linuximage', -create => 1); } - (my $ref1) = $linuximagetab->getAttribs({ imagename => $imagename }, 'rootimgdir', 'nodebootif', 'dump', 'crashkernelsize', 'partitionfile'); + (my $ref1) = $linuximagetab->getAttribs({ imagename => $imagename }, 'rootimgdir', 'nodebootif', 'dump', 'crashkernelsize', 'partitionfile', 'rootimgchecksum'); if (($ref1) && ($ref1->{'rootimgdir'})) { $img_hash{$imagename}->{rootimgdir} = $ref1->{'rootimgdir'}; } + $img_hash{$imagename}->{rootimgchecksum} = $ref1->{'rootimgchecksum'} if $ref1; if (($ref1) && ($ref1->{'nodebootif'})) { $img_hash{$imagename}->{nodebootif} = $ref1->{'nodebootif'}; } @@ -517,6 +520,7 @@ sub mknetboot $crashkernelsize = $ph->{crashkernelsize}; $dump = $ph->{dump}; $cfgpart = $ph->{'cfgpart'}; + $rootimgchecksum = $ph->{rootimgchecksum}; } else { my @ents = xCAT::TableUtils->get_site_attribute("disablenodesetwarning"); @@ -563,10 +567,11 @@ sub mknetboot $linuximagetab = xCAT::Table->new('linuximage'); } if ($linuximagetab) { - (my $ref1) = $linuximagetab->getAttribs({ imagename => $imgname }, 'dump', 'crashkernelsize', 'partitionfile'); + (my $ref1) = $linuximagetab->getAttribs({ imagename => $imgname }, 'dump', 'crashkernelsize', 'partitionfile', 'rootimgchecksum'); if ($ref1 and $ref1->{'dump'}) { $dump = $ref1->{'dump'}; } + $rootimgchecksum = $ref1->{'rootimgchecksum'} if $ref1; if ($ref1 and $ref1->{'crashkernelsize'}) { $crashkernelsize = $ref1->{'crashkernelsize'}; } @@ -664,42 +669,53 @@ sub mknetboot #mkpath "/install/postscripts/"; # Copy the boot resource to /tftpboot and check to only copy once - my $docopy = 0; my $tftppath; my $rtftppath; # the relative tftp path without /tftpboot/ + my $copykey; if ($imagename) { $tftppath = "$tftpdir/xcat/osimage/$imagename"; $rtftppath = "xcat/osimage/$imagename"; - unless ($donetftp{$imagename}) { - $docopy = 1; - $donetftp{$imagename} = 1; - } + $copykey = $imagename; } else { $tftppath = "/$tftpdir/xcat/netboot/$osver/$arch/$profile/"; $rtftppath = "xcat/netboot/$osver/$arch/$profile/"; - unless ($donetftp{ $osver, $arch, $profile }) { - $docopy = 1; - $donetftp{ $osver, $arch, $profile } = 1; + $copykey = join($;, $osver, $arch, $profile); + } + my $docopy = !$donetftp{$copykey}; + + # This runs before the boot files are copied, so a failure keeps the boot files that the node uses now. + if (!$statelite || $rootfstype eq "ramdisk") { + my $error = xCAT::SvrUtils->prepare_rootimg_checksum(value => $rootimgchecksum, rootimgdir => $rootimgdir, + imagename => $imagename, warned => \%rootimg_warned, callback => $callback); + if ($error) { + xCAT::MsgUtils->report_node_error($callback, $node, $error); + next; } } + my $copied = 1; if ($docopy && !$noupdateinitrd) { mkpath("$tftppath"); if (-f "$rootimgdir/hypervisor") { - copy("$rootimgdir/hypervisor", "$tftppath"); + $copied &&= copy("$rootimgdir/hypervisor", "$tftppath"); $xenstyle = 1; } - copy("$rootimgdir/kernel", "$tftppath"); + $copied &&= copy("$rootimgdir/kernel", "$tftppath"); if ($statelite) { if ($rootfstype eq "ramdisk") { - copy("$rootimgdir/initrd-stateless.gz", "$tftppath"); + $copied &&= copy("$rootimgdir/initrd-stateless.gz", "$tftppath"); } else { - copy("$rootimgdir/initrd-statelite.gz", "$tftppath"); + $copied &&= copy("$rootimgdir/initrd-statelite.gz", "$tftppath"); } } else { - copy("$rootimgdir/initrd-stateless.gz", "$tftppath"); + $copied &&= copy("$rootimgdir/initrd-stateless.gz", "$tftppath"); } } + unless ($copied) { + xCAT::MsgUtils->report_node_error($callback, $node, qq{Copying to $tftppath failed: $!}); + next; + } + $donetftp{$copykey} = 1; if ($statelite) { my $initrdloc = "$tftppath"; @@ -719,6 +735,13 @@ sub mknetboot } } + # Nodes boot the initial ramdisk that is in TFTP now, so that copy decides the check. + my $checksum_arg = ''; + if (!$statelite || $rootfstype eq "ramdisk") { + $checksum_arg = xCAT::SvrUtils->rootimg_checksum_boot(value => $rootimgchecksum, rootimgdir => $rootimgdir, + initrd => "$tftppath/initrd-stateless.gz", imagename => $imagename, warned => \%rootimg_warned, callback => $callback); + } + $ent = $reshash->{$node}->[0]; #$restab->getNodeAttribs($node, ['primarynic']); my $sent = $hmhash->{$node}->[0]; @@ -815,6 +838,7 @@ sub mknetboot } else { $kcmdline = "imgurl=$httpmethod://$imgsrvip:$httpport/$rootimgdir/rootimg-statelite.gz STATEMNT="; } + $kcmdline = $checksum_arg . $kcmdline; } # add support for subVars in the value of "statemnt" @@ -872,6 +896,7 @@ sub mknetboot } else { $kcmdline = "imgurl=$httpmethod://$imgsrvip:$httpport/$rootimgdir/$compressedrootimg "; } + $kcmdline .= $checksum_arg; $kcmdline .= "XCAT=$xcatmasterip:$xcatdport "; $kcmdline .= "NODE=$node "; @@ -3673,4 +3698,5 @@ EOMS return @inserted_dd; } + 1; diff --git a/xCAT-server/lib/xcat/plugins/debian.pm b/xCAT-server/lib/xcat/plugins/debian.pm index dc5753a43..eede30e5f 100644 --- a/xCAT-server/lib/xcat/plugins/debian.pm +++ b/xCAT-server/lib/xcat/plugins/debian.pm @@ -1448,6 +1448,7 @@ sub mknetboot } my %donetftp = (); + my %rootimg_warned; my %oents = %{ $ostab->getNodesAttribs(\@nodes, [qw(os arch profile provmethod)]) }; my $restab = xCAT::Table->new('noderes'); my $hmtab = xCAT::Table->new('nodehm'); @@ -1479,6 +1480,7 @@ sub mknetboot my $rootfstype; my $nfsrootopts; my $tftpdir; + my $rootimgchecksum; my $imagename; # set it if running of 'nodeset osimage=xxx' if ($reshash->{$node}->[0] and $reshash->{$node}->[0]->{tftpdir}) { @@ -1505,10 +1507,11 @@ sub mknetboot if (!$linuximagetab) { $linuximagetab = xCAT::Table->new('linuximage', -create => 1); } - (my $ref1) = $linuximagetab->getAttribs({ imagename => $imagename }, 'rootimgdir'); + (my $ref1) = $linuximagetab->getAttribs({ imagename => $imagename }, 'rootimgdir', 'rootimgchecksum'); if (($ref1) && ($ref1->{'rootimgdir'})) { $img_hash{$imagename}->{rootimgdir} = $ref1->{'rootimgdir'}; } + $img_hash{$imagename}->{rootimgchecksum} = $ref1->{'rootimgchecksum'} if $ref1; if (($ref1) && ($ref1->{'nodebootif'})) { $img_hash{$imagename}->{nodebootif} = $ref1->{'nodebootif'}; } @@ -1541,6 +1544,7 @@ sub mknetboot $nodebootif = $ph->{nodebootif}; $crashkernelsize = $ph->{crashkernelsize}; $dump = $ph->{dump}; + $rootimgchecksum = $ph->{rootimgchecksum}; } else { # This is deprecated mode to define node's provmethod, not supported now. @@ -1582,10 +1586,11 @@ sub mknetboot $linuximagetab = xCAT::Table->new('linuximage'); } if ($linuximagetab) { - (my $ref1) = $linuximagetab->getAttribs({ imagename => $imgname }, 'dump', 'crashkernelsize'); + (my $ref1) = $linuximagetab->getAttribs({ imagename => $imgname }, 'dump', 'crashkernelsize', 'rootimgchecksum'); if ($ref1 and $ref1->{'dump'}) { $dump = $ref1->{'dump'}; } + $rootimgchecksum = $ref1->{'rootimgchecksum'} if $ref1; if ($ref1 and $ref1->{'crashkernelsize'}) { $crashkernelsize = $ref1->{'crashkernelsize'}; } @@ -1664,42 +1669,53 @@ sub mknetboot #xCAT::Postage->writescript($node,"/install/postscripts/".$node, "netboot", $callback); # Copy the boot resource to /tftpboot and check to only copy once - my $docopy = 0; my $tftppath; my $rtftppath; # the relative tftp path without /tftpboot/ + my $copykey; if ($imagename) { $tftppath = "$tftpdir/xcat/osimage/$imagename"; $rtftppath = "xcat/osimage/$imagename"; - unless ($donetftp{$imagename}) { - $docopy = 1; - $donetftp{$imagename} = 1; - } + $copykey = $imagename; } else { $tftppath = "/$tftpdir/xcat/netboot/$osver/$arch/$profile/"; $rtftppath = "xcat/netboot/$osver/$arch/$profile/"; - unless ($donetftp{ $osver, $arch, $profile }) { - $docopy = 1; - $donetftp{ $osver, $arch, $profile } = 1; + $copykey = join($;, $osver, $arch, $profile); + } + my $docopy = !$donetftp{$copykey}; + + # This runs before the boot files are copied, so a failure keeps the boot files that the node uses now. + if (!$statelite || $rootfstype eq "ramdisk") { + my $error = xCAT::SvrUtils->prepare_rootimg_checksum(value => $rootimgchecksum, rootimgdir => $rootimgdir, + imagename => $imagename, warned => \%rootimg_warned, callback => $callback); + if ($error) { + xCAT::MsgUtils->report_node_error($callback, $node, $error); + next; } } + my $copied = 1; if ($docopy) { mkpath("$tftppath"); if (-f "$rootimgdir/hypervisor") { - copy("$rootimgdir/hypervisor", "$tftppath"); + $copied &&= copy("$rootimgdir/hypervisor", "$tftppath"); $xenstyle = 1; } - copy("$rootimgdir/kernel", "$tftppath"); + $copied &&= copy("$rootimgdir/kernel", "$tftppath"); if ($statelite) { if ($rootfstype eq "ramdisk") { - copy("$rootimgdir/initrd-stateless.gz", "$tftppath"); + $copied &&= copy("$rootimgdir/initrd-stateless.gz", "$tftppath"); } else { - copy("$rootimgdir/initrd-statelite.gz", "$tftppath"); + $copied &&= copy("$rootimgdir/initrd-statelite.gz", "$tftppath"); } } else { - copy("$rootimgdir/initrd-stateless.gz", "$tftppath"); + $copied &&= copy("$rootimgdir/initrd-stateless.gz", "$tftppath"); } } + unless ($copied) { + xCAT::MsgUtils->report_node_error($callback, $node, qq{Copying to $tftppath failed: $!}); + next; + } + $donetftp{$copykey} = 1; if ($statelite) { my $initrdloc = "$tftppath"; @@ -1719,6 +1735,13 @@ sub mknetboot next; } } + # Nodes boot the initial ramdisk that is in TFTP now, so that copy decides the check. + my $checksum_arg = ''; + if (!$statelite || $rootfstype eq "ramdisk") { + $checksum_arg = xCAT::SvrUtils->rootimg_checksum_boot(value => $rootimgchecksum, rootimgdir => $rootimgdir, + initrd => "$tftppath/initrd-stateless.gz", imagename => $imagename, warned => \%rootimg_warned, callback => $callback); + } + my $ent = $reshash->{$node}->[0]; #$restab->getNodeAttribs($node, ['primarynic']); my $sent = $hmhash->{$node}->[0]; @@ -1785,6 +1808,7 @@ sub mknetboot } } else { $kcmdline = "imgurl=http://$imgsrv:$httpport/$rootimgdir/rootimg-statelite.gz STATEMNT="; + $kcmdline = $checksum_arg . $kcmdline; } @@ -1841,6 +1865,7 @@ sub mknetboot { $kcmdline = "imgurl=http://$imgsrv:$httpport/$rootimgdir/$compressedrootimg "; + $kcmdline .= $checksum_arg; $kcmdline .= "XCAT=$xcatmaster:$xcatdport "; } @@ -2110,4 +2135,5 @@ sub subVars() return $fdir; } + 1; diff --git a/xCAT-server/lib/xcat/plugins/genimage.pm b/xCAT-server/lib/xcat/plugins/genimage.pm index 8d982cf26..92493e92a 100644 --- a/xCAT-server/lib/xcat/plugins/genimage.pm +++ b/xCAT-server/lib/xcat/plugins/genimage.pm @@ -451,6 +451,12 @@ sub process_request { $installdir = $siteents[0]; } + # The new initial ramdisk also checks for nodes with older boot entries, so an archive packed before the check gets a checksum. + if (my $error = xCAT::SvrUtils->publish_missing_rootimg_checksums($rootimg_dir)) { + $error =~ s/\.$//x; + $callback->({ error => ["$error. The new initial ramdisk is not copied to $tftppath. Run packimage $imagename, then nodeset."], errorcode => [1] }); + return 1; + } unless (-d $tftppath) { mkpath $tftppath; } diff --git a/xCAT-server/lib/xcat/plugins/imgport.pm b/xCAT-server/lib/xcat/plugins/imgport.pm index 8cae2b7eb..43b55cbde 100644 --- a/xCAT-server/lib/xcat/plugins/imgport.pm +++ b/xCAT-server/lib/xcat/plugins/imgport.pm @@ -637,6 +637,9 @@ sub get_files { $errors++; } else { $attrs->{ramdisk} = $ramdisk; + + # nodeset reads this marker to tell whether the ramdisk checks the root image checksum. + $attrs->{ramdiskcheck} = "$ramdisk.rootimgcheck" if -r "$ramdisk.rootimgcheck"; } unless ($kernel) { @@ -917,7 +920,7 @@ sub make_bundle { # these are the only files we copy in. (unless you have extras) - for my $a ("kernel", "ramdisk", "rootimg") { + for my $a ("kernel", "ramdisk", "ramdiskcheck", "rootimg") { my $filenames = $attribs->{$a}; if ($filenames) { my @file_array = split(',', $filenames); @@ -1333,7 +1336,7 @@ sub change_profile { $data->{linuximage}->{rootimgdir} = "$installdir/netboot/" . $data->{osimage}->{osvers} . "/" . $data->{osimage}->{osarch} . "/$new_profile"; } - for my $a ("kernel", "ramdisk", "rootimg", "rootimgtree", "litefile") { + for my $a ("kernel", "ramdisk", "ramdiskcheck", "rootimg", "rootimgtree", "litefile") { if ($data->{$a}) { my $fn = basename($data->{$a}); $data->{$a} = $data->{linuximage}->{rootimgdir} . "/$fn"; @@ -1946,7 +1949,7 @@ sub make_files { # }; # The root image goes first, so a failed publication leaves the kernel and ramdisk that match the published image. - for my $a ("rootimg", "kernel", "ramdisk", "rootimgtree", "litefile") { + for my $a ("rootimg", "kernel", "ramdisk", "ramdiskcheck", "rootimgtree", "litefile") { my $filenames = $data->{$a}; if ($filenames) { my @file_array = split(',', $filenames); diff --git a/xCAT-server/lib/xcat/plugins/sles.pm b/xCAT-server/lib/xcat/plugins/sles.pm index 6aaa5cfb9..d2c9d37e8 100644 --- a/xCAT-server/lib/xcat/plugins/sles.pm +++ b/xCAT-server/lib/xcat/plugins/sles.pm @@ -150,6 +150,7 @@ sub mknetboot my $reshash = $restab->getNodesAttribs(\@nodes, [ 'primarynic', 'tftpserver', 'tftpdir', 'xcatmaster', 'nfsserver', 'nfsdir', 'installnic' ]); my %donetftp = (); + my %rootimg_warned; foreach my $node (@nodes) { @@ -164,6 +165,7 @@ sub mknetboot my $rootfstype; my $nfsrootopts; my $cfgpart; + my $rootimgchecksum; my $imagename; # set it if running of 'nodeset osimage=xxx' my $ent = $ntents->{$node}->[0]; @@ -184,10 +186,11 @@ sub mknetboot if (!$linuximagetab) { $linuximagetab = xCAT::Table->new('linuximage', -create => 1); } - (my $ref1) = $linuximagetab->getAttribs({ imagename => $imagename }, 'rootimgdir', 'nodebootif', 'dump', 'crashkernelsize', 'partitionfile'); + (my $ref1) = $linuximagetab->getAttribs({ imagename => $imagename }, 'rootimgdir', 'nodebootif', 'dump', 'crashkernelsize', 'partitionfile', 'rootimgchecksum'); if (($ref1) && ($ref1->{'rootimgdir'})) { $img_hash{$imagename}->{rootimgdir} = $ref1->{'rootimgdir'}; } + $img_hash{$imagename}->{rootimgchecksum} = $ref1->{'rootimgchecksum'} if $ref1; if (($ref1) && ($ref1->{'nodebootif'})) { $img_hash{$imagename}->{nodebootif} = $ref1->{'nodebootif'}; } @@ -237,6 +240,7 @@ sub mknetboot $dump = $ph->{dump}; $crashkernelsize = $ph->{crashkernelsize}; $cfgpart = $ph->{'cfgpart'}; + $rootimgchecksum = $ph->{rootimgchecksum}; $rootimgdir = $ph->{rootimgdir}; unless ($rootimgdir) { @@ -287,10 +291,11 @@ sub mknetboot $linuximagetab = xCAT::Table->new('linuximage'); } if ($linuximagetab) { - (my $ref1) = $linuximagetab->getAttribs({ imagename => $imgname }, 'dump', 'crashkernelsize', 'partitionfile'); + (my $ref1) = $linuximagetab->getAttribs({ imagename => $imgname }, 'dump', 'crashkernelsize', 'partitionfile', 'rootimgchecksum'); if ($ref1 && $ref1->{'dump'}) { $dump = $ref1->{'dump'}; } + $rootimgchecksum = $ref1->{'rootimgchecksum'} if $ref1; if ($ref1 and $ref1->{'crashkernelsize'}) { $crashkernelsize = $ref1->{'crashkernelsize'}; } @@ -408,34 +413,45 @@ sub mknetboot # Copy the boot resource to /tftpboot and check to only copy once - my $docopy = 0; my $tftppath; my $rtftppath; # the relative tftp path without /tftpboot/ + my $copykey; if ($imagename) { $tftppath = "$tftpdir/xcat/osimage/$imagename"; $rtftppath = "xcat/osimage/$imagename"; - unless ($donetftp{$imagename}) { - $docopy = 1; - $donetftp{$imagename} = 1; - } + $copykey = $imagename; } else { $tftppath = "/$tftpdir/xcat/netboot/$osver/$arch/$profile/"; $rtftppath = "xcat/netboot/$osver/$arch/$profile/"; - unless ($donetftp{ $osver, $arch, $profile, $tftpdir }) { - $docopy = 1; - $donetftp{ $osver, $arch, $profile, $tftpdir } = 1; + $copykey = join($;, $osver, $arch, $profile, $tftpdir); + } + my $docopy = !$donetftp{$copykey}; + + # This runs before the boot files are copied, so a failure keeps the boot files that the node uses now. + if (!$statelite || $rootfstype eq "ramdisk") { + my $error = xCAT::SvrUtils->prepare_rootimg_checksum(value => $rootimgchecksum, rootimgdir => $rootimgdir, + imagename => $imagename, warned => \%rootimg_warned, callback => $callback); + if ($error) { + xCAT::MsgUtils->report_node_error($callback, $node, $error); + next; } } + my $copied = 1; if ($docopy && !$noupdateinitrd) { mkpath("$tftppath"); - copy("$rootimgdir/kernel", "$tftppath"); + $copied &&= copy("$rootimgdir/kernel", "$tftppath"); if ($statelite) { - copy("$rootimgdir/initrd-statelite.gz", "$tftppath"); + $copied &&= copy("$rootimgdir/initrd-statelite.gz", "$tftppath"); } else { - copy("$rootimgdir/initrd-stateless.gz", "$tftppath"); + $copied &&= copy("$rootimgdir/initrd-stateless.gz", "$tftppath"); } } + unless ($copied) { + xCAT::MsgUtils->report_node_error($callback, $node, qq{Copying to $tftppath failed: $!}); + next; + } + $donetftp{$copykey} = 1; if ($statelite) { unless (-r "$tftppath/kernel" and -r "$tftppath/initrd-statelite.gz") { @@ -450,6 +466,13 @@ sub mknetboot } } + # Nodes boot the initial ramdisk that is in TFTP now, so that copy decides the check. + my $checksum_arg = ''; + if (!$statelite || $rootfstype eq "ramdisk") { + $checksum_arg = xCAT::SvrUtils->rootimg_checksum_boot(value => $rootimgchecksum, rootimgdir => $rootimgdir, + initrd => "$tftppath/initrd-stateless.gz", imagename => $imagename, warned => \%rootimg_warned, callback => $callback); + } + # TODO: move the table operations out of the foreach loop my $hmtab = xCAT::Table->new('nodehm'); my $sent = @@ -509,6 +532,7 @@ sub mknetboot } } else { $kcmdline = "imgurl=$httpmethod://$imgsrv:$httpport/$rootimgdir/rootimg-statelite.gz STATEMNT="; + $kcmdline = $checksum_arg . $kcmdline; } # add support for subVars in the value of "statemnt" @@ -562,6 +586,7 @@ sub mknetboot { $kcmdline = "imgurl=$httpmethod://$imgsrv:$httpport/$rootimgdir/$compressedrootimg "; + $kcmdline .= $checksum_arg; } $kcmdline .= "XCAT=$xcatmaster:$xcatdport "; $kcmdline .= " XCATHTTPPORT=$httpport "; @@ -2788,4 +2813,5 @@ sub insert_dd () { # return ""; #} + 1; diff --git a/xCAT-server/share/xcat/netboot/rh/genimage b/xCAT-server/share/xcat/netboot/rh/genimage index 74d88fa02..1afc5d325 100755 --- a/xCAT-server/share/xcat/netboot/rh/genimage +++ b/xCAT-server/share/xcat/netboot/rh/genimage @@ -1304,11 +1304,21 @@ sub mkinitrd_dracut { "$rootimg_dir/tmp/initrd.$$.gz", $mode); } or xdie($@); print "the initial ramdisk for $mode is generated successfully.\n"; + mark_rootimg_check($mode); return; } print "the initial ramdisk for $mode is generated successfully.\n"; print("Move $rootimg_dir/tmp/initrd.$$.gz -> $destdir/initrd-$mode.gz"); - move("$rootimg_dir/tmp/initrd.$$.gz", "$destdir/initrd-$mode.gz"); + move("$rootimg_dir/tmp/initrd.$$.gz", "$destdir/initrd-$mode.gz") and mark_rootimg_check($mode); +} + +# nodeset warns unless this marker says that the stateless initial ramdisk checks the root image checksum. +sub mark_rootimg_check { + my ($mode) = @_; + return unless $mode eq 'stateless' && $dracutdir =~ /^dracut_(?:033|047|105)$/x; + require xCAT::SvrUtils; + my $error = xCAT::SvrUtils->write_initrd_check_marker("$destdir/initrd-$mode.gz"); + print "Warning: $error\n" if $error; } sub mkinitrd { diff --git a/xCAT-server/share/xcat/netboot/sles/genimage b/xCAT-server/share/xcat/netboot/sles/genimage index 5c7e62e7c..e6475a64f 100755 --- a/xCAT-server/share/xcat/netboot/sles/genimage +++ b/xCAT-server/share/xcat/netboot/sles/genimage @@ -1178,10 +1178,19 @@ sub mkinitrd_dracut { !system("chroot $rootimg_dir dracut $additional_options -f /tmp/initrd.$$.gz $kernelver") or die("Error: failed to generate the initial ramdisk for $mode.\n"); print "the initial ramdisk for $mode is generated successfully.\n"; - move("$rootimg_dir/tmp/initrd.$$.gz", "$destdir/initrd-$mode.gz"); + move("$rootimg_dir/tmp/initrd.$$.gz", "$destdir/initrd-$mode.gz") and mark_rootimg_check($mode); } +# nodeset warns unless this marker says that the stateless initial ramdisk checks the root image checksum. +sub mark_rootimg_check { + my ($mode) = @_; + return unless $mode eq 'stateless' && $dracutdir eq 'dracut_033'; + require xCAT::SvrUtils; + my $error = xCAT::SvrUtils->write_initrd_check_marker("$destdir/initrd-$mode.gz"); + print "Warning: $error\n" if $error; +} + sub mkinitrd { my ($mode) = @_; # statelite or stateless diff --git a/xCAT-server/share/xcat/netboot/ubuntu/genimage b/xCAT-server/share/xcat/netboot/ubuntu/genimage index 970a97d59..34f681ac3 100755 --- a/xCAT-server/share/xcat/netboot/ubuntu/genimage +++ b/xCAT-server/share/xcat/netboot/ubuntu/genimage @@ -1060,7 +1060,16 @@ sub mkinitrd_dracut { !system("chroot $rootimg_dir dracut -f /tmp/initrd.$$.gz $kernelver") or die("Error: failed to generate the initial ramdisk for $mode.\n"); print "the initial ramdisk for $mode is generated successfully.\n"; - move("$rootimg_dir/tmp/initrd.$$.gz", "$destdir/initrd-$mode.gz"); + move("$rootimg_dir/tmp/initrd.$$.gz", "$destdir/initrd-$mode.gz") and mark_rootimg_check($mode); +} + +# nodeset warns unless this marker says that the stateless initial ramdisk checks the root image checksum. +sub mark_rootimg_check { + my ($mode) = @_; + return unless $mode eq 'stateless' && $dracutdir eq 'dracut'; + require xCAT::SvrUtils; + my $error = xCAT::SvrUtils->write_initrd_check_marker("$destdir/initrd-$mode.gz"); + print "Warning: $error\n" if $error; } sub mkinitrd {