#/usr/bin/perl # # Utility for dealing with raw nfsdump records. package nfsparse; use Exporter; @ISA = ('Exporter'); @EXPORT = qw (&nfsDumpParseLineQuick &nfsDumpParseLine &nfsDumpCompressFH); use strict; # nfsDumpParseLine -- initializes the global associative array # %nfsd'nfsDumpLine with information about a record from nfsdump. # Returns an empty list if anything goes wrong. Otherwise, returns a # list of the protocol (eg R2, C2, R3, C3), the name of the operation, # and the xid, the client host ID, and the time. The reason for this # particular return list is that these are very frequently-accessed # values, so it can save time to avoid going through the associative # array to access them. # # All records begin with several fixed fields, and then are followed # by some number of name/value pairs, and finally some diagnostic # fields (which are mostly ignored by this routine-- the only # diagnostic this routine cares about is whether the packet as part of # a jumbo packet or not. If so, then 'truncated' is set.) sub nfsDumpParseLine { my ($rawLine, $parsedLine) = @_; foreach my $key (keys %$parsedLine) { $parsedLine->{$key} = ''; } # If the line doesn't start with a digit, then it's certainly # not properly formed, so bail out immediately. if (! ($rawLine =~ /^[0-9]/)) { return; } my @items = split (' ', $rawLine); my $lineLen = $#items; if ($items[$lineLen - 1] eq 'LONGPKT') { print "in funny LONGPKT code\n"; splice (@items, $lineLen - 1); $parsedLine->{'truncated'} = 1; $lineLen--; } $parsedLine->{'status'} = ''; $parsedLine->{'time'} = $items[0]; $parsedLine->{'srchost'} = $items[1]; $parsedLine->{'deshost'} = $items[2]; $parsedLine->{'proto'} = $items[4]; $parsedLine->{'xid'} = $items[5]; $parsedLine->{'opcode'} = $items[6]; $parsedLine->{'opname'} = $items[7]; my $reseen = 0; if (($items[4] eq 'R3') || ($items[4] eq 'R2')) { ### REQUEST ### $parsedLine->{'status'} = $items[8]; $parsedLine->{'client_id'} = $items[2]; for (my $i = 9; $i < $lineLen - 10; $i += 2) { if (defined $parsedLine->{$items[$i]}) { $reseen = 1; $parsedLine->{"$items[$i]-2"} = $items[$i + 1]; } else { $parsedLine->{$items[$i]} = $items[$i + 1]; } } } else { ### RESPONSE ### $parsedLine->{'client_id'} = $items[1]; for (my $i = 8; $i < $lineLen - 6; $i += 2) { if (defined $parsedLine->{$items[$i]}) { $parsedLine->{"$items[$i]-2"} = $items[$i + 1]; $reseen = 1; } else { $parsedLine->{$items[$i]} = $items[$i + 1]; } } } return; } sub nfsDumpParseLineQuick { my ($rawLine, $parsedLine)= @_; my @items = split (' ', $rawLine, 9); if (($items[4] eq 'R3') || ($items[4] eq 'R2')) { $parsedLine->{'client_id'} = $items[2]; } else { $parsedLine->{'client_id'} = $items[1]; } $parsedLine->{'proto'} = $items[4]; $parsedLine->{'opname'} = $items[7]; $parsedLine->{'xid'} = $items[5]; $parsedLine->{'time'} = $items[0]; return; } # nfsDumpParseLineFields -- initializes the global associative array # %nfsDumpLine with information about a record from nfsdump. Returns # an empty list if anything goes wrong. Otherwise, returns a list of # the protocol (eg R2, C2, R3, C3), the name of the operation, and the # xid, and the client host ID. The reason for this particular return # list is that these are very frequently-accessed values, so it can # save time to avoid going through the associative array to access # them. # # All records begin with several fixed fields, and then are followed # by some number of name/value pairs, and finally some diagnostic # fields (which are mostly ignored by this routine-- the only # diagnostic this routine cares about is whether the packet as part of # a jumbo packet or not. If so, then 'truncated' is set.) sub nfsDumpParseLineFields { my ($line, @fields) = @_; my $client_id; # If the line doesn't start with a digit, then # it's certainly not properly formed, so bail out # immediately. if (! ($line =~ /^[0-9]/)) { return (); } my @l = split (' ', $line, 9); my $rest = $l[8]; my $fl = @fields; for (my $i = 0; $i < $fl; $i++) { my $field = $fields[$i]; $rest =~ /\ $field\ +([^\ ]+)/; $l[$i + 9] = $1; } if (($l[4] eq 'R3') || ($l[4] eq 'R2')) { $client_id = $l[2]; if ($rest =~ /^OK\ /) { $l[8] = 'OK'; } else { $l[8] = 'ERR'; } } else { $l[8] = 'SNT'; $client_id = $l[1]; } } sub nfsDumpCompressFH { my ($mode, $fh) = @_; if ($mode eq 'advfs') { # The fh is a long hex string: # 8 chars: file system ID # 8 chars: apparently unused. # 8 chars: unused. # 8 chars: inode # 8 chars: generation # rest of string: mount point (not interesting). # So all we do is pluck out the fsid, inode, # and generation number, and throw the rest away. $fh =~ /^(........)(........)(........)(........)(........)/; return ("$1-$4-$5"); } elsif ($mode eq 'netapp') { # Here's the netapp format (from Shane Owara): # # 4 bytes mount point file inode number # 4 bytes mount point file generation number # # 2 bytes flags # 1 byte snapshot id # 1 byte unused # # 4 bytes file inode number # 4 bytes file generation number # 4 bytes volume identifier # # 4 bytes export point fileid # 1 byte export point snapshot id # 3 bytes export point snapshot generation number # # The only parts of this that are interesting are # inode, generation, and volume identifier (and probably # a lot of the bits of the volume identifier could be # tossed, since we don't have many volumes...). if ($fh =~ /^(........)(........)(........)(........)(........)(........)(........)/) { return ("$4-$5-$6-$1"); } else { print "bad fh: $fh\n"; exit (0); } } else { return ($fh); } } sub testMain { my $lineNum = 0; while (my $line = ) { $lineNum++; &nfsDumpParseLine ($line); } } 1; # end of nfsdump.pl