Linux kernel packages for "squeeze" will use libata-based drivers in preference to old-style IDE drivers. On some systems this will change the names of PATA devices and will also change the names of SCSI devices already present in those systems if they are enumerated after the PATA devices.
On upgrade, we will recommend that users identify hard disk volumes in configuration files by label or UUID (unique identifier) rather than by device name, which will work with both old and new kernel versions. I think that in most cases it should be possible to update the system configuration automatically, if the user agrees to this. The following script implements this. Please review and verify that it does the right thing for your package's configuration file(s). (I would also welcome a more general review of the code.) For optical disc drives, it may be possible to update udev naming rules in /etc/udev/rules.d/70-persistent-cd.rules; I have not tried to implement this yet. For tape drives, I have no suggestions (in fact I have little idea which packages would use tape drive names) but hopefully the affected users are competent to update the configuration themselves. Ben. #!/usr/bin/perl # Copyright 2009 Ben Hutchings # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA use strict; use warnings; use Debconf::Client::ConfModule ':all'; use FileHandle; use POSIX (); ### utility sub id_to_path { my ($id) = @_; $id =~ m|^/| or $id =~ s{^(LABEL|UUID)=}{'/dev/disk/by-' . lc($1) . '/'}x or die "Could not map id $id to path"; return $id; } ### /etc/fstab sub fstab_next { # Based on my_getmntent() in mount_mntent.c my ($file) = @_; my $text = <$file>; unless (defined($text)) { return (); } my $line = $text; $line =~ s/\r?\n$//; $line =~ s/^[ \t]*//; if ($line =~ /^(#|$)/) { return ($text); } else { return ($text, map({ s/\\([0-7][0-7][0-7])/chr(oct($1) & 0xff)/eg; $_; } split(/[ \t]+/, $line))); } } sub fstab_list { my ($file) = @_; my @bdevs; while (1) { my ($text, $bdev) = fstab_next($file); last unless defined($text); if (defined($bdev)) { push @bdevs, $bdev; } } return @bdevs; } sub fstab_update { my ($old, $new, $map) = @_; while (1) { my ($text, $bdev) = fstab_next($old); last unless defined($text); if (defined($bdev) && defined(my $id = $map->{$bdev})) { $text =~ s/^(\s*\S+)(.*)/# $1$2\n$id$2/; } $new->print("$text"); } } ### Kernel parameters sub kernel_list { my ($cmd_line) = @_; return ($cmd_line =~ /\broot=(\S+)/) ? ($1) : (); } sub kernel_update { my ($cmd_line, $map) = @_; if ($cmd_line =~ /\broot=(\S+)/ && defined(my $id = $map->{$1})) { $cmd_line =~ s/\broot=(\S+)/root=$id/; return $cmd_line; } else { return undef; } } ### shell script variable assignment # Maintains enough context to find statement boundaries, and can parse # variable definitions that do not include substitutions. I think. sub shellvars_next { my ($file) = @_; my $text = ''; my @context = (''); my $first = 1; my $in_value = 0; my ($name, $value); my $unhandled = 0; LINE: while (<$file>) { $text .= $_; # variable assignment if ($first && m/^\s*([A-Za-z_][A-Za-z0-9_]*)=/g) { $name = $1; $value = ''; $in_value = 1; } while (/\G(.*?)([#`'"(){}\s]|\\.|\$[({]?)/gs) { my $end_pos = pos; my $special = $2; if ($in_value) { # add non-special characters to the value verbatim $value .= $1; } if ($context[$#context] eq '') { # space outside quotes or brackets ends the value if ($special =~ /^\s/) { $in_value = 0; if ($special eq "\n") { last LINE; } } # something else after the value means this is a command # with an environment override, not a variable definition elsif (defined($name) && !$in_value) { $unhandled = 1; } } # in single-quoted string if ($context[$#context] eq "'") { # only the terminating single-quote is special if ($special eq "'") { pop @context; } else { $value .= $special; } } # backslash escape elsif ($special =~ /^\\/) { if ($in_value && $special ne "\\\n") { $value .= substr($special, 1, 1); } } # in backtick substitution elsif ($context[$#context] eq '`') { # backtick does not participate in nesting, so only the # terminating backtick should be considered special if ($special eq '`') { pop @context; } } # comment elsif ($context[$#context] !~ /^['"]/ && $special eq '#') { # ignore rest of the physical line, except the new-line pos = $end_pos; /\G.*/g; next; } # start of backtick substitution elsif ($special eq '`') { push @context, '`'; $unhandled = 1; } # start of single/double-quoted string elsif ($special =~ /^['"]/ && $context[$#context] !~ /^['"]/) { push @context, $special; } # end of double-quoted string elsif ($special eq '"' && $context[$#context] eq '"') { pop @context; } # open bracket elsif ($special =~ /^\$?\(/) { push @context, ')'; $unhandled = 1; } elsif ($special =~ /^\$\{/) { push @context, '}'; $unhandled = 1; } # close bracket elsif ($special =~ /^[)}]/ && $special eq $context[$#context]) { pop @context; } # variable substitution elsif ($special eq '$') { $unhandled = 1; } # not a special character in this context (or a syntax error) else { if ($in_value) { $value .= $special; } } pos = $end_pos; } $first = 0; } if ($text eq '') { return (); } elsif ($unhandled) { return ($text); } else { return ($text, $name, $value); } } sub shellvars_quote { my ($value) = @_; $value =~ s/'/'\''/g; return "'$value'"; } ### GRUB 1 (grub-legacy) config sub grub1_parse { my ($file) = @_; my @results = (); my $text = ''; my $in_auto = 0; my $in_opts = 0; while (<$file>) { if ($in_opts && /^\# (\w+)=(.*)/) { push @results, [$text]; $text = ''; push @results, [$_, $1, $2]; } else { $text .= $_; if ($_ eq "### BEGIN AUTOMAGIC KERNELS LIST\n") { $in_auto = 1; } elsif ($_ eq "### END DEBIAN AUTOMAGIC KERNELS LIST\n") { $in_auto = 0; } elsif ($_ eq "## ## Start Default Options ##\n") { $in_opts = $in_auto; } elsif ($_ eq "## ## End Default Options ##\n") { $in_opts = 0; } } } if ($text ne '') { push @results, [$text]; } return @results; } sub grub1_list { my ($file) = @_; my %options; for (grub1_parse($file)) { my ($text, $name, $value) = @$_; next unless defined($name); $options{$name} = $value; } my @bdevs; if (exists($options{kopt_2_6})) { push @bdevs, kernel_list($options{kopt_2_6}); } elsif (exists($options{kopt})) { push @bdevs, kernel_list($options{kopt}); } if (exists($options{xenkopt})) { push @bdevs, kernel_list($options{xenkopt}); } return @bdevs; } sub grub1_update { my ($old, $new, $map) = @_; my %options; for (grub1_parse($old)) { my ($text, $name, $value) = @$_; next unless defined($name); $options{$name} = $value; } $old->seek(0, 0); for (grub1_parse($old)) { my ($text, $name, $value) = @$_; next unless defined($name); if ($name eq 'kopt_2_6' || ($name eq 'kopt' && !exists($options{kopt_2_6})) || $name eq 'xenkopt') { if (defined(my $new_value = kernel_update($value))) { $text = "## $name=$value\n# $name=$new_value\n"; } } $new->print($text); } } sub grub1_post { system('update-grub'); } ### GRUB 2 config sub grub2_list { my ($file) = @_; my @bdevs; while (1) { my ($text, $name, $value) = shellvars_next($file); last unless defined($text); if (defined($name) && $name =~ /^GRUB_CMDLINE_LINUX(?:_DEFAULT)?$/) { push @bdevs, kernel_list($value); } } return @bdevs; } sub grub2_update { my ($old, $new, $map) = @_; my @bdevs; while (1) { my ($text, $name, $value) = shellvars_next($old); last unless defined($text); if (defined($name) && $name =~ /^GRUB_CMDLINE_LINUX(?:_DEFAULT)?$/ && defined(my $new_value = kernel_update($value, $map))) { $text =~ s/^/# /gm; $text .= sprintf("%s=%s\n", $name, shellvars_quote($new_value)); } $new->print($text); } } sub grub2_post { system('grub-mkconfig', '-o', '/boot/grub/grub.cfg'); } ### LILO sub lilo_tokenize { # Based on cfg_get_token() and next() in cfg.c. # Line boundaries are *not* significant (except as white space) so # we tokenize the whole file at once. my ($file) = @_; my @tokens = (); my $text = ''; my $token; my $in_quote = 0; while (<$file>) { # If this is the continuation of a multi-line quote, skip # leading space and push back the necessary context. if ($in_quote) { s/^[ \t]*/"/; $text .= $&; } pos = 0; while (/\G \s* (?:\#.*)? (?: (=) | " ((?:[^"] | \\[\\"n])*) (" | \\\r?\n) | ((?:[^\s\#="\\] | \\[^\r\n])+) (\\\r?\n)?)? /gsx) { my $cont; my $new_text = $&; if (defined($1)) { # equals sign $text = $new_text; $token = $1; $cont = 0; } elsif (defined($2)) { # quoted text if (!$in_quote) { $text = $new_text; $token = $2; } else { $text .= substr($new_text, 1); # remove the quote again; ick $token .= ' ' . $2; } $cont = $3 ne '"'; } elsif (defined($4)) { # unquoted word if (!defined($token)) { $token = ''; } $text .= $new_text; $token .= $4; $cont = defined($5); } else { $text .= $new_text; $cont = $new_text eq ''; } if (!$cont) { if ($text =~ /(?:^|[^\\])\$/) { # unhandled expansion $token = undef; } elsif (defined($token)) { if ($in_quote) { $token =~ s/\\(.)/$1 eq 'n' ? "\n" : $1/eg; } else { $token =~ s/\\(.)/$1/g; } } push @tokens, [$text, $token]; $text = ''; $token = undef; $in_quote = 0; } } } return @tokens; } sub lilo_list { my ($file) = @_; my @bdevs = (); my @tokens = lilo_tokenize($file); my $i = 0; my $in_generic = 1; # global or image=/vmlinuz or image=/vmlinuz.old while ($i <= $#tokens) { # Configuration items are either <name> "=" <value> or <name> alone. if ($#tokens - $i >= 2 && defined($tokens[$i + 1][1]) && $tokens[$i + 1][1] eq '=') { my ($name, $value) = ($tokens[$i][1], $tokens[$i + 2][1]); if (defined($name) && defined($value)) { if ($name eq 'image') { $in_generic = ($value =~ m|^/vmlinuz(?:\.old)?$|); next; } if (!$in_generic) { next; } if ($name =~ /^(?:boot|root)$/) { push @bdevs, $value; } elsif ($name =~ /^(?:addappend|append|literal)$/) { push @bdevs, kernel_list($value); } } $i += 3; } else { $i += 1; } } return @bdevs; } sub lilo_update { my ($old, $new, $map) = @_; my @tokens = lilo_tokenize($old); my $i = 0; my $in_generic = 1; # global or image=/vmlinuz or image=/vmlinuz.old while ($i <= $#tokens) { my $text = $tokens[$i][0]; if ($#tokens - $i >= 2 && defined($tokens[$i + 1][1]) && $tokens[$i + 1][1] eq '=') { my ($name, $value) = ($tokens[$i][1], $tokens[$i + 2][1]); my $new_value; if (defined($name) && defined($value)) { if ($name eq 'image') { $in_generic = ($value =~ m|^/vmlinuz(?:\.old)?$|); next; } if (!$in_generic) { next; } if ($name eq 'boot') { # 'boot' is used directly by the lilo command, which # doesn't use libblkid $new_value = $map->{$value} && id_to_path($map->{$value}); } elsif ($name eq 'root') { # 'root' adds a root parameter to the kernel command # line $new_value = $map->{$value}; } elsif ($name =~ /^(?:addappend|append|literal)$/) { # These are all destined for the kernel command line # in some way $new_value = kernel_update($value, $map); } } if (defined($new_value)) { $text = "\n# $name = $value\n$name = $new_value\n"; } else { $text .= $tokens[$i + 1][0] . $tokens[$i + 2][0]; } $i += 3; } else { $i += 1; } $new->print($text); } } sub lilo_post { system('lilo'); } ### ELILO sub elilo_post { system('elilo'); } ### PALO sub palo_next { my ($file, $expect_opt) = @_; my $text = <$file>; if (!defined($text) || $text eq '') { return (); } my $arg = $text; $arg =~ s/^\s*(?:#.*)?//s; $arg =~ s/\s*$//; # I would like to use Getopt::Long but it would make it # impossible to determine which source text to replace. if ($expect_opt && $arg =~ /^-(?!-)[?v]*(.)(.+)?$/) { return ($text, "-$1", $2, defined($2)); } elsif ($expect_opt && $arg =~ /^(--[^=]+)(?:=(.*))?$/) { return ($text, $1, $2, defined($2)); } elsif ($arg ne '') { return ($text, undef, $arg, 1); } else { return ($text, undef, undef, $expect_opt); } } sub palo_list { my ($file) = @_; my $optopt; my @bdevs; while (1) { my ($text, $optarg, $complete); if (defined($optopt)) { ($text, undef, $optarg, $complete) = palo_next($file, 0); } else { ($text, $optopt, $optarg, $complete) = palo_next($file, 1); } last unless defined($text); if ($complete && defined($optopt)) { if ($optopt eq '-c' || $optopt eq '--commandline') { # If PALO is not configured to use the generic sym-link, # ignore it if ($optarg !~ m|^\d+/vmlinux\b|) { return (); } push @bdevs, kernel_list($optarg); } elsif ($optopt eq '-I' || $optopt eq '--init-partitioned') { push @bdevs, $optarg; } $optopt = undef; } if (!defined($optopt) && defined($optarg) && $optarg eq '--') { last; } } return @bdevs; } sub palo_update { my ($old, $new, $map) = @_; my $optopt; my $allow_opts = 1; while (1) { my ($text, $optarg, $complete); if (defined($optopt)) { ($text, undef, $optarg, $complete) = palo_next($old, 0); } else { ($text, $optopt, $optarg, $complete) = palo_next($old, $allow_opts); } last unless defined($text); if (defined($optopt)) { if ($optopt eq '-c' || $optopt eq '--commandline') { $text = "# $text"; if ($complete) { my $new_cmdline = kernel_update($optarg, $map); if (!defined($new_cmdline)) { $new_cmdline = $optarg; } $text .= "--commandline=$new_cmdline\n"; } } $optopt = undef; } $new->print($text); if (!defined($optopt) && defined($optarg) && $optarg eq '--') { $allow_opts = 0; } } } sub palo_post { system('palo'); } ### delo sub delo_next { # Based on getconfig() in config.c my ($file) = @_; my $text = <$file>; if (!defined($text) || $text eq '') { return (); } local $_ = $text; s/[ \t]*(?:#.*)?\n//; s/^[ \t]*//; if (/^([a-z]+)=(.*)$/) { return ($text, $1, $2); } else { return ($text); } } sub delo_sections { my ($file) = @_; my @sections; my $section = {}; while (1) { my ($text, $name, $value) = delo_next($file); # If this is EOF or a new section, finish the current section if (!defined($text) || (defined($name) && $name eq 'label')) { $section->{is_generic} = (exists($section->{image}) && exists($section->{append}) && $section->{image} =~ m|^/vmlinux(?:\.old)?$|); push @sections, $section; $section = {}; } last unless defined($text); if (defined($name)) { if ($name eq 'append') { $value =~ s/^"([^"]*).*/$1/; } $section->{$name} = $value; } } return @sections; } sub delo_list { my ($file) = @_; my ($globals, @entries) = delo_sections($file); my @bdevs; if (exists($globals->{boot})) { push @bdevs, $globals->{boot}; } for my $entry (@entries) { if ($entry->{is_generic}) { push @bdevs, kernel_list($entry->{append}); } } return @bdevs; } sub delo_update { my ($old, $new, $map) = @_; my ($globals, @entries) = delo_sections($old); my $i = -1; $old->seek(0, 0); while (1) { my ($text, $name, $value) = delo_next($old); last unless defined($text); if (defined($name)) { if ($name eq 'label') { ++$i; # next entry } elsif ($name eq 'boot' && $i < 0) { my $new_value = $map->{$value} && id_to_path($map->{$value}); if (defined($new_value)) { $text = "# $text" . "boot=$new_value\n"; } } elsif ($name eq 'append' && $i >= 0 && $entries[$i]->{is_generic}) { my $new_cmdline = kernel_update($value, $map); if (defined($new_cmdline)) { $text = "# $text" . "append=\"$new_cmdline\"\n"; } } } $new->print($text); } } ### extlinux sub extlinux_path { for ('/boot/extlinux', '/boot/boot/exlinux', '/extlinux') { if (-e) { return "$_/options.cfg"; } } return undef; } sub extlinux_list { my ($file) = @_; while (<$file>) { if (/^## ROOT=(.*)/) { return kernel_list($1); } } return (); } sub extlinux_update { my ($old, $new, $map) = @_; while (<$old>) { my $text = $_; if (/^## ROOT=(.*)/) { my $new_params = kernel_update($1, $map); if (defined($new_params)) { $text = "## $text" . "## ROOT=$new_params\n"; } } $new->print($text); } } sub extlinux_post { system('update-extlinux'); } ### aboot sub aboot_next { my ($file) = @_; my $text = <$file>; if (!defined($text) || $text eq '') { return (); } if ($text =~ /^([0-9]):([^ ]*) (.*)/) { return ($text, $1, $2, $3); } else { return ($text); } } sub aboot_list { my ($file) = @_; my @bdevs; while (1) { my ($text, $preset, $kernel, $params) = aboot_next($file); last unless defined($text); if (defined($params) && $kernel =~ m|^\d+/vmlinux(?:\.old)?$|) { push @bdevs, kernel_list($params); } } return @bdevs; } sub aboot_update { my ($old, $new, $map) = @_; while (1) { my ($text, $preset, $kernel, $params) = aboot_next($old); last unless defined($text); if (defined($params) && $kernel =~ m|^\d+/vmlinux(?:\.old)?$|) { my $new_params = kernel_update($params, $map); if (defined($new_params)) { $text = "# $text" . "$preset:$kernel $new_params\n"; } } $new->print($text); } } ### Filesystem relabelling sub ext2_label { my ($bdev, $label) = @_; system('e2label', $bdev, $label) == 0 or die "e2label failed: $?"; } sub jfs_label { my ($bdev, $label) = @_; system('jfs_tune', '-L', $label, $bdev) == 0 or die "jfs_tune failed: $?"; } sub fat_label { my ($bdev, $label) = @_; system('dosfslabel', $bdev, $label) == 0 or die "dosfslabel failed: $?";; } sub ntfs_label { my ($bdev, $label) = @_; system('ntfslabel', $bdev, $label) == 0 or die "ntfslabel failed: $?"; } sub reiserfs_label { my ($bdev, $label) = @_; system('reiserfstune', '--label', $label, $bdev) or die "reiserfstune failed: $?"; } # There is no command to relabel swap, and we mustn't run mkswap if # the partition is already in use. Thankfully the header format is # pretty simple; it starts with this structure: # struct swap_header_v1_2 { # char bootbits[1024]; /* Space for disklabel etc. */ # unsigned int version; # unsigned int last_page; # unsigned int nr_badpages; # unsigned char uuid[16]; # char volume_name[16]; # unsigned int padding[117]; # unsigned int badpages[1]; # }; # and has the signature 'SWAPSPACE2' at the end of the first page. use constant { SWAP_SIGNATURE => 'SWAPSPACE2', SWAP_LABEL_OFFSET => 1052, SWAP_LABEL_LEN => 16 }; sub swap_label { my ($bdev, $label) = @_; my $pagesize = POSIX::sysconf(POSIX::_SC_PAGESIZE) or die "$!"; my ($length, $signature); my $fd = POSIX::open($bdev, POSIX::O_RDWR); defined($fd) or die "$!"; # Check the signature POSIX::lseek($fd, $pagesize - length(SWAP_SIGNATURE), POSIX::SEEK_SET); $length = POSIX::read($fd, $signature, length(SWAP_SIGNATURE)); if (!defined($length) || $signature ne SWAP_SIGNATURE) { POSIX::close($fd); die "swap signature not found on $bdev"; } # Set the label $label = pack('Z' . SWAP_LABEL_LEN, $label); POSIX::lseek($fd, SWAP_LABEL_OFFSET, POSIX::SEEK_SET); $length = POSIX::write($fd, $label, SWAP_LABEL_LEN); if (!defined($length) || $length != SWAP_LABEL_LEN) { my $error = "$!"; POSIX::close($fd); die $error; } POSIX::close($fd); } sub ufs_label { my ($bdev, $label) = @_; system('tunefs.ufs', '-L', $label, $bdev) or die "tunefs.ufs failed: $?"; } sub xfs_label { my ($bdev, $label) = @_; system('xfs_admin', '-L', $label, $bdev) or die "xfs_admin failed: $?"; } my %label_types = (ext2 => { len => 16, relabel => \&ext2_label }, ext3 => { len => 16, relabel => \&ext2_label }, ext4 => { len => 16, relabel => \&ext2_label }, jfs => { len => 16, relabel => \&jfs_label }, msdos => { len => 11, relabel => \&fat_label }, ntfs => { len => 128, relabel => \&ntfs_label }, reiserfs => { len => 16, relabel => \&reiserfs_label }, swap => { len => SWAP_LABEL_LEN, relabel => \&swap_label }, ufs => { len => 32, relabel => \&ufs_label }, vfat => { len => 11, relabel => \&fat_label }, xfs => { len => 12, relabel => \&xfs_label }); ### general my @config_files = ({packages => 'mount', path => '/etc/fstab', list => \&fstab_list, update => \&fstab_update}, {packages => 'grub grub-legacy', path => '/boot/grub/menu.lst', list => \&grub1_list, update => \&grub1_update, post_update => \&grub1_post}, {packages => 'grub-common', path => '/etc/default/grub', list => \&grub2_list, update => \&grub2_update, post_update => \&grub2_post}, {packages => 'lilo', path => '/etc/lilo.conf', list => \&lilo_list, update => \&lilo_update, post_update => \&lilo_post}, {packages => 'silo', path => '/etc/silo.conf', list => \&lilo_list, update => \&lilo_update}, {packages => 'quik', path => '/etc/quik.conf', list => \&lilo_list, update => \&lilo_update}, {packages => 'yaboot', path => '/etc/yaboot.conf', list => \&lilo_list, update => \&lilo_update}, {packages => 'elilo', path => '/etc/elilo.conf', list => \&lilo_list, update => \&lilo_update, post_update => \&elilo_post}, {packages => 'palo', path => '/etc/palo.conf', list => \&palo_list, update => \&palo_update, post_update => \&palo_post}, {packages => 'delo', path => '/etc/delo.conf', list => \&delo_list, update => \&delo_update}, {packages => 'arcboot', path => '/etc/arcboot.conf', list => \&delo_list, update => \&delo_update}, {packages => 'extlinux', path => extlinux_path(), list => \&extlinux_list, update => \&extlinux_update, post_update => \&extlinux_post}, {packages => 'aboot', path => '/etc/aboot.conf', list => \&aboot_list, update => \&aboot_update}); my %bdev_map = (); my @matched_configs = (); my %id_map; sub scan_config_files { # Find all IDE/SCSI disks mentioned in configurations for my $config (@config_files) { # Is the file present? my $path = $config->{path}; if (!defined($path)) { next; } my $file = new FileHandle($path, 'r'); if (!defined($file)) { if ($! == POSIX::ENOENT) { next; } die $!; } # Are any of the related packages wanted or installed? my $wanted = 0; my $installed = 0; my $packages = $config->{packages}; for (`dpkg-query 2>/dev/null --showformat '\${status}\\n' -W $packages`) { $wanted = 1 if /^install /; $installed = 1 if / installed\n$/; } if (!$wanted && !$installed) { next; } my @matched_bdevs = (); for my $bdev (&{$config->{list}}($file)) { if ($bdev =~ m{^/dev/(?:[hs]d[a-z]\d*|s(?:cd|r)\d+)$}) { $bdev_map{$bdev} = {}; push @matched_bdevs, $bdev; } } if (@matched_bdevs) { push @matched_configs, {config => $config, devices => \...@matched_bdevs, installed => $installed}; } } my $fstab = new FileHandle('/etc/fstab', 'r'); while (1) { my ($text, $bdev, $path, $type) = fstab_next($fstab); last unless defined($text); if (defined($type) && exists($bdev_map{$bdev})) { $bdev_map{$bdev}->{path} = $path; $bdev_map{$bdev}->{type} = $type; } } $fstab->close(); } sub add_tag { # Map disks to labels/UUIDs and vice versa. Include all disks in # the reverse mapping so we can detect ambiguity. my ($bdev, $name, $value) = @_; my $id = "$name=$value"; push @{$id_map{$id}}, $bdev; if (exists($bdev_map{$bdev})) { $bdev_map{$bdev}->{$name} = $value; push @{$bdev_map{$bdev}->{ids}}, $id; } } sub scan_devices { for (`blkid -o device`) { chomp; my $bdev = $_; for (`blkid -o udev -s LABEL -s UUID '$bdev'`) { if (/^ID_FS_(LABEL|UUID)_ENC=(.*)\n$/) { add_tag($bdev, $1, $2); } } } # Discard all device ids that are ambiguous. for my $bdev (keys(%bdev_map)) { @{$bdev_map{$bdev}->{ids}} = grep({ $#{$id_map{$_}} == 0 } @{$bdev_map{$bdev}->{ids}}); } } sub assign_labels { my $hostname = (POSIX::uname())[1]; # For all devices that have no alternate device ids, suggest labelling # them based on fstab or just using a generic label. for my $bdev (keys(%bdev_map)) { if ($#{$bdev_map{$bdev}->{ids}} >= 0) { my $id = $bdev_map{$bdev}->{ids}->[0]; } else { my $type = $bdev_map{$bdev}->{type}; if (!exists($label_types{$type})) { next; } my $label_len = $label_types{$type}->{len}; my $label; use bytes; # string lengths are in bytes if (defined($bdev_map{$bdev}->{path})) { # Convert path/type to label; prepend hostname if possible; # append numeric suffix if necessary. my $base; if ($bdev_map{$bdev}->{path} =~ m|^/|) { $base = $bdev_map{$bdev}->{path}; } else { $base = $bdev_map{$bdev}->{type}; } $base =~ s/[^\w]+/-/g; $base =~ s/^-//g; $base =~ s/-$//g; my $n = 0; my $suffix = ''; do { $label = "$hostname-$base$suffix"; if (length($label) > $label_len) { $label = substr($base, 0, $label_len - length($suffix)) . $suffix; } $n++; $suffix = "-$n"; } while (exists($id_map{"LABEL=$label"})); } else { my $n = 0; my $suffix; do { $n++; $suffix = "-$n"; $label = substr($hostname, 0, $label_len - length($suffix)) . $suffix; } while (exists($id_map{"LABEL=$label"})); } add_tag($bdev, 'LABEL', $label); $bdev_map{$bdev}->{relabel} = 1; } } } sub relabel { for my $bdev (keys(%bdev_map)) { my $bdev_info = $bdev_map{$bdev}; if ($bdev_info->{relabel}) { my $relabel = $label_types{$bdev_info->{type}}->{relabel}; &{$relabel}($bdev, $bdev_info->{LABEL}); } } } sub update_config { my %map; for my $bdev (keys(%bdev_map)) { $map{$bdev} = $bdev_map{$bdev}->{ids}->[0]; } for my $match (@matched_configs) { # Generate a new config my $path = $match->{config}->{path}; my $old = new FileHandle($path, 'r'); my $new = new FileHandle("$path.new", POSIX::O_WRONLY | POSIX::O_CREAT, 0600); &{$match->{config}->{update}}($old, $new, \%map); $old->close(); $new->close(); # New config should have same permissions as the old my (undef, undef, $mode, undef, $uid, $gid) = stat($path) or die "$!"; chown($uid, $gid, "$path.new") or die "$!"; chmod($mode & 07777, "$path.new") or die "$!"; # Back up the old config and replace with the new unlink("$path.old"); link($path, "$path.old") or die "$!"; rename("$path.new", $path) or die "$!"; # If the package is installed, run the post-update function if ($match->{installed} && $match->{config}->{post_update}) { &{$match->{config}->{post_update}}(); } } } ### main scan_config_files(); if ($#matched_configs < 0) { exit 0; } my ($question, $answer, $ret, $seen); $question = 'linux-image-2.6-common/disk-id-convert-auto'; ($ret, $seen) = input('high', $question); if ($ret && $ret != 30) { die "Error setting debconf question $question: $seen"; } ($ret, $seen) = go(); if ($ret && $ret != 30) { die "Error asking debconf question $question: $seen"; } ($ret, $answer) = get($question); die "Error retrieving answer for $question: $answer" if $ret; if ($answer eq 'true') { scan_devices(); assign_labels(); $question = 'linux-image-2.6-common/disk-id-convert-plan'; ($ret, $seen) = subst($question, 'relabel', join("\\n", map({sprintf("%s: %s", $_, $bdev_map{$_}->{LABEL})} grep({$bdev_map{$_}->{relabel}} keys(%bdev_map))))); die "Error setting debconf substitutions in $question: $seen" if $ret; ($ret, $seen) = subst($question, 'id_map', join("\\n", map({sprintf("%s: %s", $_, $bdev_map{$_}->{ids}->[0])} grep({...@{$bdev_map{$_}->{ids}}} keys(%bdev_map))))); die "Error setting debconf substitutions in $question: $seen" if $ret; ($ret, $seen) = subst($question, 'files', join(', ', map({$_->{config}->{path}} @matched_configs))); die "Error setting debconf substitutions in $question: $seen" if $ret; ($ret, $seen) = input('high', $question); if ($ret && $ret != 30) { die "Error setting debconf question $question: $seen"; } ($ret, $seen) = go(); if ($ret && $ret != 30) { die "Error asking debconf question $question: $seen"; } ($ret, $answer) = get($question); die "Error retrieving answer for $question: $answer" if $ret; if ($answer ne 'true') { # TODO: go back to the auto/manual question or allow editing the plan } else { relabel(); update_config(); } } my @unconv_files = (); for my $match (@matched_configs) { my @unconv_bdevs = grep({!exists($bdev_map{$_}->{ids}) || @{$bdev_map{$_}->{ids}} == 0} @{$match->{devices}}); if (@unconv_bdevs) { push @unconv_files, sprintf('%s: %s', $match->{config}->{path}, join(', ',@unconv_bdevs)); } } if (@unconv_files) { $question = 'linux-image-2.6-common/disk-id-manual'; ($ret, $seen) = subst($question, 'unconverted', join("\\n", @unconv_files)); die "Error setting debconf substitutions in $question: $seen" if $ret; ($ret, $seen) = input('high', $question); if ($ret && $ret != 30) { die "Error setting debconf note $question: $seen"; } ($ret, $seen) = go(); if ($ret && $ret != 30) { die "Error showing debconf note $question: $seen"; } } --- END --- The debconf templates this refers to are: Template: linux-image-2.6-common/disk-id-convert-auto Type: boolean Default: true Description: Update disk device ids in system configuration? The new Linux kernel version provides different drivers for some IDE/ATA disk controllers. The names of some hard disk, CD-ROM and tape devices may change. . You are recommended to identify disk devices in configuration files by label or UUID (unique identifier) rather than by device name, which will work with both old and new kernel versions. Your system configuration can be updated automatically in most cases. Template: linux-image-2.6-common/disk-id-convert-plan Type: boolean Default: true Description: Apply these configuration changes to disk device ids? These devices will be relabelled: . ${relabel} . These configuration files will be updated: . ${files} . The device ids will be changed as follows: . ${id_map} Template: linux-image-2.6-common/disk-id-manual Type: note Description: Please check these configuration files before rebooting These configuration files still use some device names that may change when using the new kernel: . ${unconverted} --- END --- -- Ben Hutchings Reality is just a crutch for people who can't handle science fiction.
signature.asc
Description: Digital signature