2 # Copyright (c) 2002-2003
\r
3 # The President and Fellows of Harvard College.
\r
5 # Redistribution and use in source and binary forms, with or without
\r
6 # modification, are permitted provided that the following conditions
\r
8 # 1. Redistributions of source code must retain the above copyright
\r
9 # notice, this list of conditions and the following disclaimer.
\r
10 # 2. Redistributions in binary form must reproduce the above copyright
\r
11 # notice, this list of conditions and the following disclaimer in the
\r
12 # documentation and/or other materials provided with the distribution.
\r
13 # 3. Neither the name of the University nor the names of its contributors
\r
14 # may be used to endorse or promote products derived from this software
\r
15 # without specific prior written permission.
\r
17 # THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND
\r
18 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
\r
19 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
\r
20 # ARE DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE
\r
21 # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
\r
22 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
\r
23 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
\r
24 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
\r
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
\r
26 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
\r
29 # $Id: hier.pl,v 1.14 2003/07/26 20:52:03 ellard Exp $
\r
31 # hier.pl - Tools to map out the file system hierarchy. This is
\r
32 # accomplished by snooping out the lookup calls.
\r
34 # This is expensive because the hierarchy can require a LOT of space
\r
35 # to store for a large system with lots of files (especially if files
\r
36 # come and go). Don't construct the hierarchy unless you want it --
\r
37 # and be prepared to prune it from time to time.
\r
41 # Tables used by the outside world:
\r
54 # Library-private tables and variables.
\r
56 %pendingCallsXIDnow = ();
\r
57 %pendingCallsXIDfh = ();
\r
58 %pendingCallsXIDname = ();
\r
60 $nextPruneTime = -1;
\r
61 $PRUNE_INTERVAL = 5 * 60; # Five minutes.
\r
64 my ($line, $proto, $op, $xid, $client, $now, $response, $fh_type) = @_;
\r
66 if ($now > $nextPruneTime) {
\r
67 &prunePending ($now - $PRUNE_INTERVAL);
\r
68 $nextPruneTime = $now + $PRUNE_INTERVAL;
\r
71 my $uxid = "$client-$xid";
\r
73 # 'lookup', 'create', 'rename', 'delete',
\r
74 # 'getattr', 'setattr'
\r
76 #RFS: add mkdir/rmdir
\r
77 if ($op eq 'lookup' || $op eq 'create' || $op eq 'mkdir') {
\r
78 return (&doLookup ($line, $proto, $op, $uxid,
\r
79 $now, $response, $fh_type));
\r
81 elsif ($op eq 'rename') {
\r
83 elsif ($op eq 'remove' || $op eq 'rmdir') {
\r
84 # RFS: why remove these entries? Just let them exist since
\r
85 # there is generation number available to distinguish btw removed dir/file
\r
86 # and new dir/file with the same inode number.
\r
87 #return (&doRemove ($line, $proto, $op, $uxid,
\r
88 # $now, $response, $fh_type));
\r
90 elsif ($op eq 'getattr' || $op eq 'read' || $op eq 'write' ) {
\r
91 return (&doGetAttr ($line, $proto, $op, $uxid,
\r
92 $now, $response, $fh_type));
\r
94 elsif ($op eq 'setattr') {
\r
99 my ($line, $proto, $op, $uxid, $now, $response, $fh_type) = @_;
\r
101 if ($proto eq 'C3' || $proto eq 'C2') {
\r
102 my $tag = ($proto eq 'C3') ? 'name' : 'fn';
\r
103 my $name = nfsd::nfsDumpParseLineField ($line, $tag);
\r
105 # All directories have (at least) three names: the
\r
106 # given name, and "." and "..". We're only interested
\r
107 # in the given name.
\r
109 if ($name eq '"."' || $name eq '".."') {
\r
113 my $fh = nfsd::nfsDumpCompressFH ($fh_type,
\r
114 nfsd::nfsDumpParseLineField ($line, 'fh'));
\r
116 $pendingCallsXIDnow{$uxid} = $now;
\r
117 $pendingCallsXIDfh{$uxid} = $fh;
\r
118 $pendingCallsXIDname{$uxid} = $name;
\r
120 elsif ($proto eq 'R3' || $proto eq 'R2') {
\r
121 if (! exists $pendingCallsXIDnow{$uxid}) {
\r
125 my $pfh = $pendingCallsXIDfh{$uxid};
\r
126 my $name = $pendingCallsXIDname{$uxid};
\r
128 delete $pendingCallsXIDnow{$uxid};
\r
129 delete $pendingCallsXIDfh{$uxid};
\r
130 delete $pendingCallsXIDname{$uxid};
\r
132 if ($response eq 'OK') {
\r
133 my $cfh = nfsd::nfsDumpCompressFH ($fh_type,
\r
134 nfsd::nfsDumpParseLineField ($line, 'fh'));
\r
136 my $type = nfsd::nfsDumpParseLineField ($line, 'ftype');
\r
139 $fhIsDir{$cfh} = 1;
\r
142 $fh2Parent{$cfh} = $pfh;
\r
143 $fh2Name{$cfh} = $name;
\r
144 $parent2fh{"$pfh,$name"} = $cfh;
\r
146 my ($size, $mode, $atime, $mtime, $ctime) =
\r
147 nfsd::nfsDumpParseLineFields ($line,
\r
149 'atime', 'mtime', 'ctime');
\r
151 # RFS: modify here to get/maintain more file attributes
\r
152 # we can just check the ctime and compare it with trace-start-time
\r
153 # to decide whether to create a file/diretory.
\r
154 # atime - last access time of the file
\r
155 # mtime - last modification time of the file
\r
156 # ctime - last file status change time
\r
158 #$fh2Attr{$cfh} = "$size $mode $atime $mtime $ctime";
\r
159 if (! exists $fh2AttrOrig{$cfh} ) {
\r
160 $fh2AttrOrig{$cfh} = "$size $mode $op $atime $mtime $ctime";
\r
162 $fh2Attr{$cfh} = "$size $mode $op $atime $mtime $ctime";
\r
171 my ($line, $proto, $op, $uxid, $now, $response, $fh_type) = @_;
\r
173 if ($proto eq 'C3' || $proto eq 'C2') {
\r
174 my $tag = ($proto eq 'C3') ? 'name' : 'fn';
\r
175 my $name = nfsd::nfsDumpParseLineField ($line, $tag);
\r
177 # All directories have (at least) three names: the
\r
178 # given name, and "." and "..". We're only interested
\r
179 # in the given name.
\r
181 if ($name eq '"."' || $name eq '".."') {
\r
185 my $pfh = nfsd::nfsDumpCompressFH ($fh_type,
\r
186 nfsd::nfsDumpParseLineField ($line, 'fh'));
\r
188 if (! exists $parent2fh{"$pfh,$name"}) {
\r
192 $pendingCallsXIDnow{$uxid} = $now;
\r
193 $pendingCallsXIDfh{$uxid} = $pfh;
\r
194 $pendingCallsXIDname{$uxid} = $name;
\r
196 elsif ($proto eq 'R3' || $proto eq 'R2') {
\r
197 if (! exists $pendingCallsXIDnow{$uxid}) {
\r
201 my $pfh = $pendingCallsXIDfh{$uxid};
\r
202 my $name = $pendingCallsXIDname{$uxid};
\r
204 delete $pendingCallsXIDfh{$uxid};
\r
205 delete $pendingCallsXIDname{$uxid};
\r
206 delete $pendingCallsXIDnow{$uxid};
\r
208 if (! exists $parent2fh{"$pfh,$name"}) {
\r
212 my $cfh = $parent2fh{"$pfh,$name"};
\r
214 if ($response eq 'OK') {
\r
215 if ($op eq 'remove') {
\r
216 printFileInfo ($cfh, 'D');
\r
218 delete $fh2Parent{$cfh};
\r
219 delete $fh2Name{$cfh};
\r
220 delete $fh2Attr{$cfh};
\r
221 delete $fhs2AttrOrig{$cfg};
\r
222 delete $parent2fh{"$pfh,$name"};
\r
231 my ($line, $proto, $op, $uxid, $now, $response, $fh_type) = @_;
\r
233 if ($proto eq 'C3' || $proto eq 'C2') {
\r
234 my $fh = nfsd::nfsDumpCompressFH ($fh_type,
\r
235 nfsd::nfsDumpParseLineField ($line, 'fh'));
\r
237 #if (nfsd::nfsDumpParseLineField ($line, 'fh')
\r
238 # eq '00018961-57570100-d2440000-61890100') {
\r
239 # printf STDERR "Seen it ($op)\n";
\r
242 if (! defined $fh) {
\r
246 $pendingCallsXIDnow{$uxid} = $now;
\r
247 $pendingCallsXIDfh{$uxid} = $fh;
\r
249 my $wantfh = "6189010057570100200000000000862077ed3800d24400006189010057570100";
\r
250 if ($fh eq $wantfh) {
\r
251 print "JIAWU: doGetAttr call $wantfh\n";
\r
255 if (! exists $pendingCallsXIDnow{$uxid}) {
\r
259 my $fh = $pendingCallsXIDfh{$uxid};
\r
260 delete $pendingCallsXIDfh{$uxid};
\r
261 delete $pendingCallsXIDnow{$uxid};
\r
263 my $wantfh = "6189010057570100200000000000862077ed3800d24400006189010057570100";
\r
264 if ($fh eq $wantfh) {
\r
265 print "JIAWU: doGetAttr response $wantfh\n";
\r
268 if ($response ne 'OK') {
\r
272 my ($ftype) = nfsd::nfsDumpParseLineFields ($line, 'ftype');
\r
273 if (!defined $ftype) {
\r
274 print STDERR "BAD $line";
\r
282 #RFS comment: here if fh is a directory, then it will not be add
\r
283 # in the two hash table %fh2Attr(%fh2AttrOrig) and %fh2Name
\r
284 # if ($ftype != 1) {
\r
292 my ($mode, $size, $atime, $mtime, $ctime) =
\r
293 nfsd::nfsDumpParseLineFields ($line,
\r
294 'mode', 'size', 'atime', 'mtime', 'ctime');
\r
296 # RFS: modify here to get/maintain more file attributes
\r
297 # we can just check the ctime and compare it with trace-start-time
\r
298 # to decide whether to create a file/diretory.
\r
299 # atime - last access time of the file
\r
300 # mtime - last modification time of the file
\r
301 # ctime - last file status change time
\r
303 # $fh2Attr{$fh} = "$size $mode $atime $mtime $ctime";
\r
305 if (! exists $fh2AttrOrig{$fh} ) {
\r
306 $fh2AttrOrig{$fh} = "$size $mode $op $atime $mtime $ctime";
\r
308 $fh2Attr{$fh} = "$size $mode $op $atime $mtime $ctime";
\r
312 # Purge all the pending XID records dated earlier than $when (which is
\r
313 # typically at least $PRUNE_INTERVAL seconds ago). This is important
\r
314 # because otherwise missing XID records can pile up, eating a lot of
\r
320 foreach my $uxid ( keys %pendingCallsXIDnow ) {
\r
321 if ($pendingCallsXIDnow{$uxid} < $when) {
\r
323 my $fh = $pendingCallsXIDfh{$uxid};
\r
324 my $wantfh = "6189010057570100200000000000862077ed3800d24400006189010057570100";
\r
325 if ($fh eq $wantfh) {
\r
326 print "JIAWU: prunePending $wantfh\n";
\r
329 delete $pendingCallsXIDnow{$uxid};
\r
336 # Return as much of the path for the given fh as possible. It may or
\r
337 # may not reach the root (or the mount point of the file system), but
\r
338 # right now we don't check. Usually on busy systems the data is
\r
339 # complete enough so that most paths are complete back to the mount
\r
346 my $MaxPathLen = 40;
\r
348 if (exists $fhIsDir{$fh}) {
\r
353 while ($fh && exists $fh2Name{$fh}) {
\r
354 unshift (@path, $fh2Name{$fh});
\r
355 if ($fh eq $fh2Parent{$fh}) {
\r
356 unshift (@path, '(LOOP)');
\r
360 if ($cnt++ > $MaxPathLen) {
\r
361 print STDERR "findPath: path too long (> $MaxPathLen)\n";
\r
362 unshift (@path, '(TOO-LONG)');
\r
366 $fh = $fh2Parent{$fh};
\r
369 # RFS: append the ~user (fh and !exists $fh2Name{$fh} and type is Directory)
\r
370 if ($fh && !exists $fh2Name{$fh} && exists $fhIsDir{$fh}) {
\r
371 if (exists $rootsName{$fh}) {
\r
372 print "JIAWU: $rootsName{$fh}\n";
\r
373 unshift(@path, $rootsName{$fh});
\r
375 print "JIAWU: WARNING! No rootsName for this fh: $fh\n";
\r
376 unshift(@path, $fh);
\r
379 if ($fh && !exists $fh2Name{$fh} && !exists $fhIsDir{$fh}) {
\r
380 if (exists $discardFHs{$fh}) {
\r
381 open NOATTRDIR, ">>noattrdirdiscard";
\r
382 print NOATTRDIR "$fh DISCARD\n";
\r
385 # RFS: if a possible fh without attr and name, then regard it as a special root ~/RFSNN0
\r
386 unshift(@path, '"RFSNN0"');
\r
388 $fh2Name{$fh} = '"RFSNN0"';
\r
389 $rootsName{$fh} = '"RFSNN0"';
\r
390 open NOATTRDIR, ">>noattrdir-root";
\r
391 print NOATTRDIR "$fh RFSNN0\n";
\r
400 foreach my $p ( @path ) {
\r
415 return ($str, $cnt);
\r
419 $total_unknown_fh = 0;
\r
420 $total_known_fh = 0;
\r
423 my ($start_time, $out) = @_;
\r
430 # RFS print more information here
\r
431 open (OUT_RFS, ">rfsinfo") ||
\r
432 die "Can't create $OutFileBaseName.rfs.";
\r
434 foreach $fh ( keys %fh2Attr ) {
\r
437 foreach $fh ( keys %fh2Name ) {
\r
441 #RFS: before printFileInfo, name those roots' name
\r
443 #RFS there are three kind of fh
\r
444 # 1. fh/name paired (fh/attr must)
\r
445 # 2. fh/attr but no fh/name: type file (discard related operations)
\r
446 # 3. fh/attr but no fh/name: type dir (keep as persuedo root)
\r
449 foreach $fh ( keys %allfh ) {
\r
450 if (exists $fh2Parent{$fh} ) {
\r
455 my $type = (exists $fhIsDir{$fh}) ? 'D' : 'F';
\r
456 if ($type eq 'D') {
\r
457 $rootsName{$fh} = sprintf("\"RFSNN%d\"", $sn++);
\r
458 $rootsFHs{$fh} = 1;
\r
461 $discardFHs{$fh} = 1;
\r
465 print OUT_RFS "#stat: fh with parent = $k, fh without parent = $u\n";
\r
466 $u = keys %rootsFHs;
\r
467 print OUT_RFS "#RFS: root fh list($u)\n";
\r
468 foreach $fh (keys %rootsName) {
\r
469 print OUT_RFS "#RFS: $rootsName{$fh} $fh\n";
\r
471 $u = keys %discardFHs;
\r
472 print OUT_RFS "#RFS: discard fh list($u)\n";
\r
473 print OUT_RFS join("\n", keys %discardFHs, "");
\r
476 print $out "#F type state fh path pathcount attrOrig(size,mode,op,atime,mt,ct) attrLast(size,mode,op,at,mt,ct)\n";
\r
478 print $out "#T starttime = $start_time\n";
\r
479 foreach $fh ( keys %allfh ) {
\r
480 printFileInfoOutputFile ($fh, 'A', $out);
\r
484 my $numfh2Name = keys %fh2Name;
\r
485 my $numfh2Attr = keys %fh2Attr;
\r
486 print OUT_RFS "fh2name has $numfh2Name, fh2Attr has $numfh2Attr\n";
\r
487 my $wantfh = "6189010057570100200000000000862077ed3800d24400006189010057570100";
\r
488 if ($allfh{$wantfh} == 1) {
\r
489 print OUT_RFS "JIAWU: found $wantfh\n";
\r
491 print OUT_RFS "JIAWU: NOT found $wantfh\n";
\r
493 foreach $fh ( keys %allfh ) {
\r
494 if ( $fh eq $wantfh ) {
\r
495 print OUT_RFS "JIAWU: found $wantfh\n";
\r
496 printFileInfoOutputFile ($fh, 'JIAWU', *OUT_RFS);
\r
500 print OUT_RFS "JIAWU: after \n";
\r
504 foreach $fh ( keys %allfh ) {
\r
505 if ( exists $fh2Name{$fh} ) {$k++;}
\r
508 print OUT_RFS "#stat: total known fh = $total_known_fh, unknown = $total_unknown_fh\n";
\r
509 print OUT_RFS "#stat: total fh with name = $k, without name = $u\n";
\r
511 print OUT_RFS "#RFS\n";
\r
516 sub printFileInfoOutputFile {
\r
517 my ($fh, $state, $out) = @_;
\r
519 my ($p, $c) = findPath ($fh);
\r
521 if ($c == 0) {$total_unknown_fh++;}
\r
522 else {$total_known_fh++;}
\r
524 my $type = (exists $fhIsDir{$fh}) ? 'D' : 'F';
\r
525 my $attr = (exists $fh2Attr{$fh}) ?
\r
526 $fh2Attr{$fh} : "-1 -1 -1 -1 -1";
\r
527 my $attrOrig = (exists $fh2AttrOrig{$fh}) ?
\r
528 $fh2AttrOrig{$fh} : "-1 -1 -1 -1 -1";
\r
530 print $out "F $type $state $fh $p $c $attrOrig $attr\n";
\r
533 sub printFileInfo {
\r
534 my ($fh, $state) = @_;
\r
536 my ($p, $c) = findPath ($fh);
\r
538 if ($c == 0) {$total_unknown_fh++;}
\r
539 else {$total_known_fh++;}
\r
541 my $type = (exists $fhIsDir{$fh}) ? 'D' : 'F';
\r
542 my $attr = (exists $fh2Attr{$fh}) ?
\r
543 $fh2Attr{$fh} : "-1 -1 -1 -1 -1";
\r
544 my $attrOrig = (exists $fh2AttrOrig{$fh}) ?
\r
545 $fh2AttrOrig{$fh} : "-1 -1 -1 -1 -1";
\r
547 print "F $type $state $fh $p $c $attrOrig $attr\n";
\r