$Parse::NetApp::ASUP::VERSION='1.14';

=head1 NAME:

Parse::NetApp::ASUP - Parse NetApp Weekly Auto Support Files

=head1 SYNOPSIS:

Parse NetApp Weekly Auto Support Files

=head1 USAGE:

  use Parse::NetApp::ASUP;
  
  my $pna = Parse::NetApp::ASUP->new();
  
  $pna->load($raw_asup_data_as_scalar);
  
=cut 

use Carp;

use strict;
use warnings;

package Parse::NetApp::ASUP;

=head3 new() 

Instance a new parser.

=cut

sub new {
	my $self = {};
	bless $self;
	return $self;
}

=head3 load($raw_asup_data) 

Load a raw asup data file for parsing.

=cut

sub load {
  my $self = shift @_;
  my $asup = shift @_;
  if ( not defined $asup or not length $asup ) {
    warn "load() called without input data.";
    return undef;
  }
  $self->{asup} = $asup;
  return 1;
}

sub version {
	return $Parse::NetApp::ASUP::VERSION;
}

### Utilties

sub _agnostic_line_split { # Now dealing with CRLF and the occasional standalone LF
  my $all = $_[0];
  my @lines = split /\r?\n/, $all;
  return @lines;
}  

sub _regex_lun_name {
  return qr/[\d\w\/\{\}\.-]+/;
}

sub _regex_qtree_name {
  return qr/[\w\-\? ]+/;
}

sub _regex_vol_name {
  return qr/[\w\-\?]+/;
}

sub _regex_path {
  return qr/[\w\-\?\$\\]+/;
}

=head1 GENERAL METHODS

=head3 asup_version()

Returns the version of the loaded ASUP file.

=cut

sub asup_version {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	
	if ( $raw =~ /^VERSION=NetApp Release ([\w\.\d]+)/ms ) {
		return "$1";
	}
	
	if ( $raw =~ /^X-Netapp-asup-os-version: NetApp Release ([\w\.\d]+)/ms ) {
		return "$1";
	}

	return 'unknown';
}	

=head3 extract($raw)

This method attempts to return key and commonly used sections of the ASUP as
a parsed data structure.

=cut

sub extract {
  my $raw = shift @_;

  my $version = Parse::NetApp::ASUP::asup_version($raw);  
  $version =~ /^(\d)\.(\d)/;
  my $maj = $1;
  my $min = $2;

  my $extracts;
  
  if ( $maj == 8 and $min > 0 ) {
    my $export     = Parse::NetApp::ASUP::extract_exports($raw);
    my $lun_conf   = Parse::NetApp::ASUP::extract_lun_configuration($raw);
    my $qtree_stat = Parse::NetApp::ASUP::extract_qtree_status($raw);
    my $xheader    = Parse::NetApp::ASUP::extract_xheader($raw);

    $extracts = ASUP::iterative_extract($raw);
    $extracts->{_METHOD} = 'Progressive';
    
    $extracts->{export}     = $export;
    $extracts->{lun_conf}   = $lun_conf;
    $extracts->{qtree_stat} = $qtree_stat;    
    $extracts->{xheader}    = $xheader;
  } else {
    $extracts->{_METHOD} = 'Singular';
    $extracts->{xml} = [];

    $extracts->{df}           = Parse::NetApp::ASUP::extract_df($raw);
    $extracts->{export}       = Parse::NetApp::ASUP::extract_exports($raw);
    $extracts->{header}       = Parse::NetApp::ASUP::extract_headers($raw);
    $extracts->{lun_conf}     = Parse::NetApp::ASUP::extract_lun_configuration($raw);
    $extracts->{qtree_stat}   = Parse::NetApp::ASUP::extract_qtree_status($raw);
    $extracts->{sysconfig_a}  = Parse::NetApp::ASUP::extract_sysconfig_a($raw);
    $extracts->{vol_status}   = Parse::NetApp::ASUP::extract_vol_status($raw);
    $extracts->{xheader}      = Parse::NetApp::ASUP::extract_xheader($raw);
  }

  $extracts->{_VERSION} = $version;        
  return $extracts;
}

=head3 iterative_extract()

Version 8 and higher extract has to be iterative

=cut

sub iterative_extract {
  my $raw = shift @_;  
  my %ex = ( xml => [] );
 
  ($ex{mailheader},$ex{header},$raw) = split /\n\n+/, $raw, 3;

  # Now dealing with CRLF and the occasional standalone LF
  
  my @lines = Parse::NetApp::ASUP::_agnostic_line_split($raw);

  # sysconfig -a
  
  while ( $lines[0] =~ /^(\t|\s{5})/ ) {
    $ex{sysconfig_a} .= (shift @lines) . "\n";
  }

  # sysconfig -d 

  $ex{sysconfig_d} .= (shift @lines) . "\n" if ( $lines[0] =~ /^Device/ );
  $ex{sysconfig_d} .= (shift @lines) . "\n" if ( $lines[0] =~ /^------/ );
  while ( $lines[0] =~ /^[0-9a-f]{2}\.[0-9a-f]{2}/ ) {
    $ex{sysconfig_d} .= (shift @lines) . "\n";
  }
  
  # sn

  $ex{serialnum} = (shift @lines) . "\n" if $lines[0] =~ /^system serial number/;
   
  # options

  while ( $lines[0] =~ /^[a-z_\-0-9]+(\.[a-z_\-0-9]+)+\s+/i ) {
    $ex{options} .= (shift @lines) . "\n";
  }

  # service usage
  
  while ( $lines[0] =~ /^Service statistics as of/ ) {
    $ex{service_usage} .= (shift @lines)."\n";
    while ( $lines[0] =~ /^ \S+\s+\(\S+\).+recorded/ ) {
      $ex{service_usage} .= (shift @lines)."\n";
      while ( $lines[0] =~ /^\s+[A-Z](\s+\d+,){3}/ ) {
        $ex{service_usage} .= (shift @lines)."\n";
      }
    }
  }

  # ifconfig -a
  
  while ( $lines[0] =~ /^[a-zA-Z0-9\-]+: flags/ ) {
    $ex{ifconfig_a} .= (shift @lines) . "\n";
    while ( $lines[0] =~ /^(\t|\s{3})/ ) {
      $ex{ifconfig_a} .= (shift @lines) . "\n";
    }
  }
    
  # ifstat -a
 
  shift @lines if $lines[0] =~ /^$/; # Remove a blank line
  
  while ( $lines[0] =~ /^-- interface/ ) {
    $ex{ifstat_a} .= (shift @lines)."\n";
    $ex{ifstat_a} .= (shift @lines)."\n" if $lines[0] =~ /^$/;
    while ( $lines[0] =~ /^[A-Z_]+\w/ ) {
      $ex{ifstat_a} .= (shift @lines)."\n";
      while ( $lines[0] =~ /^ [A-Z]/i ) {
        $ex{ifstat_a} .= (shift @lines)."\n";      
      }
    }
    while ( $lines[0] =~ /^$/ ) { $ex{ifstat_a} .= (shift @lines)."\n"; }
  }
  
  # cifs_stat
  
  while ( $lines[0] =~ /^\s+(\S+\s+)+\d+(\s+\d+\%)?$/ ) {
    $ex{cifs_stat} .= (shift @lines)."\n";      
  }
  while ( $lines[0] =~ /^(Max|Local|RPC)/ ) {
    $ex{cifs_stat} .= (shift @lines)."\n";      
  }
  
  # Volume-language

  while ( $lines[0] =~ /^\s+Volume Language$/ ) {
    $ex{volume_language} .= (shift @lines)."\n";
    while ( $lines[0] =~ /^(\s+)?\S+( \S+){1,2} \(.+\)$/ ) {
      $ex{volume_language} .= (shift @lines)."\n";
    }
  }

  # httpstat
  
  while ( $lines[0] =~ /^            Requests$/ ) {
    $ex{httpstat} .= (shift @lines)."\n";
    $ex{httpstat} .= (shift @lines) . "\n" if ( $lines[0] =~ /^\s+Accept\s+Reuse\s+Response\s+InBytes\s+OutBytes$/ );
    $ex{httpstat} .= (shift @lines)."\n" if $lines[0] =~ /^$/;
    while ( $lines[0] =~ /^\S+ Stats:$/ ) {
      $ex{httpstat} .= (shift @lines)."\n";
      while ( $lines[0] =~ /^(\s+\d+)+$/ ) {
        $ex{httpstat} .= (shift @lines)."\n";
        $ex{httpstat} .= (shift @lines)."\n" if $lines[0] =~ /^$/;
      }
    }
  }

  # df

  while ( $lines[0] =~ /^Filesystem\s+kbytes/ ) {
    $ex{df} .= (shift @lines)."\n";
    while ( $lines[0] =~ /^(\/vol|snap reserve)/ ) {
      $ex{df} .= (shift @lines)."\n";
    }
  }

  # df_i

  while ( $lines[0] =~ /^Filesystem\s+iused/ ) {
    $ex{df_i} .= (shift @lines)."\n";
    while ( $lines[0] =~ /^\/vol/ ) {
      $ex{df_i} .= (shift @lines)."\n";
    }
  }
  
  # snap-sched

  while ( $lines[0] =~ /^Volume \S+: \d+/ ) {
    $ex{snapsched} .= (shift @lines)."\n";
  }

  # vol-status

  while ( $lines[0] =~ /^\s+Volume\s+State\s+Status\s+Options/ ) {
    $ex{vol_status} .= (shift @lines)."\n";
    while ( $lines[0] =~ /^(\s+)?\S+\s+(online|offline|restricted)\s+\S+/ ) {
      $ex{vol_status} .= (shift @lines)."\n";
      while ( $lines[0] =~ /^\s{14}\s+\S+/ ) {
        $ex{vol_status} .= (shift @lines)."\n";
      }
    }
  }

  # sysconfig_r

  while ( $lines[0] =~ /^(Volume|Aggregate) \S+ \(.+?\) \(.+?\)$/ ) {
    $ex{sysconfig_r} .= (shift @lines)."\n";
    while ( $lines[0] =~ /^  Plex \S+ \(.+?\)$/ ) {
      $ex{sysconfig_r} .= (shift @lines)."\n";
      while ( $lines[0] =~ /^    RAID group \S+ \(.+?\)$/ ) {
        $ex{sysconfig_r} .= (shift @lines)."\n";
        $ex{sysconfig_r} .= (shift @lines)."\n" if $lines[0] =~ /^$/;
        $ex{sysconfig_r} .= (shift @lines)."\n" if $lines[0] =~ /^      RAID Disk/;
        $ex{sysconfig_r} .= (shift @lines)."\n" if $lines[0] =~ /^      ---------/;
        while ( $lines[0] =~ /^(\s+\S+){3}(\s+\d+){2}(\s+\S+\s+(\d+|\-)){2}(\s+\d+\/\d+){2}(\s+)?$/ ) {
          $ex{sysconfig_r} .= (shift @lines)."\n";
        }
        while ( $lines[0] =~ /^$/ ) {
          $ex{sysconfig_r} .= (shift @lines)."\n";
        }
        while ( $lines[0] =~ /(spare|broken|partner) disks/i ) {
          $ex{sysconfig_r} .= (shift @lines)."\n";
          $ex{sysconfig_r} .= (shift @lines)."\n" if $lines[0] =~ /^$/;
          $ex{sysconfig_r} .= (shift @lines)."\n" if $lines[0] =~ /^RAID Disk/;
          $ex{sysconfig_r} .= (shift @lines)."\n" if $lines[0] =~ /^---------/;
          while ( $lines[0] =~ /^(spare|failed|partner)/i ) {
            $ex{sysconfig_r} .= (shift @lines)."\n";
          }
          $ex{sysconfig_r} .= (shift @lines)."\n" if $lines[0] =~ /^$/;
        }
      }
    }
  }

  # fc stats

  while ( $lines[0] =~ /^\S+ driver statistics for slot/ ) {
    $ex{fc_stats} .= (shift @lines)."\n";
    while ( $lines[0] =~ /^ \S+\s+\d+$/ ) {
      $ex{fc_stats} .= (shift @lines)."\n";      
    }
    $ex{fc_stats} .= (shift @lines)."\n" if $lines[0] =~ /^ device status:\s+/;
    while ( $lines[0] =~ /^  \S+\s+.*total:\s+\d+$/ ) {
      $ex{fc_stats} .= (shift @lines)."\n";      
    }
  }
  while ( $lines[0] =~ /^Cannot complete operation on channel \S+; link is DOWN/ ) {
    $ex{fc_stats} .= (shift @lines)."\n";
    while ( $lines[0] =~ /^$/ ) { $ex{fc_stats} .= (shift @lines)."\n" }
  }
  
  # FC device map

  while ( $lines[0] =~ /^Loop Map for channel/ ) {
    $ex{fc_dev_map} .= (shift @lines)."\n";
    while ( $lines[0] =~ /^(Translated Map|Shelf mapping|(Target SES devices|Initiators) on this loop):/ ) {
      $ex{fc_dev_map} .= (shift @lines)."\n";
      while ( $lines[0] =~ /^$/ ) { $ex{fc_dev_map} .= (shift @lines)."\n" }
      while ( $lines[0] =~ /^\s+(Shelf (\d+|Unknown):\s+)?((\d+|XXX)\s+)+/ ) {
        $ex{fc_dev_map} .= (shift @lines)."\n";
      }
      while ( $lines[0] =~ /^$/ ) { $ex{fc_dev_map} .= (shift @lines)."\n" }
    }
    while ( $lines[0] =~ /^$/ ) { $ex{fc_dev_map} .= (shift @lines)."\n" }
    while ( $lines[0] =~ /^Cannot complete operation on channel \S+; link is DOWN/ ) {
      $ex{fc_dev_map} .= (shift @lines)."\n";
      while ( $lines[0] =~ /^$/ ) { $ex{fc_dev_map} .= (shift @lines)."\n" }
    }
  }

  # FC link stats
  
  while ( $lines[0] =~ /Loop\s+Link\s+Transport\s+Loss of\s+Invalid\s+Frame In\s+Frame Out/ ) {
    $ex{fc_link_stats} .= (shift @lines)."\n".(shift @lines)."\n".(shift @lines)."\n";
    while ( $lines[0] =~ /^\w+\.\w+(\s+\d+){6}$/ ) {
      $ex{fc_link_stats} .= (shift @lines)."\n";
    }
    while ( $lines[0] =~ /^$/ ) { $ex{fc_link_stats} .= (shift @lines)."\n" }
    while ( $lines[0] =~ /^Cannot complete operation on channel \S+; link is DOWN/ ) {
      $ex{fc_link_stats} .= (shift @lines)."\n";
      while ( $lines[0] =~ /^$/ ) { $ex{fc_link_stats} .= (shift @lines)."\n" }
    }
  }
  
  # Registry
  
  while ( $lines[0] =~ /^Loaded=/ ) {
    for ( 1 .. 15 ) { $ex{registry} .= (shift @lines)."\n"; }
  }
  
  # Usage
  
  while ( $lines[0] =~ /^[\w\-]+(\.[\w\-]+)+=\d+$/ ) {
    $ex{usage} .= (shift @lines)."\n";
  }

  # ACP list all
  
  $ex{acp_list_all} .= (shift @lines)."\n" if $lines[0] =~ /^\[acpadmin list.all\]/;
  $ex{acp_list_all} .= (shift @lines)."\n" if length($ex{acp_list_all}) and $lines[0] =~ /^$/;
  
  # DNS info? - should be IP?

  $ex{dns_info} .= (shift @lines)."\n" if $lines[0] =~ /^IP\s+MAC/;
  $ex{dns_info} .= (shift @lines)."\n" if $lines[0] =~ /^Address\s+Address/;
  $ex{dns_info} .= (shift @lines)."\n" if $lines[0] =~ /^\-+$/;
  while ( $lines[0] =~ /^\d{1,3}(\.\d{1,3}){3}/ ) {
    $ex{dns_info} .= (shift @lines)."\n";
  } 
  
  # media_scrub

  while ( $lines[0] =~ /^\S+ media_scrub: status/ ) {
    $ex{media_scrub} .= (shift @lines)."\n";
    while ( $lines[0] =~ /^\t\S/ ) {
      $ex{media_scrub} .= (shift @lines)."\n";
    }
    $ex{media_scrub} .= (shift @lines)."\n" if $lines[0] =~ /^$/;
  }
  
  # scrub
  
  while ( $lines[0] =~ /^\S+ scrub/ ) {
    $ex{scrub} .= (shift @lines)."\n";
  }

  # UNKNOWN
  
  while ( $lines[0] =~ /^(Aggregate|Volume) \S+ \(.+?\) \(.+?\)$/ ) {
    $ex{UNKNOWN} .= (shift @lines)."\n";
    while ( $lines[0] =~ /^  Plex \S+ \(.+?\)$/ ) {
      $ex{UNKNOWN} .= (shift @lines)."\n";
      while ( $lines[0] =~ /^    RAID group \S+ \(.+?\)$/ ) {
        $ex{UNKNOWN} .= (shift @lines)."\n";
        $ex{UNKNOWN} .= (shift @lines)."\n" if $lines[0] =~ /^$/;
        $ex{UNKNOWN} .= (shift @lines)."\n" if $lines[0] =~ /^      RAID Disk/;
        $ex{UNKNOWN} .= (shift @lines)."\n" if $lines[0] =~ /^      ---------/;
        while ( $lines[0] =~ /^      (dparity|data|parity)/ ) {
          $ex{UNKNOWN} .= (shift @lines)."\n";
        }
        while ( $lines[0] =~ /^$/ ) {
          $ex{UNKNOWN} .= (shift @lines)."\n";
        }
      }
    }
  }

  # aggr_status
  
  while ( $lines[0] =~ /^\s+Aggr\s+State\s+Status\s+Options/ ) {
    $ex{aggr_status} .= (shift @lines)."\n";
    while ( $lines[0] =~ /^(\s+)?\S+\s+(online|offline|restricted)\s+\S+/ ) {
      $ex{aggr_status} .= (shift @lines)."\n";
      while ( $lines[0] =~ /^\s{14}\s+\S+/ ) {
        $ex{aggr_status} .= (shift @lines)."\n";
      }
    }
    $ex{aggr_status} .= (shift @lines)."\n" if $lines[0] =~ /^$/;
    while ( $lines[0] =~ /^\s+Volumes: \S/ ) {
      $ex{aggr_status} .= (shift @lines)."\n";
      while ( $lines[0] =~ /^\s{8}\s+\S/ ) {
        $ex{aggr_status} .= (shift @lines)."\n";
      }
      $ex{aggr_status} .= (shift @lines)."\n" if $lines[0] =~ /^$/;
      while ( $lines[0] =~ /^\s+Plex \S+: \S/ ) {
        $ex{aggr_status} .= (shift @lines)."\n";
        while ( $lines[0] =~ /^\s+RAID group \S+: \S/ ) {
          $ex{aggr_status} .= (shift @lines)."\n";
        }
      }
      $ex{aggr_status} .= (shift @lines)."\n" if $lines[0] =~ /^$/;
      while ( $lines[0] =~ /^\s*\S+\s+(online|offline|restricted)(\s+\S+){2}/ ) {
        $ex{aggr_status} .= (shift @lines)."\n";
        while ( $lines[0] =~ /^\s{19}\s+\S+/ ) {
          $ex{aggr_status} .= (shift @lines)."\n";
        }
      }
      $ex{aggr_status} .= (shift @lines)."\n" if $lines[0] =~ /^$/;
    }
  }

  # xml

  while (  scalar(@lines) and $lines[0] =~ /<\?xml version="1.0"\?>/ ) {
    my $xml = (shift @lines)."\n";
    while ( scalar(@lines) and $lines[0] !~ /<\?xml version="1.0"\?>/ ) {
      $xml .= (shift @lines)."\n";
    }
    push @{$ex{xml}}, $xml;
  }

  $ex{_REMAINDER} = \@lines;
  
  return \%ex;
}

=head3 parse($raw)

Returns an array of hash references representing key information:

  ( \%header, \%luns, \%qtree, \%vols )

=cut

sub parse {
	my $asup = shift @_;
	my $extracts = ASUP::extract($asup);
	
	my $df           = $extracts->{df};
	my $export       = $extracts->{export};
	my $header       = $extracts->{header};
	my $lun_conf     = $extracts->{lun_conf};
	my $qtree_stat   = $extracts->{qtree_stat};
	my $sysconfig_a  = $extracts->{sysconfig_a};
	my $vol_status   = $extracts->{vol_status};
	my $xheader      = $extracts->{xheader};

	Interface::warning("NO DF DATA IN ASUP")     unless $df;
	Interface::warning("NO EXPORT DATA IN ASUP") unless $export;
	Interface::warning("NO HEADER DATA IN ASUP") unless $header;
	# It's ok not to have LUN conf data
	Interface::warning("NO QTREE STATUS IN ASUP")      unless $qtree_stat;
	Interface::warning("NO SYSYCONFIG-A DATA IN ASUP") unless $sysconfig_a;
	Interface::warning("NO VOL-STATUS DATA IN ASUP")   unless $vol_status;
	Interface::warning("NO X-HEADER DATA IN ASUP")     unless $xheader;

	# Parse header data;

	my %header = Parse::NetApp::ASUP::parse_header($header);
	Interface::message("Filer version is $header{short_version}");
	my %xheader = Parse::NetApp::ASUP::parse_xheader($xheader);
        for my $key ( keys %xheader ) { $header{$key} = $xheader{$key} unless defined $header{$key}; }

	# Parse sysconfig data

	my %sysconfig = Parse::NetApp::ASUP::parse_sysconfig($sysconfig_a);

	# Add to header info, unless already there
	for my $key ( keys %sysconfig ) {
		if ( defined $header{$key} and $key ne 'SERIAL_NUM' ) {
			Interface::warning("Skipping duplicate sysconfig data of type $key alread found in \%header");
			push(@Parse::NetApp::ASUP::concerns,"Duplicate header data of type $key");
		} elsif ( defined $header{$key} and $header{$key} ne $sysconfig{$key} ) {
			Interface::warning("Duplicate header key of $key has varying data: [$header{$key}] vs [$sysconfig{$key}]");
			push(@Parse::NetApp::ASUP::concerns,"Duplicate and varied header data of type $key");
		} else {
			$header{$key} = $sysconfig{$key};
		}
	}

	# Parse LUN CONFiguration

	my %luns = Parse::NetApp::ASUP::parse_lun($lun_conf);
	Interface::message("No lun data found.") unless scalar( keys %luns ) or not defined $lun_conf;

	# Parse DF

	my %vols = Parse::NetApp::ASUP::parse_df($df);
	Interface::warning("NO VOLUME DATA!") unless scalar( keys %vols );

	# Parse qtree status data

	my %qtree = Parse::NetApp::ASUP::parse_qtree($qtree_stat);

	# Form a deduped list of styles of qtree on each volume for the Volume tab
	my %dedupe;
	for my $key ( keys %qtree ) {
		my $vol   = $qtree{$key}{volume};
		my $style = $qtree{$key}{style};
		$dedupe{$vol}{$style}++;
	}

	for my $vol ( keys %dedupe ) {
		my @qtree_styles = sort keys %{ $dedupe{$vol} };
		$vols{$vol}{qtree} = join( ', ', @qtree_styles );
	}

	# parse Volstatus data

	my %volstatus = Parse::NetApp::ASUP::parse_volstatus($vol_status);

	# Add to vol info, unless already there
	for my $volume ( keys %volstatus ) {
		for my $key ( keys %{ $volstatus{$volume} } ) {

			if ( defined $vols{$volume}{$key} ) {
				Interface::warning("Skipping duplicate volstatus data of type $key alread found in \%vols");
			} else {
				$vols{$volume}{$key} = $volstatus{$volume}{$key};
			}
		}
	}

	# Parse Export dara

	my %export = Parse::NetApp::ASUP::parse_export($export);

	for my $exported ( keys %export ) {
		for my $volume ( keys %vols ) {
			my $test = defined $vols{$volume}{mounted_on} ? $vols{$volume}{mounted_on} : '';
			chop $test if $test =~ /\/$/; # The export can have a closing slash
			if ( $exported =~ /^$test$/ ) {
				$vols{$volume}{export} = $export{$exported};
			}
		}
	}

	return ( \%header, \%luns, \%qtree, \%vols );
}

=head1 PARSE METHODS:

Parse methods first extract the raw section and then parse them into a perl
data structure for quick usage.

=head3 parse_df()

=cut

sub parse_df {
	my %vols = ();
	return ( wantarray ? %vols : \%vols ) unless defined $_[0];

	my @df = split "\n", $_[0];
	shift @df if $df[0] =~ /^===== DF =====$/;
	shift @df if $df[0] =~ /^Filesystem.*kbytes.*used.*avail.*capacity.*Mounted on/; # Drop the header row

	while ( scalar(@df) ) {
		my $line = shift @df;
		$line .= shift @df if defined $df[0] and $df[0] =~ /^[\w\d\/\.]+$/;          # Wraparound on mount point

		Carp::croak "BAD DF LINE: '$line'\n"
			unless $line =~ /^([\w\d\/\.]+).+?(\d+).+?(\d+).+?(\d+).+?([-\d]+\%).+?([\w\d\/\.]+)$/;

		my ( $volume, $kbytes, $used, $avail, $capacity, $mounted_on ) = ( $1, $2, $3, $4, $5, $6 );
		next if $volume =~ /.snapshot$/;

		my $volname_clean = $volume;
		$volname_clean =~ s/^\/vol\///i;
		$volname_clean =~ s/\/$//;

		next if $volume =~ /^snap$/;

		$vols{$volname_clean}{kbytes}     = $kbytes;
		$vols{$volname_clean}{used}       = $used;
		$vols{$volname_clean}{avail}      = $avail;
		$vols{$volname_clean}{capacity}   = $capacity;
		$vols{$volname_clean}{mounted_on} = $mounted_on;
	}

	return wantarray ? %vols : \%vols;
}

=head3 parse_export()

=cut

sub parse_export {
	my %export = ();
	return ( wantarray ? %export : \%export ) unless defined $_[0];
	my @lines = split "\n", $_[0];
	for my $line (@lines) {
		next if $line =~ /^#/;
		next if $line =~ /^\s*$/;
		if ( $line =~ /^(\S+)\s+(\S+)$/ ) {
			$export{$1} = $2;
		}
	}
	return wantarray ? %export : \%export;
}

=head3 parse_header()

=cut

sub parse_header {
	my %header = ();
	return ( wantarray ? %header : \%header ) unless defined $_[0];

	my @lines = split "\n", $_[0];
	for my $line (@lines) {
		next if $line =~ /^\s*$/;
		if ( $line =~ /Console is using (.+)$/ ) {
			$header{'console_charset'} = $1;
		} elsif ( $line =~ /^(\w+)=(.*)$/ ) {
			$header{$1} = $2;
		} elsif ( $line =~ /Console encoding is nfs but/ ) {
			chomp $line;
			$header{warnings} = $line;
		} else {
			Carp::croak "Bad header line: [$line]";
		}
	}

	$header{short_version} = $1 if $header{VERSION} =~ /NetApp Release ([\w\.\d]+)/;
	$header{short_version} = $header{VERSION} unless defined $header{short_version};

	return wantarray ? %header : \%header;
}

=head3 parse_lun()

=cut

sub parse_lun {
	my %luns = ();
	return ( wantarray ? %luns : \%luns ) unless length $_[0]; # Return empty if no data given

	my @lun_conf = split "\n", $_[0];

	shift @lun_conf while $lun_conf[0] =~ /^\s*$/;
	shift @lun_conf if defined $lun_conf[0] and $lun_conf[0] eq '===== LUN CONFIGURATION ====='; # remove the first line

	my $regex_lun_name = Parse::NetApp::ASUP::_regex_lun_name();

	while ( scalar(@lun_conf) ) {
		my $line = shift @lun_conf;
		next if $line =~ /^\s*$/;
		
		Carp::croak "Bad LUN Conf Line: [$line]" unless $line =~ /^(\s{7,8}|\t)(\/.+)$/;

		my $rawluninfo = $2;

		$rawluninfo .= ' ' . shift @lun_conf if $lun_conf[0] =~ /^\w/; # handle possible line wrap
		$rawluninfo =~ /^($regex_lun_name) +(.*?) +\((\d+)\).*\((.+)\)$/
			or Carp::croak "CAN'T PARSE LUN SUMMARY LINE: [$rawluninfo]";

		my $lun = $1;

		$luns{$lun}{_size}     = $2;
		$luns{$lun}{_raw_size} = $3;
		$luns{$lun}{_status}   = $4;

		$luns{$lun}{_size} =~ s/g$//;
		$luns{$lun}{_size} = ( $1 * 1024 ) if $luns{$lun}{_size} =~ /(.*)t$/;

		while ( defined $lun_conf[0] and $lun_conf[0] =~ /^(\s{15,16}|\t{2})(\w.+): (.+)$/ ) {
			my $name = $2; my $data = $3;
			shift @lun_conf;

			$data .= ' ' . shift @lun_conf
				if defined $lun_conf[0] and $lun_conf[0] =~ /^\w/; # handle possible line wrap

			$luns{$lun}{$name} = $data;
		}
	}
	return wantarray ? %luns : \%luns;
}

=head3 parse_qtree()

=cut

sub parse_qtree {
	my %qtree = ();
	return ( wantarray ? %qtree : \%qtree ) unless defined $_[0];

	my @lines = split "\n", $_[0];
	shift @lines if $lines[0] eq '===== QTREE-STATUS =====';

	my $reg_path = Parse::NetApp::ASUP::_regex_path();
	my $reg_qtree_name = Parse::NetApp::ASUP::_regex_qtree_name();
	my $reg_vol_name = Parse::NetApp::ASUP::_regex_vol_name();

	for my $line (@lines) {
		next if $line =~ /^[\s-]*$/;                                 # Blank and line rows.
		next if $line =~ /Volume.*Tree.*Style.*Oplocks.*Status.*ID/; # header row

		my ( $vol, $tree, $style, $oplocks, $status, $id, $vfiler );
		if ( $line =~ /^($reg_vol_name)\s+($reg_qtree_name)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\d+)\s+(\w+)$/ ) { # v8 with vfiler
			( $vol, $tree, $style, $oplocks, $status, $id, $vfiler ) = ( $1, $2, $3, $4, $5, $6, $7 );
		} elsif ( $line =~ /^($reg_vol_name)\s+($reg_qtree_name)\s+(\w+)\s+(\w+)\s+(\d+)\s+(\w+)$/ ) {          # v8
			( $vol, $style, $oplocks, $status, $id, $vfiler ) = ( $1, $2, $3, $4, $5, $6 );
		} elsif ( $line =~ /^($reg_vol_name)\s+($reg_qtree_name)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\d+)\s*$/ ) { # v7 and earlier
			( $vol, $tree, $style, $oplocks, $status, $id ) = ( $1, $2, $3, $4, $5, $6 );
		} elsif ( $line =~ /^($reg_path)\s+(\w+)\s+(\w+)\s+(\w+)\s+(\d+)\s*$/ ) {               # v7 and earlier
			( $vol, $style, $oplocks, $status, $id ) = ( $1, $2, $3, $4, $5 );
		} else {
			Carp::croak "Bad Qtree Status Line: [$line]\n";
		}
		my $key = $vol . $id;

		$qtree{$key}{volume}  = $vol;
		$qtree{$key}{tree}    = $tree;
		$qtree{$key}{style}   = $style;
		$qtree{$key}{oplocks} = $oplocks;
		$qtree{$key}{status}  = $status;
		$qtree{$key}{id}      = $id;

	}
	return wantarray ? %qtree : \%qtree;
}

=head3 parse_sysconfig()

=cut

sub parse_sysconfig {
	my %sysconfig = ();
	return ( wantarray ? %sysconfig : \%sysconfig ) unless defined $_[0];

	my @lines = split "\n", $_[0];
	for my $line (@lines) {
		$sysconfig{SERIAL_NUM} = $1 if $line =~ /System Serial Number: (\d+) \(/;
	}

	return wantarray ? %sysconfig : \%sysconfig;
}

=head3 parse_volstatus()

=cut

sub parse_volstatus {
	my %vols = ();
	return ( wantarray ? %vols : \%vols ) unless defined $_[0];

	my @lines = split "\n", $_[0];
	shift @lines if $lines[0] eq '===== VOL-STATUS =====';

	my @chunk; my @next_chunk; my $header_count;

	for my $line (@lines) {
		next if $line =~ /^[\s-]*$/; # Blank and line rows.

		if ( $line =~ /Volume.*State.*Status.*Options/ ) { # header row
			$header_count++;
			next;
		}

		last if $header_count > 1;

		if ( scalar(@chunk) > 0 and $line =~ /^\s*([\w\-]+)\s+(online|offline)\s+(.+?)\s\s\s+(.+)$/ ) {
			push @next_chunk, $line;

			my ( $volname, $volopts ) = Parse::NetApp::ASUP::_parse_volstatus_block(@chunk);
			$vols{$volname} = $volopts;

			@chunk      = @next_chunk;
			@next_chunk = ();
		} else {
			push @chunk, $line;
		}
	}

	if (@chunk) {
		my ( $volname, $volinfo ) = Parse::NetApp::ASUP::_parse_volstatus_block(@chunk);
		$vols{$volname} = $volinfo;
	}

	return wantarray ? %vols : \%vols;
}

sub _parse_volstatus_block {
	my @lines = @_;
	my %volinfo;

	my ( $volname, $volstate, $voloptions, $volstatus, $volaggr ) = ('','','','','');
	
	for my $line (@lines) {

		# v7 unused
		next if $line =~ /Plex \//;
		next if $line =~ /RAID group/;
		next if $line =~ /Snapshot autodelete settings/;
		next if $line =~ /Volume autosize settings/;

		# v8
		next if $line =~ /Volume UUID: /;
		next if $line =~ /Volinfo mode: /;
		next if $line =~ /Volume has clones: /;
		next if $line =~ /Clone, backed by volume '/;

		if ( $line =~ /^\s*([\w\-]+)\s+(online|offline|restricted)\s+(.+?)\s\s\s+(.+)$/ ) {
			$volname    = $1;
			$volstate   = $2;
			$volstatus  = $3;
			$voloptions = $4;
		} elsif ( $line =~ /^(\s{27,30}\s*|\t{5})(\S+)\s\s\s+(\S.+)$/ ) { # v7 and earlier
			$volstatus  .= ', ' . $2;
			$voloptions .= $3;
		} elsif ( $line =~ /^(\s{27,30}\s*|\t{5})(.*)$/ ) { # v7 and earlier
			$voloptions .= $2;
		} elsif ( $line =~ /^\s*(\w+=[\w\(\)]+,?)$/ ) {  # v8
			$voloptions .= $1;
		} elsif ( $line =~ /^vol status: Volume '(.*?)' is temporarily busy \(snapmirror destination\)/ ) {
			$volname = $1;
			$volstate = 'busy';
		} elsif ( $line =~ /Containing aggregate: ('[\w\-]+'|<N\/A>)/ ) {
			$volaggr = $1;
			$volaggr = $1 if $volaggr =~ /^'(.+)'$/;
		} else {
			Carp::croak "Bad Vol Status Line: [$line]\n";
		}
	}

	$volinfo{state}     = $volstate;
	$volinfo{status}    = $volstatus;
	$volinfo{options}   = $voloptions;
	$volinfo{aggregate} = $volaggr;
	$volinfo{notes}     = '';

        $volinfo{notes} = 'Volume is offline' if $volstate eq 'offline';
        $volinfo{notes} = 'Volume is busy'    if $volstate eq 'busy';
	push(@Parse::NetApp::ASUP::concerns, "$volname is marked as $volstate") if $volstate eq 'offline' or $volstate eq 'busy';

	return ( $volname, \%volinfo );
}

=head3 parse_xheader()

=cut

sub parse_xheader {
	my %xheader = ();
	return ( wantarray ? %xheader : \%xheader ) unless defined $_[0];

	my @lines = split "\n", $_[0];
	shift @lines if $lines[0] eq '===== X-HEADER DATA =====';

	for my $line (@lines) {
		next if $line =~ /^\s*$/;
		if ( $line =~ /^([\w\-]+): (.*)$/ ) {
			$xheader{$1} = $2;
		} else {
			Carp::croak "Bad x-header line: [$line]";
		}
	}

	return wantarray ? %xheader : \%xheader;
}

=head1 EXTRACT METHODS:

=head3 extract_acp_list_all()

=cut

sub extract_acp_list_all {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ACP LIST ALL =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_aggr_status()

=cut

sub extract_aggr_status {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0]; 
	my $trim = '';	
	if ( $raw =~ /(===== AGGR-STATUS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_cf_monitor()

=cut

sub extract_cf_monitor {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== CF MONITOR =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_cifs_domaininfo()

=cut

sub extract_cifs_domaininfo {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== CIFS DOMAININFO =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_cifs_sessions()

=cut

sub extract_cifs_sessions {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== CIFS SESSIONS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_cifs_shares()

=cut

sub extract_cifs_shares {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== CIFS SHARES =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_cifs_stat()

=cut

sub extract_cifs_stat {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== CIFS STAT =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_cluster_monitor()

=cut

sub extract_cluster_monitor {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== CLUSTER MONITOR =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_df()

=cut

sub extract_df {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== DF =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_df_a()

=cut

sub extract_df_a {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== DF-A =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_df_i()

=cut

sub extract_df_i {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== DF-I =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_df_r()

=cut

sub extract_df_r {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== DF-R =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_df_s()

=cut

sub extract_df_s {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== DF-S =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_dns_info()

=cut

sub extract_dns_info {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== DNS info =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_ecc_memory_scrubber_stats()

=cut

sub extract_ecc_memory_scrubber_stats {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ECC MEMORY SCRUBBER STATS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_environment()

=cut

sub extract_environment {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ENVIRONMENT =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_exports()

=cut

sub extract_exports {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	
	# v7
	
	if ( $raw =~ /(===== EXPORTS =====.*?)=====/s ) {
		my $trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
		return $trim;
	}
	
	# v8

	my @lines = Parse::NetApp::ASUP::_agnostic_line_split($raw);
	my @trim;
		
	while ( @lines ) {
		my $line = shift @lines;
		if ( $line =~ /^\/vol\/\S+\s+-sec=/ ) {
			@trim = ( $line );
			while ( $lines[0] =~ /^\/vol\/\S+\s+-sec=/ ) {
				push @trim, shift @lines;
			}
		}
	}
	
	return join("\n",@trim) . "\n" if scalar(@trim);

	# give up
	return '';
}

=head3 extract_failed_disk_registry()

=cut

sub extract_failed_disk_registry {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== FAILED_DISK_REGISTRY =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_fc_device_map()

=cut

sub extract_fc_device_map {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== FC DEVICE MAP =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_fc_link_stats()

=cut

sub extract_fc_link_stats {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== FC LINK STATS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_fc_stats()

=cut

sub extract_fc_stats {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== FC STATS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_fcp_cfmode()

=cut

sub extract_fcp_cfmode {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== FCP CFMODE =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_fcp_initiator_status()

=cut

sub extract_fcp_initiator_status {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== FCP INITIATOR STATUS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_fcp_status()

=cut

sub extract_fcp_status {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== FCP STATUS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_fcp_target_adapters()

=cut

sub extract_fcp_target_adapters {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== FCP TARGET ADAPTERS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_fcp_target_configuration()

=cut

sub extract_fcp_target_configuration {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== FCP TARGET CONFIGURATION =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_fcp_target_stats()

=cut

sub extract_fcp_target_stats {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== FCP TARGET STATS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_flash_card_info()

=cut

sub extract_flash_card_info {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== FLASH CARD INFO =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_fmm_data()

=cut

sub extract_fmm_data {
	# Space at end to handle lots of equal signs in the content.
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== FMM-DATA =====.*?)===== /s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_fpolicy()

=cut

sub extract_fpolicy {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== FPOLICY =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_headers()

=cut

sub extract_headers {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(GENERATED_ON=.*?\n)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_hosts()

=cut

sub extract_hosts {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== HOSTS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_httpstat()

=cut

sub extract_httpstat {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== HTTPSTAT =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_hwassist_stats()

=cut

sub extract_hwassist_stats {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== HWASSIST_STATS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_ifconfig_a()

=cut

sub extract_ifconfig_a {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== IFCONFIG-A =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_ifgrp_status()

=cut

sub extract_ifgrp_status {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== IFGRP-STATUS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_ifstat_a()

=cut

sub extract_ifstat_a {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== IFSTAT-A =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_initiator_groups()

=cut

sub extract_initiator_groups {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== INITIATOR GROUPS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_interconnect_config()

=cut

sub extract_interconnect_config {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== INTERCONNECT CONFIG =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_interconnect_stats()

=cut

sub extract_interconnect_stats {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== INTERCONNECT STATS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_iscsi_alias()

=cut

sub extract_iscsi_alias {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ISCSI ALIAS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_iscsi_connections()

=cut

sub extract_iscsi_connections {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ISCSI CONNECTIONS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_iscsi_initiator_status()

=cut

sub extract_iscsi_initiator_status {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ISCSI INITIATOR STATUS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_iscsi_interface()

=cut

sub extract_iscsi_interface {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ISCSI INTERFACE =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_iscsi_interface_accesslist()

=cut

sub extract_iscsi_interface_accesslist {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ISCSI INTERFACE ACCESSLIST =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_iscsi_isns()

=cut

sub extract_iscsi_isns {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ISCSI ISNS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_iscsi_nodename()

=cut

sub extract_iscsi_nodename {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ISCSI NODENAME =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_iscsi_portals()

=cut

sub extract_iscsi_portals {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ISCSI PORTALS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_iscsi_security()

=cut

sub extract_iscsi_security {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ISCSI SECURITY =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_iscsi_sessions()

=cut

sub extract_iscsi_sessions {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ISCSI SESSIONS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_iscsi_statistics()

=cut

sub extract_iscsi_statistics {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ISCSI STATISTICS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_iscsi_status()

=cut

sub extract_iscsi_status {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ISCSI STATUS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_iscsi_target_portal_groups()

=cut

sub extract_iscsi_target_portal_groups {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ISCSI TARGET PORTAL GROUPS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_lun_config_check()

=cut

sub extract_lun_config_check {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== LUN CONFIG CHECK =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_lun_configuration()

=cut

sub extract_lun_configuration {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	
	# v7
	
	if ( $raw =~ /(===== LUN CONFIGURATION =====.*?)=====/s ) {
		my $trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
		return $trim;
	}
	
	#v8
	
	my @lines = Parse::NetApp::ASUP::_agnostic_line_split($raw);
	my @trim;
	
	my $regex_lun_name = Parse::NetApp::ASUP::_regex_lun_name();
	
	while ( @lines ) {
		while ( $lines[0] =~ /^(\s{7,8}|\t)($regex_lun_name) +(.*?) +\((\d+)\).*\((.+)\)$/ ) {
			push @trim, shift @lines;
			push @trim, shift @lines if $lines[0] =~ /^\w/; # handle word-wrap on summary line
			while ( $lines[0] =~ /^(\s{7,8}|\t)\s+.+: .+$/ ) {
				push @trim, shift @lines;
			}
		}
		shift @lines;
	}
	
	return join("\n",@trim) . "\n" if scalar(@trim);	

	# give up
	return '';
}

=head3 extract_lun_hist()

=cut

sub extract_lun_hist {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== LUN HIST =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_lun_statistics()

=cut

sub extract_lun_statistics {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== LUN STATISTICS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_messages()

=cut

sub extract_messages {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';

	if ( $raw =~ /(===== MESSAGES =====.*?)(=====|\n\n)/s ) { # Often the last item
		$trim = $1;
	}
	
	if ( not $trim and $raw =~ /((^[MTWFS][ouehra][neduit] [JFMASOND]\w\w \d\d? \d\d:\d\d:\d\d [A-Z]{1,4}([\+\-]\d+)? (\[[^\]]+]: .+?|last message repeated \d+ times.+?)\n\n?)+)/ms ) { # v8 is unlabelled
		$trim = "===== MESSAGES =====\n" . $1;
	}

	while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }

	undef($raw);
	return $trim;	
}

=head3 extract_nbtstat_c()

=cut

sub extract_nbtstat_c {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== NBTSTAT-C =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_netstat_s()

=cut

sub extract_netstat_s {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== NETSTAT-S =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_nfsstat_cc()

=cut

sub extract_nfsstat_cc {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== NFSSTAT-CC =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_nfsstat_d()

=cut

sub extract_nfsstat_d {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== NFSSTAT-D =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_nis_info()

=cut

sub extract_nis_info {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== NIS info =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_nsswitch_conf()

=cut

sub extract_nsswitch_conf {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== NSSWITCH-CONF =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_options()

=cut

sub extract_options {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== OPTIONS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_portsets()

=cut

sub extract_portsets {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== PORTSETS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_priority_show()

=cut

sub extract_priority_show {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== PRIORITY_SHOW =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_qtree_status()

=cut

sub extract_qtree_status {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];

	# v7

	if ( $raw =~ /(===== QTREE-STATUS =====.*?)=====/s ) {
		my $trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
		return $trim;
   	}

	# v8

	my @lines = Parse::NetApp::ASUP::_agnostic_line_split($raw);
	my @trim;
	
	my $word = Parse::NetApp::ASUP::_regex_path();
	
	while ( @lines ) {
		my $line = shift @lines;
		if ( $line =~ /^Volume\s+Tree\s+Style\s+Oplocks\s+Status\s+ID/ ) {
			@trim = ( $line, shift @lines ); # grab the "---" line too
			while ( $lines[0] =~ /^(${word}\s+){4,6}\d/ ) {
				push @trim, shift @lines;
			}
		}
	}
	
	return join("\n",@trim) . "\n" if scalar(@trim);

	# give up
	return '';
}

=head3 extract_quotas()

=cut

sub extract_quotas {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== QUOTAS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_rc()

=cut

sub extract_rc {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== RC =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_resolv_conf()

=cut

sub extract_resolv_conf {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== RESOLV-CONF =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_route_gsn()

=cut

sub extract_route_gsn {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== ROUTE-GSN=====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sas_adapter_state()

=cut

sub extract_sas_adapter_state {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SAS ADAPTER STATE =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sas_dev_stats()

=cut

sub extract_sas_dev_stats {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SAS DEV STATS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sas_expander_map()

=cut

sub extract_sas_expander_map {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SAS EXPANDER MAP =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sas_expander_phy_state()

=cut

sub extract_sas_expander_phy_state {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SAS EXPANDER PHY STATE =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sas_shelf()

=cut

sub extract_sas_shelf {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SAS SHELF =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_service_usage()

=cut

sub extract_service_usage {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SERVICE USAGE =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_shelf_log_esh()

=cut

sub extract_shelf_log_esh {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SHELF-LOG-ESH =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_shelf_log_iom()

=cut

sub extract_shelf_log_iom {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SHELF-LOG-IOM =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sis_stat()

=cut

sub extract_sis_stat {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SIS STAT =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sis_stat_l()

=cut

sub extract_sis_stat_l {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SIS STAT L =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sis_status()

=cut

sub extract_sis_status {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SIS STATUS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sis_status_l()

=cut

sub extract_sis_status_l {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SIS STATUS L =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sm_allow()

=cut

sub extract_sm_allow {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SM-ALLOW =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sm_conf()

=cut

sub extract_sm_conf {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SM-CONF =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snap_list_n()

=cut

sub extract_snap_list_n {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAP-LIST-N =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snap_list_n_a()

=cut

sub extract_snap_list_n_a {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAP-LIST-N-A =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snap_reserve()

=cut

sub extract_snap_reserve {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAP-RESERVE =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snap_reserve_a()

=cut

sub extract_snap_reserve_a {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAP-RESERVE-A =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snap_sched()

=cut

sub extract_snap_sched {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAP-SCHED =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snap_sched_a()

=cut

sub extract_snap_sched_a {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAP-SCHED-A =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snap_status()

=cut

sub extract_snap_status {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAP-STATUS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snap_status_a()

=cut

sub extract_snap_status_a {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAP-STATUS-A =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snapmirror_destinations()

=cut

sub extract_snapmirror_destinations {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAPMIRROR DESTINATIONS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snapmirror_status()

=cut

sub extract_snapmirror_status {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAPMIRROR STATUS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snapvault_destinations()

=cut

sub extract_snapvault_destinations {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAPVAULT DESTINATIONS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snapvault_snap_sched()

=cut

sub extract_snapvault_snap_sched {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAPVAULT SNAP SCHED =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snapvault_status_l()

=cut

sub extract_snapvault_status_l {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAPVAULT STATUS L =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snaplock()

=cut

sub extract_snaplock {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAPLOCK =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_snaplock_clock()

=cut

sub extract_snaplock_clock {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SNAPLOCK-CLOCK =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_software_licenses()

=cut

sub extract_software_licenses {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SOFTWARE LICENSES =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_ssh()

=cut

sub extract_ssh {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SSH =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_storage()

=cut

sub extract_storage {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== STORAGE =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sysconfig_a()

=cut

sub extract_sysconfig_a {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SYSCONFIG-A =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sysconfig_ac()

=cut

sub extract_sysconfig_ac {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SYSCONFIG-AC =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sysconfig_c()

=cut

sub extract_sysconfig_c {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SYSCONFIG-C =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sysconfig_d()

=cut

sub extract_sysconfig_d {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SYSCONFIG-D =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sysconfig_hardware_ids()

=cut

sub extract_sysconfig_hardware_ids {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SYSCONFIG HARDWARE IDS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sysconfig_m()

=cut

sub extract_sysconfig_m {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SYSCONFIG-M =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_sysconfig_r()

=cut

sub extract_sysconfig_r {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SYSCONFIG-R =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_system_serial_number()

=cut

sub extract_system_serial_number {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== SYSTEM SERIAL NUMBER =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_unowned_disks()

=cut

sub extract_unowned_disks {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== UNOWNED-DISKS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_usage()

=cut

sub extract_usage {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== USAGE =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_usermap_cfg()

=cut

sub extract_usermap_cfg {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== USERMAP-CFG =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_vfiler_startup_times()

=cut

sub extract_vfiler_startup_times {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== VFILER STARTUP TIMES =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_vfilers()

=cut

sub extract_vfilers {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== VFILERS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_vif_status()

=cut

sub extract_vif_status {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== VIF-STATUS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_vlan_stat()

=cut

sub extract_vlan_stat {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== VLAN STAT =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_vol_language()

=cut

sub extract_vol_language {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== VOL-LANGUAGE =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_vol_status()

=cut

sub extract_vol_status {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== VOL-STATUS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_vscan()

=cut

sub extract_vscan {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== VSCAN =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_vscan_options()

=cut

sub extract_vscan_options {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== VSCAN OPTIONS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_vscan_scanners()

=cut

sub extract_vscan_scanners {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	my $trim = '';	
	if ( $raw =~ /(===== VSCAN SCANNERS =====.*?)=====/s ) {
		$trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
	}
	undef($raw);
	return $trim;
}

=head3 extract_xheader()

=cut

sub extract_xheader {
	my $raw = defined $_[0]->{asup} ? $_[0]->{asup} : $_[0];
	
	# v7
	if ( $raw =~ /(===== X-HEADER DATA =====.*?)(=====|\n\n)/s ) {
		my $trim = $1;
		while ( $trim !~ /\n\n$/s ) { $trim .= "\n"; }
		return $trim;
	}

	# v8

	my @lines = Parse::NetApp::ASUP::_agnostic_line_split($raw);
	my @trim;
	
	while ( @lines and $lines[0] !~ /^GENERATED_ON/ ) {
		shift @lines; # Skip the mail header, to avoid confusion
	}
	while ( @lines ) {
		my $line = shift @lines;
		push @trim, $line if $line =~ /^X-Netapp-asup-/;
	}
	
	return join("\n",@trim) . "\n" if scalar(@trim);

	# give up
	return '';
}

1;

=head1 BUGS AND SOURCE

	Bug tracking for this module: https://rt.cpan.org/Dist/Display.html?Name=Parse-NetApp-ASUP

	Source hosting: http://www.github.com/bennie/perl-Parse-NetApp-ASUP

=head1 VERSION

	Parse::NetApp::ASUP v1.14 (2014/02/24)

=head1 COPYRIGHT

	(c) 2012-2014, Phillip Pollard <bennie@cpan.org>

=head1 LICENSE

This source code is released under the "Perl Artistic License 2.0," the text of 
which is included in the LICENSE file of this distribution. It may also be 
reviewed here: http://opensource.org/licenses/artistic-license-2.0