// Copyright (c) 1999-2018 David Muse
// See the COPYING file for more information

#include <rudiments/filesystem.h>
#include <rudiments/bytestring.h>
#include <rudiments/directory.h>
#include <rudiments/error.h>
#include <rudiments/charstring.h>
#ifdef RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE
	#include <rudiments/filedescriptor.h>
#endif
#include <rudiments/stdio.h>

#ifdef RUDIMENTS_HAVE_SYS_TYPES_H
	#include <sys/types.h>
#endif
#ifdef RUDIMENTS_HAVE_SYS_STAT_H
	#include <sys/stat.h>
#endif
#ifdef RUDIMENTS_HAVE_STDLIB_H
	#include <stdlib.h>
#endif
#ifdef RUDIMENTS_HAVE_FCNTL_H
	#include <fcntl.h>
#endif
#ifdef RUDIMENTS_HAVE_UNISTD_H
	#include <unistd.h>
#endif

#ifdef RUDIMENTS_HAVE_IO_H
	#undef _POSIX_
	#include <io.h>
#endif

#ifndef PATH_MAX
	#define PATH_MAX 1024
#endif

#ifdef RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE
	#ifdef RUDIMENTS_HAVE_WINDOWS_H
		#include <windows.h>
	#endif
	#ifdef RUDIMENTS_HAVE_PSAPI_H
		#define PSAPI_VERSION 1
		#include <psapi.h>
	#endif
	#ifdef RUDIMENTS_HAVE_SYS_PARAM_H
		#include <sys/param.h>
	#endif
	struct	statfs {
		int64_t	f_type;
		DWORD	f_bsize;
		int64_t	f_blocks;
		int64_t	f_bfree;
		int64_t	f_fsid;
		DWORD	f_namelen;
		CHAR	f_typename[PATH_MAX];
		CHAR	f_volumename[PATH_MAX];
	};
#endif

#ifdef RUDIMENTS_HAVE_ULTRIX_STATFS
#include <sys/param.h>
#include <sys/mount.h>
extern "C" int statfs(char *,struct fs_data *);
extern "C" int fstatfs(int,struct fs_data *);
#endif

class filesystemprivate {
	friend class filesystem;
	private:
		#if defined(RUDIMENTS_HAVE_SOME_KIND_OF_STATVFS)
			struct		statvfs	_st;
		#elif defined(RUDIMENTS_HAVE_SOME_KIND_OF_STATFS) || \
				defined(RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE)
			#ifdef RUDIMENTS_HAVE_ULTRIX_STATFS
				struct		fs_data	_st;
			#else
				struct		statfs	_st;
			#endif
		#else
			#error no statvfs, statfs or anything like it
		#endif
		int32_t		_fd;
		bool		_closeflag;
		#ifdef RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE
			char		*_volume;
		#endif
};

filesystem::filesystem() {
	pvt=new filesystemprivate;
	pvt->_fd=-1;
	pvt->_closeflag=false;
	#ifdef RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE
		pvt->_volume=NULL;
	#endif
	bytestring::zero(&pvt->_st,sizeof(pvt->_st));
}

filesystem::filesystem(const filesystem &f) {
	pvt=new filesystemprivate;
	filesystemClone(f);
}

filesystem &filesystem::operator=(const filesystem &f) {
	if (this!=&f) {
		filesystemClone(f);
	}
	return *this;
}

void filesystem::filesystemClone(const filesystem &f) {
	pvt->_fd=f.pvt->_fd;
	pvt->_closeflag=f.pvt->_closeflag;
	#ifdef RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE
		delete[] pvt->_volume;
		pvt->_volume=charstring::duplicate(f.pvt->_volume);
	#endif
	pvt->_st=f.pvt->_st;
}

filesystem::~filesystem() {
	close();
	delete pvt;
}

bool filesystem::open(const char *path) {
	close();
	if (charstring::isNullOrEmpty(path)) {
		path=directory::getCurrentWorkingDirectory();
	}
	#ifndef RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE
		pvt->_closeflag=true;
		error::clearError();
		do {
			#if defined(RUDIMENTS_HAVE__OPEN)
				pvt->_fd=_open(path,O_RDONLY);
			#elif defined(RUDIMENTS_HAVE_OPEN)
				pvt->_fd=::open(path,O_RDONLY);
			#else
				#error no open or anything like it
			#endif
		} while (pvt->_fd==-1 && error::getErrorNumber()==EINTR);
		return (pvt->_fd!=-1 && getCurrentProperties());
	#else 
		// extract the volume name from the path
		delete[] pvt->_volume;
		pvt->_volume=new CHAR[MAX_PATH];
		CHAR	*ptr;
		DWORD	len=GetFullPathName(path,MAX_PATH,pvt->_volume,&ptr);
		if (len>=3 && pvt->_volume[1]==':' && pvt->_volume[2]=='\\') {
			pvt->_volume[3]='\0';
		}
		return getCurrentProperties();
	#endif
}

bool filesystem::open(int32_t fd) {
	close();
	#ifndef RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE
		pvt->_fd=fd;
		return getCurrentProperties();
	#else
		// This is complex on Windows.

		// We have to determine the file name that the file descriptor
		// refers to and call the other version of open().

		// On Vista and newer we can use GetFinalPathNameByHandle(),
		// but on older versions we have to memory-map the file, get
		// the file name from the map and convert the volume name to a
		// dos-style drive letter, so we'll just do that.

		// get the file handle
		HANDLE	fh=(HANDLE)filedescriptor::
					getHandleFromFileDescriptor(fd);
		if (fh==INVALID_HANDLE_VALUE) {
			return false;
		}

		// get the file size, can't map a zero-byte file
		DWORD sizehi=0;
		DWORD sizelo=GetFileSize(fh,&sizehi);
		if (!sizehi && !sizelo) {
			return false;
		}

		// map the file
		HANDLE	fm=CreateFileMapping(fh,NULL,PAGE_READONLY,0,1,NULL);
		if (!fm) {
			return false;
		}

		// map view of file
		void	*fv=MapViewOfFile(fm,FILE_MAP_READ,0,0,1);
		if (!fv) {
			CloseHandle(fm);
			return false;
		}

		// get file name
		// NOTE: If GetMappedFileName is dynamically loaded on
		// Windows 8 or higher then it corrupts the stack when it is
		// used.  It works fine on Windows 7 and lower though.
		char	filename[MAX_PATH+1];
		if (!GetMappedFileName(GetCurrentProcess(),
					fv,filename,MAX_PATH)) {
			UnmapViewOfFile(fv);
			CloseHandle(fm);
			return false;
		}

		// This will start with something like:
		// 	\Device\HarddiskVolume3\...
		// instead of a drive letter.
		// We need to translate it to a drive letter...

		// get the volume bitmap
		DWORD	volumes=GetLogicalDrives();

		// set up a template for the volume name
		char	volume[3];
		charstring::copy(volume," :");
		volume[0]='\0';

		// set up a buffer for the drive mapping
		char	mapping[MAX_PATH+1];

		// for each volume...
		for (char driveletter='c'; driveletter<='z'; driveletter++) {

			// ignore volumes that don't exist
			DWORD	exists=volumes&0x0001;
			volumes=volumes>>1;
			if (!exists) {
				continue;
			}

			// create the volume name, eg: C:
			volume[0]=driveletter;

			// get the drive mapping
			// FIXME: If the volume is mapped to more than 1
			// location then this could return 0 with
			// ERROR_INSUFFICIENT_BUFFER if mapping[] is too small
			// to store the list of locations.  In that case we
			// should loop back, resize mapping[] and try again.
			DWORD	result=QueryDosDevice(volume,mapping,
							MAX_PATH/sizeof(TCHAR));
			if (!result) {
				UnmapViewOfFile(fv);
				CloseHandle(fm);
				return false;
			}

			// if the drive mapping for this volume matches
			// then we're done
			// FIXME: If the volume is mapped to more than 1
			// location then we need to test the filename against
			// each of the mappings.
			if (!charstring::compare(filename,mapping,
						charstring::length(mapping))) {
				break;
			}

			// reset the volume name
			volume[0]='\0';
		}

		// clean up
		UnmapViewOfFile(fv);
		CloseHandle(fm);

		// replace drive mapping with volume name in filename
		size_t	mappinglength=charstring::length(mapping);
		char	*ptr=filename;
		if (volume[0] && mappinglength>2) {
			ptr=filename+charstring::length(mapping)-2;
			*ptr=volume[0];
			*(ptr+1)=':';
		}
		
		// open using file name
		return (volume[0])?open(ptr):false;
	#endif
}

bool filesystem::close() {
	int32_t	result=0;
	#ifndef RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE
		if (pvt->_fd>-1 && pvt->_closeflag) {
			error::clearError();
			do {
				#if defined(RUDIMENTS_HAVE__CLOSE)
					result=_close(pvt->_fd);
				#elif defined(RUDIMENTS_HAVE_CLOSE)
					result=::close(pvt->_fd);
				#else
					#error no close or anything like it
				#endif
			} while (result==-1 && error::getErrorNumber()==EINTR);
		}
	#else
		delete[] pvt->_volume;
		pvt->_volume=NULL;
	#endif
	pvt->_fd=-1;
	pvt->_closeflag=false;
	bytestring::zero(&pvt->_st,sizeof(pvt->_st));
	return !result;
}

bool filesystem::getCurrentProperties() {
	#if (defined(RUDIMENTS_HAVE_SOME_KIND_OF_STATVFS) || \
			defined(RUDIMENTS_HAVE_SOME_KIND_OF_STATFS)) && \
			!defined(__VMS)

		int32_t	result;
		error::clearError();
		do {
			#if defined(RUDIMENTS_HAVE_SOME_KIND_OF_STATVFS)
				result=fstatvfs(pvt->_fd,&pvt->_st);
			#elif defined(RUDIMENTS_HAVE_SOME_KIND_OF_STATFS)
				result=fstatfs(pvt->_fd,&pvt->_st);
			#endif
		} while (result==-1 && error::getErrorNumber()==EINTR);
		return !result;

	#elif defined(RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE)

		// clear the statfs buffer
		bytestring::zero(&pvt->_st,sizeof(pvt->_st));

		// bail if no volume was specified
		if (!pvt->_volume) {
			return false;
		}

		// Get free space the old-school and well-supported way, by
		// sectors and clusters.  If it fails, fall back to some safe
		// defaults.
		DWORD	sectorspercluster;
		DWORD	bytespersector;
		DWORD	freeclusters;
		DWORD	totalclusters;
		if (GetDiskFreeSpace(pvt->_volume,&sectorspercluster,
						&bytespersector,
						&freeclusters,
						&totalclusters)==TRUE) {
			// calculate bytes, blocks and files available
			// (apparently cluster size = block size on windows)
			pvt->_st.f_bsize=sectorspercluster*bytespersector;
			pvt->_st.f_blocks=totalclusters;
			pvt->_st.f_bfree=freeclusters;
		}

		// get volume information
		DWORD	volumeserialnumber;
		if (!GetVolumeInformation(pvt->_volume,
						pvt->_st.f_volumename,
						PATH_MAX,
						&volumeserialnumber,
						&(pvt->_st.f_namelen),
						NULL,
						pvt->_st.f_typename,
						PATH_MAX)) {
			return false;
		}

		// build the volume serial number
		union fsid {
			int32_t	val32[2];
		};
		fsid	*f_fsid=(union fsid *)&(pvt->_st.f_fsid);
		f_fsid->val32[0]=HIWORD(volumeserialnumber);
		f_fsid->val32[1]=LOWORD(volumeserialnumber);

		// translate the filesystem type name to hex
		pvt->_st.f_type=0x0;
		if (!charstring::compare(pvt->_st.f_typename,"FAT") ||
			!charstring::compare(pvt->_st.f_typename,"FAT32")) {
			pvt->_st.f_type=0x4d44;
		} else if (!charstring::compare(pvt->_st.f_typename,"NTFS")) {
			pvt->_st.f_type=0x5346544e;
		} else if (!charstring::compare(pvt->_st.f_typename,"CDFS")) {
			pvt->_st.f_type=0x9660;
		}
		return true;
	#else
		RUDIMENTS_SET_ENOSYS
		return false;
	#endif
}

int64_t filesystem::getType() const {
	int64_t	result=0;
#if defined(RUDIMENTS_HAVE_LINUX_STATFS) || \
	defined(RUDIMENTS_HAVE_LINUX_LIBC4_STATFS) || \
	defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_DARWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_CYGWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE)
	result=pvt->_st.f_type;
#endif
	if (result) {
		return result;
	}
	const char	*fstypename=NULL;
#if defined(RUDIMENTS_HAVE_STATVFS)
	fstypename=(const char *)pvt->_st.f_basetype;
#elif defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_OPENBSD_STATFS)
	fstypename=(const char *)pvt->_st.f_fstypename;
#endif
	if (!charstring::compareIgnoringCase(
				fstypename,"adfs")) {
		return 0xADF5;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"affs")) {
		return 0xADFF;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"befs")) {
		return 0x42465331;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"bfs")) {
		return 0x1BADFACE;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"cifs")) {
		return 0xFF534D42;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"codafs")) {
		return 0x73757245;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"coherent")) {
		return 0x012FF7B7;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"cramfs")) {
		return 0x28cd3d45;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"devfs")) {
		return 0x1373;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"efs")) {
		return 0x00414A53;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"ext")) {
		return 0x137D;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"ext2 (old)")) {
		return 0xEF51;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"ext2")) {
		return 0xEF53;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"hfs")) {
		return 0x4244;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"hpfs")) {
		return 0xF995E849;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"htfs")) {
		return 0xA060492A;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"hugetlbfs")) {
		return 0x958458f6;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"isofs")) {
		return 0x9660;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"jffs2")) {
		return 0x72b6;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"jfs")) {
		return 0x3153464a;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"minix (original)")) {
		return 0x137F;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"minix (30 char)")) {
		return 0x138F;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"minix2 (original)")) {
		return 0x2468;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"minix2 (30 char)")) {
		return 0x2478;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"msdos")) {
		return 0x4d44;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"ncp")) {
		return 0x564c;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"nfs")) {
		return 0x6969;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"ntfs")) {
		return 0x5346544e;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"openprom")) {
		return 0x9fa1;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"proc")) {
		return 0x9fa0;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"qnx4")) {
		return 0x002f;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"reiserfs")) {
		return 0x52654973;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"romfs")) {
		return 0x7275;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"smb")) {
		return 0x517B;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"sysv2")) {
		return 0x012FF7B6;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"sysv4")) {
		return 0x012FF7B5;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"tmpfs")) {
		return 0x01021994;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"udf")) {
		return 0x15013346;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"ufs") ||
			!charstring::compareIgnoringCase(
				fstypename,"ffs")) {
		return 0x00011954;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"usbdevfs")) {
		return 0x9fa2;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"vxfs")) {
		return 0xa501FCF5;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"xenix")) {
		return 0x012FF7B4;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"xfs")) {
		return 0x58465342;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"xiafs")) {
		return 0x012FD16D;
	} else if (!charstring::compareIgnoringCase(
				fstypename,"zfs")) {
		return 0x00BAB10C;
	}
	return 0;
}

int64_t filesystem::getBlockSize() const {
#if defined(RUDIMENTS_HAVE_LINUX_STATFS) || \
	defined(RUDIMENTS_HAVE_LINUX_LIBC4_STATFS) || \
	defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_OPENBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_DARWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_CYGWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE)
	return pvt->_st.f_bsize;
#elif defined(RUDIMENTS_HAVE_STATVFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS)
	return pvt->_st.f_frsize;
#else
	return 0;
#endif
}

int64_t filesystem::getOptimumTransferBlockSize() const {
#if defined(RUDIMENTS_HAVE_LINUX_STATFS) || \
	defined(RUDIMENTS_HAVE_LINUX_LIBC4_STATFS) || \
	defined(RUDIMENTS_HAVE_CYGWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_STATVFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE)
	return pvt->_st.f_bsize;
#elif defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_OPENBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_DARWIN_STATFS)
	return pvt->_st.f_iosize;
#else
	return 0;
#endif
}

int64_t filesystem::getTotalBlocks() const {
#if defined(RUDIMENTS_HAVE_LINUX_STATFS) || \
	defined(RUDIMENTS_HAVE_LINUX_LIBC4_STATFS) || \
	defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_OPENBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_DARWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_CYGWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_STATVFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_MINIX_HAIKU_STATVFS) || \
	defined(RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE)
	return pvt->_st.f_blocks;
#else
	return 0;
#endif
}

int64_t filesystem::getFreeBlocks() const {
#if defined(RUDIMENTS_HAVE_LINUX_STATFS) || \
	defined(RUDIMENTS_HAVE_LINUX_LIBC4_STATFS) || \
	defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_OPENBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_DARWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_CYGWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_STATVFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_MINIX_HAIKU_STATVFS) || \
	defined(RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE)
	return pvt->_st.f_bfree;
#else
	return 0;
#endif
}

int64_t filesystem::getAvailableBlocks() const {
#if defined(RUDIMENTS_HAVE_LINUX_STATFS) || \
	defined(RUDIMENTS_HAVE_LINUX_LIBC4_STATFS) || \
	defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_OPENBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_DARWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_CYGWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_STATVFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_MINIX_HAIKU_STATVFS)
	return pvt->_st.f_bavail;
#else
	return 0;
#endif
}

int64_t filesystem::getReservedBlocks() const {
#if defined(RUDIMENTS_HAVE_STATVFS_RESVD)
	return pvt->_st.f_bresvd;
#else
	return 0;
#endif
}

int64_t filesystem::getTotalFileNodes() const {
#if defined(RUDIMENTS_HAVE_LINUX_STATFS) || \
	defined(RUDIMENTS_HAVE_LINUX_LIBC4_STATFS) || \
	defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_OPENBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_DARWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_CYGWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_STATVFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_MINIX_HAIKU_STATVFS)
	return pvt->_st.f_files;
#else
	return 0;
#endif
}

int64_t filesystem::getFreeFileNodes() const {
#if defined(RUDIMENTS_HAVE_LINUX_STATFS) || \
	defined(RUDIMENTS_HAVE_LINUX_LIBC4_STATFS) || \
	defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_OPENBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_DARWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_CYGWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_STATVFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_MINIX_HAIKU_STATVFS)
	return pvt->_st.f_ffree;
#else
	return 0;
#endif
}

int64_t filesystem::getAvailableFileNodes() const {
#if defined(RUDIMENTS_HAVE_STATVFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_MINIX_HAIKU_STATVFS)
	return pvt->_st.f_favail;
#else
	return 0;
#endif
}

int64_t filesystem::getReservedFileNodes() const {
#if defined(RUDIMENTS_HAVE_STATVFS_RESVD)
	return pvt->_st.f_fresvd;
#else
	return 0;
#endif
}

int64_t filesystem::getFileSystemId() const {
#if defined(RUDIMENTS_HAVE_LINUX_STATFS)
	// fsid_t is an opaque struct, but it typically contains an array of
	// some number of bytes which are collectively set to some unique value.
	// Attempt to handle various possibilities...
	if (sizeof(fsid_t)==sizeof(uint16_t)) {
		uint16_t	temp;
		bytestring::copy(&temp,&pvt->_st.f_fsid,sizeof(uint16_t));
		return (int64_t)temp;
	} else if (sizeof(fsid_t)==sizeof(uint32_t)) {
		uint32_t	temp;
		bytestring::copy(&temp,&pvt->_st.f_fsid,sizeof(uint32_t));
		return (int64_t)temp;
	} else {
		uint64_t	temp;
		bytestring::copy(&temp,&pvt->_st.f_fsid,sizeof(uint64_t));
		return (int64_t)temp;
	}
	return 0;
#elif defined(RUDIMENTS_HAVE_STATVFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_MINIX_HAIKU_STATVFS) || \
	defined(RUDIMENTS_HAVE_CYGWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE)
	return (int64_t)pvt->_st.f_fsid;
#else
	return 0;
#endif
}

int64_t filesystem::getMaximumFileNameLength() const {
#if defined(RUDIMENTS_HAVE_LINUX_STATFS) || \
	defined(RUDIMENTS_HAVE_LINUX_LIBC4_STATFS) || \
	defined(RUDIMENTS_HAVE_CYGWIN_STATFS) || \
	defined(RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE)
	return pvt->_st.f_namelen;
#elif defined(RUDIMENTS_HAVE_STATVFS) || \
	defined(RUDIMENTS_HAVE_MINIX_HAIKU_STATVFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS)
	return pvt->_st.f_namemax;
#else
	return 0;
#endif
}

uid_t filesystem::getOwner() const {
#if defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_OPENBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_DARWIN_STATFS)
	return pvt->_st.f_owner;
#else
	return 0;
#endif
}

int64_t filesystem::getSyncWrites() const {
#if defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_OPENBSD_STATFS)
	return pvt->_st.f_syncwrites;
#else
	return 0;
#endif
}

int64_t filesystem::getAsyncWrites() const {
#if defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_OPENBSD_STATFS)
	return pvt->_st.f_asyncwrites;
#else
	return 0;
#endif
}

const char *filesystem::getMountPoint() const {
#if defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_OPENBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_DARWIN_STATFS)
	return (pvt->_st.f_mntonname[0])?
			(const char *)pvt->_st.f_mntonname:NULL;
#elif defined(RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE)
	return (!charstring::isNullOrEmpty(pvt->_volume))?
				(const char *)pvt->_volume:NULL;
#else
	return NULL;
#endif
}

int64_t filesystem::getSyncReads() const {
#if defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS)
	return pvt->_st.f_syncreads;
#else
	return 0;
#endif
}

int64_t filesystem::getAsyncReads() const {
#if defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS)
	return pvt->_st.f_asyncreads;
#else
	return 0;
#endif
}

const char *filesystem::getDeviceName() const {
#if defined(RUDIMENTS_HAVE_NETBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_OPENBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_DARWIN_STATFS)
	return (pvt->_st.f_mntfromname[0])?
			(const char *)pvt->_st.f_mntfromname:NULL;
#else
	return NULL;
#endif
}

const char *filesystem::getFilesystemSpecificString() const {
#ifdef RUDIMENTS_HAVE_STATVFS
	return (pvt->_st.f_fstr[0])?
			(const char *)pvt->_st.f_fstr:NULL;
#elif defined(RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE)
	return (pvt->_st.f_volumename[0])?
			(const char *)pvt->_st.f_volumename:NULL;
#else
	return NULL;
#endif
}

void *filesystem::getInternalFilesystemStatisticsStructure() {
	return (void *)&pvt->_st;
}

const char *filesystem::getTypeName() const {
#if defined(RUDIMENTS_HAVE_FREEBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_NETBSD_STATVFS) || \
	defined(RUDIMENTS_HAVE_OPENBSD_STATFS) || \
	defined(RUDIMENTS_HAVE_DARWIN_STATFS)
	return (pvt->_st.f_fstypename[0])?
			(const char *)pvt->_st.f_fstypename:NULL;
#elif defined(RUDIMENTS_HAVE_STATVFS)
	return (pvt->_st.f_basetype[0])?
			(const char *)pvt->_st.f_basetype:NULL;
#elif defined(RUDIMENTS_HAVE_LINUX_STATFS) || \
	defined(RUDIMENTS_HAVE_LINUX_LIBC4_STATFS)
	// NOTE: casting pvt->_st.f_type to uint64_t in the switch below
	// resolves issues on systems where it's defined as a signed int
	// and comparisons with values like 0xFF534D42 would throw a
	// "narrowing" warning as those values are larger than a signed int
	// can hold.  I'm casting it to a uint64_t rather than uint32_t
	// because, who knows, it might be 64-bits on some system and I
	// don't want to down-cast it to 32-bits.
	switch ((uint64_t)pvt->_st.f_type) {
		case 0xADF5:
			return "adfs";
		case 0xADFF:
			return "affs";
		case 0x42465331:
			return "befs";
		case 0x1BADFACE:
			return "bfs";
		case 0xFF534D42:
			return "cifs";
		case 0x73757245:
			return "codafs";
		case 0x012FF7B7:
			return "coherent";
		case 0x28cd3d45:
			return "cramfs";
		case 0x1373:
			return "devfs";
		case 0x00414A53:
			return "efs";
		case 0x137D:
			return "ext";
		case 0xEF51:
			return "ext2 (old)";
		case 0xEF53:
			return "ext2";
		case 0x4244:
			return "hfs";
		case 0xF995E849:
			return "hpfs";
		case 0xA060492A:
			return "htfs";
		case 0x958458f6:
			return "hugetlbfs";
		case 0x9660:
			return "isofs";
		case 0x72b6:
			return "jffs2";
		case 0x3153464a:
			return "jfs";
		case 0x137F:
			return "minix (original)";
		case 0x138F:
			return "minix (30 char)";
		case 0x2468:
			return "minix2 (original)";
		case 0x2478:
			return "minix2 (30 char)";
		case 0x4d44:
			return "msdos";
		case 0x564c:
			return "ncp";
		case 0x6969:
			return "nfs";
		case 0x5346544e:
			return "ntfs";
		case 0x9fa1:
			return "openprom";
		case 0x9fa0:
			return "proc";
		case 0x002f:
			return "qnx4";
		case 0x52654973:
			return "reiserfs";
		case 0x7275:
			return "romfs";
		case 0x517B:
			return "smb";
		case 0x012FF7B6:
			return "sysv2";
		case 0x012FF7B5:
			return "sysv4";
		case 0x01021994:
			return "tmpfs";
		case 0x15013346:
			return "udf";
		case 0x00011954:
			return "ufs";
		case 0x9fa2:
			return "usbdevfs";
		case 0xa501FCF5:
			return "vxfs";
		case 0x012FF7B4:
			return "xenix";
		case 0x58465342:
			return "xfs";
		case 0x012FD16D:
			return "xiafs";
		case 0x00BAB10C:
			return "zfs";
	}
	return NULL;
#elif defined(RUDIMENTS_HAVE_WINDOWS_GETDISKFREESPACE)
	return (pvt->_st.f_typename[0])?
			(const char *)pvt->_st.f_typename:NULL;
#else
	return NULL;
#endif
}
