#! /usr/bin/perl
# 
# $Id$
# Checks assumptions on Cc* (Cache Manager) behaviour by reading TraceFS log
# Copyright (C) 2003 Jan Kratochvil <project-captive@jankratochvil.net>
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; exactly version 2 of June 1991 is required
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


use strict;
use warnings;
use Data::Dumper;
use Carp qw(cluck confess);


my $filter=0;
$Data::Dumper::Sortkeys=1;
my $ntfs_blocksize=0x200;

# $Object->{"by"}="CcSomeFunction";
# $Object->{"line_enter"}=123;
# $Object->{"line_leave"}=124;
# $Object->{"ProcessThread"}="0x12345678/0x12345678";
# $Object->{"data"}[dataline]{"FileObject"}="0x12345678";
# $Object->{"data"}[dataline]{"FileName"}="\filename" or undef() if NULL;
# $Object->{"data"}[dataline]{"Flags"}="0x40100";
# $Object->{"data"}[dataline]{"SectionObjectPointer"}="0x12345678";
# $Object->{"data"}[dataline]{"SharedCacheMap"}="0x12345678";
# $FileObject{$FileObject}{"FileObject"}="0x12345678";
# $FileObject{$FileObject}{"SectionObjectPointer"}="0x12345678";
# $SectionObjectPointer{$SectionObjectPointer}{"SectionObjectPointer"}="0x12345678";
# $SectionObjectPointer{$SectionObjectPointer}{"SharedCacheMap"}="0x12345678";
# $SharedCacheMap{$SharedCacheMap}{"SharedCacheMap"}="0x12345678";
# $SharedCacheMap{$SharedCacheMap}{"SectionObjectPointer"}="0x12345678";
# $SharedCacheMap{$SharedCacheMap}{"Allocation"}="0x12345";
# $SharedCacheMap{$SharedCacheMap}{"FileSize"}="0x12345";
# $SharedCacheMap{$SharedCacheMap}{"ref_count"}=1;
# $SharedCacheMap{$SharedCacheMap}{"map"}="0x12345678" (Bcb);
# $SharedCacheMap{$SharedCacheMap}{"pin"}{"0x1000"}="0x12345678" (Bcb) if !Bcb->{"OwnerPointer"};
# $SharedCacheMap{$SharedCacheMap}{"PinAccess"}=0 or 1;
# $SharedCacheMap{$SharedCacheMap}{"LogHandle"}="0x12345678" optional;
# $SharedCacheMap{$SharedCacheMap}{"AcquireForLazyWrite"}=0;	# count
# $LogHandle{$LogHandle}{"LogHandle"}="0x12345678";
# $Bcb{$Bcb}{"Bcb"}="0x12345678";
# $Bcb{$Bcb}{"SharedCacheMap"}="0x12345678";
# $Bcb{$Bcb}{"type"}="pin" or "map";
# $Bcb{$Bcb}{"ref_count"}=1;
# $Bcb{$Bcb}{"FileOffset"}="0x1000" if {"type"} eq "pin";
# $Bcb{$Bcb}{"Buffer"}="0x12345678";	# PAGE_SIZE-aligned for "pin", FileOffset_0-aligned for "map"
# $Bcb{$Bcb}{"OwnerPointer"}="0x12345678" optional;
# $Bcb{$Bcb}{"Lsn"}="0x12345678" optional;
# $Bcb{$Bcb}{"dirty"}=1 optional;
# $MdlChain{$MdlChain}{"MdlChain"}="0x12345678";
# $MdlChain{$MdlChain}{"FileObject"}="0x12345678";
# $MdlChain{$MdlChain}{"FileOffset"}="0x5000";
# $MdlChain{$MdlChain}{"Length"}="0x9000";

my %FileObject;
my %LogHandle;
my %SectionObjectPointer;
my %SharedCacheMap;
my %Bcb;
my %MdlChain;
my %LastLeave;	# $ProcessThread=>[$Object,$Object,...]
my $LastLeave;	# ref copy of the last item for the current $ProcessThread
my $ProcessThread;
my %EnterLeave;
my $EnterLeave;	# ref copy of the list for the current $ProcessThread

END {
	print Data::Dumper->Dump([\%FileObject,\%SectionObjectPointer,\%SharedCacheMap,\%Bcb],
	                       [qw(%FileObject  %SectionObjectPointer  %SharedCacheMap  %Bcb)]) if !$filter;
	}

my $Object;

sub tohex($)
{
my($num)=@_;

	return sprintf("0x%X",$num);
}

sub FObject($)
{
my($FileObject)=@_;

	my $FObject=$FileObject{$FileObject};
	if (!$FObject) {
		my($package,$filename,$line,$subroutine)=caller 0;
		warn "Non-existent FileObject $FileObject by line $line";
		}
	return $FObject;
}

sub delete_FObject($)
{
my($FObject)=@_;

	my $FileObject=$FObject->{"FileObject"};
	delete $FileObject{$FileObject};
}

sub SObject($)
{
my($SectionObjectPointer)=@_;

	my $SObject=$SectionObjectPointer{$SectionObjectPointer};
	if (!$SObject) {
		my($package,$filename,$line,$subroutine)=caller 0;
		warn "Non-existent SectionObjectPointer $SectionObjectPointer by line $line"
		}
	return $SObject;
}

sub SObject_from_FileObject($)
{
my($FileObject)=@_;

	return if !(my $FObject=FObject $FileObject);
	my $SObject=SObject $FObject->{"SectionObjectPointer"};
	if (!$SObject) {
		my($package,$filename,$line,$subroutine)=caller 0;
		warn "by line $line";
		}
	return $SObject;
}

sub delete_CObject($)
{
my($CObject)=@_;

	my $SharedCacheMap=$CObject->{"SharedCacheMap"};
	do { warn "Trailing map $_ of SharedCacheMap $SharedCacheMap during its deletion" if $_; } for ($CObject->{"map"});
	do { warn "Trailing pin $_ of SharedCacheMap $SharedCacheMap during its deletion" if $_; } for (values(%{$CObject->{"pin"}}));
	if (my $LogHandle=$CObject->{"LogHandle"}) {
		do { warn "INTERNAL: Missing LogHandle $LogHandle for SharedCacheMap $SharedCacheMap"; return; }
			if !(my $LObject=$LogHandle{$LogHandle});
		# Do not delete $LogHandle as it may be used by many SharedCacheMap-s
		}
	delete $SharedCacheMap{$SharedCacheMap};
}

sub CObject($)
{
my($SharedCacheMap)=@_;

	my $CObject=$SharedCacheMap{$SharedCacheMap};
	if (!$CObject) {
		my($package,$filename,$line,$subroutine)=caller 0;
		warn "Non-existent SharedCacheMap $SharedCacheMap by line $line";
		}
	return $CObject;
}

sub CObject_from_FileObject($)
{
my($FileObject)=@_;

	return if !(my $SObject=SObject_from_FileObject $FileObject);
	return if !(my $CObject=CObject $SObject->{"SharedCacheMap"});
	return $CObject;
}

sub SharedCacheMap_valid($)
{
my($SharedCacheMap)=@_;

	cluck if !defined $SharedCacheMap;
	return 0 if "0x".("F"x8) eq $SharedCacheMap;
	return 0 if !eval $SharedCacheMap;
	return 1;
}

sub check_data($)
{
my($data)=@_;

	if (!eval $data->{"SectionObjectPointer"}) {
		return if $Object->{"by"} eq "IRP_MJ_CREATE";	# SectionObjectPointer is not yet initialized
		warn "Existing FileObject ".$data->{"FileObject"}." but no SectionObjectPointer found"
				if $FileObject{$data->{"FileObject"}} && eval($FileObject{$data->{"FileObject"}}{"SectionObjectPointer"});
		return;
		}
	my $SectionObjectPointer=$data->{"SectionObjectPointer"};
	if (!SharedCacheMap_valid $data->{"SharedCacheMap"} && $SectionObjectPointer{$SectionObjectPointer}) {
		return if !(my $SObject=SObject $SectionObjectPointer);
		my $SharedCacheMap=$SObject->{"SharedCacheMap"};
		return if !eval $SharedCacheMap;
		my $CObject=CObject $SharedCacheMap;
		warn "Existing SectionObjectPointer ".$data->{"SectionObjectPointer"}." but no SharedCacheMap found,"
						." ref_count of SharedCacheMap is ".$CObject->{"ref_count"}
				if $CObject->{"ref_count"};
#				if $SectionObjectPointer{$data->{"SectionObjectPointer"}};
		# SharedCacheMap was droppped by async task as it had ref_count==0.
		delete_CObject $CObject;
		$SObject->{"SharedCacheMap"}=tohex(0);
		# FileObject is still valid!
		return;
		}
	return if !$FileObject{$data->{"FileObject"}};
	return if !(my $FObject=FObject $data->{"FileObject"});
	return if !(my $SObject=SObject $FObject->{"SectionObjectPointer"});
	my $SharedCacheMap=$SObject->{"SharedCacheMap"};
	warn "FileObject ".$FObject->{"FileObject"}." SectionObjectPointer ".$SObject->{"SectionObjectPointer"}
					." expected SharedCacheMap $SharedCacheMap"
					." but found SharedCacheMap ".$data->{"SharedCacheMap"}
			if $SharedCacheMap ne $data->{"SharedCacheMap"};
	warn "INTERNAL: SharedCacheMap $SharedCacheMap of FileObject ".$FObject->{"FileObject"}." got destroyed"
			if !$SharedCacheMap{$SharedCacheMap};
}

sub CcInitializeCacheMap($$$$$)
{
my($FileObject,$AllocationSize,$FileSize,$ValidDataLength,$PinAccess)=@_;

	$ValidDataLength=$FileSize if $ValidDataLength==eval("0x".("F"x8));
	$Object->{"ref_count"}=1;
	$Object->{"AllocationSize"}=tohex($AllocationSize);
	$Object->{"FileSize"}=tohex($FileSize);
	$Object->{"ValidDataLength"}=tohex($ValidDataLength);
	$Object->{"map"}=undef();
	$Object->{"pin"}={};
	$Object->{"PinAccess"}=$PinAccess;
	$Object->{"FileObject"}=$FileObject;
}

sub CcInitializeCacheMap_leave()
{
	my $SharedCacheMap=$Object->{"data"}[1]{"SharedCacheMap"};
	$Object->{"SharedCacheMap"}=$SharedCacheMap;
	my $old=$SharedCacheMap{$SharedCacheMap};
	if (!SharedCacheMap_valid $Object->{"data"}[0]{"SharedCacheMap"} && $old) {
		# SharedCacheMap got deleted in the meantime
		delete_CObject CObject $SharedCacheMap;
		my $SObject=SObject $Object->{"data"}[0]{"SectionObjectPointer"};
		$SObject->{"SharedCacheMap"}=tohex(0);
		$old=undef();
		}
	if (!$old != !SharedCacheMap_valid $Object->{"data"}[0]{"SharedCacheMap"}) {
		warn "Expecting old SharedCacheMap validity ".(!!$old)
				." but found old SharedCacheMap ".$Object->{"data"}[0]{"SharedCacheMap"};
		}
	warn "New SharedCacheMap ".$Object->{"data"}[1]{"SharedCacheMap"}." is not valid"
			if !SharedCacheMap_valid $Object->{"data"}[1]{"SharedCacheMap"};
	if (SharedCacheMap_valid $Object->{"data"}[0]{"SharedCacheMap"}) {
		warn "Existing SharedCacheMap changed"
						." from ".$Object->{"data"}[0]{"SharedCacheMap"}." to ".$Object->{"data"}[1]{"SharedCacheMap"}
				if $Object->{"data"}[0]{"SharedCacheMap"} ne $Object->{"data"}[1]{"SharedCacheMap"};
		}
	if ($old) {
		for my $field (qw(AllocationSize FileSize PinAccess)) {
			warn "SharedCacheMap $SharedCacheMap old instance $field ".$old->{$field}
							." != new instance $field ".$Object->{$field}
					if $old->{$field} ne $Object->{$field};
			}
		do { warn "Existing map Bcb $_ during CcInitializeCacheMap()" if $_; } for ($old->{"map"});
		do { warn "Existing pin Bcb $_ during CcInitializeCacheMap()" if $_; } for (values(%{$old->{"pin"}}));
		$Object->{"ref_count"}+=$old->{"ref_count"};
		}
	$SharedCacheMap{$SharedCacheMap}=$Object;

	warn "Changed SectionObjectPointer inside CcInitializeCacheMap()"
					." from ".$Object->{"data"}[0]{"SectionObjectPointer"}." to ".$Object->{"data"}[1]{"SectionObjectPointer"}
			if $Object->{"data"}[0]{"SectionObjectPointer"} ne $Object->{"data"}[1]{"SectionObjectPointer"};
	my $SectionObjectPointer=$Object->{"data"}[1]{"SectionObjectPointer"};

	my $FileObject=$Object->{"FileObject"};
	if (my $FObject=$FileObject{$FileObject}) {
		if (my $SObject=$SectionObjectPointer{$FObject->{"SectionObjectPointer"}}) {
			warn "Changed SectionObjectPointer of FileObject $FileObject"
							." from ".$FObject->{"SectionObjectPointer"}." to ".$SectionObjectPointer
					if $FObject->{"SectionObjectPointer"} ne $SectionObjectPointer;
			}
		# Otherwise SectionObjectPointer could be deleted and rebuilt async in the meantime.
		}
	$FileObject{$FileObject}={
			"FileObject"=>$FileObject,
			"SectionObjectPointer"=>$SectionObjectPointer,
			};

	if (my $SObject=$SectionObjectPointer{$SectionObjectPointer}) {
		warn "Changed SharedCacheMap of SectionObjectPointer $SectionObjectPointer"
						." from ".$SObject->{"SharedCacheMap"}." to ".$SharedCacheMap
				if $SObject->{"SharedCacheMap"} ne $SharedCacheMap && eval($SObject->{"SharedCacheMap"});
		}
	$SectionObjectPointer{$SectionObjectPointer}={
			"SectionObjectPointer"=>$SectionObjectPointer,
			"SharedCacheMap"=>$SharedCacheMap,
			};

	CcSetFileSizes($FileObject,map({ eval($Object->{$_}); } qw(AllocationSize FileSize ValidDataLength)));
	delete $Object->{$_} for (qw(FileObject ValidDataLength));
}

sub CcUninitializeCacheMap($$)
{
my($FileObject,$TruncateSize)=@_;

	$Object->{"FileObject"}=$FileObject;
}

sub CcUninitializeCacheMap_leave($)
{
my($r)=@_;

	my $FileObject=$Object->{"FileObject"};
	# 'r' means function success.
	# r=0 either if no CcInitializeCacheMap() was called at all
	# or if Cc was unable to detach SharedCacheMap and it remains valid
	# (FIXME: Do we SharedCacheMap->ref_count-- on in such case?).
	my $SectionObjectPointer=$FileObject{$FileObject}->{"SectionObjectPointer"} if $FileObject{$FileObject};
	my $SharedCacheMap=$SectionObjectPointer{$SectionObjectPointer}->{"SharedCacheMap"}
			if $SectionObjectPointer && $SectionObjectPointer{$SectionObjectPointer};
	warn "Unexpected 'r' result $r for CcUninitializeCacheMap of FileObject $FileObject"
			if !(eval($SharedCacheMap) && !SharedCacheMap_valid($Object->{"data"}[1]{"SharedCacheMap"})) != !$r;
	if (!eval $SharedCacheMap) {
		for my $SharedCacheMap ($Object->{"data"}[0]{"SharedCacheMap"},$Object->{"data"}[1]{"SharedCacheMap"}) {
			warn "Not expecting valid SharedCacheMap $SharedCacheMap"
					if SharedCacheMap_valid $SharedCacheMap;
			}
		return;
		}
	for my $SharedCacheMap ($Object->{"data"}[0]{"SharedCacheMap"}) {
		warn "Expecting valid SharedCacheMap $SharedCacheMap"
				if !SharedCacheMap_valid $SharedCacheMap;
		}
	return if !(my $FObject=FObject $FileObject);
	return if !(my $SObject=SObject $FObject->{"SectionObjectPointer"});
	return if !(my $CObject=CObject $SObject->{"SharedCacheMap"});
	if (--$CObject->{"ref_count"}) {
		for my $SharedCacheMap ($Object->{"data"}[1]{"SharedCacheMap"}) {
			warn "Expecting still valid SharedCacheMap $SharedCacheMap after CcUninitializeCacheMap()"
							." with ref_count=".$CObject->{"ref_count"}
					if !SharedCacheMap_valid $SharedCacheMap;
			}
		return;
		}
	if (!SharedCacheMap_valid $Object->{"data"}[1]{"SharedCacheMap"}) {
		delete_CObject $CObject;
		$SObject->{"SharedCacheMap"}=tohex(0);
		# FileObject is still valid!
		}
	else {
		# FIXME: Do we SharedCacheMap->ref_count-- on in such case?
		}
}

sub CcSetFileSizes($$$$)
{
my($FileObject,$AllocationSize,$FileSize,$ValidDataLength)=@_;

	return if !(my $CObject=CObject_from_FileObject $FileObject);
	my $SharedCacheMap=$CObject->{"SharedCacheMap"};
	if ($AllocationSize!=eval($CObject->{"AllocationSize"})) {
		do { warn "Existing map $_ of FileObject $FileObject SharedCacheMap $SharedCacheMap during CcSetAllocationSizes(),"
						." AllocationSize=".$CObject->{"AllocationSize"} if $_; }
				for ($CObject->{"map"});
		do { warn "Existing pin $_ of FileObject $FileObject SharedCacheMap $SharedCacheMap during CcSetAllocationSizes(),"
						." AllocationSize=".$CObject->{"AllocationSize"} if $_; }
				for (values(%{$CObject->{"pin"}}));
		}
	# $ValidDataLength can be > $CObject->{"FileSize"};
	warn "ValidDataLength ".tohex($ValidDataLength)." > FileSize ".tohex($FileSize)
			if $ValidDataLength>$FileSize;
	warn "0 != AllocationSize ".tohex($AllocationSize)." % ntfs_blocksize ".tohex($ntfs_blocksize)
			if 0!=($AllocationSize%$ntfs_blocksize);
	# $AllocationSize can be higher
	warn "FileSize ".tohex($FileSize)." > AllocationSize ".tohex($AllocationSize)
			if $FileSize>$AllocationSize;
	$CObject->{"FileSize"}=tohex($FileSize);
	$CObject->{"AllocationSize"}=tohex($AllocationSize);
}

sub IRP_MJ_CREATE_leave()
{
	do { warn "Non-NULL SectionObjectPointer $_ not expected" if eval($_); } for ($Object->{"data"}[0]{"SectionObjectPointer"});
	my $FileObject=$Object->{"data"}[0]{"FileObject"};
	warn "Existing FileObject $FileObject not expected" if $FileObject{$FileObject};
	my $SectionObjectPointer=$Object->{"data"}[1]{"SectionObjectPointer"};
	# We want to track even FileObject without SectionObjectPointer yet.
#	if ($SectionObjectPointer && $SectionObjectPointer{$SectionObjectPointer})
	{
		$FileObject{$FileObject}={
				"FileObject"=>$FileObject,
				"SectionObjectPointer"=>$SectionObjectPointer,
				};
		}
	if (eval $SectionObjectPointer) {
		my $SharedCacheMap=$Object->{"data"}[1]{"SharedCacheMap"};
		if (my $SObject=$SectionObjectPointer{$SectionObjectPointer}) {
			warn "Changed SharedCacheMap from stored ".$SObject->{"SharedCacheMap"}." to ".$SharedCacheMap
					if $SObject->{"SharedCacheMap"} ne $SharedCacheMap && $Object->{"by"} ne "IRP_MJ_CREATE";
			}
		$SectionObjectPointer{$SectionObjectPointer}={
				"SectionObjectPointer"=>$SectionObjectPointer,
				"SharedCacheMap"=>$SharedCacheMap,
				};
		}
}

sub BObject($)
{
my($Bcb)=@_;

	cluck if !defined $Bcb;
	my $BObject=$Bcb{$Bcb};
	warn "Non-existent Bcb $Bcb" if !$BObject;
	return $BObject;
}

sub delete_BObject($)
{
my($BObject)=@_;

	my $Bcb=$BObject->{"Bcb"};
	warn "Deleting ref_count=".$BObject->{"ref_count"}." Bcb $Bcb" if $BObject->{"ref_count"};
	# Do not: warn "Deleting dirty Bcb $Bcb" if $BObject->{"dirty"};
	# as it is valid to allow sanity check below.
	warn "Deleting dirty Bcb $Bcb" if $BObject->{"dirty"} && $BObject->{"ref_count"};
	return if !(my $CObject=CObject $BObject->{"SharedCacheMap"});
	if ($BObject->{"type"} eq "map") {
		for my $pin (values(%{$CObject->{"pin"}})) {
			next if !defined $pin;
			warn "unpin map but CcPinMappedData pin $pin still exists"
					if $Bcb{$pin}->{"by"} eq "CcPinMappedData";
			}
		}
	for my $ref ($BObject->{"type"} eq "map" ? \$CObject->{"map"} : \$CObject->{"pin"}{$BObject->{"FileOffset"}}) {
		warn "Final unpin but ".$BObject->{"type"}." Bcb $Bcb not registered"
						." in SharedCacheMap ".$CObject->{"SharedCacheMap"}." ref ".($$ref || "<undef>")
				if !defined($BObject->{"OwnerPointer"}) && !($$ref && $$ref eq $Bcb)
						&& !($BObject->{"ref_count"}==0 && $BObject->{"dirty"});
		if ($$ref && $$ref eq $Bcb) {
			$$ref=undef();
			# Do not: delete $CObject->{"pin"}{$BObject->{"FileOffset"}} if $BObject->{"type"} eq "pin";
			# as it would destroy $$ref slot in &Bcb_checkref '($$ref && $Bcb ne $$ref)' codepath.
			}
		}
	delete $Bcb{$Bcb};
}

sub MObject($)
{
my($MdlChain)=@_;

	cluck if !defined $MdlChain;
	my $MObject=$MdlChain{$MdlChain};
	warn "Non-existent MdlChain $MdlChain" if !$MObject;
	return $MObject;
}

sub Bcb_conflict($;@)
{
my($CObject,@Bcb_list)=@_;

	my $arg=0;
	my %check=(
		"map"=>$CObject->{"map"},
		map(("arg".($arg++)=>$_),@Bcb_list),
		%{$CObject->{"pin"}},
		);
	my %reversed;
	my $BufferBase;	# relativized to FileOffset 0
	my $BufferBase_val;
	while (my($key,$val)=each(%check)) {
		next if !defined $val;
		warn "Conflicting Bcb $val of keys $key and ".$reversed{$val}." of SharedCacheMap ".$CObject->{"SharedCacheMap"}
				if $reversed{$val};
		# Buffer base should match even between 'map's and 'pin's
		# as the data are always mapped only once.
		if (my $BObject=BObject $val) {
			my $Buffer=eval $BObject->{"Buffer"};
			$Buffer-=eval($BObject->{"FileOffset"}) if exists $BObject->{"FileOffset"};
			warn "Non-matching Bcb ".$BObject->{"type"}." $val Buffer base ".tohex($Buffer)
							." with Bcb ".$Bcb{$BufferBase_val}->{"type"}." $BufferBase_val BufferBase ".tohex($BufferBase)
					if defined($BufferBase) && $Buffer!=$BufferBase;
			$BufferBase=$Buffer;
			$BufferBase_val=$val;
			}
		$reversed{$val}=$key;
		}
}

# New $BObject will always be forced as the last stored reference.
sub Bcb_checkref($$)
{
my($BObject,$ref)=@_;

	return if !(my $CObject=CObject $BObject->{"SharedCacheMap"});
	my $type=$BObject->{"type"};
	my $Bcb=$BObject->{"Bcb"};
	if ($$ref && $Bcb ne $$ref) {
		my $BObject2=$Bcb{$$ref};
		warn "new $type Bcb $Bcb != old ".$BObject2->{"type"}." Bcb $$ref";
		delete_BObject $BObject2;
		warn "INTERNAL: Trailing ref to Bcb $$ref" if $$ref;
		}
	if ($$ref) {
		my $BObject2=$Bcb{$$ref};
		warn "new $type $Bcb type ".$BObject->{"type"}." != old type $type $$ref type ".$BObject2->{"type"}
				if $BObject->{"type"} ne $BObject2->{"type"};
		warn "new $type $Bcb Buffer ".$BObject->{"Buffer"}." != old $type $$ref Buffer ".$BObject2->{"Buffer"}
				if $BObject->{"Buffer"} ne $BObject2->{"Buffer"};
		}
	if ($$ref && $$ref eq $Bcb) {
		$BObject->{"ref_count"}+=$Bcb{$$ref}->{"ref_count"};
		$$ref=undef();
		}
	$Bcb{$Bcb}=$BObject;	# &Bcb_conflict needs this reference
	Bcb_conflict $CObject,$Bcb;
	$$ref=$Bcb;
}

sub map_new($;$$)
{
my($SharedCacheMap,$FileOffset,$Length)=@_;

	return if !(my $CObject=CObject $SharedCacheMap);
	if (defined($FileOffset) && defined($Length)) {
		warn "Mapping data (end ".tohex($FileOffset+$Length).") out of FileSize ".$CObject->{"FileSize"}
				if $FileOffset+$Length>eval($CObject->{"FileSize"});
		}
	$Object->{"SharedCacheMap"}=$CObject->{"SharedCacheMap"};
	if (defined $FileOffset) {
		$Object->{"FileOffset"}=tohex($FileOffset);
		}
	$Object->{"type"}="map";
	$Object->{"ref_count"}=1;
}

sub map_new_from_FileObject($;$$)
{
my($FileObject,$FileOffset,$Length)=@_;

	return if !(my $CObject=CObject_from_FileObject $FileObject);
	map_new $CObject->{"SharedCacheMap"},$FileOffset,$Length;
}

sub map_new_leave($;$)
{
my($Bcb,$Buffer)=@_;

	$Object->{"Bcb"}=$Bcb;
	return if !(my $CObject=CObject $Object->{"SharedCacheMap"});

	if (defined $Buffer) {
		$Object->{"Buffer"}=tohex(eval($Buffer)-(eval($Object->{"FileOffset"}) || 0));
		}
	delete $Object->{"FileOffset"};

	my $ref=\$CObject->{"map"};
	# There may exist some pin bcbs even if we are creating the new map bcb.
	Bcb_checkref $Object,$ref;
}

sub CcMapData($$$)
{
my($FileObject,$FileOffset,$Length)=@_;

	map_new_from_FileObject $FileObject,$FileOffset,$Length;
}

sub CcMapData_leave($$)
{
my($Bcb,$Buffer)=@_;

	map_new_leave $Bcb,$Buffer;
}

sub pin_new($$$)
{
my($FileObject,$FileOffset,$Length)=@_;

	return if !(my $CObject=CObject_from_FileObject $FileObject);
	warn "Pinning of non-PinAccess FileObject $FileObject" if !$CObject->{"PinAccess"};
	warn "Mapping data (end ".tohex($FileOffset+$Length).") out of FileSize ".$CObject->{"FileSize"}
			if $FileOffset+$Length>eval($CObject->{"FileSize"});
	warn "Pinning Length ".tohex($Length)." > 0x1000" if $Length>0x1000;
	warn "Pinning across file page (start=".tohex($FileOffset).",end-1=".tohex($FileOffset+$Length-1).")"
			if ($FileOffset&~0xFFF)!=(($FileOffset+$Length-1)&~0xFFF);
	$Object->{"SharedCacheMap"}=$CObject->{"SharedCacheMap"};
	$Object->{"FileOffset"}=tohex($FileOffset);
	$Object->{"type"}="pin";
	$Object->{"ref_count"}=1;
}

sub pin_new_leave($$)
{
my($Bcb,$Buffer)=@_;

	$Object->{"Bcb"}=$Bcb;
	return if !(my $CObject=CObject $Object->{"SharedCacheMap"});
	$Object->{"Buffer"}=tohex(eval($Buffer)-(eval($Object->{"FileOffset"})&0xFFF));
	my $shift=eval($Object->{"FileOffset"})&0xFFF;
	$Object->{"FileOffset"}=tohex(eval($Object->{"FileOffset"})-$shift);
	$Object->{"Buffer"}=tohex(eval($Buffer)-$shift);

	my $ref=\$CObject->{"pin"}{$Object->{"FileOffset"}};
	# There may not exist map bcb even if we are creating the new pin bcb.
	Bcb_checkref $Object,$ref;
}

sub CcPinRead($$$)
{
my($FileObject,$FileOffset,$Length)=@_;

	pin_new $FileObject,$FileOffset,$Length;
}

sub CcPinRead_leave($$)
{
my($Bcb,$Buffer)=@_;

	pin_new_leave $Bcb,$Buffer;
}

sub CcPreparePinWrite($$$)
{
my($FileObject,$FileOffset,$Length)=@_;

	pin_new $FileObject,$FileOffset,$Length;
}

sub CcPreparePinWrite_leave($$)
{
my($Bcb,$Buffer)=@_;

	pin_new_leave $Bcb,$Buffer;
	my $BObject=BObject $Bcb;
	$BObject->{"dirty"}=1;
}

sub CcPinMappedData($$$)
{
my($FileObject,$FileOffset,$Length)=@_;

	pin_new $FileObject,$FileOffset,$Length;
}

sub CcPinMappedData_leave($)
{
my($Bcb)=@_;

	return if !(my $CObject=CObject $Object->{"SharedCacheMap"});
	do { warn "CcPinMappedData() with Bcb $Bcb on non-CcMapData()ed SharedCacheMap ".$CObject->{"SharedCacheMap"}; return; }
			if !(my $mapBcb=$CObject->{"map"});
	return if !(my $BmapObject=BObject $mapBcb);
	my $Buffer=tohex(eval($BmapObject->{"Buffer"})+eval($Object->{"FileOffset"}));

	my $Bcb2=$CObject->{"pin"}{tohex(eval($Object->{"FileOffset"})&~0xFFF)};
	my $BObject2=BObject $Bcb2 if $Bcb2;

	pin_new_leave $Bcb,$Buffer;
}

sub CcSetDirtyPinnedData($$)
{
my($Bcb,$Lsn)=@_;

	return if !(my $BObject=BObject $Bcb);
	# Do not: warn "Lsn already set for Bcb $Bcb as ".$BObject->{"Lsn"}." while current Lsn=$Lsn" if $BObject->{"Lsn"};
	# as it is permitted.
	warn "Lsn goes backward for Bcb $Bcb old Lsn ".$BObject->{"Lsn"}." to a new Lsn=$Lsn"
			if $BObject->{"Lsn"} && eval($BObject->{"Lsn"})>eval($Lsn);
	$BObject->{"Lsn"}=$Lsn if $Lsn ne "0x".("F"x8);
	$BObject->{"dirty"}=1;
	return if !(my $CObject=CObject $BObject->{"SharedCacheMap"});
}

sub FlushToLsnRoutine($$)
{
my($LogHandle,$Lsn)=@_;

	$Object->{"LogHandle"}=$LogHandle;
	$Object->{"Lsn"}=$Lsn;
}

my $LogHandle_static;
sub CcSetLogHandleForFile($$$)
{
my($FileObject,$LogHandle,$FlushToLsnRoutine)=@_;

	return if !(my $CObject=CObject_from_FileObject $FileObject);
	warn "LogHandle ".$CObject->{"LogHandle"}." already exists for SharedCacheMap ".$CObject->{"SharedCacheMap"}
			if $CObject->{"LogHandle"};
	return if !eval $LogHandle;	# $LogHandle may be "0x0"
	# ntfs.sys uses single LogHandle for its whole session:
	warn "Non-unique LogHandle $LogHandle while last LogHandle was $LogHandle_static"
			if $LogHandle_static && $LogHandle ne $LogHandle_static;
	$CObject->{"LogHandle"}=$LogHandle;
	if (!$LogHandle{$LogHandle}) {
		$LogHandle{$LogHandle}={
				"LogHandle"=>$LogHandle,
				};
		}
}

sub IRP_MJ_WRITE_leave_page($$)
{
my($ByteOffset,$Lsn_check)=@_;

	my $SharedCacheMap=$Object->{"data"}[0]{"SharedCacheMap"};
	return if !(my $CObject=CObject $SharedCacheMap);
	my $FlushToLsnRoutine=$LastLeave if $LastLeave->{"by"} eq "FlushToLsnRoutine";
	# Do not: my $Bcb=$CObject->{"pin"}{$ByteOffset};
	# as Bcbs with $BObject->{"OwnerPointer"} are no longer stored in $CObject->{"pin"}.
	my @Bcbs;
	for my $Bcb (keys(%Bcb)) {
		my $BObject=BObject $Bcb;
		if (1
				&& $BObject->{"type"} eq "pin"
				&& $BObject->{"SharedCacheMap"} eq $SharedCacheMap
				&& $BObject->{"FileOffset"} eq $ByteOffset) {
			push @Bcbs,$Bcb;
			}
		}
	if (!@Bcbs) {
		do {
				warn "Non-Bcb IRP_MJ_WRITE ByteOffset=$ByteOffset as non-toplevel function"
						." (".join(",",map({ $_->{"line_enter"}.":".$_->{"by"}; } @$EnterLeave)).")";
#				warn Dumper $CObject;
				# Direct IRP_MJ_WRITE can be from callbacked 'FlushToLsnRoutine'.
				# It can occur even from other callbacks ('DirtyPageRoutine' etc.)
				# but it was not needed here yet.
				} if @$EnterLeave && !(${$EnterLeave}[$#$EnterLeave]->{"by"} eq "FlushToLsnRoutine");
		warn "Non-Bcb IRP_MJ_WRITE ByteOffset=$ByteOffset but FlushToLsnRoutine was preceding"
				if $FlushToLsnRoutine;
		return;
		}
	warn "Ambiguous matching Bcbs ".join(",",@Bcbs)
					." to SharedCacheMap $SharedCacheMap WRITE ByteOffset $ByteOffset"
			if @Bcbs>=2;
	my $Bcb=$Bcbs[0];
	return if !(my $BObject=BObject $Bcb);
	warn "IRP_MJ_WRITE on non-dirty Bcb $Bcb" if !$BObject->{"dirty"};
	if ($FlushToLsnRoutine) {
		push @$Lsn_check,{
				"Bcb"=>$Bcb,
				"Bcb_Lsn",$BObject->{"Lsn"},
				} if $Lsn_check;
		}
	else {
		warn "Missing preceding FlushToLsnRoutine during IRP_MJ_WRITE of Bcb $Bcb with Lsn ".$BObject->{"Lsn"}
				if $BObject->{"Lsn"};
		}
	warn "IRP_MJ_WRITE with FlushToLsnRoutine although not in AcquireForLazyWrite"
			if $FlushToLsnRoutine && !($CObject->{"AcquireForLazyWrite"}>=1);
	warn "IRP_MJ_WRITE not the toplevel function"
						." (".join(",",map({ $_->{"line_enter"}.":".$_->{"by"}; } @$EnterLeave)).")"
			if !(0==@$EnterLeave
			 || (1==@$EnterLeave && ${$EnterLeave}[0]->{"by"} eq "CcFlushCache")
			 || (2==@$EnterLeave && ${$EnterLeave}[0]->{"by"} eq "IRP_MJ_FILE_SYSTEM_CONTROL"
			                     && ${$EnterLeave}[1]->{"by"} eq "CcFlushCache"));
	my $CcFlushCache=${$EnterLeave}[$#$EnterLeave];
	if ($CcFlushCache && $CcFlushCache->{"by"} eq "CcFlushCache") {
		$CcFlushCache->{"CcFlushCached"}++;
		if ($CcFlushCache->{"FileOffset"} ne "0x".("F"x8) || $CcFlushCache->{"Length"} ne "0x0") {
			warn "IRP_MJ_WRITE outside of range of active CcFlushCache()"
					if eval($ByteOffset)< eval($CcFlushCache->{"FileOffset"})
					|| eval($ByteOffset)>=eval($CcFlushCache->{"FileOffset"})+eval($CcFlushCache->{"Length"});
			}
		}
	# Keep $BObject->{"dirty"} there for &delete_BObject sanity checks.
	delete_BObject $BObject if $BObject->{"dirty"} && !$BObject->{"ref_count"};
}

sub IRP_MJ_WRITE_leave()
{
	do { warn "Length $_ not divisible by 0x1000" if eval($_)%0x1000; } for ($Object->{"WRITE"}{"Length"});
	my @Lsn_check;
	for my $reloffs (0..(eval($Object->{"WRITE"}{"Length"})/0x1000)-1) {
		IRP_MJ_WRITE_leave_page tohex(eval($Object->{"WRITE"}{"ByteOffset"})+0x1000*$reloffs),\@Lsn_check;
		}

	if ($LastLeave->{"by"} eq "FlushToLsnRoutine" && (my $FlushToLsnRoutine=$LastLeave)) {
		my $Lsn_max;
		for (@Lsn_check) {
			my $Lsn=eval $_->{"Bcb_Lsn"};
			$Lsn_max=$Lsn if !defined($Lsn_max) || $Lsn_max<$Lsn;
			}
		warn "FlushToLsnRoutine of line_enter ".$FlushToLsnRoutine->{"line_enter"}
						." got Lsn ".$FlushToLsnRoutine->{"Lsn"}." although Bcbs have "
						.join(",",map({ "(".$_->{"Bcb"}.":".$_->{"Bcb_Lsn"}.")"; } @Lsn_check))
				if tohex($Lsn_max) ne $FlushToLsnRoutine->{"Lsn"};
		}
}

sub CcFlushCache($$$$)
{
my($SectionObjectPointer,$SharedCacheMap,$FileOffset,$Length)=@_;

	$Object->{"CcFlushCached"}=0;
	$Object->{"FileOffset"}=$FileOffset;
	$Object->{"Length"}=$Length;
}

sub CcFlushCache_leave($$)
{
my($Status,$Information)=@_;

	warn "CcFlushCache() not the toplevel function"
						." (".join(",",map({ $_->{"line_enter"}.":".$_->{"by"}; } @$EnterLeave)).")"
			if !(0==@$EnterLeave
			 || (1==@$EnterLeave && ${$EnterLeave}[0]->{"by"} eq "IRP_MJ_FILE_SYSTEM_CONTROL"));
	if ($Status ne "0x".("F"x8) || $Information ne "0x".("F"x8)) {
		warn "Unexpected Status $Status" if eval $Status;
		warn "Unexpected Information $Information while CcFlushCached=".$Object->{"CcFlushCached"}
				if eval($Information)!=eval($Object->{"CcFlushCached"})*0x1000;
		}
}

sub CcPrepareMdlWrite($$$)
{
my($FileObject,$FileOffset,$Length)=@_;

	$Object->{"FileObject"}=$FileObject;
	warn "FileOffset $FileOffset not divisible by 0x1000" if eval($FileOffset)%0x1000;
	$Object->{"FileOffset"}=$FileOffset;
	warn "Length $Length not divisible by 0x1000" if eval($Length)%0x1000;
	$Object->{"Length"}=$Length;
}

sub CcPrepareMdlWrite_leave($$$)
{
my($MdlChain,$Status,$Information)=@_;

	do { warn "Unexpected Status $Status"; return; } if eval $Status;
	warn "Unexpected Information $Information" if $Information ne $Object->{"Length"};
	warn "MdlChain $MdlChain already exists" if $MdlChain{$MdlChain};
	$MdlChain{$MdlChain}=$Object;
}

sub CcMdlWriteComplete($$$)
{
my($FileObject,$FileOffset,$MdlChain)=@_;

	return if !(my $MObject=MObject $MdlChain);
	warn "CcMdlWriteComplete() parameter FileObject $FileObject"
					." not matching MdlChain $MdlChain FileObject ".$MObject->{"FileObject"}
			if $FileObject ne $MObject->{"FileObject"};
	warn "CcMdlWriteComplete() parameter FileOffset $FileOffset"
					." not matching MdlChain $MdlChain FileOffset ".$MObject->{"FileOffset"}
			if $FileOffset ne $MObject->{"FileOffset"};
	# Propose MdlChain to a simulated Bcb.
	# We must split it by pages as pin can be just 0x1000 sized.
	return if !(my $CObject=CObject_from_FileObject $MObject->{"FileObject"});
	for my $reloffs (0..eval($MObject->{"Length"})/0x1000-1) {
		my $BObject={ %$MObject };
		$BObject->{"Bcb"}="MdlChain $MdlChain reloffs $reloffs";
		$BObject->{"FileOffset"}=tohex(eval($MObject->{"FileOffset"})+$reloffs*0x1000);
		$BObject->{"SharedCacheMap"}=$CObject->{"SharedCacheMap"};
		$BObject->{"type"}="pin";
		$BObject->{"ref_count"}=0;
		$BObject->{"dirty"}=1;
		warn "Bcb ".$BObject->{"Bcb"}." already exists" if $Bcb{$BObject->{"Bcb"}};
		$Bcb{$BObject->{"Bcb"}}=$BObject;
		}
	delete $MdlChain{$MdlChain};
}

sub CcMdlWriteAbort($$)
{
my($FileObject,$MdlChain)=@_;

	warn "CcMdlWriteAbort() not handled";
}

sub AcquireForLazyWrite_leave($)
{
my($r)=@_;

	warn "Unexpected 'r' $r" if $r ne "1";
	warn "AcquireForLazyWrite() not the toplevel function" if @$EnterLeave;
	return if !(my $CObject=CObject $Object->{"data"}[0]{"SharedCacheMap"});
	$CObject->{"AcquireForLazyWrite"}++;
}

sub ReleaseFromLazyWrite_leave()
{
	warn "ReleaseFromLazyWrite() not the toplevel function" if @$EnterLeave;
	return if !(my $CObject=CObject $Object->{"data"}[0]{"SharedCacheMap"});
	warn "Invalid 'AcquireForLazyWrite' value ".$CObject->{"AcquireForLazyWrite"}
			if !($CObject->{"AcquireForLazyWrite"}>=1);
	$CObject->{"AcquireForLazyWrite"}--;
}

sub CcRemapBcb($)
{
my($Bcb)=@_;

	return if !(my $BObject=BObject $Bcb);
	map_new $BObject->{"SharedCacheMap"};
	$Object->{"Buffer"}=tohex(eval($BObject->{"Buffer"})-eval($BObject->{"FileOffset"} || 0));
}

sub CcRemapBcb_leave($)
{
my($r)=@_;

	map_new_leave $r;
}

sub unpin($)
{
my($Bcb)=@_;

	return if !(my $BObject=BObject $Bcb);
	return if --$BObject->{"ref_count"};
	if ($BObject->{"dirty"}) {
		# last unpin of unreferenced dirty Bcb will no longer allow reincarnation
		# of the same Bcb to the pin map of its SharedCacheMap.
		return if !(my $CObject=CObject $BObject->{"SharedCacheMap"});
		warn "unpin() of pin Bcb $Bcb but it is already not registered in SharedCacheMap ".$BObject->{"SharedCacheMap"}." pin map"
				if (!$CObject->{"pin"}{$BObject->{"FileOffset"}} || $CObject->{"pin"}{$BObject->{"FileOffset"}} ne $Bcb)
						&& !$BObject->{"OwnerPointer"};
		delete $CObject->{"pin"}{$BObject->{"FileOffset"}}
				if $CObject->{"pin"}{$BObject->{"FileOffset"}} && ($CObject->{"pin"}{$BObject->{"FileOffset"}} eq $Bcb);
		return;
		}
	delete_BObject $BObject;
}

sub CcUnpinData($)
{
my($Bcb)=@_;

	unpin $Bcb;
}

sub CcUnpinDataForThread($)
{
my($Bcb)=@_;

	unpin $Bcb;
}

sub CcSetBcbOwnerPointer($$)
{
my($Bcb,$OwnerPointer)=@_;

	return if !(my $BObject=BObject $Bcb);
	warn "CcSetBcbOwnerPointer() on map Bcb $Bcb" if $BObject->{"type"} ne "pin";
	return if !(my $CObject=CObject $BObject->{"SharedCacheMap"});
	warn "Double CcSetBcbOwnerPointer() on Bcb $Bcb" if defined $BObject->{"OwnerPointer"};
	my $val=$CObject->{"pin"}{$BObject->{"FileOffset"}};
	warn "CcSetBcbOwnerPointer() on unregistered pin Bcb $Bcb" if !$val || $val ne $Bcb;
	delete $CObject->{"pin"}{$BObject->{"FileOffset"}} if $val && $val eq $Bcb;
	$BObject->{"OwnerPointer"}=$OwnerPointer;
}

sub IRP_MJ_CLOSE_leave()
{
	my $FileObject=$Object->{"data"}[0]{"FileObject"};
#	# IRP_MJ_CLOSE of FileObject w/o CcInitializeCacheMap()?
#	return if !$FileObject{$FileObject};
	return if !(my $FObject=FObject $FileObject);
	if (eval(my $SectionObjectPointer=$FObject->{"SectionObjectPointer"})) {
		return if !(my $SObject=SObject $SectionObjectPointer);
		my $SharedCacheMap=$SObject->{"SharedCacheMap"};
		if (eval $SharedCacheMap) {
			return if !(my $CObject=CObject $SObject->{"SharedCacheMap"});
			# SharedCacheMap may still exist for FCB although this FileObject gets destroyed now.
#			warn "SectionObjectPointer $SectionObjectPointer still exists during IRP_MJ_CLOSE"
#							." while SharedCacheMap ".$CObject->{"SharedCacheMap"}." ref_count ".$CObject->{"ref_count"}
#					if $SectionObjectPointer && $CObject->{"ref_count"};
			}
		}
	delete_FObject $FObject;
}


local $_;
my $hex='0x[\dA-F]+';
my %last_irp_mj;
while (<>) {
	chomp;
	s/\r$//;
	# We may get some foreign garbage without '\n' before our debug data line:
	# Do not use '\bTraceFS' as there really can be precediny _any_ data.
	s#^.*?TraceFS[(]($hex/$hex)[)]: ## or do { print "$_\n" if $filter; next; };
	$ProcessThread=$1;

	$Object=undef();
	if (/^enter: (\w+)/) {
		$Object={};
		$Object->{"by"}=$1;
		$Object->{"line_enter"}=$.;
		$Object->{"ProcessThread"}=$ProcessThread;
		push @{$EnterLeave{$ProcessThread}},$Object;
		}
	elsif (/^leave: (\w+)/) {
		warn "Empty pop stack during 'leave' of $1" if !($Object=pop @{$EnterLeave{$ProcessThread}});
		warn "Non-matching popped 'by' ".$Object->{"by"}." ne current 'leave' $1"
				if $Object->{"by"} ne $1;
		$Object->{"line_leave"}=$.;
		push @{$LastLeave{$ProcessThread}},$Object;
		}
	elsif (my($FileObject,$FileName,$Flags,$SectionObjectPointer,$SharedCacheMap)=
			/^FileObject=($hex): FileName=(?:NULL|'(.*)'),Flags=($hex),SectionObjectPointer=($hex),->SharedCacheMap=($hex)/) {
		my $aref=$EnterLeave{$ProcessThread};
		warn "Empty stack during 'data' line" if !($Object=${$aref}[$#$aref]);
		my $data={
				"FileObject"=>$FileObject,
				"FileName"=>$FileName,
				"Flags"=>$Flags,
				"SectionObjectPointer"=>$SectionObjectPointer,
				"SharedCacheMap"=>$SharedCacheMap,
				"line"=>$.,
				};
		push @{$Object->{"data"}},$data;
		my $isinit={ map(($_=>1),qw(
				CcInitializeCacheMap
				CcUninitializeCacheMap
				IRP_MJ_CREATE
				)) }->{$Object->{"by"}};
		check_data $data
				if 1==@{$Object->{"data"}} || !$isinit;
		if ($isinit) {
			# Prevent 'SharedCacheMap' 0->N change by CcInitializeCacheMap() called inside.
			for my $ref (@$aref[0..$#$aref-1]) {
				$ref->{"data"}[0]->{"SharedCacheMap"}=$SharedCacheMap;
				}
			}
		if (2<=@{$Object->{"data"}}) {
			my $data_prev=$Object->{"data"}[$#{$Object->{"data"}}-1];
			for my $field (qw(FileObject FileName Flags),($isinit ? () : qw(SharedCacheMap))) {
				next if !defined(my $prev=$data_prev->{$field});
				next if !defined(my $now=$data->{$field});
				my $by=$Object->{"by"};
				if ($field eq "Flags") {
					next if $by eq "IRP_MJ_CREATE" && $field eq "Flags";
					my $FO_CLEANUP_COMPLETE=0x4000;
					$now=tohex(eval($now)&~$FO_CLEANUP_COMPLETE) if $by eq "IRP_MJ_CLEANUP";
					my $FO_FILE_FAST_IO_READ=0x80000;
					$prev=tohex(eval($prev)&~$FO_FILE_FAST_IO_READ) if $by eq "IRP_MJ_CLEANUP";
					$now=tohex(eval($now)&~$FO_FILE_FAST_IO_READ) if $by eq "IRP_MJ_READ" && !(eval($prev)&$FO_FILE_FAST_IO_READ);
					my $FO_FILE_MODIFIED=0x1000;
					$now=tohex(eval($now)&~$FO_FILE_MODIFIED) if $by eq "IRP_MJ_WRITE" && !(eval($prev)&$FO_FILE_MODIFIED);
					my $FO_FILE_SIZE_CHANGED=0x2000;
					$prev=tohex(eval($prev)&~$FO_FILE_MODIFIED)
							if $by eq "IRP_MJ_SET_INFORMATION" && !(eval($now)&$FO_FILE_MODIFIED);
					$prev=tohex(eval($prev)&~$FO_FILE_SIZE_CHANGED)
							if $by eq "IRP_MJ_SET_INFORMATION" && !(eval($now)&$FO_FILE_SIZE_CHANGED);
					}
				next if $by eq "IRP_MJ_CLOSE" && $field eq "FileName";
				$prev=~s#\\$## if $by eq "IRP_MJ_CREATE";
				$prev="\\" if $by eq "IRP_MJ_CREATE" && $prev eq "";
				$prev=~s#:.*## if $by eq "IRP_MJ_CREATE" && $prev ne $now;
				next if $field eq "SharedCacheMap" && !SharedCacheMap_valid $prev && !SharedCacheMap_valid $now;
				do { warn "Changed data field $field, prev=".$data_prev->{$field}.", now=".$data->{$field}." by $by";
#						print STDERR Dumper $data_prev,$data;
						} if $prev ne $now;
				}
			}
		next;
		}
	elsif (my($ByteOffset,$Length)=
			/^WRITE: ByteOffset=($hex),Length=($hex)/) {
		my $aref=$EnterLeave{$ProcessThread};
		warn "Empty stack during 'data' line" if !($Object=${$aref}[$#$aref]);
		$Object->{"WRITE"}={
			"ByteOffset"=>$ByteOffset,
			"Length"=>$Length,
			};
		next;
		}

	$LastLeave=${$LastLeave{$ProcessThread}}[$#{$LastLeave{$ProcessThread}}-1];
	$EnterLeave=$EnterLeave{$ProcessThread};

	if (my($r)=
			/^leave: IRP_MJ_\w+: r=($hex)/) {
		# Failed requests should make no consequences.
		next if eval($r);
		}

	if (my($FileObject,$AllocationSize,$FileSize,$ValidDataLength,$PinAccess)=
			/^enter: CcInitializeCacheMap: FileObject=($hex),FileSizes,->AllocationSize=($hex),->FileSize=($hex),->ValidDataLength=($hex),PinAccess=([01]),/) {
		CcInitializeCacheMap $FileObject,eval($AllocationSize),eval($FileSize),eval($ValidDataLength),eval($PinAccess);
		next;
		}
	if (/^leave: CcInitializeCacheMap\b/) {
		CcInitializeCacheMap_leave;
		next;
		}

	if (my($FileObject,$TruncateSize)=
			/^enter: CcUninitializeCacheMap: FileObject=($hex),TruncateSize=($hex),/) {
		CcUninitializeCacheMap $FileObject,eval($TruncateSize);
		next;
		}
	if (my($r)=
			/^leave: CcUninitializeCacheMap: r=([01])/) {
		CcUninitializeCacheMap_leave $r;
		next;
		}

	if (my($FileObject,$AllocationSize,$FileSize,$ValidDataLength)=
			/^enter: CcSetFileSizes: FileObject=($hex),FileSizes,->AllocationSize=($hex),->FileSize=($hex),->ValidDataLength=($hex)/) {
		CcSetFileSizes $FileObject,eval($AllocationSize),eval($FileSize),eval($ValidDataLength);
		next;
		}

	if (/^leave: IRP_MJ_CREATE\b/) {
		IRP_MJ_CREATE_leave;
		next;
		}

	if (/^leave: IRP_MJ_CLOSE\b/) {
		IRP_MJ_CLOSE_leave;
		next;
		}

	if (my($FileObject,$FileOffset,$Length)=
			/^enter: CcMapData: FileObject=($hex),FileOffset=($hex),Length=($hex),Flags=0x1/) {
		CcMapData $FileObject,eval($FileOffset),eval($Length);
		next;
		}
	if (my($Bcb,$Buffer)=
			/^leave: CcMapData: r=1,Bcb=($hex),Buffer=($hex)/) {
		CcMapData_leave $Bcb,$Buffer;
		next;
		}

	if (my($FileObject,$FileOffset,$Length)=
			/^enter: CcPinRead: FileObject=($hex),FileOffset=($hex),Length=($hex),Flags=0x1/) {
		CcPinRead $FileObject,eval($FileOffset),eval($Length);
		next;
		}
	if (my($Bcb,$Buffer)=
			/^leave: CcPinRead: r=1,Bcb=($hex),Buffer=($hex)/) {
		CcPinRead_leave $Bcb,$Buffer;
		next;
		}

	if (my($FileObject,$FileOffset,$Length)=
			/^enter: CcPreparePinWrite: FileObject=($hex),FileOffset=($hex),Length=($hex),Zero=([01]),Flags=0x1/) {
		CcPreparePinWrite $FileObject,eval($FileOffset),eval($Length);
		next;
		}
	if (my($Bcb,$Buffer)=
			/^leave: CcPreparePinWrite: r=1,Bcb=($hex),Buffer=($hex)/) {
		CcPreparePinWrite_leave $Bcb,$Buffer;
		next;
		}

	if (my($FileObject,$FileOffset,$Length)=
			/^enter: CcPinMappedData: FileObject=($hex),FileOffset=($hex),Length=($hex),Flags=0x1/) {
		CcPinMappedData $FileObject,eval($FileOffset),eval($Length);
		next;
		}
	if (my($Bcb)=
			/^leave: CcPinMappedData: r=1,Bcb=($hex)/) {
		CcPinMappedData_leave $Bcb;
		next;
		}

	if (my($BcbVoid,$Lsn)=
			/^enter: CcSetDirtyPinnedData: BcbVoid=($hex),Lsn=($hex)/) {
		CcSetDirtyPinnedData $BcbVoid,$Lsn;
		next;
		}

	if (my($LogHandle,$Lsn)=
			/^enter: FlushToLsnRoutine: LogHandle=($hex),Lsn=($hex)/) {
		FlushToLsnRoutine $LogHandle,$Lsn;
		next;
		}

	if (/^leave: IRP_MJ_WRITE\b/) {
		IRP_MJ_WRITE_leave;
		next;
		}

	if (my($SectionObjectPointer,$SharedCacheMap,$FileOffset,$Length)=
			/^enter: CcFlushCache: SectionObjectPointer=($hex),->SharedCacheMap=($hex),FileOffset=($hex),Length=($hex)/) {
		CcFlushCache $SectionObjectPointer,$SharedCacheMap,$FileOffset,$Length;
		next;
		}

	if (my($Status,$Information)=
			/^leave: CcFlushCache: IoStatus->Status=($hex),IoStatus->Information=($hex)/) {
		CcFlushCache_leave $Status,$Information;
		next;
		}

	if (my($r)=
			/^leave: AcquireForLazyWrite: r=([01])/) {
		AcquireForLazyWrite_leave $r;
		}

	if (/^leave: ReleaseFromLazyWrite\b/) {
		ReleaseFromLazyWrite_leave;
		}

	if (my($FileObject,$LogHandle,$FlushToLsnRoutine)=
			/^enter: CcSetLogHandleForFile: FileObject=($hex),LogHandle=($hex),FlushToLsnRoutine=($hex)/) {
		CcSetLogHandleForFile $FileObject,$LogHandle,$FlushToLsnRoutine;
		next;
		}

	if (my($FileObject,$FileOffset,$Length)=
			/^enter: CcPrepareMdlWrite: FileObject=($hex),FileOffset=($hex),Length=($hex)/) {
		CcPrepareMdlWrite $FileObject,$FileOffset,$Length;
		next;
		}
	if (my($MdlChain,$Status,$Information)=
			/^leave: CcPrepareMdlWrite: MdlChain=($hex),IoStatus->Status=($hex),IoStatus->Information=($hex)/) {
		CcPrepareMdlWrite_leave $MdlChain,$Status,$Information;
		next;
		}

	if (my($FileObject,$FileOffset,$MdlChain)=
			/^enter: CcMdlWriteComplete: FileObject=($hex),FileOffset=($hex),MdlChain=($hex)/) {
		CcMdlWriteComplete $FileObject,$FileOffset,$MdlChain;
		next;
		}

	if (my($FileObject,$MdlChain)=
			/^enter: CcMdlWriteAbort: FileObject=($hex),MdlChain=($hex)/) {
		CcMdlWriteAbort $FileObject,$MdlChain;
		next;
		}

	if (my($Bcb)=
			/^enter: CcRemapBcb: Bcb=($hex)/) {
		CcRemapBcb $Bcb;
		next;
		}
	if (my($r)=
			/^leave: CcRemapBcb: r=($hex)/) {
		CcRemapBcb_leave $r;
		next;
		}

	if (my($Bcb)=
			/^enter: CcUnpinData: Bcb=($hex)/) {
		CcUnpinData $Bcb;
		next;
		}
	if (my($Bcb)=
			/^enter: CcUnpinDataForThread: Bcb=($hex)/) {
		CcUnpinDataForThread $Bcb;
		next;
		}

	if (my($Bcb,$OwnerPointer)=
			/^enter: CcSetBcbOwnerPointer: Bcb=($hex),OwnerPointer=($hex)/) {
		CcSetBcbOwnerPointer $Bcb,$OwnerPointer;
		next;
		}

	print "$_\n" if $filter;
	}
for my $FileObject (keys(%FileObject)) {
	warn "EXIT: still CcInitializeCacheMap FileObject $FileObject";
	next if !(my $FObject=FObject $FileObject);
	}
