1#!/usr/bin/perl 2 3# 4# CDDL HEADER START 5# 6# The contents of this file are subject to the terms of the 7# Common Development and Distribution License (the "License"). 8# You may not use this file except in compliance with the License. 9# 10# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 11# or http://www.opensolaris.org/os/licensing. 12# See the License for the specific language governing permissions 13# and limitations under the License. 14# 15# When distributing Covered Code, include this CDDL HEADER in each 16# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 17# If applicable, add the following below this CDDL HEADER, with the 18# fields enclosed by brackets "[]" replaced with your own identifying 19# information: Portions Copyright [yyyy] [name of copyright owner] 20# 21# CDDL HEADER END 22# 23 24# 25# Copyright 2008 Sun Microsystems, Inc. All rights reserved. 26# Use is subject to license terms. 27# 28 29use Getopt::Std; 30use Cwd; 31 32use strict; 33 34package MDesc; 35 36use constant { 37 MDEND => 0x45, 38 MDNODE => 0x4e, 39 MDARC => 0x61, 40 MDDATA => 0x64, 41 MDSTR => 0x73, 42 MDVAL => 0x76, 43}; 44 45 46sub new { 47 my $class = shift; 48 my $self = {}; 49 $self->{FILE} = undef; 50 $self->{MAJOR} = undef; 51 $self->{MINOR} = undef; 52 $self->{NODE_SEC_SZ} = undef; 53 $self->{NAME_SEC_SZ} = undef; 54 $self->{DATA_SEC_SZ} = undef; 55 $self->{NODES} = undef; 56 $self->{NAMES} = undef; 57 $self->{DATA} = undef; 58 bless($self, $class); 59 return $self; 60} 61 62sub open { 63 my $self = shift; 64 my ($mdhdr, $size); 65 66 if (@_) { 67 $self->{NAME} = shift; 68 } else { 69 $self->{NAME} = '/dev/mdesc'; 70 } 71 return unless open(MD, "$self->{NAME}"); 72 73 # Read and parse MD header 74 unless (read(MD, $mdhdr, 16) == 16) { 75 close (MD); 76 return; 77 } 78 79 ($self->{MAJOR}, $self->{MINOR}, 80 $self->{NODE_SEC_SZ}, 81 $self->{NAME_SEC_SZ}, 82 $self->{DATA_SEC_SZ}) = unpack("nnNNN", $mdhdr); 83 84 $size = read(MD, $self->{NODES}, $self->{NODE_SEC_SZ}); 85 $size = read(MD, $self->{NAMES}, $self->{NAME_SEC_SZ}); 86 $size = read(MD, $self->{DATA}, $self->{DATA_SEC_SZ}); 87 88 1; 89} 90 91# 92# return hash of given node's information 93# 94sub getnode { 95 my ($self, $nodeid) = @_; 96 my ($tag, $name, $namelen, $nameoff, $datalen, $dataoff, %node); 97 98 ($tag, $namelen, $nameoff, $datalen, $dataoff) = 99 unpack("CCx2NNN", substr($self->{NODES}, $nodeid * 16, 16)); 100 $name = substr($self->{NAMES}, $nameoff, $namelen); 101 %node = (tag => $tag, name => $name, nameid => $nameoff); 102 103 if ($tag == MDSTR || $tag == MDDATA) { 104 $node{'datalen'} = $datalen; 105 $node{'dataoff'} = $dataoff; 106 } elsif ($tag == MDVAL) { 107 $node{'val'} = ($datalen << 32) | $dataoff; 108 } elsif ($tag == MDARC || $tag == MDNODE) { 109 $node{'idx'} = ($datalen << 32) | $dataoff; 110 } 111 112 return %node; 113} 114 115 116# 117# return hash of given property's information 118# 119sub getprop { 120 my ($self, $propid) = @_; 121 my (%node, $tag, %prop); 122 123 %node = $self->getnode($propid); 124 $tag = $node{'tag'}; 125 %prop = (name => $node{'name'}, tag => $tag); 126 127 if ($tag == MDSTR) { 128 $prop{'string'} = 129 substr($self->{DATA}, $node{'dataoff'}, $node{'datalen'} - 1); 130 } elsif ($tag == MDARC) { 131 $prop{'arc'} = $node{'idx'}; 132 } elsif ($tag == MDVAL) { 133 $prop{'val'} = $node{'val'}; 134 } elsif ($tag == MDDATA) { 135 $prop{'length'} = $node{'datalen'}; 136 $prop{'offset'} = $node{'dataoff'}; 137 } else { 138 return undef; 139 } 140 141 return %prop; 142} 143 144 145# 146# find name table index of given name 147# 148sub findname { 149 my ($self, $name) = @_; 150 my ($idx, $next, $p); 151 152 for ($idx = 0; $idx < $self->{NAME_SEC_SZ}; $idx = $next + 1) { 153 $next = index($self->{NAMES}, "\0", $idx); 154 $p = substr($self->{NAMES}, $idx, $next - $idx); 155 return $idx if ($p eq $name); 156 } 157 158 return -1; 159} 160 161 162# 163# find given property in node 164# 165sub findprop { 166 my ($self, $nodeid, $propname, $type) = @_; 167 my (%node, $nameid); 168 169 %node = $self->getnode($nodeid); 170 return -1 if ($node{'tag'} != MDNODE); 171 172 $nameid = $self->findname($propname); 173 return -1 if ($nameid == -1); 174 175 do { 176 $nodeid++; 177 %node = $self->getnode($nodeid); 178 if ($node{'tag'} == $type && $node{'nameid'} == $nameid) { 179 return $nodeid; 180 } 181 } while ($node{'tag'} != MDEND); 182 183 return -1; 184} 185 186 187# 188# lookup property in node and return its hash 189# 190sub lookup { 191 my ($self, $nodeid, $propname, $type) = @_; 192 my ($propid); 193 194 $propid = $self->findprop($nodeid, $propname, $type); 195 return undef if ($propid == -1); 196 197 return $self->getprop($propid); 198} 199 200 201sub scan_node { 202 my ($self, $nodeid, $nameid, $arcid, $ret, $seen) = @_; 203 my (%node); 204 205 return if ($seen->[$nodeid] == 1); 206 $seen->[$nodeid] = 1; 207 208 %node = $self->getnode($nodeid); 209 return if ($node{'tag'} != MDNODE); 210 push(@$ret, $nodeid) if ($node{'nameid'} == $nameid); 211 212 do { 213 $nodeid++; 214 %node = $self->getnode($nodeid); 215 if ($node{'tag'} == MDARC && $node{'nameid'} == $arcid) { 216 $self->scan_node($node{'idx'}, $nameid, $arcid, $ret, $seen); 217 } 218 } while ($node{'tag'} != MDEND); 219} 220 221 222# 223# scan dag from 'start' via 'arcname' 224# return list of nodes named 'nodename' 225# 226sub scan { 227 my ($self, $start, $nodename, $arcname) = @_; 228 my ($nameid, $arcid, @ret, @seen); 229 230 $nameid = $self->findname($nodename); 231 $arcid = $self->findname($arcname); 232 $self->scan_node($start, $nameid, $arcid, \@ret, \@seen); 233 return @ret; 234} 235 236 237 238package main; 239 240 241# 242# 'find' needs to use globals anyway, 243# so we might as well use the same ones 244# everywhere 245# 246our ($old, $new); 247our %opts; 248 249 250# 251# fix path_to_inst 252# 253sub fixinst { 254 use File::Copy; 255 my ($oldpat, $newpat); 256 my ($in, $out); 257 258 $oldpat = '"' . $old . '/'; 259 $newpat = '"' . $new . '/'; 260 261 $in = "etc/path_to_inst"; 262 $out = "/tmp/path$$"; 263 264 open(IN, "<", $in) or die "can't open $in\n"; 265 open(OUT, ">", $out) or die "can't open $out\n"; 266 267 my ($found, $path); 268 # 269 # first pass 270 # see if there are any old paths that need to be re-written 271 # 272 $found = 0; 273 while (<IN>) { 274 ($path, undef, undef) = split; 275 if ($path =~ /^$oldpat/) { 276 $found = 1; 277 last; 278 } 279 } 280 # return if no old paths found 281 if ($found == 0) { 282 close(IN); 283 close(OUT); 284 unlink $out; 285 return 0; 286 } 287 288 print "replacing $old with $new in /etc/path_to_inst\n"; 289 # 290 # 2nd pass 291 # substitute new for old 292 # 293 seek(IN, 0, 0); 294 while (<IN>) { 295 ($path, undef, undef) = split; 296 if ($path =~ /^$oldpat/) { 297 s/$oldpat/$newpat/; 298 } 299 print OUT; 300 } 301 close(IN); 302 close(OUT); 303 304 if ($opts{v}) { 305 print "path_to_inst changes:\n"; 306 system("/usr/bin/diff", $in, $out); 307 print "\n"; 308 } 309 310 move $out, $in or die "can't modify $in\n"; 311 312 return 1; 313} 314 315 316our $oldpat; 317 318sub wanted { 319 my $targ; 320 321 -l or return; 322 $targ = readlink; 323 if ($targ =~ /$oldpat/) { 324 $targ =~ s/$old/$new/; 325 unlink; 326 symlink $targ, $_; 327 print "symlink $_ changed to $targ\n" if ($opts{v}); 328 } 329} 330 331# 332# fix symlinks 333# 334sub fixdev { 335 use File::Find; 336 $oldpat = "/devices" . $old; 337 338 print "updating /dev symlinks\n"; 339 find \&wanted, "dev"; 340} 341 342 343# 344# fixup path_to_inst and /dev symlinks 345# 346sub fixup { 347 # setup globals 348 ($old, $new) = @_; 349 350 # if fixinst finds no matches, no need to run fixdev 351 return if (fixinst == 0); 352 fixdev; 353 print "\n" if ($opts{v}); 354} 355 356# 357# remove caches 358# 359sub rmcache { 360 unlink "etc/devices/devid_cache"; 361 unlink "etc/devices/devname_cache"; 362 unlink <etc/devices/mdi_*_cache>; 363 unlink "etc/devices/retire_store"; 364 unlink "etc/devices/snapshot_cache"; 365 unlink "dev/.devlink_db"; 366} 367 368 369# $< == 0 or die "$0: must be run as root\n"; 370 371getopts("vR:", \%opts); 372 373if ($opts{R}) { 374 chdir $opts{R} or die "can't chdir to $opts{R}\n"; 375} 376cwd() ne "/" or die "can't run on root directory\n"; 377 378if ($#ARGV == 1) { 379 # 380 # manual run (no MD needed) 381 # 382 fixup @ARGV; 383 rmcache; 384 exit; 385} 386 387 388my ($md, @nodes, $nodeid, @aliases, $alias); 389my (%newpath, %roots); 390 391# 392# scan MD for ioaliases 393# 394$md = MDesc->new; 395$md->open; 396 397@nodes = $md->scan(0, "ioaliases", "fwd"); 398$#nodes == 0 or die "missing ioaliases node\n"; 399 400# 401# foreach ioalias node, replace any 'alias' paths 402# with the 'current' one 403# 404# complicating this is that the alias paths can be 405# substrings of each other, which can cause false 406# hits in /etc/path_to_inst, so first gather all 407# aliases with the same root into a list, then sort 408# it by length so we always fix the longer alias 409# paths before the shorter ones 410# 411@nodes = $md->scan(@nodes[0], "ioalias", "fwd"); 412foreach $nodeid (@nodes) { 413 my (%prop, $current); 414 415 %prop = $md->lookup($nodeid, "aliases", $md->MDSTR); 416 @aliases = split(/ /, $prop{'string'}); 417 418 %prop = $md->lookup($nodeid, "current", $md->MDSTR); 419 $current = $prop{'string'}; 420 421 foreach $alias (@aliases) { 422 next if ($alias eq $current); 423 424 my ($slash, $root); 425 $newpath{$alias} = $current; 426 $slash = index($alias, '/', 1); 427 if ($slash == -1) { 428 $root = $alias; 429 } else { 430 $root = substr($alias, 0, $slash); 431 } 432 push(@{ $roots{$root} }, $alias); 433 } 434} 435 436my $aref; 437foreach $aref (values %roots) { 438 @aliases = sort { length($b) <=> length($a) } @$aref; 439 foreach $alias (@aliases) { 440 fixup $alias, $newpath{$alias}; 441 } 442} 443 444rmcache; 445