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 29 use Getopt::Std; 30 use Cwd; 31 32 use strict; 33 34 package MDesc; 35 36 use constant { 37 MDEND => 0x45, 38 MDNODE => 0x4e, 39 MDARC => 0x61, 40 MDDATA => 0x64, 41 MDSTR => 0x73, 42 MDVAL => 0x76, 43 }; 44 45 46 sub 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 62 sub 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 # 94 sub 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 # 119 sub 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 # 148 sub 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 # 165 sub 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 # 190 sub 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 201 sub 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 # 226 sub 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 238 package main; 239 240 241 # 242 # 'find' needs to use globals anyway, 243 # so we might as well use the same ones 244 # everywhere 245 # 246 our ($old, $new); 247 our %opts; 248 249 250 # 251 # fix path_to_inst 252 # 253 sub 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 316 our $oldpat; 317 318 sub 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 # 334 sub 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 # 346 sub 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 # 359 sub 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 371 getopts("vR:", \%opts); 372 373 if ($opts{R}) { 374 chdir $opts{R} or die "can't chdir to $opts{R}\n"; 375 } 376 cwd() ne "/" or die "can't run on root directory\n"; 377 378 if ($#ARGV == 1) { 379 # 380 # manual run (no MD needed) 381 # 382 fixup @ARGV; 383 rmcache; 384 exit; 385 } 386 387 388 my ($md, @nodes, $nodeid, @aliases, $alias); 389 my (%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"); 412 foreach $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 436 my $aref; 437 foreach $aref (values %roots) { 438 @aliases = sort { length($b) <=> length($a) } @$aref; 439 foreach $alias (@aliases) { 440 fixup $alias, $newpath{$alias}; 441 } 442 } 443 444 rmcache; 445