#!/usr/bin/perl # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # use Getopt::Std; use Cwd; use strict; package MDesc; use constant { MDEND => 0x45, MDNODE => 0x4e, MDARC => 0x61, MDDATA => 0x64, MDSTR => 0x73, MDVAL => 0x76, }; sub new { my $class = shift; my $self = {}; $self->{FILE} = undef; $self->{MAJOR} = undef; $self->{MINOR} = undef; $self->{NODE_SEC_SZ} = undef; $self->{NAME_SEC_SZ} = undef; $self->{DATA_SEC_SZ} = undef; $self->{NODES} = undef; $self->{NAMES} = undef; $self->{DATA} = undef; bless($self, $class); return $self; } sub open { my $self = shift; my ($mdhdr, $size); if (@_) { $self->{NAME} = shift; } else { $self->{NAME} = '/dev/mdesc'; } return unless open(MD, "$self->{NAME}"); # Read and parse MD header unless (read(MD, $mdhdr, 16) == 16) { close (MD); return; } ($self->{MAJOR}, $self->{MINOR}, $self->{NODE_SEC_SZ}, $self->{NAME_SEC_SZ}, $self->{DATA_SEC_SZ}) = unpack("nnNNN", $mdhdr); $size = read(MD, $self->{NODES}, $self->{NODE_SEC_SZ}); $size = read(MD, $self->{NAMES}, $self->{NAME_SEC_SZ}); $size = read(MD, $self->{DATA}, $self->{DATA_SEC_SZ}); 1; } # # return hash of given node's information # sub getnode { my ($self, $nodeid) = @_; my ($tag, $name, $namelen, $nameoff, $datalen, $dataoff, %node); ($tag, $namelen, $nameoff, $datalen, $dataoff) = unpack("CCx2NNN", substr($self->{NODES}, $nodeid * 16, 16)); $name = substr($self->{NAMES}, $nameoff, $namelen); %node = (tag => $tag, name => $name, nameid => $nameoff); if ($tag == MDSTR || $tag == MDDATA) { $node{'datalen'} = $datalen; $node{'dataoff'} = $dataoff; } elsif ($tag == MDVAL) { $node{'val'} = ($datalen << 32) | $dataoff; } elsif ($tag == MDARC || $tag == MDNODE) { $node{'idx'} = ($datalen << 32) | $dataoff; } return %node; } # # return hash of given property's information # sub getprop { my ($self, $propid) = @_; my (%node, $tag, %prop); %node = $self->getnode($propid); $tag = $node{'tag'}; %prop = (name => $node{'name'}, tag => $tag); if ($tag == MDSTR) { $prop{'string'} = substr($self->{DATA}, $node{'dataoff'}, $node{'datalen'} - 1); } elsif ($tag == MDARC) { $prop{'arc'} = $node{'idx'}; } elsif ($tag == MDVAL) { $prop{'val'} = $node{'val'}; } elsif ($tag == MDDATA) { $prop{'length'} = $node{'datalen'}; $prop{'offset'} = $node{'dataoff'}; } else { return undef; } return %prop; } # # find name table index of given name # sub findname { my ($self, $name) = @_; my ($idx, $next, $p); for ($idx = 0; $idx < $self->{NAME_SEC_SZ}; $idx = $next + 1) { $next = index($self->{NAMES}, "\0", $idx); $p = substr($self->{NAMES}, $idx, $next - $idx); return $idx if ($p eq $name); } return -1; } # # find given property in node # sub findprop { my ($self, $nodeid, $propname, $type) = @_; my (%node, $nameid); %node = $self->getnode($nodeid); return -1 if ($node{'tag'} != MDNODE); $nameid = $self->findname($propname); return -1 if ($nameid == -1); do { $nodeid++; %node = $self->getnode($nodeid); if ($node{'tag'} == $type && $node{'nameid'} == $nameid) { return $nodeid; } } while ($node{'tag'} != MDEND); return -1; } # # lookup property in node and return its hash # sub lookup { my ($self, $nodeid, $propname, $type) = @_; my ($propid); $propid = $self->findprop($nodeid, $propname, $type); return undef if ($propid == -1); return $self->getprop($propid); } sub scan_node { my ($self, $nodeid, $nameid, $arcid, $ret, $seen) = @_; my (%node); return if ($seen->[$nodeid] == 1); $seen->[$nodeid] = 1; %node = $self->getnode($nodeid); return if ($node{'tag'} != MDNODE); push(@$ret, $nodeid) if ($node{'nameid'} == $nameid); do { $nodeid++; %node = $self->getnode($nodeid); if ($node{'tag'} == MDARC && $node{'nameid'} == $arcid) { $self->scan_node($node{'idx'}, $nameid, $arcid, $ret, $seen); } } while ($node{'tag'} != MDEND); } # # scan dag from 'start' via 'arcname' # return list of nodes named 'nodename' # sub scan { my ($self, $start, $nodename, $arcname) = @_; my ($nameid, $arcid, @ret, @seen); $nameid = $self->findname($nodename); $arcid = $self->findname($arcname); $self->scan_node($start, $nameid, $arcid, \@ret, \@seen); return @ret; } package main; # # 'find' needs to use globals anyway, # so we might as well use the same ones # everywhere # our ($old, $new); our %opts; # # fix path_to_inst # sub fixinst { use File::Copy; my ($oldpat, $newpat); my ($in, $out); $oldpat = '"' . $old . '/'; $newpat = '"' . $new . '/'; $in = "etc/path_to_inst"; $out = "/tmp/path$$"; open(IN, "<", $in) or die "can't open $in\n"; open(OUT, ">", $out) or die "can't open $out\n"; my ($found, $path); # # first pass # see if there are any old paths that need to be re-written # $found = 0; while () { ($path, undef, undef) = split; if ($path =~ /^$oldpat/) { $found = 1; last; } } # return if no old paths found if ($found == 0) { close(IN); close(OUT); unlink $out; return 0; } print "replacing $old with $new in /etc/path_to_inst\n"; # # 2nd pass # substitute new for old # seek(IN, 0, 0); while () { ($path, undef, undef) = split; if ($path =~ /^$oldpat/) { s/$oldpat/$newpat/; } print OUT; } close(IN); close(OUT); if ($opts{v}) { print "path_to_inst changes:\n"; system("/usr/bin/diff", $in, $out); print "\n"; } move $out, $in or die "can't modify $in\n"; return 1; } our $oldpat; sub wanted { my $targ; -l or return; $targ = readlink; if ($targ =~ /$oldpat/) { $targ =~ s/$old/$new/; unlink; symlink $targ, $_; print "symlink $_ changed to $targ\n" if ($opts{v}); } } # # fix symlinks # sub fixdev { use File::Find; $oldpat = "/devices" . $old; print "updating /dev symlinks\n"; find \&wanted, "dev"; } # # fixup path_to_inst and /dev symlinks # sub fixup { # setup globals ($old, $new) = @_; # if fixinst finds no matches, no need to run fixdev return if (fixinst == 0); fixdev; print "\n" if ($opts{v}); } # # remove caches # sub rmcache { unlink "etc/devices/devid_cache"; unlink "etc/devices/devname_cache"; unlink ; unlink "etc/devices/retire_store"; unlink "etc/devices/snapshot_cache"; unlink "dev/.devlink_db"; } # $< == 0 or die "$0: must be run as root\n"; getopts("vR:", \%opts); if ($opts{R}) { chdir $opts{R} or die "can't chdir to $opts{R}\n"; } cwd() ne "/" or die "can't run on root directory\n"; if ($#ARGV == 1) { # # manual run (no MD needed) # fixup @ARGV; rmcache; exit; } my ($md, @nodes, $nodeid, @aliases, $alias); my (%newpath, %roots); # # scan MD for ioaliases # $md = MDesc->new; $md->open; @nodes = $md->scan(0, "ioaliases", "fwd"); $#nodes == 0 or die "missing ioaliases node\n"; # # foreach ioalias node, replace any 'alias' paths # with the 'current' one # # complicating this is that the alias paths can be # substrings of each other, which can cause false # hits in /etc/path_to_inst, so first gather all # aliases with the same root into a list, then sort # it by length so we always fix the longer alias # paths before the shorter ones # @nodes = $md->scan(@nodes[0], "ioalias", "fwd"); foreach $nodeid (@nodes) { my (%prop, $current); %prop = $md->lookup($nodeid, "aliases", $md->MDSTR); @aliases = split(/ /, $prop{'string'}); %prop = $md->lookup($nodeid, "current", $md->MDSTR); $current = $prop{'string'}; foreach $alias (@aliases) { next if ($alias eq $current); my ($slash, $root); $newpath{$alias} = $current; $slash = index($alias, '/', 1); if ($slash == -1) { $root = $alias; } else { $root = substr($alias, 0, $slash); } push(@{ $roots{$root} }, $alias); } } my $aref; foreach $aref (values %roots) { @aliases = sort { length($b) <=> length($a) } @$aref; foreach $alias (@aliases) { fixup $alias, $newpath{$alias}; } } rmcache;