/*
 * \brief  Libc functions for FUSE (& lwext4)
 * \author Josef Soentgen
 * \date   2017-08-01
 */

//h-o-g:
// We can't use the actual LibC in a VFS plug-in, so create fake
// read/write/malloc etc calls that forward to pure Genode APIs.

/*
 * Copyright (C) 2017 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 <base/allocator.h>
#include <base/allocator_avl.h>
#include <base/log.h>
#include <block_session/connection.h>
#include <format/snprintf.h>
#include <log_session/log_session.h>

//libc
#include <stdio.h>

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

/* local includes */
#if 0
#include "block_dev.h"
extern Blockdev * ptr_to_blockdev;

	//change of plans:
	//let's use Block_FS instead of Block
	
	//otherwise we get non-aligned write access, which is a no-no:

[init -> test-libc_vfs] -- Starting fuse vfs plug-in --
[init -> test-libc_vfs] libc_fuse_ntfs-3g: try to mount /dev/blkdev...
..
[init -> test-libc_vfs] calling mkdir(trailing_slash_dir_name, 0777) dir_name=testdir/
[init -> test-libc_vfs] ---->leaf_path path=/testdir
[init -> test-libc_vfs] ---->opendir path=/testdir create=1
.. [init -> test-libc_vfs] .fuse-PWRITE fd=0 count=4088 offset=8200
[init -> test-libc_vfs] Error: pwrite: offset is not a multiple of sector size ! not implemented !
[init -> test-libc_vfs] Error: pwrite: count is not a multiple of sector size ! not implemented !
[init -> test-libc_vfs] Error: fuselibc_pwrite failed at sector 16 -- p.size: 3584
qemu-system-x86_64: ahci: PRDT length for NCQ command (0x1) is smaller than the requested size (0x2000000)

#else  // .................................
// Let's have a "blocks as file" layer that forwards to a <block> vfs plug-in:

#include "blocks_as_file.h"


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


void Blocks_as_file::open( const char * path )
{
	//Genode::log( " --block-as-file.open(", path, ") ---" );
	
	Genode::Directory dir( _vfs_env );
	file.construct( /*dir, */path, _vfs_env );
	///ToDo: Dunno how to pass dir's private _path, so assuming it's equal to "/", seems to work?
}

int Blocks_as_file::pread( int fd, void * buf, size_t count, size_t offset )
{
	//Genode::log( "  Blocks_as_file::pread fd=", fd, " with file.constructed=", file.constructed());
	
	///? if file.constructed()
	///? while (total_read < _buffer.size) ...
	Vfs::file_size read_bytes =
		file->read(
			Readwrite_file::At{offset},
			buf,
			count
			);
	if (read_bytes != count)
		Genode::error( "failed to read some/all bytes (", read_bytes, " != ", count, ")" );

	return read_bytes;
}

int Blocks_as_file::pwrite( int fd, const void * buf, size_t count, size_t offset )
{
	//Genode::log( "  Blocks_as_file::pwrite fd=", fd, " with file.constructed=", file.constructed());
	
	///? if file.constructed()
	///? while (total_ < _buffer.size) ...
	Vfs::file_size written_bytes =
		file->write(
			Readwrite_file::At{offset},
			buf,
			count
			);
	if (written_bytes != count)
		Genode::error( "failed to write some/all bytes (", written_bytes, " != ", count, ")" );

	return written_bytes;
}


extern Blocks_as_file * ptr_to_blockdev;

#endif


//#pragma mark -



/*
 * Genode enviroment
 */
static Genode::Env       *_global_env;
static Genode::Allocator *_global_alloc;

void /*Lwext4::*/malloc_init( Genode::Env & env, Genode::Allocator & alloc )
{
	//Genode::log( "malloc_init" );
	
	_global_env   = &env;
	_global_alloc = &alloc;
	
#if defined( LEAK_CHECKER_ENABLED )
	LeakChecker::InitSingleton( env, alloc );
#endif
}



extern "C" {
	// stat(), etc will get remapped to fuselibc_stat(), etc
	// thanks to -Dopen=fuselibc_stat macro & friends


// ** errno.h **

int *	__error(void)
{
	///ToDo: fix errno ?
	//Genode::error("accessing errno");
	
	static int dummy;
	return &dummy;
}



/*************
 ** stdio.h **
 *************/

///most of the below are NOT yet aliased with "-D..." but they seem to correctly override their LibC counterpart as needed anyhow

int fflush(FILE *)
{
	///ToDo: flush (and sync) impl ?
	
	return 0;
}


int vfprintf( FILE * /*stream*/, const char * fmt, __va_list list)
{
	/*static*/ /**/ char buffer[Genode::Log_session::MAX_STRING_LEN];

	Format::String_console sc(buffer, sizeof (buffer));
	sc.vprintf(fmt, list);

	/* remove newline as Genode::log() will implicitly add one */
	size_t n = sc.len();
	if (n > 0 && buffer[n-1] == '\n') { n--; }

	Genode::log(Genode::Cstring(buffer, n));
	
	return 0;
}


//int printf(char const *fmt, ...)
int fprintf( FILE * stream, char const * fmt, ...)
{
	va_list list;
	va_start(list, fmt);
	
	vfprintf( stream, fmt, list );
	
	return 0;
}

int puts( const char *  s)
{
	fprintf(stdout, "%s\n", s);
	return 0;
}

// libntfs-3g might sometimes use snprintf() (when accessed by e.g. Falkon)
int snprintf( char * str, size_t size, const char * format, ... )
{
	va_list vl;
	
	va_start( vl, format );
	Format::String_console sc( str, size );
	sc.vprintf( format, vl );
	va_end( vl );
	
	return sc.len();
}




//#pragma mark - stdlib.h -


// We insert a 4-byte "header" to keep track of the allocation size, for realloc()'s sake.
typedef int32 alloc_size_header;


void *_malloc_with_caller(size_t sz, const void * caller_address)
{
	//Genode::log( "    <> directfs malloc( ", sz, " )" );
	
	if( ! _global_alloc )
		Genode::error( "malloc_init() was not called, going to crash now" );
	
	// allocate 4 additional bytes to embed the size (needed in the realloc() case)
	char * buf = (char*) _global_alloc->alloc( sz + sizeof(alloc_size_header) );
	if( buf )
	{
#if defined( LEAK_CHECKER_ENABLED )
		LeakChecker::Singleton()
			->track_malloc(
				buf,
				sz + sizeof(alloc_size_header),
				caller_address
				);
#endif
		
		*(alloc_size_header*)buf = sz;
		
		// hide the header from caller
		buf += sizeof( alloc_size_header );
	}
	else
		Genode::error( "global_alloc.alloc() returned NULL ?!" );
	
	//Genode::log( "    .. directfs malloc( ", sz, " ) Done !" );
	//Genode::log( "        used ram: ", _global_env->pd().used_ram().value );
	
	return buf;
}


void *malloc(size_t sz)
{
	return _malloc_with_caller( sz, __builtin_return_address(0) );
}

void *calloc(size_t n, size_t sz)
{
	/* XXX overflow check : might possibly occur if at least one term > sqrt(2^31), eg if n>9999 || sz>9999... */
	size_t size = n * sz;
	void *p = _malloc_with_caller( size, __builtin_return_address(0) );
	if (p) { Genode::memset(p, 0, size); }
	return p;
}

void free(void *p)
{
	if( ! _global_alloc )
		Genode::error( "malloc_init() was not called, going to crash now" );
	
	if (p == NULL) { return; }
	
	// calculate the actual allocation (header included)
	char * ptr = static_cast<char*>( p );
	if( ptr )
		ptr -= sizeof( alloc_size_header );
	
#if defined( LEAK_CHECKER_ENABLED )
	LeakChecker::Singleton()
		->track_free(
			ptr,
			*(alloc_size_header*)ptr + sizeof(alloc_size_header),
			__builtin_return_address(0)
			);
#endif
	_global_alloc->free( ptr, 0 );
}


void *realloc(void *p, size_t n)
{
	/* naive/inefficient implementation: copy all to new buffer */

	void *newer = _malloc_with_caller( n, __builtin_return_address(0) );
	if (!newer)
		///later: fix the leak. Or maybe don't free() the old buf, the app can continue to use it ?
		return NULL;
	
	if (p)
	{
		char * old = static_cast<char*>( p );
		old -= sizeof( alloc_size_header );
		size_t old_size = *(alloc_size_header*)old;
		//Genode::log( "     realloc from ", old_size, " bytes to ", n, " bytes" );
		
		Genode::memcpy (
			newer,
			p,
			Genode::min( n, old_size )
			);
		
		free (p);
	}
	return newer;
}


char * strdup( const char * str )
{
	//assert(str != nullptr);
	Genode::size_t const size = Genode::strlen(str) + sizeof('\0');
	char * copy = (char*)_malloc_with_caller( size, __builtin_return_address(0) );
	Genode::copy_cstring(copy, str, size);
	
	return copy;
}



//#pragma mark -


/* might need these (depending on gcc10 build? or other symbol pollution?)
*/
// Genode 22.02, "jam distro" scenario: we need "optarg" now..? :
char * optarg = 0;
int opterr = 0;
int optind = 0;

// libntfs-3g requires strncmp() for xattr writing, and possibly when renaming a node. (otherwise we get "Error: LD: jump slot relocation failed for symbol: 'strncmp'")
int strncmp( const char * s1, const char * s2, size_t len )
{
	return Genode::strcmp( s1, s2, len );
}

// libntfs-3g might require this via fuse.listxattr() sometimes
char * strncpy( char * dest, const char * from, size_t len )
{
	Genode::copy_cstring(
		dest,
		from,
		len + sizeof( '\0' )  // it seems Genode's semantics is: "trailing nul byte included", versus libc: "trailing nul NOT included"
		);
	
	return dest;
}



int	stat(const char * path, struct stat * fields)
{
	//Genode::log( ".fuse-Stat: ", path );
	
	*fields = {};//fields->st_mode = 0;
	
	///?  st_size = ptr_to_blockdev->info.block_count * _info.block_size ...
	/*
	(xx move this comment to a README.md)
	Is the above st_size assignment useful ?
	Seems not, the NTFS lib does not derive filesystem size information from stat() and lseek(),
	seems it's all decoded from the Boot Sector instead:
	Tracing sample (from a win10 part #1):
		[init -> vfs_ntfs] ntfs_boot_sector_parse () :182: SectorSize = 0x200 
		[init -> vfs_ntfs] ntfs_boot_sector_parse () :183: SectorSizeBits = 9 
		[init -> vfs_ntfs] ntfs_boot_sector_parse () :190: SectorsPerCluster = 0x8 
		[init -> vfs_ntfs] ntfs_boot_sector_parse () :198: NumberOfSectors = 1021951 
		[init -> vfs_ntfs] .fuse-Lseek fd=0 off=523238400 mode=0
		[init -> vfs_ntfs] Warning: ** lseek not implemented
		...
	That indeed looks good, 1021951 * 512 == 523238912, and Haiku claims that /SystemReserved has capacity 499.0 MiB so it checks out.
	So,
	ntfs_boot_sector_parse() in file bootsect.c is the one that actually matters, at least for
	mounting an existing partition.
	By contrast, the various functions in device.c (ntfs_device_size_get(), ntfs_device_offset_valid(), etc)
	are used for mkfs-style utilities, which we don't use here.
	*/
	
	//fields->st_mode = 0060000;
	//
	// No point in doing the above, ntfs-3g *still* requests non-whole-sectors, even if we specify S_ISBLK...
	// running  grep -r 'NDev.*Block'
	// shows that libntfs-3g does not make much use of that stuff indeed, it just governs flock() usage.
	// So we add support for "random" sized reads since we can't make libntfs-3g make rounded "block" sized reads, hence the ptr_to_blockdev->pread() stuff below.
	
	return 0;// -1;
}



int	open(const char * path, int, ...)
{
	//Genode::log( ".fuse-Open: ", path );

	ptr_to_blockdev->open( path );

	return 0;//come up with a file descriptor scheme, other than "let's just always return fd 0" ! (though in the case of ntfs-3g, this ultra-lazyness does work...)
		///ToDo: at least come up with error reporting if open() is called twice!
}


//int pread( int fd, char * buf, int64_t count, int64_t offset )
//int pread( int fd, char * buf, size_t count, size_t offset )
ssize_t	 pread(int fd, void * buf, size_t count, off_t offset)
{
	//	GENODE_LOG_TSC(10);
	//Genode::log( ".fuse-PRead fd=", fd, " count=", count, " offset=", offset, " (block #", offset/512, ", ", count, " bytes)" );
	
	return ptr_to_blockdev->pread(fd, buf, count, offset);
}

//int pwrite( int fd, const char * buf, size_t bytecount, size_t byteoffset )
ssize_t	 pwrite(int fd, const void * buf, size_t bytecount, off_t byteoffset)
{
	//Genode::log( ".fuse-PWRITE fd=", fd, " count=", bytecount, " offset=", byteoffset );
	
/*
	// fuse/ntfs writes to 'random' (non-aligned) beginning/end offsets, so we
	// have to convert those into 512-bytes-aligned offsets, as needed by
	// BlockDev... AFTER retrieving the previous block contents in the "gray zone"
	// between aligned and non aligned offsets, so that we don't write back zeros (!):
	
	... read old contents..
	.... substitute new contents, at the one place <byteoffset> - <byteoffset of sector>
	... write back modified block...
	
	Genode::size_t const sector_size = ptr_to_blockdev->_info.block_size;
	first_sector = byteoffset / sector_size;
	
	return ptr_to_blockdev->pwrite(
		buf,
		bytecount,
		first_sector * sector_size//byteoffset
		);

	Nah, let's use Vfs-Blocks instead, which means no change at our end:
*/
	return ptr_to_blockdev->pwrite(fd, buf, bytecount, byteoffset);
}



/*
	_d_ ? We no longer need to provide an implementation of fcntl() for unix_io.c, after adding
	support for mkntfs we added a patch to null it out, which also applies in the "vfs driver" case,
	and grepping the libntfs source shows no other relevant occurence, so comment this out:

int	fcntl(int fd, int val, ...)
{
	//Genode::log( ".fuse-Fcntl fd=", fd, " val=", val );
	
	//xx called by libntfs-3g for "locking/exclusive access", demands success from us so we fake it...
	
	return 0;//-1;
}
*/

off_t lseek(int fd, off_t off, int mode)
{
	Genode::log( ".fuse-Lseek fd=", fd, " off=", off, " mode=", mode,
		" -> ", "** lseek not implemented, remaining as-is (should be ok, at least for NTFS-FUSE which uses seeking just for extra validation of bootsector-provided stats)"
		);
	
	off_t new_pos = 0;
	
	///later: implement lseek(), so as to provide that extra validation step alluded to below
	
	//switch( mode )
	// case SEEK_SET.. SEEK_CUR.. SEEK_END..
	// new_pos = old_pos + ..
	//if new_pos >= partition_bytesize
	//	return -1;
	
	return new_pos;
}

int fsync( int )
{
	///later-2: when is fsync() called ? It would appear we're currently running 100% synchronously (no caching), so no need to sync?
	Genode::log( ".fuse-FSync: N/I" );
	return -1;  // this -1 status ensures ntfs_device_unix_io_sync() will report failure to sync...
}

int close( int )
{
	Genode::log( ".fuse-close: N/I" );
	return -1;  // this -1 triggers ntfs_device_unix_io_close() logging/warning
}

}  // ~extern "C"

