如何在设备之间复制(可启动的)Linux 发行版?

如何在设备之间复制(可启动的)Linux 发行版?

如何在设备之间复制(可启动的)Linux 发行版?这很有用,例如,如果我有一个运行 Linux 的可启动分区(可能但不一定运行),而我现在想将其复制到 USB 记忆棒。

我写了一个脚本,它有一些安全措施,然后执行必要的步骤。我会把它作为答案发布。脚本中最大的限制是发行版必须基于 syslinux。显然,这个脚本并不适合所有人。我也不是这方面的专家。我试图拼凑一个可行的解决方案为我. 这个想法是为了帮助那些正在努力创造类似或更好的东西的人。

答案1

虽然该脚本看起来很长,但其中大部分内容都是为了确保您没有做一些愚蠢的事情(例如尝试写入您自己的内置硬盘)。该脚本的主要用处在于它可以提供一个必要步骤的工作示例(截至 2014 年 12 月)...至少在我的 Linux 系统上。它完全免费且可以更改。

#!/usr/bin/perl -w
use strict;
use warnings FATAL => qw{ uninitialized };

=pod

=head1 Title

  distrocopy.pl -- copy a syslinux-loader based linux distribution from one device (e.g., a HD) to another (e.g., USB flash)

=head1 Options

  -f   actually execute.  without it, we are just checking all the requirements
  -rw  allow running this script even when the root partition is not mounted rw
  -unremovable   allow writing to an unremovable drive (protects from the worst error!)

=head1 Description

  This script can copy a syslinux-based linux distribution from one device to another and make it bootable.

  However, we are making some assumptions:

=over 4

=item other non-root /etc/fstab partitions will be available after boot.  this is good if your /home partition sits on another device.  if your home partition sits on the same device, then you need to hand copy it (cp -a ...).

=item the root distribution is syslinux based.  we need it to find the scripts that come with syslinux.  note that you should be able to run this script while booted from a ubuntu grub partition when you want to copy a syslinux-based distribution from one external USB drive to another.  you can also boot from a syslinux-based distribution and use the script.

=item we are creating one giant ext4 partition on the destination device.  if you want it smaller, use the ext-tools to shrink it later.

=back


=head1 Bugs

  many.

  IMHO, even syslinux is a mess, but it is a lesser mess than grub.  why can't we simply have a loader that passes control to just one file?
     # mkbootable /dev/sdb /dev/sdb2
  this would alter the boot sector on sdb, so that it passes control to whatever linux distro lives on sdb2
     # mkbootable /dev/sdc 8538185f-e75c-4e43-823e-43cfb3d14774
  this would alter the boot sector on sdc, so that it passes control to whatever linux distro lives on this uuid

  and then
     # whatboots /dev/sdb
     MBR, will boot linux from /dev/sdb2.  checks confirm boot-startup


=head1 License

  Totally free.  Please improve.

=head1 Versions

  0.00: Ivo Welch, December 2014.

=cut

################################################################

my $usage= "Usage: $0 [-f] [-rw] /mounted-root-of-distro /dev/sd[a-z]";

($#ARGV >= 0) or die "Too few arguments: $#ARGV\n$usage";

my $testrun = 1;
if ($ARGV[0] eq "-f") { shift(@ARGV); $testrun= 0; print "\nACTUAL RUN\n"; } else { print "[test run only]\n"; }
my $allowrw = 0;
if ($ARGV[0] eq "-rw") { shift(@ARGV); $allowrw= 1; print "Work on rw mounted source\n"; }
my $allowunremovable = 0;
if ($ARGV[0] eq "-unremovable") { shift(@ARGV); $allowunremovable= 1; print "Allowing on unremovable destination\n"; }

($#ARGV == 1) or die "There must be exactly two further arguments, not ".($#ARGV+1)."\n$usage";

my ($src, $devdest) = @ARGV;

my $mounts= `mount | grep \/dev\/sd`."\n".`mount | grep \/dev\/mmcblk`;  ## ok, so I lied.  I am prepping for mmc cards here

################################################################################################################################

print "
################ extensive checks of the source directory
";
($src =~ /^\//) or die "root device '$src' must start with \/\n";
($src =~ /^\/dev\//) and die "root device '$src' must not be a device file, but a mounted partition.\n";
(-e "$src/boot") or die "root device '$src' must have boot directory.\n";
# could check that /boot lives on the same device
(-e "$src/bin") or die "root device '$src' must have bin directory.\n";
(-e "$src/etc/fstab") or die "root device '$src' must have etc/fstab file.\n";
# could warn if it references other partitions, esp on the same device
(-e "$src/boot/syslinux/syslinux.cfg") or die "root device '$src' must have syslinux.cfg in install.\n";
# (-e "/usr/bin/bash") or die "/usr/bin/syslinux-install_update is buggy.  pls link /bin/bash to /usr/bin/bash";
$src =~ s/([a-z0-9])\/$/$1/;  ## delete trailing /

my @srcmount= grep(/$src/, split(/\n/, $mounts));
($#srcmount == 0) or die "I do not understand the source mount output\n";
my $matchcount = ($srcmount[0] =~ /([^ ]+) on ([^ ]+) type ([^ ]+) \(([^ ]+)\)/);
($matchcount) or die "I cannot parse the source mount output in ".($srcmount[0])."\n";
my $sourcedevice = $1;
my $sourcemountloc = $2;
($sourcemountloc eq $src) or die "root device '$src' must be a first-order mount point in:\n$mounts\n";
my $filesystemtype = $3;
my $features = $4;  ## things like ro
my $srcuuid = `blkid -s UUID -o value $sourcedevice`;
chomp($srcuuid);

if (!($allowrw)) {
  ($features =~ /ro/) or die "source file system '$src' was not mounted ro, and no -rw switch used\n";
}
(($filesystemtype =~ /ext[2-4]/)||($filesystemtype =~ /btrfs/)) or die "source file system '$src' was not ext[2-4] or btrfs\n";



print "
################ check the destination directory
";

(($devdest =~ /^\/dev\/sd[a-z]$/) || ($devdest =~ /^\/dev\/mmcblk[0-9]$/))
  or die "dest device '$devdest' must be a device file in /dev/sdX format.\n";
($mounts =~ /$devdest/sg) and die "dest device '$devdest' must not have any mounts active:\n\n$mounts\n\n";

(my $destsysblock= $devdest) =~ s/\/dev\//\/sys\/block\//;

if (!($allowunremovable)) {
  my $isremovable = `udevadm info -a -p $destsysblock | grep -i ATTR{removable}`;
  ($isremovable =~ /ATTR{removable}=="1"/) or
    die "Sorry, but device '$destsysblock' is not removable\n";
}


print "
################ check available programs
";

## we could search some for these instead of assuming they are here.
my @needed= ("/bin/dd", "/sbin/sfdisk", "/sbin/mkfs.ext4", "/bin/mount", "/usr/bin/rsync",
         "$src/usr/bin/syslinux-install_update", "/bin/bash", "$src/usr/bin/extlinux", "/bin/umount");

foreach (@needed) { (-e $_) or die "Sorry, but I refuse to work without '$_'\n"; }


print "[validity tests passed]\n";


($testrun) and die "now run with -f flag to actually execute it.\n";


################################################################
### this is the business end of the script
################################################################

## we will try to do this early.  if it fails, we state that this command cannot be run.
my $tempmntloc= "/tmp/writermount";
(-d $tempmntloc) or mkdir( $tempmntloc );  ## could already exist.  we don't care.
(-d $tempmntloc) or die "cannot make or see $tempmntloc ";


print "
################ partition the drive
";


## zero out old partition table and more: 512 bytes
system0("/bin/dd if=/dev/zero of=$devdest bs=4096 count=1024", "cannot dd $devdest --- do you have permission? $!\n");

print "[Crossed the Rubicon] I have destroyed the boot sector in $devdest and am writing a new one.\n";

open(my $SFD, "| /sbin/sfdisk -L $devdest > /dev/null") or die "cannot write to sfdisk: $!\n";
print $SFD ";\n";
close($SFD);

system0("/sbin/sfdisk -L -A1 ${devdest} > /dev/null", "cannot mark my only partition as active.\n");


print "
################ format the partition
";

system0("/sbin/mkfs.ext4 ${devdest}1", "cannot make a file system on ${devdest}1: $!\n");

system0("/bin/mount ${devdest}1 $tempmntloc", "cannot mount new file system: $!\n");

(-d "$tempmntloc/lost+found/") or die "cannot see '$tempmntloc/lost+found/'\n";


print "
################ copy the partition.  note the trailing slash on src
";

system0("/usr/bin/rsync -aAX $src/ $tempmntloc --exclude={${src}/dev/*,${src}/proc/*,${src}/sys/*,${src}/tmp/*,${src}/run/*,${src}/mnt/*,${src}/media/*,${src}/lost+found,${src}/home/*/.gvfs,/mnt/*}", "cannot copy device partition contents.  do you have enough space?? $!\n");

my $destuuid = `blkid -s UUID -o value  ${devdest}1`;
chomp($destuuid);


print "
################ update the partition
";

replaceinfile("$tempmntloc/etc/fstab", $srcuuid, $destuuid);
replaceinfile("$tempmntloc/boot/syslinux/syslinux.cfg", $srcuuid, $destuuid);


print "
################ install bootloader
";

my $syscmd1= "$tempmntloc/usr/bin/syslinux-install_update -i -a -m -c $tempmntloc";
print "exec: $syscmd1 --- please ignore error\n";
system($syscmd1);  # this can give an error.  ignore it

my $syscmd2= "$tempmntloc/usr/bin/extlinux --install $tempmntloc/boot/syslinux";
system0($syscmd2, "cannot exec syslinux 2 install to boot\n");


print "
################ unmount and finish
";

open(my $FOUT, ">", "$tempmntloc/$0.log") or die "cannot open '$tempmntloc/$0.log' for write\n";
print $FOUT "Written by $0 on `date`\n";
close($FOUT);

system0("/bin/umount $tempmntloc", "failed umounting $tempmntloc\n");

print "written a new bootable distro copy to $devdest.\n";





################################################################
sub replaceinfile {
  my ($fname, $instr, $ostr)= @_;
  (-r $fname) or die "cannot open $fname for read: $!\n";
  rename( $fname, "$fname.old" ) or die "cannot rename file $fname to $fname.old: $!\n";
  open(my $FIN, "<", "$fname.old") or die "cannot open '$fname.old' again: $!\n";
  open(my $FOUT, ">", $fname) or die "cannot open $fname for write: $!\n";
  my $success=0;
  while (<$FIN>) {
    (s/$instr/$ostr/) and $success=1;
    print $FOUT $_;
  }
  close($FIN);
  close($FOUT);
  ($success) or die "Sorry, but I did not find string '$instr' in file '$fname'\n";
  return "ok";
}

sub system0 {
  print "\nsystem running '$_[0]'\n";
  (system($_[0])==0) or die "failed: $_[1]\n";
}

相关内容