/*
 * Copyright 2002-2012, Haiku Inc.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Tyler Dauwalder
 *		Ingo Weinhold, bonefish@users.sf.net
 */


#include <Entry.h>

#include <fcntl.h>
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifndef HoG_GENODE
#include <compat/sys/stat.h>
#else
	static int debug = 0;
	// plus lots of raw if( debug ).... statements down there
#endif  // ~HoG_GENODE

#include <Directory.h>
#include <Path.h>
#include <SymLink.h>

#include <syscalls.h>

#include "storage_support.h"


using namespace std;


//	#pragma mark - struct entry_ref


entry_ref::entry_ref()
	:
	device((dev_t)-1),
	directory((ino_t)-1),
	name(NULL)
{
}


entry_ref::entry_ref(dev_t dev, ino_t dir, const char* name)
	:
	device(dev),
	directory(dir),
	name(NULL)
{
	set_name(name);
}


entry_ref::entry_ref(const entry_ref& ref)
	:
	device(ref.device),
	directory(ref.directory),
	name(NULL)
{
	set_name(ref.name);
}


entry_ref::~entry_ref()
{
	free(name);
}


status_t
entry_ref::set_name(const char* name)
{
	free(this->name);

	if (name == NULL) {
		this->name = NULL;
	} else {
		this->name = strdup(name);
		if (!this->name)
			return B_NO_MEMORY;
	}

	return B_OK;
}


bool
entry_ref::operator==(const entry_ref& ref) const
{
	return (device == ref.device
		&& directory == ref.directory
		&& (name == ref.name
			|| (name != NULL && ref.name != NULL
				&& strcmp(name, ref.name) == 0)));
}


bool
entry_ref::operator!=(const entry_ref& ref) const
{
	return !(*this == ref);
}


entry_ref&
entry_ref::operator=(const entry_ref& ref)
{
	if (this == &ref)
		return *this;

	device = ref.device;
	directory = ref.directory;
	set_name(ref.name);
	return *this;
}


//	#pragma mark - BEntry


BEntry::BEntry()
	:
	fDirFd(-1),
	fName(NULL),
	fCStatus(B_NO_INIT)
{
}


BEntry::BEntry(const BDirectory* dir, const char* path, bool traverse)
	:
	fDirFd(-1),
	fName(NULL),
	fCStatus(B_NO_INIT)
{
	SetTo(dir, path, traverse);
}


BEntry::BEntry(const entry_ref* ref, bool traverse)
	:
	fDirFd(-1),
	fName(NULL),
	fCStatus(B_NO_INIT)
{
	SetTo(ref, traverse);
}


BEntry::BEntry(const char* path, bool traverse)
	:
	fDirFd(-1),
	fName(NULL),
	fCStatus(B_NO_INIT)
{
	SetTo(path, traverse);
}


BEntry::BEntry(const BEntry& entry)
	:
	fDirFd(-1),
	fName(NULL),
	fCStatus(B_NO_INIT)
{
	*this = entry;
}


BEntry::~BEntry()
{
	Unset();
}


status_t
BEntry::InitCheck() const
{
	return fCStatus;
}


bool
BEntry::Exists() const
{
	// just stat the beast
	struct stat st;
	return GetStat(&st) == B_OK;
}


const char*
BEntry::Name() const
{
	if (fCStatus != B_OK)
		return NULL;

	return fName;
}


status_t
BEntry::SetTo(const BDirectory* dir, const char* path, bool traverse)
{
	// check params
	if (!dir)
		return (fCStatus = B_BAD_VALUE);
	if (path && path[0] == '\0')	// R5 behaviour
		path = NULL;

	// if path is absolute, let the path-only SetTo() do the job
	if (BPrivate::Storage::is_absolute_path(path))
		return SetTo(path, traverse);

	Unset();

	if (dir->InitCheck() != B_OK)
		fCStatus = B_BAD_VALUE;

	// dup() the dir's FD and let set() do the rest
	int dirFD = _kern_dup(dir->get_fd());
	if (dirFD < 0)
		return (fCStatus = dirFD);
	return (fCStatus = _SetTo(dirFD, path, traverse));
}


status_t
BEntry::SetTo(const entry_ref* ref, bool traverse)
{
	Unset();
	if (ref == NULL)
		return (fCStatus = B_BAD_VALUE);

	// if ref-name is absolute, let the path-only SetTo() do the job
	if (BPrivate::Storage::is_absolute_path(ref->name))
		return SetTo(ref->name, traverse);

	// open the directory and let set() do the rest
	int dirFD = _kern_open_dir_entry_ref(ref->device, ref->directory, NULL);
	if (dirFD < 0)
		return (fCStatus = dirFD);
	return (fCStatus = _SetTo(dirFD, ref->name, traverse));
}


status_t
BEntry::SetTo(const char* path, bool traverse)
{
	Unset();
	// check the argument
	if (!path)
		return (fCStatus = B_BAD_VALUE);
	return (fCStatus = _SetTo(-1, path, traverse));
}


void
BEntry::Unset()
{
	// Close the directory
	if (fDirFd >= 0)
		_kern_close(fDirFd);

	// Free our leaf name
	free(fName);

	fDirFd = -1;
	fName = NULL;
	fCStatus = B_NO_INIT;
}


status_t
BEntry::GetRef(entry_ref* ref) const
{
	if (fCStatus != B_OK)
		return B_NO_INIT;

	if (ref == NULL)
		return B_BAD_VALUE;

	struct stat st;
	status_t error = _kern_read_stat(fDirFd, NULL, false, &st,
		sizeof(struct stat));
	if (error == B_OK) {
		ref->device = st.st_dev;
		ref->directory = st.st_ino;
		error = ref->set_name(fName);
	}
	return error;
}


status_t
BEntry::GetPath(BPath* path) const
{
	if (fCStatus != B_OK)
		return B_NO_INIT;

	if (path == NULL)
		return B_BAD_VALUE;

	return path->SetTo(this);
}


status_t BEntry::GetParent(BEntry* entry) const
{
	// check parameter and initialization
	if (fCStatus != B_OK)
		return B_NO_INIT;
	if (entry == NULL)
		return B_BAD_VALUE;

	// check whether we are the root directory
	// It is sufficient to check whether our leaf name is ".".
	if (strcmp(fName, ".") == 0)
		return B_ENTRY_NOT_FOUND;

	// open the parent directory
	char leafName[B_FILE_NAME_LENGTH];
	int parentFD = _kern_open_parent_dir(fDirFd, leafName, B_FILE_NAME_LENGTH);
	if (parentFD < 0)
		return parentFD;

	// set close on exec flag on dir FD
	fcntl(parentFD, F_SETFD, FD_CLOEXEC);

	// init the entry
	entry->Unset();
	entry->fDirFd = parentFD;
	entry->fCStatus = entry->_SetName(leafName);
	if (entry->fCStatus != B_OK)
		entry->Unset();
	return entry->fCStatus;
}


status_t
BEntry::GetParent(BDirectory* dir) const
{
	// check initialization and parameter
	if (fCStatus != B_OK)
		return B_NO_INIT;
	if (dir == NULL)
		return B_BAD_VALUE;
	// check whether we are the root directory
	// It is sufficient to check whether our leaf name is ".".
	if (strcmp(fName, ".") == 0)
		return B_ENTRY_NOT_FOUND;
	// get a node ref for the directory and init it
	struct stat st;
	status_t error = _kern_read_stat(fDirFd, NULL, false, &st,
		sizeof(struct stat));
	if (error != B_OK)
		return error;
	node_ref ref;
	ref.device = st.st_dev;
	ref.node = st.st_ino;
	return dir->SetTo(&ref);
	// TODO: This can be optimized: We already have a FD for the directory,
	// so we could dup() it and set it on the directory. We just need a private
	// API for being able to do that.
}


status_t
BEntry::GetName(char* buffer) const
{
	if (fCStatus != B_OK)
		return B_NO_INIT;
	if (buffer == NULL)
		return B_BAD_VALUE;

	strcpy(buffer, fName);
	return B_OK;
}


status_t
BEntry::Rename(const char* path, bool clobber)
{
	// check parameter and initialization
	if (path == NULL)
		return B_BAD_VALUE;
	if (fCStatus != B_OK)
		return B_NO_INIT;
	// get an entry representing the target location
	BEntry target;
	status_t error;
	if (BPrivate::Storage::is_absolute_path(path)) {
		error = target.SetTo(path);
	} else {
		int dirFD = _kern_dup(fDirFd);
		if (dirFD < 0)
			return dirFD;
		// init the entry
		error = target.fCStatus = target._SetTo(dirFD, path, false);
	}
	if (error != B_OK)
		return error;
	return _Rename(target, clobber);
}


status_t
BEntry::MoveTo(BDirectory* dir, const char* path, bool clobber)
{
	// check parameters and initialization
	if (fCStatus != B_OK)
		return B_NO_INIT;
	if (dir == NULL)
		return B_BAD_VALUE;
	if (dir->InitCheck() != B_OK)
		return B_BAD_VALUE;
	// NULL path simply means move without renaming
	if (path == NULL)
		path = fName;
	// get an entry representing the target location
	BEntry target;
	status_t error = target.SetTo(dir, path);
	if (error != B_OK)
		return error;
	return _Rename(target, clobber);
}


status_t
BEntry::Remove()
{
	if (fCStatus != B_OK)
		return B_NO_INIT;

	if (IsDirectory())
		return _kern_remove_dir(fDirFd, fName);

	return _kern_unlink(fDirFd, fName);
}


bool
BEntry::operator==(const BEntry& item) const
{
	// First check statuses
	if (this->InitCheck() != B_OK && item.InitCheck() != B_OK) {
		return true;
	} else if (this->InitCheck() == B_OK && item.InitCheck() == B_OK) {

		// Directories don't compare well directly, so we'll
		// compare entry_refs instead
		entry_ref ref1, ref2;
		if (this->GetRef(&ref1) != B_OK)
			return false;
		if (item.GetRef(&ref2) != B_OK)
			return false;
		return (ref1 == ref2);

	} else {
		return false;
	}

}


bool
BEntry::operator!=(const BEntry& item) const
{
	return !(*this == item);
}


BEntry&
BEntry::operator=(const BEntry& item)
{
	if (this == &item)
		return *this;

	Unset();
	if (item.fCStatus == B_OK) {
		fDirFd = _kern_dup(item.fDirFd);
		if (fDirFd >= 0)
			fCStatus = _SetName(item.fName);
		else
			fCStatus = fDirFd;

		if (fCStatus != B_OK)
			Unset();
	}

	return *this;
}


void BEntry::_PennyEntry1(){}
void BEntry::_PennyEntry2(){}
void BEntry::_PennyEntry3(){}
void BEntry::_PennyEntry4(){}
void BEntry::_PennyEntry5(){}
void BEntry::_PennyEntry6(){}


/*!	Updates the BEntry with the data from the stat structure according
	to the \a what mask.

	\param st The stat structure to set.
	\param what A mask

	\returns A status code.
	\retval B_OK Everything went fine.
	\retval B_FILE_ERROR There was an error writing to the BEntry object.
*/
status_t
BEntry::set_stat(struct stat& st, uint32 what)
{
	if (fCStatus != B_OK)
		return B_FILE_ERROR;

	return _kern_write_stat(fDirFd, fName, false, &st, sizeof(struct stat),
		what);
}


/*!	Sets the entry to point to the entry specified by the path \a path
	relative to the given directory.

	If \a traverse is \c true and the given entry is a symbolic link, the
	object is recursively set to point to the entry pointed to by the symlink.

	If \a path is an absolute path, \a dirFD is ignored.

	If \a dirFD is -1, \a path is considered relative to the current directory
	(unless it is an absolute path).

	The ownership of the file descriptor \a dirFD is transferred to the
	method, regardless of whether it succeeds or fails. The caller must not
	close the FD afterwards.

	\param dirFD File descriptor of a directory relative to which path is to
		be considered. May be -1 if the current directory shall be considered.
	\param path Pointer to a path relative to the given directory.
	\param traverse If \c true and the given entry is a symbolic link, the
		object is recursively set to point to the entry linked to by the
		symbolic link.

	\returns \c B_OK on success, or an error code on failure.
*/
status_t
BEntry::_SetTo(int dirFD, const char* path, bool traverse)
{
#if defined HoG_GENODE
///FIXME-2: Hack to make create_directory() work -- it calls entry.SetTo(dirPath.Path(), true) (instead of ", false)"), because "false" train-wrecks when calling us as BEntry("/ram_rw/Haiku_createdir") because the "if(!traverse)" call below mis-behaves
//xx is the above still true now that fs.cpp has been fixed (early june22) ?
//xx Yes, as of 4sep23 we still fail create_directory() tests if this line is removed :-/... Shame because Deskbar would probably work better if BEntry could traverse symlinks...
	traverse = false;
#endif
	bool requireConcrete = false;
	FDCloser fdCloser(dirFD);
#ifndef HoG_GENODE
	char tmpPath[B_PATH_NAME_LENGTH];
	char leafName[B_FILE_NAME_LENGTH];
#else  // HoG:
	char tmpPath[B_PATH_NAME_LENGTH] = {};  // zero-out, as these might be used un-initialized when fs.cpp is buggy
	char leafName[B_FILE_NAME_LENGTH] = {};
#endif  // ~HoG
	int32 linkLimit = B_MAX_SYMLINKS;
	while (true) {
		if( debug )
			printf(" entry._SetTo(fd %d, path <%s>) : iterating... -------\n", dirFD, path);
		
		if (!path || strcmp(path, ".") == 0) {
			if( debug )
				printf("  entry._SetTo() : dot case\n");
			
			// "."
			// if no dir FD is supplied, we need to open the current directory
			// first
			if (dirFD < 0) {
				dirFD = _kern_open_dir(-1, ".");
			
				if( debug && dirFD < 0)
					printf("   ******* dirFD is %d\n", dirFD);
				
				if (dirFD < 0)
					return dirFD;
				fdCloser.SetTo(dirFD);
			}
			// get the parent directory
#ifdef HoG_GENODE
///FIXME-2: need this "break" statement here, otherwise we fail in most cases... Find a better fix ?
//update: seems we'd need to remove this 'break' for at least one case indeed: BDirectory.Contains( B_ANY_NODE or B_SYMLINK_NODE )...
//update2: now that fs.cpp has been fixed for node_ref et al, we can do without this and still pass all tests in jam t8 ; we need a "return" below though:
			// !
//do without:
//			break;
			// !
#endif
			int parentFD = _kern_open_parent_dir(dirFD, leafName,
				B_FILE_NAME_LENGTH);

			if( debug )
				printf("  entry . kern_open_Parent_dir %d <%s> -> Returns FD : %d\n", dirFD, leafName, parentFD);
			
#ifndef HoG_GENODE
			if (parentFD < 0)
				return parentFD;
#else
			// with fs.cpp fixed for node_ref handling, we no longer "break" above and now reach this, but then THIS needs a "break" statement instead of return:
			if (parentFD < 0)
			{
				printf( "..........** entry._setto: _kern_open_parent_dir() failed, maybe it was called on '/' (which has no parent) ? How come that upstream-Haiku is okay with getting parent-of-slash?\n");// maybe upstream-libroot handles that, but libroot-Build fails to open-slash-as-parent-to-itself whereas it should ? Anyway let's try to dodge that error
				break;// !
			}
#endif
			dirFD = parentFD;
			fdCloser.SetTo(dirFD);
			break;
		} else if (strcmp(path, "..") == 0) {
			if( debug )
				printf(" entry._SetTo() : dot-dot '..' case\n");
			
			// ".."
			// open the parent directory
			int parentFD = _kern_open_dir(dirFD, "..");
			if (parentFD < 0)
				return parentFD;
			dirFD = parentFD;
			fdCloser.SetTo(dirFD);
			// get the parent's parent directory
			parentFD = _kern_open_parent_dir(dirFD, leafName,
				B_FILE_NAME_LENGTH);
			if (parentFD < 0)
				return parentFD;
			dirFD = parentFD;
			fdCloser.SetTo(dirFD);
			break;
		} else {
			if( debug )
				printf(" entry._SetTo() : general case\n");
			
			// an ordinary path; analyze it
			char dirPath[B_PATH_NAME_LENGTH];
			status_t error = BPrivate::Storage::parse_path(path, dirPath,
				leafName);
			
			if( debug )
				printf(" entry._SetTo: processing path:<%s> dir:<%s> leaf:<%s>\n", path, dirPath, leafName);
			
			if (error != B_OK)
				return error;
			// special case: root directory ("/")
			if (leafName[0] == '\0' && dirPath[0] == '/')
				strcpy(leafName, ".");
			if (leafName[0] == '\0') {
				// the supplied path is already a leaf
				error = BPrivate::Storage::check_entry_name(dirPath);
				if (error != B_OK)
					return error;
				strcpy(leafName, dirPath);
				// if no directory was given, we need to open the current dir
				// now
				if (dirFD < 0) {
					char* cwd = getcwd(tmpPath, B_PATH_NAME_LENGTH);
					if (!cwd)
						return B_ERROR;
					dirFD = _kern_open_dir(-1, cwd);
					if (dirFD < 0)
						return dirFD;
					fdCloser.SetTo(dirFD);
				}
			} else if (strcmp(leafName, ".") == 0
					|| strcmp(leafName, "..") == 0) {
				// We have to resolve this to get the entry name. Just open
				// the dir and let the next iteration deal with it.
				dirFD = _kern_open_dir(-1, path);
				if( debug )
					printf( "  entry._SetTo: Sub-Case for dot or dot-dot -- kern_open_dir returns: %d\n", dirFD );
				
				if( debug && dirFD < 0 )
					printf("  ******* got dirFD %d  from _kern_open_dir <%s>\n", dirFD, path);
				
				if (dirFD < 0)
					return dirFD;
				fdCloser.SetTo(dirFD);
				path = NULL;
				continue;
			} else {
				if( debug )
					printf( "  entry._SetTo: Sub-Case for _kern_open_dir() !\n" );
				
				int parentFD = _kern_open_dir(dirFD, dirPath);
				if (parentFD < 0)
					return parentFD;
				dirFD = parentFD;
				fdCloser.SetTo(dirFD);
			}
			// traverse symlinks, if desired
			if (!traverse)
				break;
			struct stat st;
			error = _kern_read_stat(dirFD, leafName, false, &st,
				sizeof(struct stat));
			
			if( debug )
				printf( "  res of _kern_read_stat(fd %d, <%s>) : %d\n", dirFD, leafName, error);
			
			if (error == B_ENTRY_NOT_FOUND && !requireConcrete) {
				// that's fine -- the entry is abstract and was not target of
				// a symlink we resolved
				break;
			}
			if (error != B_OK)
				return error;
			// the entry is concrete
			if (!S_ISLNK(st.st_mode))
				break;
			requireConcrete = true;
			// we need to traverse the symlink
			if (--linkLimit < 0)
				return B_LINK_LIMIT;
			size_t bufferSize = B_PATH_NAME_LENGTH - 1;
			error = _kern_read_link(dirFD, leafName, tmpPath, &bufferSize);
			if (error < 0)
				return error;
			tmpPath[bufferSize] = '\0';
			path = tmpPath;
			// next round...
		}
	}

#ifndef HoG_GENODE
	// set close on exec flag on dir FD
	fcntl(dirFD, F_SETFD, FD_CLOEXEC);
#else
	///later: review silencing of fcntl() (which triggers tons of Genode::error()s), but seems harmless
	
	// plus tracing:
	if( debug )
		printf( " entry setting leafname: <%s>\n", leafName );
#endif

	// set the result
	status_t error = _SetName(leafName);
	if (error != B_OK)
		return error;
	fdCloser.Detach();
	fDirFd = dirFD;
	return B_OK;
}


/*!	Handles string allocation, deallocation, and copying for the
	leaf name of the entry.

	\param name The leaf \a name of the entry.

	\returns A status code.
	\retval B_OK Everything went fine.
	\retval B_BAD_VALUE \a name is \c NULL.
	\retval B_NO_MEMORY Ran out of memory trying to allocate \a name.
*/
status_t
BEntry::_SetName(const char* name)
{
	if (name == NULL)
		return B_BAD_VALUE;

	free(fName);

	fName = strdup(name);
	if (fName == NULL)
		return B_NO_MEMORY;

	return B_OK;
}


/*!	Renames the entry referred to by this object to the location
	specified by \a target.

	If an entry exists at the target location, the method fails, unless
	\a clobber is \c true, in which case that entry is overwritten (doesn't
	work for non-empty directories, though).

	If the operation was successful, this entry is made a clone of the
	supplied one and the supplied one is uninitialized.

	\param target The entry specifying the target location.
	\param clobber If \c true, the an entry existing at the target location
		   will be overwritten.

	\return \c B_OK, if everything went fine, another error code otherwise.
*/
status_t
BEntry::_Rename(BEntry& target, bool clobber)
{
	// check, if there's an entry in the way
	if (!clobber && target.Exists())
		return B_FILE_EXISTS;
	// rename
	status_t error = _kern_rename(fDirFd, fName, target.fDirFd, target.fName);
	if (error == B_OK) {
		Unset();
		fCStatus = target.fCStatus;
		fDirFd = target.fDirFd;
		fName = target.fName;
		target.fCStatus = B_NO_INIT;
		target.fDirFd = -1;
		target.fName = NULL;
	}
	return error;
}


/*!	Debugging function, dumps the given entry to stdout.

	\param name A pointer to a string to be printed along with the dump for
		   identification purposes.
*/
void
BEntry::_Dump(const char* name)
{
	if (name != NULL) {
		printf("------------------------------------------------------------\n");
		printf("%s\n", name);
		printf("------------------------------------------------------------\n");
	}

	printf("fCStatus == %" B_PRId32 "\n", fCStatus);

	struct stat st;
	if (fDirFd != -1
		&& _kern_read_stat(fDirFd, NULL, false, &st,
				sizeof(struct stat)) == B_OK) {
		printf("dir.device == %" B_PRIdDEV "\n", st.st_dev);
		printf("dir.inode  == %" B_PRIdINO "\n", st.st_ino);
	} else {
		printf("dir == NullFd\n");
	}

	printf("leaf == '%s'\n", fName);
	printf("\n");

}


status_t
#ifndef HoG_GENODE
BEntry::_GetStat(struct stat* st) const
#else
BEntry::GetStat(struct stat* st) const
#endif
{
	if (fCStatus != B_OK)
		return B_NO_INIT;

	return _kern_read_stat(fDirFd, fName, false, st, sizeof(struct stat));
}


#ifndef HoG_GENODE
status_t
BEntry::_GetStat(struct stat_beos* st) const
{
	struct stat newStat;
	status_t error = _GetStat(&newStat);
	if (error != B_OK)
		return error;

	convert_to_stat_beos(&newStat, st);
	return B_OK;
}
#endif


// #pragma mark -


status_t
get_ref_for_path(const char* path, entry_ref* ref)
{
	status_t error = path && ref ? B_OK : B_BAD_VALUE;
	if (error == B_OK) {
		BEntry entry(path);
		error = entry.InitCheck();
		if (error == B_OK)
			error = entry.GetRef(ref);
	}
	return error;
}


bool
operator<(const entry_ref& a, const entry_ref& b)
{
	return (a.device < b.device
		|| (a.device == b.device
			&& (a.directory < b.directory
			|| (a.directory == b.directory
				&& ((a.name == NULL && b.name != NULL)
				|| (a.name != NULL && b.name != NULL
					&& strcmp(a.name, b.name) < 0))))));
}


// #pragma mark - symbol versions


#ifdef HAIKU_TARGET_PLATFORM_LIBBE_TEST
#	if __GNUC__ == 2	// gcc 2

	B_DEFINE_SYMBOL_VERSION("_GetStat__C6BEntryP4stat",
		"GetStat__C6BEntryP4stat@@LIBBE_TEST");

#	else	// gcc 4

	// Haiku GetStat()
	B_DEFINE_SYMBOL_VERSION("_ZNK6BEntry8_GetStatEP4stat",
		"_ZNK6BEntry7GetStatEP4stat@@LIBBE_TEST");

#	endif	// gcc 4
#else	// !HAIKU_TARGET_PLATFORM_LIBBE_TEST
#	if __GNUC__ == 2	// gcc 2

	// BeOS compatible GetStat()
	B_DEFINE_SYMBOL_VERSION("_GetStat__C6BEntryP9stat_beos",
		"GetStat__C6BEntryP4stat@LIBBE_BASE");

	// Haiku GetStat()
	B_DEFINE_SYMBOL_VERSION("_GetStat__C6BEntryP4stat",
		"GetStat__C6BEntryP4stat@@LIBBE_1_ALPHA1");

#	else	// gcc 4

#ifndef HoG_GENODE
	// BeOS compatible GetStat()
	B_DEFINE_SYMBOL_VERSION("_ZNK6BEntry8_GetStatEP9stat_beos",
		"_ZNK6BEntry7GetStatEP4stat@LIBBE_BASE");

	// Haiku GetStat()
	B_DEFINE_SYMBOL_VERSION("_ZNK6BEntry8_GetStatEP4stat",
		"_ZNK6BEntry7GetStatEP4stat@@LIBBE_1_ALPHA1");
#endif

#	endif	// gcc 4
#endif	// !HAIKU_TARGET_PLATFORM_LIBBE_TEST
