Artifact [d60692d03a]

Artifact d60692d03ae4c5c7fc3bc8cbf65f962f7e477fc5:


/*
 * Copyright 2022-2023, ttcoder
 * All rights reserved. Distributed under the terms of the MIT license.
 */

// base
#include <os/path.h>
#include <os/vfs.h>  // Directory::Path
#include <file_system_session/file_system_session.h>  // Directory_entry
#include <libc/component.h>
#include <vfs/file_system_factory.h>
#include <vfs/file_system.h>
#include <vfs/vfs_handle.h>

// vfs_fuse_ntfs.so ...
#include "xattr_mapping.h"

// project
#include "IndexStore.h"


/**********/
static const char bquery_support_prefix[] = "/_BQuery_support";
static const char bquery_index[] = "_Hai_Genode_INDEX_v0.txt";
//
// In the ctor we create a top-level folder named "_Hai_Genode_Root_Folder_", and enforce
// a sort of _chroot_ to within that folder, ourselves.
// That buys us a minor benefit, and a big one
// - the minor benefit is, when re-purposing an existing Windows partition, Genode and Windows do not "pollute" each other (though, as a downside, we cannot "see" the windows files, even if we wanted to look for e.g. audio files there...)
// - the big benefit is, we hide/make-inaccessible the Index file ; otherwise the user might mess with the index file or delete it, not knowing what it is.
//
static const char _ChrootFolder[] = "_Hai_Genode_Root_Folder_";

///ToDo: weird that this whole new-opendir change seems to ONLY be needed to fix CreateSymLink()... I should
// beef up test suite "jam t7" with deep folders, to find other "trigger" cases beside the symlink case...
//
//#define OLD_STYLE_OPENDIR


#if 0
#define TRACE(x, ...) Genode::log( "---> idx.", x, ##__VA_ARGS__ )
#else
#define TRACE(x, ...)
#endif

namespace {

	using namespace Vfs;
	using Genode::Directory;

};



// Inspiration/guides:
// look at gems/src/lib/vfs/import for an example of how to call a 'remote' FS...
//
//maybe later:
// grep -r WriteAttrs shows these:
//	misc-apps/pulse/Prefs.cpp (so use Pulse instead of Clock <=====)
//	haiku-on-genode/kits/mail/MailMessage.cpp (BeMail: probably too ambitious)



struct QueryDir_vfs_handle : Vfs::Vfs_handle
{
private:
		//BObjectList< String<MAX_PATH_LEN> > entryList { 20, true };  // set "owner = true" to delete on exit
		
		//void * resultsBuf;
		//const file_size bufSize;
		BufIO resultsBuf;

public:
		QueryDir_vfs_handle(
			Vfs::Directory_service & ds,
			Vfs::File_io_service   & fs,
			Genode::Allocator & alloc,
			const void * buf,
			file_size bufsize
			)
		:	Vfs_handle(ds, fs, alloc, 0)
			//,entryList()///
			//,resultsBuf()
			//,bufSize( bufsize )
			,resultsBuf( alloc )
		{
			//resultsBuf = alloc.alloc( bufsize );//new (alloc) char[bufsize];
			//Genode::memcpy( resultsBuf, buf, bufsize );
			resultsBuf.Append( buf, bufsize );
		}
		/*
		~QueryDir_vfs_handle()
		{
			if( resultsBuf )
				alloc().free( resultsBuf, bufSize );//destroy( alloc(), (char*)resultsBuf );
		}
		*/
		
		File_io_service::Read_result read( char *dest, file_size len, file_size &out_count )
		{
			TRACE("  query.read");
			
			out_count = 0;
#if 0
			BDirectory-style access:
			
			if (len < sizeof(::File_system::Directory_entry)) {
				Genode::error("read buffer too small for directory entry");
				return File_io_service::READ_ERR_INVALID;
			}

			if (seek() % sizeof(::File_system::Directory_entry)) {
				Genode::error("seek offset not aligned to sizeof(Directory_entry)");
				return File_io_service::READ_ERR_INVALID;
			}
			const file_size
				index = seek() / sizeof(::File_system::Directory_entry);
			
			if( index >= entryList.CountItems() )//size() )
				return File_io_service::READ_ERR_INVALID;
			
			Directory_service::Stat stat;
			{
		/*
				auto ret = _rawfs.stat(
					entryList.at( index ),
					&stat
					);
				Genode::log( "  underlying stat() returns: ", ret );
		*/
			}
			/*
			struct dirent *dent = de + index;
			if (!dent)
				return File_io_service::READ_ERR_INVALID;
			*/
			::File_system::Directory_entry * e = (::File_system::Directory_entry *)(dest);
			
			Genode::Path<512> last;
			last.import( entryList.ItemAt(index)->string() );
			last.keep_only_last_element();
			
			//.size...
			e->type = nodeType( stat.type );
			//.rwx...
			//.inode..
			//.device..
			//.modification_time..
			copy_cstring( e->name.buf, last.last_element(), Genode::strlen(last.last_element()) +1 );//entryList.at(index).string(), entryList.at(index).length() );
			e->sanitize();  // null-terminate the string
			
			out_count = sizeof(::File_system::Directory_entry);
			return File_io_service::READ_OK;
#else
			// BFile-style access:
			
			//Genode::log( " seek: ", seek(), " bufsize: ", bufSize );//Genode::strlen(resultsBuf));
			if( seek() >= resultsBuf.BufferLength() )//bufSize )//Genode::strlen(resultsBuf) )
				// careful, the right way to handle EoF is to return READ_OK with "out count == 0",
				// if we return READ_ERR_INVALID the system goes haywire, read() returns double the amount of read bytes (instead of 0) !
				return File_io_service::READ_OK;
			
			
			const char * from = static_cast<const char*>( resultsBuf.Buffer() ) + seek();
			//Genode::log( " PROCEEDING with read() of result-buf: ", from );
			
			const file_size tx = Genode::min(
				len,
				resultsBuf.BufferLength() - seek()
				);
			Genode::memcpy(
				dest,
				from,
				tx
				);
			
			out_count = tx;
			return File_io_service::READ_OK;
#endif
		}
};


class Attr_vfs_handle : public Vfs::Vfs_handle
{
public: ////
		String<256>/**/ pathOurs; ///ToDo
		String<256> _attrname;
		
		Vfs_handle * _handle_raw;
		
public:
		Attr_vfs_handle(
			Vfs::Directory_service & ds,
			Vfs::File_io_service & fs,
			Allocator & alloc,
			Vfs_handle * underlying_rawfs_handle,
			const char * path,
			const char * attrname
			)
		:	Vfs_handle(ds, fs, alloc, 0)
			,pathOurs( path )
			,_attrname( attrname )
			,_handle_raw( underlying_rawfs_handle )
		{
		}
};


class Dir_vfs_handle : public Vfs::Vfs_handle
{
public: ///
		Vfs_handle * _handle_raw;

public:
		Dir_vfs_handle(
			Vfs::Directory_service & ds,
			Vfs::File_io_service & fs,
			Allocator & alloc,
			Vfs_handle * underlying_rawfs_handle
			)
		:	Vfs_handle(ds, fs, alloc, 0)
			,_handle_raw( underlying_rawfs_handle )
		{
		}
};



//#pragma mark -


class Indexed_file_system : public Vfs::File_system
{
	typedef Vfs::File_system inherited;
	
	private:
		
		// Business logic:
		IndexStore indexStore;
		
		// Genode:
		//
		
		Vfs::Env &_vfs_env;
		
		Directory::Path _redirection;
		Vfs::File_system & _rawfs;


	public:

		Indexed_file_system( Vfs::Env & env, Genode::Xml_node config )
		:	indexStore( env.env() )
			,_vfs_env( env )
			,_redirection()  // below
			,_rawfs( env.root_dir() )
		{
			TRACE( "Indexer Ctor" );
			
			// e.g. "/raw_fs"
			Genode::Directory::Path path(
				config.attribute_value(
					"fs_path",
					Genode::Directory::Path()
					));
			_redirection = String<128>( path, "/", _ChrootFolder );

			Genode::log( "-- Starting Index-Augmented FS above '", path, "' -- (chroot'ed to ", _redirection, ")" );
			
			// sanity check: supporting-FS path present ?
			if( path.length() <= 0 )
				Genode::error( "indexer misconfiguration: absent/empty path to supporting-fs" );
			
			// sanity check <path>: is there an actual supporting FS at that path ?
			{
				Vfs_handle * handle = nullptr;
				auto res = _rawfs.opendir( path.string(), false, &handle, env.alloc() );
				if( res != OPENDIR_OK || nullptr == handle )
					Genode::error( "indexer misconfiguration: No underlying file system at specified path: ", path );
				else
					_rawfs.close( handle );
			}
			
			// create/open <_redirection> chroot: we chroot within a *subfolder* of the underlying FS:
			{
				Vfs_handle * handle = nullptr;
				auto res = _rawfs
					.opendir( _redirection.string(), true /* ! */, &handle, env.alloc() );
				
				if( OPENDIR_ERR_NODE_ALREADY_EXISTS == res && NULL == handle )
					// we're good -> do nothing
					;
				else if( OPENDIR_OK == res && handle )
					// we're good, but need to close the opened handle
					_rawfs.close( handle );
				else
				{
					Genode::error( "(RES: ", int(res), ") indexer cannot work: this chroot subfolder does not exist, and cannot be created (-> FS is absent or read-only): ", _redirection );
					//+ Read-Only filesystem: attempt to give read-only access to the very-top-folder? or no point in doing that ?
					
					if( handle )
						_rawfs.close( handle );
				}
			}
			
			// load Index
			loadIndex( String<MAX_PATH_LEN>(path, "/", bquery_index) );
		}

		~Indexed_file_system()
		{
			//+ sync...
		}

		Directory::Path mapRaw( const Directory::Path & ours )
		{
			// if <path_ours> is prefixed with the redirection path (!), that's a big OOPS:
			// (might happen in opendir(), num_dirent()...)
			if( 0 == Genode::strcmp(ours.string(), _redirection.string(), _redirection.length()-sizeof('\0') /*sigh*/) )
			{
				Genode::error( "indexer misconfiguration: infinite-loop redirection detected, probably a missing supporting-fs combined with us having top-level/root realm responsibility ?" );
				
				// Bail out! Otherwise we get into some sort of infinite loop, if the "/" root ALSO points to us:
				// idx.opendir path=/raw_boot_sans_indexing/raw_boot_sans_indexing/raw_boot_sans_indexing/raw_boot_sans_indexing/raw_boot_sans_indexing/raw_boot_sans_indexing/raw_boot_sans_indexing/raw_boot_sans_indexing/raw_boot_sans_indexing/r
				
				//
				throw Genode::Exception();
			}
			
			// e.g. "/Music" => "/raw_ntfs/_Hai_Genode_Root_Folder_/Music"
			
			Directory::Path path(
				_redirection,
				ours
				);
			
			// fix-up "/raw_ntfs/_Hai_Genode_Root_Folder_/" (un-normalized trailing slash) : normalize <path> !
			// -- didn't observe that behavior from Directory::Path before implementing a chroot, but ever since chroot was added we get that problem, exclusively in the case of "top of chroot" corner case
			Genode::Path<1024> norm;
			norm.import( path.string() );  // calls canonicalize(), which calls strip_superfluous_slashes()  //XXX only with the proper patch! see ticket:
///ToDo: fix ticket https://github.com/genodelabs/genode/issues/4708
			//Genode::log( norm, " from: ", path );
			
			return norm;//path;
		}
		
		void loadIndex( const String<MAX_PATH_LEN> & path )
		{
			// <path> param: purpose: take the path as param, in order to decrease the T-/T+ risk, since we're called from the ctor, where <_redirection> is calculated.
			
			//if( debug )
				Genode::log( "open Attr-Index at <", path, ">" );
			
			file_size size = 0;
#if 1
			Stat stat;
			auto stat_res = _rawfs.stat( path.string(), stat );
			if( STAT_OK != stat_res || stat.size <= 0 )
			{
				Genode::warning( "indexer: Attr-Index empty or not stat()able" );
				//
				return;
				//
			}
			// else: open():
			
			size = stat.size;
#endif
			Vfs_handle * handle = nullptr;
///later: why does NTFS return OPEN_OK on first run (provided the above "return" statement is absent), when the file does not even exist yet ?
			auto res = _rawfs.open(
				path.string(),
				OPEN_MODE_RDONLY,
				&handle, _vfs_env.alloc()
				);
			if( OPEN_OK != res )//&& OPEN_ERR_EXISTS != res )
			{
				Genode::warning( "(RES: ", int(res), ") indexer: Attr-Index does not exist, probably just the first time we run on this volume." );
			//	Genode::error( "(RES: ", int(res), ") indexer: Attr-Index does not exist and cannot create Index.txt file (-> read-only filesystem ?!): " );
				//+ Read-Only filesystem: attempt to give read-only access to the very-top-folder? or no point in doing that ?
				//
				return;
				//
			}
			// else: read() contents:
			
			char * buf = static_cast<char*>( _vfs_env.alloc().alloc(size) );
			
			bool res1 = handle->fs().queue_read( handle, sizeof(buf) );
			//Genode::log( " INDEX queue_read: ", res1 );
			
			file_size gotten = 0;
			auto res2 = handle->fs().complete_read( handle, buf, size, gotten );
			
			TRACE( "indexer: load index, complete_read=", int(res2), " vs READ_OK=", int(READ_OK), " Got ", gotten, " bytes" );
			
			if( false == res1 || READ_OK != res2 || gotten != size )
			{
				Genode::warning( "indexer: could not read Index contents (res=", int(res2), " retrieved=", gotten, ")" );
			}
			else
			{
				indexStore.SetAs_And_Check( buf, size );
			}
			
			// Epilogue
			_vfs_env.alloc().free( buf, size );  buf = nullptr;
			_rawfs.close( handle );
		}
		
		void saveIndex()
		{
			Genode::Path<MAX_PATH_LEN> path = _redirection;
			{
				path.strip_last_element();  // remove "_Hai_Genode_Root_Folder_"
				path.append_element( bquery_index );  // append "_Hai_Genode_INDEX.txt"
			}
			
			///later: should we truncate() instead of unlink() ?
			
			// unlink()
			auto unlink_res = _rawfs.unlink( path.string() );
			if( UNLINK_OK != unlink_res )
				Genode::warning( "indexer: cannot zero-out Attr-index file, res=", int(unlink_res) );
			
			Vfs_handle * handle = nullptr;
			auto res = _rawfs.open(
				path.string(),
				OPEN_MODE_WRONLY,
				&handle, _vfs_env.alloc()
				);
			if( OPEN_OK != res || nullptr == handle )
			{
				Genode::error( "(RES: ", int(res), ") indexer: cannot write index to Attr-Index" );
				//
				return;
				//
			}
			
			TRACE( "indexer: saving index, ", indexStore.IndexBufferSize(), " bytes" );
			
			file_size written = 0;
			auto w_res = handle->fs().write(
				handle,
				indexStore.IndexBuffer(),
				indexStore.IndexBufferSize(),
				written
				);
			if( w_res != WRITE_OK || written != indexStore.IndexBufferSize() )
				Genode::error( "indexer: failed to write: res=", int(w_res), " vs WRITE_OK: ", int(WRITE_OK), " written=", written );
			
			// Epilogue
			//
			_rawfs.close( handle );
			
			// -- stress test/regression testing:
			// loadIndex(path);
			// -- doing a "loadIndex()" after each save means we re-parse/check the whole index -- a good way to detect corruptions.
		}
		
		
		/***************************
		 ** File_system interface **
		 ***************************/

		///?
		static char const *name() { return "indexing_fs"; }
		char const *type() override { return "indexing_fs"; }
		
		
		/*********************************
		 ** Directory service interface **
		 *********************************/

		Open_result open(char const *path_ours, unsigned vfs_mode,
		                 Vfs_handle **handle,
		                 Allocator  &alloc) override
		{
			//auto path_raw = mapRaw( path_ours );
			TRACE( "open path=", path_ours, " alias: ", mapRaw(path_ours), " mode=", vfs_mode );
			
#if 0
			// in opendir() instead !
#else
			// BQuery case
			Genode::Path<MAX_PATH_LEN> path;
			path.import( path_ours );
			if( //OPEN_MODE_RDONLY & vfs_mode &&
				path.strip_prefix(bquery_support_prefix) )
			{
				indexStore.SetQuery( Genode::strlen(path.string())
					? path.last_element()
					: ""
					);
				
				TRACE( " opening (virtual) query 'folder' for query: ", path.string(), " with resulting entrylist Size: ", indexStore.ResultsSize() );
				
				try {
					*handle = new (alloc) QueryDir_vfs_handle(
						*this, *this,
						alloc,
						indexStore.ResultsBuf(),
						indexStore.ResultsSize()
						);
				}
				catch (Genode::Out_of_ram)  { return OPEN_ERR_OUT_OF_RAM; }
				catch (Genode::Out_of_caps) { return OPEN_ERR_OUT_OF_CAPS; }
	
				return OPEN_OK;
			}
#endif
			
			const bool writing = (vfs_mode & OPEN_MODE_WRONLY) || (vfs_mode & OPEN_MODE_RDWR);
			
			if( writing )
			{
				// compute e.g. "Music/" from ".Music/"
				// (which itself is VFS's truncation of the original "/boot/.Music/") :
				VirtualizedXattr isattr( path_ours );
				
				// snoop
				if( isattr.initcheck() == Directory_service::STAT_OK )
				{
					//Genode::log("OPEN OWN fd here, with: ", real_path_raw);
#if 0
				// Direct delegation does NOT work for some reason, so do "matrioshka" VFS Handle !
				// (same thing with opendir(), a direct handle only works for root but not subdirs...)
			Genode::log("delegate to ntfs: ", path_raw);
			auto res = _rawfs.open(
				path_raw.string(),
				vfs_mode,
				handle,
				alloc
				);
			return res;
#else
					// forward (map() <path_ours> *without* interpreting a possible "dot-file", so that the underlying driver knows to access attributes if applicable -- it will call VirtualizedXattr on its own to detect "dot paths")
					Vfs_handle *
						underlying_rawfs_handle = nullptr;
					auto res = _rawfs.open(
						mapRaw( path_ours ).string(),
						vfs_mode,
						&underlying_rawfs_handle,
						alloc
						);
#endif
					if( OPEN_OK == res && underlying_rawfs_handle )
					{
						// compute e.g. "/Ntfs/Music/" from "Music/"
					//	auto real_path_raw = mapRaw( isattr.real_path() );
						
						// snoop: the snooping FD will use the *REAL* and *mapped* path
						try {
							*handle = new (alloc)
								Attr_vfs_handle(
									*this,//_rawfs,//*this,
									*this,//_rawfs,//*this,
									alloc,
									underlying_rawfs_handle,
									isattr.real_path(),//_d_ real_path_raw.string(),//mapRaw( isattr.real_path() ).string(),//
									isattr.xattr_name());
							return OPEN_OK;
						}
						catch (Genode::Out_of_ram)  { return OPEN_ERR_OUT_OF_RAM; }
						catch (Genode::Out_of_caps) { return OPEN_ERR_OUT_OF_CAPS; }
					}
					
					return OPEN_ERR_UNACCESSIBLE;
				}
			}
			
			// just forward
			// (map() the UNCHANGED <path_ours>, so that the underlying driver knows to access attributes if applicable -- it will call VirtualizedXattr on its own)
			auto res = _rawfs.open(
				mapRaw( path_ours ).string(),
				vfs_mode,
				handle,
				alloc
				);
			return res;
		}

		Opendir_result opendir(char const *path_ours, bool create,
		                       Vfs_handle **handle,
		                       Allocator &alloc) override
		{
			auto path_raw = mapRaw( path_ours );
			TRACE( "opendir path=", path_ours, " alias: ", path_raw, " create=", create );
			
#if 0
			// BQuery case
			Genode::Path<256> /**/ path;
			path.import( path_ours );
			if( path.strip_prefix(bquery_support_prefix) )
			{
				Genode::log( "opening (virtual) query 'folder'..." );
				
				try {
					*handle = new (alloc) QueryDir_vfs_handle( *this, *this, alloc );
				}
				catch (Genode::Out_of_ram)  { return OPENDIR_ERR_OUT_OF_RAM; }
				catch (Genode::Out_of_caps) { return OPENDIR_ERR_OUT_OF_CAPS; }
	
				return OPENDIR_OK;
			}
#else
			// in open() instead !
#endif
			
#ifdef OLD_STYLE_OPENDIR
			// just forward
			auto res = _rawfs.opendir(
				path_raw.string(), create , handle, alloc);
#else
			// Direct delegation does NOT work for some reason, so do "matrioshka" VFS Handle:
			//
			// If we just forward to _rawfs.opendir(), it won't work in most cases (except when opening the top-level directory).
			// The created VFS handle will not work read() : genode seems to call
			// handle->complete_read() on the wrong fs (i.e. the call does not land in vfs_fuse_ntfs filesystem's complete_read() but in someone else's)
			// So, let's use a "wrapper" handle, around the underlying handle, similar to what we do with Attr_vfs_handle:
			
			// forward to mapped path
			Vfs_handle * underlying_rawfs_handle = nullptr;
			auto res = _rawfs.opendir(
				path_raw.string(),
				create,
				&underlying_rawfs_handle,
				alloc
				);
			if( OPENDIR_OK == res && underlying_rawfs_handle )
			{
				try {
					*handle = new (alloc)
						Dir_vfs_handle(
							*this,
							*this,
							alloc,
							underlying_rawfs_handle
							);
					//Genode::log(" ___ idx opendir: IDX dirhandle is ", handle, " with embedded NTFS-dir-handle: ", underlying_rawfs_handle );
					return OPENDIR_OK;
				}
				catch (Genode::Out_of_ram)  { return OPENDIR_ERR_OUT_OF_RAM; }
				catch (Genode::Out_of_caps) { return OPENDIR_ERR_OUT_OF_CAPS; }
			}
			
			TRACE( "  ** ==> opendir failed for raw path=", path_raw, " status -> ", int(res) );
			
			return res;  // e.g. OPENDIR_ERR_UNACCESSIBLE;
#endif
			
			return res;
		}
		
		Openlink_result openlink(char const *path_ours, bool create,
		                         Vfs_handle **handle, Allocator &alloc) override
		{
			auto path_raw = mapRaw( path_ours );
			TRACE( "openlink <", path_ours, "> aka <", path_raw, ">" );
			
			auto res = _rawfs.openlink( path_raw.string(), create, handle, alloc );
			//Genode::log( "  openlink res: ", (int)res, " versus OPENLINK_ERR_PERMISSION_DENIED: ", (int)OPENLINK_ERR_PERMISSION_DENIED );
			
			return res;
		}
		
		void close(Vfs_handle *vfs_handle) override
		{
			///later: call sync() or sync_state() first ?

			TRACE( "close" );

#if 1
			Attr_vfs_handle * attr = dynamic_cast< Attr_vfs_handle* >( vfs_handle );
			if( attr )
				_rawfs.close( attr->_handle_raw );
#endif

#ifdef OLD_STYLE_OPENDIR
			// nothing in this case
#else
			Dir_vfs_handle * dir = dynamic_cast< Dir_vfs_handle* >( vfs_handle );
			if( dir )
				_rawfs.close( dir->_handle_raw );
#endif
			
			if (vfs_handle && (&vfs_handle->ds() == this)) {
				destroy(vfs_handle->alloc(), vfs_handle);
			}
		}

	///	Watch_result watch(char const      *path, ..

		Genode::Dataspace_capability dataspace(char const *) override
		{
			Genode::warning(__func__, " not implemented in indexer plugin");
			return Genode::Dataspace_capability();
		}

		void release(char const *, Genode::Dataspace_capability) override
		{
			Genode::warning(__func__, " (of dataspace) not implemented in indexer plugin");
		}

		file_size num_dirent( char const * path_ours ) override
		{
			auto path_raw = mapRaw( path_ours );
			TRACE( "num_dirent path=", path_ours, " alias: ", path_raw );
			
#if 0
			Genode::Path<256> /**/ path;
			path.import( path_ours );
			if( path.strip_prefix(bquery_support_prefix) )
			{
				Genode::log( "   ... bquery -> pretending to COUNT entries" );
				
				return 99;//1;///////
			}
#else
			// in stat() instead
#endif
			
			// just forward
			return _rawfs.num_dirent( path_raw.string() );
		}

		bool directory( char const * path_ours ) override
		{
			auto path_raw = mapRaw( path_ours );
			TRACE( "directory path=", path_ours, " alias: ", path_raw );
			
			// Special-Case: "BQuery"
			// called BOTH in "vfs server" and "local fs" cases ! ******
			Genode::Path<256> /**/ path;
			path.import( path_ours );
			if( path.strip_prefix(bquery_support_prefix) )
			{
#if 0
				Genode::log( "   ... bquery -> pretending this is a (virtual) directory" );
				return true;
#else
				// e.g.: stripped == </(Audio:Artist=="Pink*Floyd")>
				
				// We cannot just unconditionally "return true" here to facilitate the remote-vfs case,
				// as that would break the local-fs case... We have to be more selective than that:
				// if being called for just the top "/_BQuery_support" then we return true,
				// as that is the vfs server calling us and it needs to be told it's a directory:
				bool is_dir = path.equals( "" );  // test against empty string, not against "/" (there's not trailing slash on a normmalized path)
				
				TRACE( "    returns (BQuery case): <", is_dir?"true":"false", ">" );
				return is_dir;
#endif
			}
			
			// just forward
			auto ret = _rawfs.directory( path_raw.string() );
			
			TRACE( "    returns: <", ret?"true":"false", ">" );
			return ret;
		}

		char const * leaf_path( char const * path_ours ) override
		{
			auto path_raw = mapRaw( path_ours );
			TRACE( "leaf_path path: [", path_ours, "] alias: [", path_raw, "]" );
			
			const char * ret = nullptr;
			
			// Special-Case: "BQuery"
			// (the below code is needed when using a *vfs server* instead of a local file system)
			Genode::Path<256> /**/ path; ///ToDo
			path.import( path_ours );
			if( path.strip_prefix(bquery_support_prefix) )
			{
				ret = path_ours;
			}
			else  // Base case:
			{
				// just forward
				ret =  _rawfs.leaf_path( path_raw.string() );
			}
			
			TRACE( "    returns: <", ret, ">" );
			return ret;
		}

		Stat_result stat( char const * path_ours, Stat & out ) override
		{
			auto path_raw = mapRaw( path_ours );
			TRACE( "stat path=", path_ours, " alias=", path_raw );
			
			// Special-Case: "BQuery"
			// (the below code is needed when using a local-fs)
			Genode::Path<256> /**/ path; ///ToDo
			path.import( path_ours );
			if( path.strip_prefix(bquery_support_prefix) )
			{
				// BQuery case! :
				//Genode::log( "  stat: this is a BQuery support request for query: ", path.string() );
				
				out.device = (Genode::addr_t)this;
				out.inode = 1;// dodge "0" inode as that would break LibC
				out.rwx   = Node_rwx::ro();
#if 0
				out.type  = Node_type::DIRECTORY;
				out.size  = 0;//99;
#else
				out.type  = Node_type::CONTINUOUS_FILE;
				
				// optim -- we Query() here in stat() and then again in open(), but <indexStore> does cache query results in between...
				indexStore.SetQuery( Genode::strlen(path.string())
					? path.last_element()
					: ""
					);
				out.size = indexStore.ResultsSize();
#endif
				
				TRACE( "  -> returns (BQuery case): ", int(STAT_OK), " out.device: ", out.device, " out.size: ", out.size, " out.inode: ", out.inode );
				
				return STAT_OK;
			}
			
			// just forward
			Stat_result ret = _rawfs.stat( path_raw.string(), out );
			
			TRACE( "  -> returns: ", int(ret), " out.device: ", out.device, " out.size: ", out.size, " out.inode: ", out.inode );
			return ret;
		}

		Unlink_result unlink( char const * path_ours ) override
		{
			auto path_raw = mapRaw( path_ours );
			TRACE( "unlink path=", path_ours, " alias: ", path_raw );
			
			VirtualizedXattr isattr( path_ours );
			
			///ToDo: if bquery path -> return: illegal
			
			// snoop
			if( isattr.initcheck() == Directory_service::STAT_OK )
				indexStore.AttrDeleted( isattr.xattr_name(), isattr.real_path() ); ///+make that conditional on <ret> return code
			else
				indexStore.NodeDeleted( path_ours ); ///+make that conditional on <ret> return code
			
			// forward
			return _rawfs.unlink( path_raw.string() );
			
			// sync index
			saveIndex();
		}
		
		Rename_result rename( char const * from, char const * to ) override
		{
			auto from_dist = mapRaw( from );
			auto to_dist   = mapRaw( to );
			//TRACE( "rename" );// path=", path_ours, " alias: ", path_raw );
			TRACE( "rename from=", from, " to=", to );
			
			///ToDo: if bquery path -> return: illegal
			
			// snoop
			indexStore.NodeMoved( from, to ); ///+make that conditional on <ret> return code
			
			// forward
			auto ret = _rawfs.rename( from_dist.string(), to_dist.string() );
			
			// sync index
			saveIndex();
			
			return ret;
		}
		
		
		/************************
		 ** File I/O interface **
		 ************************/

		Write_result write(Vfs_handle *vfs_handle,
		                   char const *buf, file_size buf_size,
		                   file_size &out_count) override
		{
			TRACE( "write bufsize=", buf_size );
			
			Vfs::File_io_service::Write_result ret;// = 0;
			
			Attr_vfs_handle * attr = dynamic_cast< Attr_vfs_handle* >( vfs_handle );
#ifndef OLD_STYLE_OPENDIR
			Dir_vfs_handle * dir = dynamic_cast< Dir_vfs_handle* >( vfs_handle );
			
			if( dir )
			{
				ret = dir->_handle_raw->fs().write(
					attr->_handle_raw,  // !
					buf,
					buf_size,
					out_count
					);
			}
			else
#endif
			if( attr )  // snoop on attribute ops
			{
				ret = attr->_handle_raw->fs().write(//_rawfs.write(
					attr->_handle_raw,  // !
					buf,
					buf_size,
					out_count
					);
				//Genode::log("ret: ", (int)ret, " buf_size: ", buf_size, " out_count: ", out_count);
				
				if( WRITE_OK == ret && buf_size == out_count )
				{
					indexStore.AttrMutated(
						attr->pathOurs.string(),
						attr->_attrname.string(),
						buf,
						buf_size
						);
					
					// sync index
					saveIndex();
				}
				else
					Genode::warning( "  xa-index left unchanged, since the attribute write is failed or uncomplete" );
			}
			else
			{
				// forward
				ret = _rawfs.write(
					vfs_handle,
					buf,
					buf_size,
					out_count
					);
			}
			
			return ret;
		}

		// subtlety warning! we need this even though it's not obvious from the basics:
		// (otherwise complete_read() will NOT be called in the Dir case)
		bool queue_read(Vfs_handle *vfs_handle, file_size size) override
		{
			//Genode::warning( "IDX** Queue-Read with size ", size, ", handle ", vfs_handle );
			
#ifndef OLD_STYLE_OPENDIR
			// Dir-wrapper case, i.e. opendir() stuff
			Dir_vfs_handle * dir = dynamic_cast< Dir_vfs_handle* > ( vfs_handle );
			if( dir )
			{
				// T-
				// This is the place where we need to synchronize seek'ing offsets (only needed at T-, not T+ it seems).
				// class BSymlink won't work otherwise, when resolving deep paths via opendir().
					// seek() is not *virtual*, hence not overridable for our purpose (see: vfs_file_handle.h),
					// so we have no choice but to handle it here:
				dir->_handle_raw->seek(
					dir->seek()
					);
				//Genode::warning( "  (q) handle.seek: ", dir->seek(), "  underlying-handle.seek: ", dir->_handle_raw->seek() );
				
				// T0
				auto res = dir->_handle_raw->fs().queue_read(
					dir->_handle_raw,  // !
					size
					);
				
				return res;
			}
#endif
			return inherited::queue_read( vfs_handle, size );
		}
		
		///xx:   do we need to override queue_sync() etc to foward sync's to ntfs too ?
		///ToDo: override this?: virtual bool update_modification_timestamp(Vfs_handle *, Vfs::Timestamp)
		
		Read_result complete_read(Vfs_handle *vfs_handle, char *buf,
		                          file_size buf_size,
		                          file_size &out_count) override
		{
			TRACE( "complete_read bufsize=", buf_size );
			
#ifndef OLD_STYLE_OPENDIR
			// Dir-wrapper case
			Dir_vfs_handle * dir = dynamic_cast< Dir_vfs_handle* > ( vfs_handle );
			if( dir )
			{
				//Genode::log("  _ dynamiccast of ", vfs_handle, " yields: ", dir, " (embedded NTFS handle=", dir->_handle_raw, ")" );
				
				// seek(): unlike in queue_read(), it seems seek offset synchronization is not needed here, neither at T- nor T+...
				
				//Genode::warning( "  handle.seek: ", dir->seek(), "  underlying-handle.seek: ", dir->_handle_raw->seek() );
				
				auto res = dir->_handle_raw->fs().complete_read(
					dir->_handle_raw,  // !
					buf,
					buf_size,
					out_count
					);
				
				return res;
			}
#endif
			// BQuery case
			auto * query_handle = dynamic_cast< QueryDir_vfs_handle* >( vfs_handle );
			if( query_handle )
				return query_handle->read( buf, buf_size, out_count );
			
			// there ought to be no other cases (underlying FS reads go directly to underlying's hook, not this hook)
			return READ_ERR_INVALID;
		}

		Ftruncate_result ftruncate(Vfs_handle *vfs_handle, file_size len) override
		{
			TRACE( "ftruncate len=", len );
			Genode::error( "indexer: ftruncate(): not implemented ; when is that called anyway ?" );
			
			return FTRUNCATE_ERR_NO_PERM;//
		}

		bool read_ready(Vfs_handle *) override { return true; }
};


extern "C" Vfs::File_system_factory *vfs_file_system_factory(void)
{
	struct Factory : Vfs::File_system_factory
	{
		Vfs::File_system *create(Vfs::Env &vfs_env, Genode::Xml_node config) override
		{
			TRACE( "new idx (Indexer).." );
			return new (vfs_env.alloc()) Indexed_file_system(vfs_env, config);
		}
	};

	static Factory f;
	return &f;
}