/*
 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */
#ifndef FS_ATTR_EXTATTR_H
#define FS_ATTR_EXTATTR_H

/*!	Included by fs_attr_untyped.cpp. Interfaces with FreeBSD extattr support.
*/


#include <string.h>
#include <sys/extattr.h>


///later: move to fs_attr_untyped.cpp rather than this .h ?

#if defined( HoG_GENODE )
	// Genode provides <sys/extattr.h> but does not seem to implement it; hence the stubs:

#define TRACE()
#define TRACE2(...)
//#define TRACE() puts( __func__ )
//#define TRACE2(...) printf(__VA_ARGS__)


int	extattr_delete_fd(int _fd, int _attrnamespace, const char *_attrname)
{
	// Haiku's libroot-build will always use this function (*delete_fd() and not *delete_link() below)
	
	TRACE();
	
#if 0
	xattr_name_val_pair pair { .name = _attrname, .data = NULL, .datasize = 0 };
	int ret =
		ioctl( _fd, XATTR_REMOVE, &pair );
	return ret;
#else
	BString path = genode_xattr_magic_path( _fd, "attr", _attrname );
	//printf(" Unlink <%s>\n", path.String());
	
	// unlink() and juggle with result and errno in the error case:
	// indeed unlink() returns the error in 'errno', even though we need it to be
	// in 'result' so that our upstream caller will (re) affect it to errno:
	// (also, map errno 'ENOENT' to 'ENOATTR')
	//
	int result = unlink( path.String() );
	if( result != 0 )
	{
		///ToDo: should have a fixup_errno() all over the place, instead of just translating this one case ?
		if( errno == ENOENT )
		{
			errno  = -ENOATTR;
			result = -ENOATTR;
		}
	}
	
	return result;
#endif
}
int	extattr_delete_link(const char *_path, int _attrnamespace,
	    const char *_attrname)
{
	TRACE();
	return -1;
}


ssize_t	extattr_get_fd(int _fd, int _attrnamespace, const char *_attrname,
	    void *_data, size_t _nbytes)
{
	TRACE();
/*
	printf(" [extattr_get_fd fd <%d> _attrnamespace 0x%x _attrname <%s> bufsize: %lu]\n", _fd, _attrnamespace, _attrname, _nbytes);
	printf(" [extattr_get_fd] calling ioctl(%d, 0x%x)\n", _fd, XATTR_GET);
*/
	// Haiku's libroot-build ignores <_attrnamespace> and always prefixes <_attrname> with "user."
	// (i.e. no need to test for EXTATTR_NAMESPACE_USER == _attrnamespace and add the prefix,
	// since the prefix is baked-in already, see below)
	
#if 0
	xattr_name_val_pair pair { .name = _attrname, .data = _data, .datasize = _nbytes };
	int ret =
		// on success this returns 0, whereas we ought to return the number of read bytes
		ioctl( _fd, XATTR_GET, &pair );
	
	// hence the following:
	return ret == 0
		? pair.datasize  // success
		: ret  // failure
		;
#else
	BString path = genode_xattr_magic_path( _fd, "attr", _attrname );
	
	int file = open( path.String(), O_RDONLY );
	TRACE2(" open <%s> -> %d\n", path.String(), file);
	if( file < 0 )
	{
		TRACE2("  open-status -> %d  errno -> %d vs. EINVAL=%d\n", file, errno, EINVAL);
		
		if( errno == ENOENT )
		{
			errno  = -ENOATTR;
			//
			return -ENOATTR;
			//
		}
		
		//
		return -ENOTSUP;
		//
	}
	
	int retrieved = read( file, _data, _nbytes );
	
	TRACE2(" retrieved -> %d  errno -> %d vs. EINVAL=%d\n", retrieved, errno, EINVAL);
	
	close( file );
	
	return retrieved;
#endif
}
ssize_t	extattr_get_link(const char *_path, int _attrnamespace,
	    const char *_attrname, void *_data, size_t _nbytes)
{
	TRACE();
	return -1;
}

ssize_t	extattr_list_fd(int _fd, int _attrnamespace, void *_data,
	    size_t _nbytes)
{
	TRACE();
	
#if 0
	xattr_name_val_pair pair { .name = NULL, .data = _data, .datasize = _nbytes };
	int ret =
		// on success this returns 0, whereas we ought to return the number of read bytes
		ioctl( _fd, XATTR_LIST, &pair );
	
	// hence the following:
	return ret == 0
		? pair.datasize  // success
		: ret  // failure
		;
#else
	// Here we work on "attr_list" instead of "attr/<attrname>"
	// (conceptually, we could also keep with the "attrs as files" paradigm and use
	// opendir() instead of open(), but this does not mesh too well with the FUSE back-end)
	BString path = genode_xattr_magic_path( _fd, "attr_list", NULL );
	TRACE2( "   extattr_list_fd: list-open <%s>\n", path.String() );
	
	int file = open( path.String(), O_RDONLY );
	if( file < 0 )
		//
		return -ENOTSUP;
		//
	
	int retrieved = read( file, _data, _nbytes );
	TRACE2( "  got %d bytes\n", retrieved );
	if( retrieved < 0 )
	{
		if( errno == EINVAL )
		{
			errno = -errno;
//xx why should we return NOATTR on an attr LISTING op? rem out for now...
//			errno  = ENOATTR;
//			retrieved = ENOATTR;
		}
	}
	
	close( file );
	
	return retrieved;
#endif
}
ssize_t	extattr_list_link(const char *_path, int _attrnamespace, void *_data,
	    size_t _nbytes)
{
	TRACE();
	return -1;
}


//int  // in FreeBSD 8
ssize_t  // in FreeBSD 12
extattr_set_fd(int _fd, int _attrnamespace, const char *_attrname,
	    const void *_data, size_t _nbytes)
{
	TRACE();
	//printf(" [extattr_set_fd fd <%d> _attrnamespace 0x%x _attrname <%s> buf <%s>]\n", _fd, _attrnamespace, _attrname, (const char*)_data);
	//printf(" [extattr_set_fd] calling ioctl(%d, 0x%x)\n", _fd, XATTR_SET);
	
#if 0
	xattr_name_val_pair pair { .name = _attrname, .data = (void*)_data, .datasize = _nbytes };
	return
		// here we return the "0" (on success) retcode directly; we don't bother
		// with returning the actual written bytes, since fs_write_attr() would not use it anyway (unlike fs_read_attr())
		ioctl( _fd, XATTR_SET, &pair );
#else
	BString path = genode_xattr_magic_path( _fd, "attr", _attrname );
	TRACE2("open <%s>\n", path.String());
	
	int file = open( path.String(), O_WRONLY );
	if( file < 0 )
		//
		return -ENOTSUP;
		//
	
	int written = write( file, _data, _nbytes );
	//printf("*** got write res: %d\n", written);
	
	close( file );
	
	return written;
#endif
}

//int  // in FreeBSD 8
ssize_t  // in FreeBSD 12
extattr_set_link(const char *_path, int _attrnamespace,
	    const char *_attrname, const void *_data, size_t _nbytes)
{
	TRACE();
	return -1;
}

#endif  // ~HoG_GENODE




// the namespace all attributes live in
#ifndef HoG_GENODE
static const char* kAttributeNamespace = "haiku.";
static const int kAttributeNamespaceLen = 6;
#else  // HoG_GENODE:
// use Linux-compatible xattr (i.e. prefix with "user.", more restrictive than BSD)
#	include "_GeShared_xattr_defines.h"
static const char* kAttributeNamespace = GeShared_Attribute_Namespace;  // "user.haiku."
static const int kAttributeNamespaceLen = 11;
#endif  // ~HoG_GENODE


static ssize_t
list_attributes(int fd, const char* path, char* buffer, size_t bufferSize)
{
	TRACE2( " list_attributes(%d, <%s>, %ld)\n", fd, path, bufferSize );
	
	ssize_t bytesRead;
	if (fd >= 0) {
		bytesRead = extattr_list_fd(fd, EXTATTR_NAMESPACE_USER, buffer,
			bufferSize);
	} else {
		bytesRead = extattr_list_link(path, EXTATTR_NAMESPACE_USER, buffer,
			bufferSize);
	}

	if (bytesRead <= 0)
		return bytesRead;

#ifndef HoG_GENODE
	// The listing is in a different format than expected by the caller. Here
	// we get a sequence of (<namelen>, <unterminated name>) pairs, but expected
	// is a sequence of null-terminated names. Let's convert it.
	int index = *buffer;
	memmove(buffer, buffer + 1, bytesRead - 1);

	while (index < bytesRead - 1) {
		int len = buffer[index];
		buffer[index] = '\0';
		index += len + 1;
	}

	buffer[bytesRead - 1] = '\0';
#else  // HoG_GENODE:
	// Dodge the above "translate BSD" code, since the underlying Genode code actually
	// relies on FUSE, which uses the Linux xattr convention, not the BSD convention.
#endif  // ~HoG_GENODE

	TRACE2( "      list_attributes -> %ld bytes : <%s>\n", bytesRead, buffer );
	
	return bytesRead;
}


static ssize_t
get_attribute(int fd, const char* path, const char* attribute, void* buffer,
	size_t bufferSize)
{
	if (fd >= 0) {
		return extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, attribute, buffer,
			bufferSize);
	}
	return extattr_get_link(path, EXTATTR_NAMESPACE_USER, attribute, buffer,
		bufferSize);
}


static int
set_attribute(int fd, const char* path, const char* attribute,
	const void* buffer, size_t bufferSize)
{
 	if (fd >= 0) {
		return extattr_set_fd(fd, EXTATTR_NAMESPACE_USER, attribute, buffer,
			bufferSize);
	}
	return extattr_set_link(path, EXTATTR_NAMESPACE_USER, attribute, buffer,
			bufferSize);
}


static int
remove_attribute(int fd, const char* path, const char* attribute)
{
	if (fd >= 0)
		return extattr_delete_fd(fd, EXTATTR_NAMESPACE_USER, attribute);
	return extattr_delete_link(path, EXTATTR_NAMESPACE_USER, attribute);
}


#undef TRACE
#undef TRACE2


#endif	// FS_ATTR_EXTATTR_H
