/*
 * Copyright (C) 2021 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU Affero General Public License version 3.
 */

/* Genode includes */
#include <os/path.h>
#include <os/vfs.h>  // Directory::Path
#include <libc/component.h>
#include <vfs/file_system_factory.h>
#include <vfs/file_system.h>
#include <vfs/vfs_handle.h>

#include <file_system_session/file_system_session.h>  // Directory_entry

/* libc includes */
#include <sys/dirent.h>
#include <errno.h>

/* FUSe includes */
#include <fuse.h>
#include <fuse_private.h>

/* local includes */
#if 0
#	include "block_dev.h"
#else
#	include "blocks_as_file.h"
#endif
#include "cache_nument.h"
#include "fuse_symlink.h"
#include "fuse_xattr.h"
#include "xattr_mapping.h"

//#define LEAK_CHECKER_ENABLED
#if defined( LEAK_CHECKER_ENABLED )
#	include "LeakChecker.h"
#endif


#if 0
#	define TRACE(x, ...) Genode::log( "   -> ntfs.", x, ##__VA_ARGS__ )
#else
#	define TRACE(x, ...) if( debug ) Genode::log( "    {ntfs} ", x, ##__VA_ARGS__ )
static int debug = 0;
#endif

// profiling/TSC:
#if 0
#	define SCAN_TIME  GENODE_LOG_TSC(5)
#else
#	define SCAN_TIME
#endif


namespace {

	using namespace Vfs;

};


#if 0
Blockdev * ptr_to_blockdev = NULL;
#else
Blocks_as_file * ptr_to_blockdev = NULL;
#endif


// Mutual-exclusion mechanism (temporary ?)
// (alternatively, could "lock a dir":
//const char * dir name....
//  we'd need to be informed of which dir we reside in though)
static int count_fuse_instances = 0;
	// Two reasons for that mechanism:
	// 1) quickly catching the "oops, we're re-using a non-re-entrant plugin" condition (not that stacking 2 plugins occured often in the past!)
	// 2) on-purpose stacking of 2 plug-ins, so that the second takes over if the first fails to initialize (example: mounting at /boot the second NTFS partition, and if that fails, trying for the first NTFS partition)
	// If some day the code becomes clean enough to be re-entrant, and we do want to instantiate multiple NTFS plug-ins per PD, we'll need to find an alternative to scheme #2



//#pragma mark -


class Fuse_file_system/*Fuse___for_ntfs__file_system*/ : public Vfs::File_system
{
	private:

		const Genode::String<32> mode_yes = "yes";
		const Genode::String<32> mode_no  = "no";
		const Genode::String<32> mode_yes_or_fallback_to_no = "yes_or_fallback_to_no";

		struct Dir_vfs_handle : Vfs::Vfs_handle
		{
			String<MAX_PATH_LEN> _path;/// or B_PATH_NAME_LENGTH (1024) ? also see note in fuse_symlink.h
			struct fuse_file_info _file_info;
			
			//int ctor_res = 0;
			struct fuse_dirhandle dh;
			
			Dir_vfs_handle(
				Directory_service &ds,
				File_io_service   &fs,
				Genode::Allocator &alloc,
				Directory::Path path
				)
			:	Vfs_handle(ds, fs, alloc, 0)
				,_path( path )
				,_file_info()
				,dh()
			{
				TRACE( " ctor Dir_vfs_handle <", path, ">  -> ", this);
				
				///later: fuse is odd: seems we can either opendir(), or open(), or do nothing at all (!), and yet we are allowed to call readdir() afterwards, it makes no difference...
				//				int res = Fuse::fuse()->op.open/*dir*/(path.string(), &_file_info);
				// Same re. memory consumption: we don't leak memory (at least not here) even if missing said opendir() or release/releasedir() mentionned in https://www.cs.hmc.edu/~geoff/classes/hmc.cs135.201001/homework/fuse/fuse_doc.html
				
				// If we do call opendir(), we get this, oddly:
				//xx says "fuse-opendir Handle CTOR res: -2"...
				
#if 0
				// read the whole dir listing at once, once and for all here in ctor...
				// The way FUSe works, readdir() collects the whole directory in one go.
				int ctor_res = Fuse::fuse()->op.readdir(
					_path.string(),
					&dh,
					Fuse::fuse()->filler,
					0,
					&_file_info
					);
				
				Genode::log/*				TRACE*/(" .read_res: ", res);
#endif
///later: maybe look at vfs_rump.cc , _finish_read() of directory ?
			}
			
			/*
			~Dir_vfs_handle()
			{
				TRACE( " ~DTor: Dir-handle!" );
			}
			*/
			
			Read_result read(char * dest, file_size len/*buflen*/, size_t & out_count) //override
			{
				SCAN_TIME;
				TRACE( "dir.Read( buflen: ", len, " )  seek_tell: ", seek() );
				//Genode::log("  ntfs.Handle< ", _path, " >.read with seek: ", seek());
				
				out_count = 0;
				
				if (len < sizeof(struct Dirent)) {  // [patched-Genode]: use sizeof on 'struct Dirent', not '::File_system::Directory_entry'; and same below
					Genode::error("fuse: read buffer too small for directory entry");
					return READ_ERR_INVALID;
				}

				if (seek() % sizeof(struct Dirent)) {
					Genode::error("fuse: seek offset not aligned to sizeof(Directory_entry)");
					return READ_ERR_INVALID;
				}

				const file_size index =
					seek() / sizeof(struct Dirent);
				
				// ======================================
				// First time we're called ? -> readdir()
				// ======================================
				if( dh.buf == 0 )
				{
					//GENODE_LOG_TSC_NAMED(1, "Fuse.readdir >DirVfsHandle");
					//Genode::log("   fuse.readdir(<", _path, ">)  >DirVfsHandle");
					
					int res = Fuse::fuse()->op.readdir(
						_path.string(),
						&dh,
						Fuse::fuse()->filler,
						0,
						&_file_info
						);
					TRACE(" read_res: ", res);
					
					if (res != 0)
					{
						return READ_ERR_INVALID;
					}
				}
				// ===================================
				
				///ToDo: a 'count' accessor ? If it can be of use to downstream code ? E.g.:
				/*
				unsigned count_children()
				{
					return
						dh.offset
						/
						sizeof (struct dirent)
						;
				}
				*/
				
				if (index > (file_size)(dh.offset / sizeof (struct dirent)))
				{
					Genode::log( " READ_ERR_INVALID, since index ", index, " > bytes of ", dh.offset );
					
					//
					return READ_ERR_INVALID;
					//
				}
				///? maybe I should return a "Dirent_type::END" as "sentinel" last entry, like vfs_fatfs.cc? though the caller never asks me for a "+1" sentinel here...
				
				struct dirent * dent = nullptr;
				{
					// T+, in case buf was re-alloced:
					//struct dirent *de = (struct dirent *)buf;
					struct dirent * de = (struct dirent *)dh.buf;
					
					dent = de + index;
				}
				//if (!dent)//
				//	return READ_ERR_INVALID;//
				
				//... where did I find the original code for the below? It uses the wrong cast, and thus was affecting wrong field/values...
				//::File_system::Directory_entry *e = (::File_system::Directory_entry *)( dest );
				//
				// Here's the appropriate cast:
				Dirent * e = (Dirent*) dest;
				
#if 0
				{
					Genode::String<64> s;
					switch( dent->d_type )
					{
						case DT_UNKNOWN: s = "DT_UNKNOWN"; break;
						case DT_FIFO: s = "DT_FIFO"; break;
						case DT_CHR: s = "DT_CHR"; break;
						case DT_DIR: s = "DT_DIR"; break;  // 4
						case DT_BLK: s = "DT_BLK"; break;
						case DT_REG: s = "DT_REG"; break;  // 8
						case DT_LNK: s = "DT_LNK"; break;
						case DT_SOCK: s = "DT_SOCK"; break;
						case DT_WHT: s = "DT_WHT"; break;
					}
					Genode::log("dent->d_type: ", dent->d_type, " aka: ", s);
				}
#endif
				// .type:
				switch (dent->d_type)
				{
					///later: handle TRANSACTIONAL_FILE ?
#if 0
					// these were the wrong constants, breaking dir reading! The correct ones are Dirent_type::*
					case DT_REG: e->type = ::File_system::Node_type::CONTINUOUS_FILE; break;
					case DT_DIR: e->type = ::File_system::Node_type::DIRECTORY; break;
					case DT_LNK: e->type = ::File_system::Node_type::SYMLINK; break;
#else
					case DT_REG: e->type = Dirent_type::CONTINUOUS_FILE; break;
					case DT_DIR: e->type = Dirent_type::DIRECTORY; break;
					case DT_LNK: e->type = Dirent_type::SYMLINK; break;
#endif
					/**
					 * There are FUSE file system implementations that do not fill-out
					 * d_type when calling readdir(). We mark these entries by setting
					 * their type to DT_UNKNOWN in our libfuse implementation. Afterwards
					 * we call getattr() on each entry that, hopefully, will yield proper
					 * results.
					 */
					case DT_UNKNOWN:
					{
						Genode::warning("dent -> unknown -> call getattr()");
						
						Genode::Path<4096> path(dent->d_name, _path.string());
						struct stat sbuf;
						
						int res = Fuse::fuse()->op.getattr(path.base(), &sbuf);
						if (res == 0) {
							switch (IFTODT(sbuf.st_mode)) {
#if 0
							case DT_REG: e->type = ::File_system::Node_type::CONTINUOUS_FILE ; break;
							case DT_DIR: e->type = ::File_system::Node_type::DIRECTORY; break;
#else
							case DT_REG: e->type = Dirent_type::CONTINUOUS_FILE ; break;
							case DT_DIR: e->type = Dirent_type::DIRECTORY; break;
							case DT_LNK: e->type = Dirent_type::SYMLINK; break;
#endif
							}
							/* break outer switch */
							break;
						}
					}
					default:
						Genode::error( "fuse: unknown dirent type: ", dent->d_type, " at path: ", _path );
						//
						return READ_ERR_INVALID;
						//
				}
				//	Genode::log("dent->d_type: ", dent->d_type);
				
				// .fileno:
				e->fileno = dent->d_fileno ;
				
				// .rwx:
				e->rwx = Node_rwx::rw();///
				
				// .name:
				copy_cstring( e->name.buf, dent->d_name, sizeof(e->name.buf) );
				e->sanitize();  // null-terminate the string
				
				//Genode::log( "    dir.read got: <", (const char*)e->name.buf, "> with inode: ", e->fileno);//inode);
				if(e->fileno <= 0)
				{
					Genode::warning("zero node! -> fixing...");  // :-)
					e->fileno = 1;
				}
				
				//Genode::log( " Dir.Read Success, with filename <", (const char*)e->name.buf, ">" );
				
				// success for <1> entry:
				out_count = sizeof(struct Dirent);
				//
				return READ_OK;
			}
		};
		
		struct File_vfs_handle : Vfs::Vfs_handle
		{
			//Directory::Path _path;//or in a (common/parent) class?
			String<256> _path;
			struct fuse_file_info  _file_info;
			/**/

			File_vfs_handle(Directory_service &ds,
			                File_io_service   &fs,
			                Genode::Allocator &alloc,
			                Directory::Path path
			                )
			:	Vfs_handle(ds, fs, alloc, 0)
				,_path(path)
				,_file_info({})
			{
//_d_: #warning error review this whole file for "nullptr handle" case
				//Genode::log("handle <", path, ">");
				int res = Fuse::fuse()->op.open(path.string(), &_file_info);
if (res != 0) Genode::warning(/*"unfinished"*/ "fuse: could not open path for Vfs Handle to <", path, ">");
				mode_t mode = S_IFREG | 0644;
				res = Fuse::fuse()->op.mknod(path.string(), mode, 0);
///ToDo: "unfinished fuse" ! this barely works as-is...
///				if (res != 0) Genode::warning("unfinished-fuse: could not create '", path, "'");
				
		//+		res = Fuse::fuse()->op.ftruncate(path, 0, &_file_info);
				///
			}
			
		/*
			~File_vfs_handle()
			{
				TRACE( "DTor: File_handle" );
				
				// this makes no difference, memory leak wise
		//		Fuse::fuse()->op.release( _path.string(), &_file_info );
			}
		*/
			
			Read_result read(char *dest, file_size len, size_t &out_count) //override
			{
				out_count = 0;

				/* current offset */
				file_size seek_offset = seek();
///later: seems to work without adjusted append/seek offset, but is it safe ?
//				if (seek_offset == ~0ULL)
//					seek_offset = _length();

				int ret = Fuse::fuse()->op.read(_path.string(), dest, len,
				                                seek_offset, &_file_info);

				if (ret < 0) return READ_ERR_INVALID;

				out_count = ret;
				return READ_OK;
			}

			Write_result write(char const *src, file_size len,
			                   size_t &out_count) //override
			{
				out_count = 0;

				/* current read offset */
				file_size const seek_offset = seek();

				//Genode::log( "  ntfs.write ", len, " bytes to <", _path, "> @", seek_offset );
				int ret = Fuse::fuse()->op.write(_path.string()/*.base()*/, src, len,
				                                 seek_offset, &_file_info);

				if (ret < 0) return WRITE_ERR_INVALID;//

				out_count = ret;
				return WRITE_OK;
			}

			Ftruncate_result ftruncate (file_size len)
			{
				int ret = Fuse::fuse()->op.ftruncate(_path.string(), len,
				                                     &_file_info);
				if (ret < 0) return FTRUNCATE_ERR_NO_PERM;//

				return FTRUNCATE_OK;
			}

		//	bool read_ready() override { return false; }
		};
		
	private:
		/*
		todo: add book-keeping stuff (?):
		
		class Fuse_vfs_file : public Genode::List<Fuse_vfs_file>::Element
			or : 		struct File : Genode::Avl_node<File>
		class Fuse_vfs_handle : public Vfs::Vfs_handle
			struct Fuse_file_watch_handle : Vfs_watch_handle,  Fuse_watch_handles::Element
			struct Fuse_dir_watch_handle : Vfs_watch_handle, Fuse_dir_watch_handles::Element
		class Fuse_vfs_file_handle : public Fuse_vfs_handle
			or : Fatfs_handle, Fatfs_file_handles::Element
		class Fuse_vfs_dir_handle : public Fuse_vfs_handle
		*/

		/*
		FUSe fuse....;
		
		Genode::List<Dataspace_vfs_file> _files { };
		..
			or :
		_open_files..
		//_dir_watchers..
		..
		*/
		
		Vfs::Env &_vfs_env;
		
#if 0
		Blockdev _block { _vfs_env.env(), _vfs_env.alloc() };
#else
		Blocks_as_file _block { _vfs_env };
#endif

		// Optimizations
		NumEntCache numentCache;


	public:

		Fuse_file_system(Vfs::Env &env, Genode::Xml_node config)
		: _vfs_env(env)
		{
			// T-
			// Mutual-exclusion hack:
			// (this must be at T- before we change any global var)
			//
			if (count_fuse_instances > 0 || Fuse::initialized() > 0) {
				Genode::warning("FUSE: there is already an instance in the same PD, but this VFS plug-in is not re-entrant -- bailing out");
				
				//
				throw Genode::Exception();
			}
			
			// e.g. "/dev/sda1" or "/dev/blkdev"
			Genode::Directory::Path path(config.attribute_value("block_path", Genode::Directory::Path()));
			
			// set r/o (writeable==false) by default
			// (allowed values for writeable: { "yes", "no", "yes_or_fallback_to_no" }
			const Genode::String<32> request_writeable =
				config.attribute_value( "writeable", mode_no );
			if( false == config.has_attribute("writeable") )
				Genode::warning( "attribute 'writeable' not specified in fuse config, defaulting to: ", request_writeable );

			/* mount the file system */
			
			///later: find a way to embed this pointer inside NTFS instead, to be cleaner
			// (more precisely: to be re-entrant -- we cannot/must not instantiate several instances if we have global vars, the latest instantiated would crash the previous instances) (though there is *also* Fuse::initialized() & friends that use globals, and not clear how to fix those, so maybe it's out of reach ?)
			ptr_to_blockdev = &_block;
extern void malloc_init(Genode::Env &env, Genode::Allocator &alloc);//
			malloc_init(_vfs_env.env(), _vfs_env.alloc());

			if (path.length() <= 0) {
				Genode::error("FUSE: missing block device ; please provide a Blocks-as-file and specify it, e.g. 'block_path=/dev/sda1'.");
				
				// (!) make sure to 'eject' this plug-in, otherwise it would end up as a minefield/zombie plug-in
				// throw "No block device to read from";
				throw Genode::Exception(); // or something like Service_denied();
			}

			//Genode::log( "T- fuse initialization status: ", Fuse::initialized() );

			if (false == setup_fs(path, request_writeable)) {
				Genode::error("FUSE initialization failed (no valid NTFS file system ?)");
				
				// (!) make sure to 'eject' this plug-in, otherwise it would end up as a minefield/zombie plug-in
				// throw "Block device is not a (mountable) NTFS file system";
				throw Genode::Exception();
			}

			// !
			// T+ (once we are certain we'll remain instantiated)
			count_fuse_instances += 1;

			// "clear the list" to simplify leak hunting (we're interested in regular
			// ops' leaks, rather than in -- fairly harmless -- initialization leaks)
#if defined( LEAK_CHECKER_ENABLED )
			LeakChecker::Singleton()->Clear();
#endif
		}

		~Fuse_file_system()
		{
			if (Fuse::initialized()) {
				Fuse::deinit_fs();
			}
		}

		bool setup_fs( const Genode::Directory::Path path, const Genode::String<32> request_writeable )
		{
			const bool readwrite =
				request_writeable == mode_yes
				|| request_writeable == mode_yes_or_fallback_to_no
				;
			
			// emphasize the r/o case: that case better be caught early, as that is the root cause of some really pain-in-the-neck hard to diagnose symptoms, unless aware of the read-only situation (libntfs-3g is not very good at all at reporting errors, when it unexpectedly fails to write to a partition)
			Genode::log( "-- Starting fuse with device file '", path, "' -- (", (readwrite? "Read/Write":"** READ-ONLY **"), ") --" );
			
			if (Fuse::init_fs(path.string(), readwrite))
				return true;
			// else: Failed... But that might be a refusal to mount "r/w" and we might be configured to try again "r/o":
			
			if (mode_yes_or_fallback_to_no == request_writeable) {
				// Attempt again, but fallback to writeable==false this time:
				
				if (Fuse::init_fs(path.string(), false)) {
///ToDo: be (visually) verbose in the "ambiguous" case! If this "fallback r/o" works, and end-users accidentally boot into a r/o mode, we'll want to warn them with a BAlert, not just this low-key warning() !
					Genode::warning( "FUSE: WARNING: fs mounted *READ-ONLY* even though we're configured to attempt read-write" );
					
					return true;
				}
			}
			
			// Definite fail
			return false;
		}


		/***************************
		 ** File_system interface **
		 ***************************/

		///? static char const *name() { return "fuse_ntfs3g__XX"; }
		char const *type() override { return "fuse_ntfs3g__XX"; }

		/*********************************
		 ** Directory service interface **
		 *********************************/

		Open_result open(char const *path, unsigned vfs_mode,
		                 Vfs_handle **handle,
		                 Allocator  &alloc) override
		{
			SCAN_TIME;
			TRACE( "open path=", path, " mode=", vfs_mode );
				// Open_mode::OPEN_MODE_RDONLY == 0
				// Open_mode::OPEN_MODE_WRONLY == 1
			
			bool const create = vfs_mode & OPEN_MODE_CREATE;
			
			if( create )
			{
				// !
				// any write/create access might make the cache stale
				numentCache.Clear();
			}
/*
			Dataspace_vfs_file *file { };


			if (create) {

				if (_lookup(path))
					return OPEN_ERR_EXISTS;

				if (strlen(path) >= MAX_NAME_LEN)
					return OPEN_ERR_NAME_TOO_LONG;

				try { file = new (alloc) Dataspace_vfs_file(path, alloc, _env.env().ram()); }
				catch (Allocator::Out_of_memory) { return OPEN_ERR_NO_SPACE; }

				_files.insert(file);
			} else {
				file = _lookup(path);
				if (!file) return OPEN_ERR_UNACCESSIBLE;
			}
*/

#ifndef DISABLE_XATTR
			// We are asked to open() 'path'. This might be a run-of-the-mill file or dir, or a (virtual) xattr dir.
			// First test for 1) xattr case, then 2) xattr-listing case (these are cheap if they test negative)
			// then test for plain file case.
			
			VirtualizedXattr virt(path);
			if (virt.initcheck() == Directory_service::STAT_OK)
			{
				// XAttr case
				// ==========
				
				TRACE( " : open : 'xattr' case : attr ", virt.xattr_name(), " for file ", virt.real_path() );
				
				// sanity check: can we rely on a "getXattr" operation ?  (fuse-ntfs-3g does have it, but in other FUSE back-ends the hook is likely to be NULL)
				if(Fuse::fuse()->op.getxattr == NULL || Fuse::fuse()->op.setxattr == NULL)
				{
					error("XAttr support not provided by file-system, bailing out (though we might provide a fall-back implementation some day)");
					return OPEN_ERR_UNACCESSIBLE;///ToDo: '- ENOTSUP'...
				}
				
				if( Open_mode::OPEN_MODE_RDONLY == vfs_mode )
				{
					TRACE( " : open-for-reading : test presence of attr payload" );
					
					// if the xattr does not exist we must NOT return a vfs_handle, we must (just like for
					// any 'physical' file) fail early here, in open(), otherwise downstream read() code
					// will be in trouble at best (with "packet operation 0 failed" errs)
					// or outright stall/freeze the component, at worst.
					//
					if( false == Xattr_vfs_handle::FuseHasAttr(virt.real_path(), virt.xattr_name()) )
						//
						return OPEN_ERR_UNACCESSIBLE;
						//
				}
				
				try {
					TRACE( " : open : instantiate handle" );
					
					*handle = new (alloc)
						Xattr_vfs_handle(*this, *this, alloc, virt.real_path(), virt.xattr_name());
				}
				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;
			}
			
			VirtualizedListingXa listing(path);
			if (listing.initcheck() == Directory_service::STAT_OK)
			{
				// XAttr-Listing case
				// ==================
				
				if(Fuse::fuse()->op.listxattr == NULL)
				{
					error("XAttr support not provided by file-system, bailing out (though we might provide a fall-back implementation some day)");
					return OPEN_ERR_UNACCESSIBLE;///ToDo: '- ENOTSUP'...
				}
				
				try {
					*handle = new (alloc)
						XaListing_vfs_handle(*this, *this, alloc, listing.real_path());
				}
				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
			// File case
			// =========
			
			try {
				///ToDo: transmit/enforce vfs_mode/create in the vfs_handle ? Look at how rump_fs does it
				*handle = new (alloc) File_vfs_handle(*this, *this, alloc, path);//, file);
			}
			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;
		}

		Opendir_result opendir(char const *path, bool create,
		                       Vfs_handle **handle,
		                       Allocator &alloc) override
		{
			SCAN_TIME;
			TRACE( "opendir path=", path, " create=", create );

			if (create) {
				// ! any write access might conflict with the cache, consider the cache stale
				numentCache.Clear();
				
				int res = Fuse::fuse()->op.mkdir(path, 0755);

				if (res < 0) {
					int err = -res;
					switch (err) {
						case EACCES:
							Genode::error("fuse: op.mkdir() permission denied");
							return OPENDIR_ERR_PERMISSION_DENIED;
						case EEXIST:
							return OPENDIR_ERR_NODE_ALREADY_EXISTS;
						case EIO:
							Genode::error("fuse: op.mkdir() I/O error occurred");
							return OPENDIR_ERR_LOOKUP_FAILED;
						case ENOENT:
							return OPENDIR_ERR_LOOKUP_FAILED;
						case ENOTDIR:
							return OPENDIR_ERR_LOOKUP_FAILED;
						case ENOSPC:
							Genode::error("fuse: op.mkdir() error while expanding directory");
							return OPENDIR_ERR_LOOKUP_FAILED;
						case EROFS:
							return OPENDIR_ERR_PERMISSION_DENIED;
						default:
							Genode::error("fuse: op.mkdir() returned unexpected error code: ", res);
							return OPENDIR_ERR_LOOKUP_FAILED;
					}
				}
			}
///ToDo: enabling this getattr() leaks 100-ish bytes, odd
#if 0
{
			struct stat s;
			int res = Fuse::fuse()->op.getattr(path, &s);
			
			if( res < 0 )
				Genode::log("fuse: ", create ? "create-dir" : "open-dir", " <", path, "> res: ", res);
}
#endif
			int res = 0;
			{
				//struct fuse_file_info  _file_info;///
				/////				res = Fuse::fuse()->op.open/*dir*/(path, &_file_info);
				//xxx why this immediate release ? is it because we're just interested in <res>, and will call opendir() in Dir-Vfs-Handle instead ?
				//////				Fuse::fuse()->op.release( path, &_file_info );
			}

			if (res < 0) {
				int err = -res;
				switch (err) {
					case EACCES:
						Genode::error("fuse: op.opendir() permission denied");
						return OPENDIR_ERR_PERMISSION_DENIED;
					case EIO:
						Genode::error("fuse: op.opendir() I/O error occurred");
						return OPENDIR_ERR_LOOKUP_FAILED;
					case ENOENT:
#if 0
						throw Lookup_failed();
#else
						///ToDo: ENOENT: add tracing, understand why NTFS-3g spuriously returns ENOENT ?
						
						// NTFS-3g returns that in seemingly normal operation, yet we want to proceed rather than fail
						TRACE("*** ntfs-3g: ENOENT ignored, reading directory anyway");
					break;
#endif
					case ENOTDIR:
						return OPENDIR_ERR_LOOKUP_FAILED;
					case ENOSPC:
						Genode::error("fuse: op.opendir() error while expanding directory");
						return OPENDIR_ERR_LOOKUP_FAILED;
					case EROFS:
						return OPENDIR_ERR_PERMISSION_DENIED;
					default:
						Genode::error("fuse: op.opendir() returned unexpected error code: ", res);
						return OPENDIR_ERR_LOOKUP_FAILED;
				}
			}
			
			// instead of allocating the Dir_vfs_handle at the very end, maybe I should do this:
			/* "attempt allocation before modifying blocks" */

			try {
				*handle = new (alloc) Dir_vfs_handle(*this, *this, alloc, path);
			}
			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;
		}

		Openlink_result openlink(char const *path, bool create,
		                         Vfs_handle **handle, Allocator &alloc) override
		{
			SCAN_TIME;
			TRACE( "openlink <", path, "> called" );
			
			if( create )
			{
				// ! any write/create access might make the cache stale
				numentCache.Clear();
			}
			
///ToDo: see ram_file_system.h: I should handle OPENLINK_ERR_NODE_ALREADY_EXISTS etc
			//readlink
			//symlink
			//fsync()/flush() ! or Fuse::sync_fs() in init.cc ?
			try {
				*handle = new (alloc) Symlink_vfs_handle(*this, *this, alloc, path);
			}
			catch (Genode::Out_of_ram)  { return OPENLINK_ERR_OUT_OF_RAM; }
			catch (Genode::Out_of_caps) { return OPENLINK_ERR_OUT_OF_CAPS; }
			
//_d_, solved (in ntfs driver) ! #warning error the first test passes, still getting trouble with parent.CreateSymLink() though
			return OPENLINK_OK;
		}

		void close(Vfs_handle *vfs_handle) override
		{
			///later: call sync() or sync_state() first ?

			SCAN_TIME;
			TRACE( "close ", vfs_handle );

			if (vfs_handle && (&vfs_handle->ds() == this))
			{
				destroy(vfs_handle->alloc(), vfs_handle);
			}
			
			//LeakChecker::Singleton()->DisplayStats();
		}

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

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

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

		file_size num_dirent(char const *path) override
		{
			SCAN_TIME;
			//LeakChecker::Singleton()->DisplayStats();
			TRACE( "num_dirent <", path, ">" );
			
			unsigned
				cached_count = 0;
			if( numentCache.GetNumFor(path, cached_count) )
				//
				return cached_count;
				//

			struct fuse_dirhandle dh;
			int res = 0;
	{
			struct fuse_file_info  _file_info;/// xxxx keep _file_info in vfs handle instead ?
			// rem out open(), unneeded anyway:
			res = 0;///Fuse::fuse()->op.open/*dir*/(path, &_file_info);
			;
			int err = -res;
			if( ENOENT == err )
				res = 0;  // ignore ENOENT, spuriously returned by NTFS..
			//TRACE( " 1.res: ", res );
			
			if (res == 0)
			{
				res = Fuse::fuse()->op.readdir(path/*.base()*/, &dh,
					Fuse::fuse()->filler, 0,
					&_file_info);
			}
	}

			if (res != 0)
			{
///later: this error() triggers a lot with the launcher (due to fs_rom/dynit config ?)... silence it for now, but try to improve fs_rom config later, so that it won't ask vfs_ntfs for non-existent stuff
///				Genode::error( "fuse: ntfs.num_dirent<", path, ">: fuse.readdir() failed, code: ", res );
				
				return 0;
			}
			
			TRACE( "  num_dirent(", path, ") returns: ", dh.offset / sizeof (struct dirent), " (Value *Cached* for future access!)" );
			//DONE - #warning wth, this looks like kinda crippled code. "we allocate a tiny buffer, and count the entries by how much of it is consumed"... why not iterate one by one, like vfs_fatfs.cc used to do ?

			file_size count = dh.offset / sizeof (struct dirent);
			numentCache.Memorize(
				path,
				count
				);
			
			return count;
///ToDo: should not count "." and ".." ? Do like RumpFS: Or is it necessary to keep them with fuse and lib-ntfs ? ///
			//	if (strcmp(".", dent->d_name) && strcmp("..", dent->d_name))
			//		++n;
		}

		bool directory(char const *path) override
		{
			SCAN_TIME;
			TRACE( "directory path=", path );

			struct stat s;
			if( 0 == Fuse::fuse()->op.getattr(path, &s) )
			{
				TRACE( " -> ", S_ISDIR(s.st_mode), " (physical directory)" );
				
///Todo: detect conflict here (or down there?): + if( exists(VirtXa.RealNode_Leafname()) )  Genode::warning( "ntfs: both the 'dot' path: xx and the 'base' path: xx exist as physical nodes, so the former 'shadows' any XAttr request, will not be able to fulfill XAttr requests to XX" );
				return S_ISDIR(s.st_mode);
			}
			
#ifndef DISABLE_XATTR
			// else: no such (physical) file, this may be a virt/dot-path though:
			VirtualizedXattr virt( path );
			virt.debug_dump(); ///
///ToDo: **here (or in stat() ??**) we fail a "jam t8" test, presumably because we unconditionally return "true" on dot-paths here ; we could ALLEVIATE (if not solve 100%) that problem : check that there exists a physical counterpart, then we weed out 99% of the false positives:
// + if( false == exists(VirtXa.RealNode_Leafname()) return false;
			
#if 1		// This is even more corner-case'ish than the below, as this is only required for Xa-Listing, IF THE idx and ntfs are on opposite ends of the client-server divide (!)
			if( 0 == Genode::strcmp(virt.xattr_name(), "///early-parsing-case///") )
			{
				TRACE( " -> true (virt.xa.dir : stage 1)" );
				return true;
			}
#endif
			// XAttr-writing HACK: the code needs to be a bit "temperamental", in order to accomodate
			// both the vfs-server case, and the local-fs case, which have different expectations ;
			// the below seems to satisfy both use-case expectations, just about:
			if( 0 == Genode::strcmp(virt.xattr_name(), "///second-early-parsing-case///") )
			{
				TRACE( " -> true (virt.xa.dir : stage 2)" );
				return true;
			}
			
			return false;
			///ToDo: replace cmp-to-hardcoded-str with an enum PARSING_STATE and corresponding accessor (kinda like initcheck() accessor)
			//..
			//if virt.initcheck() == STAT_OK;
#else
			return false;
#endif
		}

		char const *leaf_path(char const *path) override
		{
			//
			// " Return leaf path or nullptr if the path does not exist "
			//
			// Called by dir_file_system.h :
			// 1) its leaf_path() calls leaf_path() in each FS in the
			// (directory's) union, until it finds one that returns non-NULL.
			// Hence, this is a "can you handle this path ?" predicate, we're supposed
			// to return NULL if not concerned, or non-NULL if the file does belong to us.
			// 2) its opendir() calls us to determine whether the directory already exists,
			// so likewise, it's an "exists() ?" predicate, we're supposed to stat() the path.
			// Indeed,
			// grepping through Genode, I see no place that uses the returned const-char*,
			// they all use the return value as a boolean, so leaf_path() is very much an exists() !
			//
			SCAN_TIME;
			TRACE( "leaf_path path=", path );
#ifndef DISABLE_XATTR
			///ToDo: (though there's probably not much I CAN do...): this means we return true even to a client who is not interested in attributes, but in an actual "dot file"... This will e.g. probably break something like: create_directory("/boot/.Music")
			/// -> create a jam-t8 test to make it obvious, if-defed as #ifdef PATHOLOGICAL_TESTS, since it's a bug I (seemingly) cannot fix, given the constraints of using "dot-paths" magic for emulating extended attributes
			
			// Dot-path case
			VirtualizedXattr virt( path );
			;
			if(virt.initcheck() == STAT_OK)
			{
				TRACE( " -> ok/xa-case for leaf_path ", path );
				//
				return path;
			}
			
			// necessary for super-corner-case "Xa Listing when Idx and ntfs are on opposite sides of the client/server divide":
			VirtualizedListingXa listing(path);
			if(listing.initcheck() == STAT_OK)
			{
				TRACE( " -> ok/xa *LISTING* case for leaf_path ", path );
				//
				return path;
			}
#endif
			// Physical path case
			//
			struct stat s;
			;
			int res = Fuse::fuse()->op.getattr(path, &s);
			
			TRACE( " -> res=", res, " for leaf_path path=", path );
			
			return (res == 0)
				? path
				: nullptr;
		}

		Stat_result stat(char const *path, Stat &out) override
		{
			//LeakChecker::Singleton()->DisplayStats();
			
			///later: call sync() first ?

			SCAN_TIME;
			TRACE( "stat path=", path );

			out = Stat { };

			struct stat s;
			int res = Fuse::fuse()->op.getattr(path, &s);
			if (res == 0)
			{
				//...done: FIXME: return <0> to facilitate BQuery/BVolume work ? not a solid fix tho
				out.device = 0;//(Genode::addr_t)this;
				out.inode = s.st_ino ? s.st_ino : 1;  // dodge "0" inode as that would break LibC
				out.size  = s.st_size;
				out.rwx   = Node_rwx::rw();//?
	
				if (S_ISDIR(s.st_mode))
					out.type  = Node_type::DIRECTORY;  // 0
				else if (S_ISREG(s.st_mode))
					out.type  = Node_type::CONTINUOUS_FILE;  // 2
				else if (S_ISLNK(s.st_mode))
					out.type  = Node_type::SYMLINK;
				else
				{
					Genode::error( "fuse/getattr: cannot identify node type" );
					//
					return STAT_ERR_NO_ENTRY;  // as in the former implementation's directory.h
				}
				
				TRACE( " stat() returns OK, with: device ", out.device, " inode ", out.inode, " size ", out.size, " Type: ", (int)out.type, " (versus Node_type::DIRECTORY=", int(Node_type::DIRECTORY), ")" );
				
				return STAT_OK;
			}

#if 0  // + "if DISABLE_XATTR"
				return STAT_ERR_NO_ENTRY;
#else
			{
				//TRACE( " stat: path does not seem to exist ; but but maybe it's a 'magic xattr' dot-path ?" );
				
				// Don't give up just yet, test for XATTR handling case:
				VirtualizedXattr virt( path );
				VirtualizedListingXa listing( path );
				
				if(virt.initcheck() == STAT_OK || listing.initcheck() == STAT_OK)
				{
			//else:
			//TRACE( " stat: nope, not a dot path, *failure*, return code: ", int(virt.initcheck()) );
			//_d_ return virt.initcheck();  // STAT_ERR_NO_ENTRY if no statable file and no statable attr

				//Directory_service::Stat stat;
					TRACE( "  (stat) valid dot-path xa magic: looks like xa/listing (but only if the physical counterpart exists for dotpath: ", path, ")" );
					
					struct stat s;
if( virt.initcheck() )
{
					int res = Fuse::fuse()->op.getattr(virt.real_path(), &s);
					//TRACE( res, " for: ", virt.real_path());
}
if( listing.initcheck() )
{
					int res = Fuse::fuse()->op.getattr(listing.real_path(), &s);
					//TRACE( res, " for: ", listing.real_path());
}
/////					if(res == 0)
					{
						//
//rem out ?:			Genode::warning("XA Stat (this should never be called on xattr ? An xattr is supposed to land in open(), not in stat() -- UPDATE: I don't use ioctl() any more now !");
						
						//log(" Yes, the physical counterpart does exist, so perform ioctl!");
///ToDo: uh, this should hardcode _0_ (we have a policy that /boot and everything below is device "0") ?
						out.device = (Genode::addr_t)this;
							/// no such thing as an "inode" for an xa/l'ing, hardcode "1" ? (instead of using the inode of the physical-counterpart node like this)
						out.inode = s.st_ino ? s.st_ino : 1;  // dodge "0" inode as that would break LibC
				out.size  = 0;//99;//s.st_size; ///XXXXX
						out.rwx   = Node_rwx::rw();//?
						//out.type  = Node_type::DIRECTORY; <- use this for the "XATTR_LIST" ioctl ? or rather a pseudo-text file that contains more information
						out.type  = Node_type::CONTINUOUS_FILE;  // <- use this for the "XATTR_GET/SET" ioctl
		//				out = virt.attr_status();
						
						TRACE( " stat -> STAT_OK for path=", path );
						
						return STAT_OK;//virt.initcheck();
					}
				}
				
//				return STAT_ERR_NO_ENTRY;
			}
#endif

			// Entry Not Found:
			
#if defined( LEAK_CHECKER_ENABLED )
			LeakChecker::Singleton()->DisplayStats();
			//LeakChecker::Singleton()->Clear();
#endif
			
			TRACE( " stat -> STAT_ERR_NO_ENTRY* for path=", path );
			
			return STAT_ERR_NO_ENTRY;
		}

		Unlink_result unlink(char const *path) override
		{
			SCAN_TIME;
			TRACE( "unlink path=", path );

			// ! any write/delete access might make the cache stale
			numentCache.Clear();
			
			//if (!_writeable)
			//+	throw Permission_denied();

			int res = Fuse::fuse()->op.unlink(path);
			if (res != 0)
#if 0  // + "if DISABLE_XATTR"
			{
				Genode::error("fuse()->op.unlink() returned unexpected error code: ", res);
				return UNLINK_ERR_NO_ENTRY;
			}
#else
			{
				// Don't give up just yet, test for XATTR handling case:
				VirtualizedXattr virt( path );

				if(virt.initcheck() != STAT_OK) {
					return UNLINK_ERR_NO_ENTRY;  // no file and no attr by that name
				}
				
				//xx ret = ..
				int res = Fuse::fuse()->op.removexattr(
					virt.real_path(),
					virt.xattr_name()
					);
				if (res != 0 )
					return UNLINK_ERR_NO_ENTRY;
				
				return UNLINK_OK;
			}
#endif

			return UNLINK_OK;
		}

		Rename_result rename(char const *from, char const *to) override
		{
			SCAN_TIME;
			TRACE( "rename from=", from, " to=", to );

			// !
			// any write/rename access might make the cache stale
			numentCache.Clear();
			
			int res = Fuse::fuse()->op.rename(from,//absolute_to_path.base(),
	                                  	  	  to);//absolute_from_path.base());
			if (res != 0) {
				Genode::error("fuse()->op.rename() returned unexpected error code: ", res);
				return RENAME_ERR_NO_ENTRY;
			}

			///ToDo: should we update any open File_vfs_handle's path ?

			return RENAME_OK;
		}

		/************************
		 ** File I/O interface **
		 ************************/

		Write_result write(Vfs_handle *vfs_handle,
		                   Const_byte_range_ptr const & src,//char const *buf, file_size buf_size,
		                   size_t &out_count) override
		{
			SCAN_TIME;
			TRACE( "file.write bufsize=", src.num_bytes );

			auto *symlink_handle = dynamic_cast<Symlink_vfs_handle*>(vfs_handle);
			if(symlink_handle)
			{
				return
					symlink_handle->write (src.start, src.num_bytes, out_count);
			}

			auto *xattr_handle = dynamic_cast<Xattr_vfs_handle*>(vfs_handle);
			if(xattr_handle)
			{
				return
					xattr_handle->write (src.start, src.num_bytes, out_count);
			}
			
			auto *handle = dynamic_cast<File_vfs_handle*>(vfs_handle);
			if (!handle)
				return WRITE_ERR_INVALID;//
			//+test OPEN_MODE_RDONLY ?
			
			// XaListing_vfs_handle:
			// we do not handle writing for that one.

			return
				handle->write (src.start, src.num_bytes, out_count);
		}

///ToDo: do we need to override  queue_read() ?

		Read_result complete_read(Vfs_handle *vfs_handle,// char *buf,
		                          Byte_range_ptr const & dst,//file_size buf_size,
		                          size_t &out_count) override
		{
			// file or directory read():
			SCAN_TIME;
			TRACE( "node.complete_read bufsize=", dst.num_bytes );
			
			auto * symlink_handle = dynamic_cast<Symlink_vfs_handle*>(vfs_handle);
			if(symlink_handle)
			{
				return
					symlink_handle->read( dst.start, dst.num_bytes, out_count );
			}

			auto * dir_handle = dynamic_cast< Dir_vfs_handle* >( vfs_handle );
			if( dir_handle )
			{
				//TRACE("__complread() with vfs_handle=", vfs_handle);
				
				return dir_handle->read( dst.start, dst.num_bytes, out_count );
			}
			
			auto * xattr_handle = dynamic_cast<Xattr_vfs_handle*>(vfs_handle);
			if(xattr_handle)
			{
				return
					xattr_handle->read (dst.start, dst.num_bytes, out_count);
			}
			
			auto * xa_listing_handler = dynamic_cast<XaListing_vfs_handle*>(vfs_handle);
			if(xa_listing_handler)
			{
				return
					xa_listing_handler->read (dst.start, dst.num_bytes, out_count);
			}
			
			auto * handle = dynamic_cast<File_vfs_handle*>(vfs_handle);
			if (!handle)
				return READ_ERR_INVALID;//

			return
				handle->read (dst.start, dst.num_bytes, out_count);
		}

		Ftruncate_result ftruncate(Vfs_handle *vfs_handle, file_size len) override
		{
			SCAN_TIME;
			TRACE( "file.ftruncate len=", len );

			auto *handle = dynamic_cast<File_vfs_handle*>(vfs_handle);
			if (!handle)
				return FTRUNCATE_ERR_NO_PERM;//

			return
				handle->ftruncate (len);
		}

		bool read_ready(const Vfs_handle &) const override { return true; }
		bool write_ready(Vfs_handle const &) const override
		{
			Genode::log( "fuse.Write_Ready ?" );
			return true;
		}

///ToDo : update_modification_timestamp() ?
		//bool update_modification_timestamp(Vfs_handle *vfs_handle, Vfs::Timestamp time) override
};


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
		{
			return new (vfs_env.alloc()) Fuse_file_system(vfs_env, config);
		}
	};

	static Factory f;
	return &f;
}
