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