diff --git a/TODO b/TODO
new file mode 100644
index 0000000..1dafb5d
--- /dev/null
+++ b/TODO
@@ -0,0 +1,2 @@
+Tests for T_FSPath
+Generalize iterators
diff --git a/include/ebcl/Filesystem.hh b/include/ebcl/Filesystem.hh
new file mode 100644
index 0000000..8c33398
--- /dev/null
+++ b/include/ebcl/Filesystem.hh
@@ -0,0 +1,136 @@
+/******************************************************************************/
+/* FILESYSTEM ABSTRACTION *****************************************************/
+/******************************************************************************/
+
+#ifndef _H_EBCL_FILESYSTEM
+#define _H_EBCL_FILESYSTEM
+
+#include <ebcl/Strings.hh>
+#include <ebcl/Utilities.hh>
+namespace ebcl {
+
+
+/*= FILESYSTEM PATH ==========================================================*/
+
+class T_FSPath
+{
+    public:
+	using T_Components = T_AutoArray< T_String , 16 >;
+
+    private:
+
+	// Root will be '/' on Unix, or a drive letter on Windows. Relative
+	// paths will have a blank root.
+	T_String root_;	
+	T_Components components_;
+	bool valid_;
+
+    public:
+	//----------------------------------------------------------------------
+	// Helpers
+
+	static bool IsValidComponent( T_String const& string ) noexcept;
+	static bool IsValidRoot( T_String const& string ) noexcept;
+
+	//----------------------------------------------------------------------
+	// Basic constructors and assignment operators
+
+	T_FSPath( ) noexcept;
+
+	T_FSPath( T_FSPath const& other ) noexcept;
+	T_FSPath& operator =( T_FSPath const& other ) noexcept;
+
+	T_FSPath( T_FSPath&& other ) noexcept;
+	T_FSPath& operator =( T_FSPath&& other ) noexcept;
+
+	T_FSPath& swap( T_FSPath& other ) noexcept;
+
+	// Construct from a string
+	T_FSPath( T_String const& path ) noexcept;
+
+	//----------------------------------------------------------------------
+
+	bool isValid( ) const noexcept;
+
+	T_String const& root( ) const noexcept;
+	T_Components const& components( ) const noexcept;
+
+	bool isRelative( ) const noexcept;
+	bool isAbsolute( ) const noexcept;
+
+	//----------------------------------------------------------------------
+
+	// Convert the path into a string
+	T_String toString( ) const noexcept;
+	// Compute a hash value for the path
+	uint32_t computeHash( ) const noexcept;
+	// Append to a string builder
+	void appendTo( T_StringBuilder& sb ) const noexcept;
+
+	// Comparisons. On Windows the comparison will be case-insensitive.
+	int32_t compareTo( T_FSPath const& other ) const noexcept;
+	bool operator ==( T_FSPath const& other ) const noexcept;
+	bool operator !=( T_FSPath const& other ) const noexcept;
+	bool operator <( T_FSPath const& other ) const noexcept;
+	bool operator <=( T_FSPath const& other ) const noexcept;
+	bool operator >( T_FSPath const& other ) const noexcept;
+	bool operator >=( T_FSPath const& other ) const noexcept;
+
+	//----------------------------------------------------------------------
+
+	// Returns the parent's path
+	T_FSPath parent( ) const noexcept;
+
+	// Returns the child's path
+	T_FSPath child( T_String const& name ) const noexcept;
+
+	// Appends the specified (relative) path to the current path and return
+	// the result. If the path is absolute, the result will be invalid.
+	T_FSPath operator +( T_FSPath const& other ) const noexcept;
+
+	// Checks if the current path and the specified path have the same
+	// parent and are different.
+	bool inDirectoryOf( T_FSPath const& other ) const noexcept;
+
+	// Checks if the current path is the direct parent of the specified path
+	bool isParentOf( T_FSPath const& other ) const noexcept;
+	// Checks if the current path is the direct child of the specified path
+	bool isChildOf( T_FSPath const& other ) const noexcept;
+
+	// Checks if the current path is a parent of the specified path
+	bool isAbove( T_FSPath const& other ) const noexcept;
+	// Checks if the current path is a child of the specified path
+	bool isUnder( T_FSPath const& other ) const noexcept;
+
+	// Create a relative path based on the current path and a "parent" path.
+	// Both paths must be canonical and valid. If they have different roots,
+	// a copy of the current path will be returned.
+	T_FSPath makeRelative( T_FSPath const& relTo ) const noexcept;
+
+	//----------------------------------------------------------------------
+
+	// Checks whether the specified path is canonical
+	bool isCanonical( ) const noexcept;
+	// Return a canonical path from the current path
+	T_FSPath canonical( ) const noexcept;
+};
+M_DECLARE_SWAP( T_FSPath );
+M_DECLARE_HASH( T_FSPath );
+M_LSHIFT_OP( T_StringBuilder , T_FSPath const& );
+
+
+/*= FILESYSTEM ===============================================================*/
+
+class Filesystem final
+{
+	Filesystem( ) = delete;
+    public:
+	// Return the absolute path to the current working directory
+	static T_FSPath Cwd( ) noexcept;
+
+};
+
+
+} // namespace ebcl
+#endif // _H_EBCL_FILESYSTEM
+#include <ebcl/inline/Filesystem.hh>
diff --git a/include/ebcl/inline/Filesystem.hh b/include/ebcl/inline/Filesystem.hh
new file mode 100644
index 0000000..00af515
--- /dev/null
+++ b/include/ebcl/inline/Filesystem.hh
@@ -0,0 +1,170 @@
+/******************************************************************************/
+/* FILESYSTEM ABSTRACTION - INLINE CODE ***************************************/
+/******************************************************************************/
+
+#ifndef _H_EBCL_INLINE_FILESYSTEM
+#define _H_EBCL_INLINE_FILESYSTEM
+
+#include <ebcl/Filesystem.hh>
+namespace ebcl {
+
+
+/*= T_FSPath =================================================================*/
+
+inline T_FSPath::T_FSPath( ) noexcept
+	: root_{ } , components_{ } , valid_{ true }
+{ }
+
+/*----------------------------------------------------------------------------*/
+
+inline T_FSPath::T_FSPath(
+		T_FSPath const& other ) noexcept
+	: root_{ other.root_ } , components_{ other.components_ } ,
+		valid_{ other.valid_ }
+{ }
+
+inline T_FSPath& T_FSPath::operator =(
+		T_FSPath const& other ) noexcept
+{
+	root_ = other.root_;
+	components_ = other.components_;
+	valid_ = other.valid_;
+	return *this;
+}
+
+/*----------------------------------------------------------------------------*/
+
+inline T_FSPath::T_FSPath( T_FSPath&& other ) noexcept
+	: T_FSPath{ }
+{
+	swap( other );
+}
+
+inline T_FSPath& T_FSPath::operator =(
+		T_FSPath&& other ) noexcept
+{
+	root_ = T_String{ };
+	components_.free( );
+	valid_ = true;
+	return swap( other );
+}
+
+/*----------------------------------------------------------------------------*/
+
+inline T_FSPath& T_FSPath::swap(
+		T_FSPath& other ) noexcept
+{
+	using std::swap;
+	swap( root_ , other.root_ );
+	swap( components_ , other.components_ );
+	swap( valid_ , other.valid_ );
+	return *this;
+}
+
+inline M_DEFINE_SWAP( T_FSPath )
+{
+	lhs.swap( rhs );
+}
+
+/*----------------------------------------------------------------------------*/
+
+inline bool T_FSPath::isValid( ) const noexcept
+{
+	return valid_;
+}
+
+inline T_String const& T_FSPath::root( ) const noexcept
+{
+	return root_;
+}
+
+inline T_FSPath::T_Components const& T_FSPath::components( ) const noexcept
+{
+	return components_;
+}
+
+inline bool T_FSPath::isRelative( ) const noexcept
+{
+	return !root_;
+}
+
+inline bool T_FSPath::isAbsolute( ) const noexcept
+{
+	return bool( root_ );
+}
+
+/*----------------------------------------------------------------------------*/
+
+inline T_String T_FSPath::toString( ) const noexcept
+{
+	T_StringBuilder sb;
+	appendTo( sb );
+	return T_String{ std::move( sb ) };
+}
+
+inline M_DEFINE_HASH( T_FSPath )
+{
+	return item.computeHash( );
+}
+
+inline M_LSHIFT_OP( T_StringBuilder , T_FSPath const& )
+{
+	value.appendTo( obj );
+	return obj;
+}
+
+/*----------------------------------------------------------------------------*/
+
+inline bool T_FSPath::operator ==(
+		T_FSPath const& other ) const noexcept
+{
+	return compareTo( other ) == 0;
+}
+
+inline bool T_FSPath::operator !=(
+		T_FSPath const& other ) const noexcept
+{
+	return compareTo( other ) != 0;
+}
+
+inline bool T_FSPath::operator <(
+		T_FSPath const& other ) const noexcept
+{
+	return compareTo( other ) < 0;
+}
+
+inline bool T_FSPath::operator <=(
+		T_FSPath const& other ) const noexcept
+{
+	return compareTo( other ) <= 0;
+}
+
+inline bool T_FSPath::operator >(
+		T_FSPath const& other ) const noexcept
+{
+	return compareTo( other ) > 0;
+}
+
+inline bool T_FSPath::operator >=(
+		T_FSPath const& other ) const noexcept
+{
+	return compareTo( other ) >= 0;
+}
+
+/*----------------------------------------------------------------------------*/
+
+inline bool T_FSPath::isChildOf(
+		T_FSPath const& other ) const noexcept
+{
+	return other.isParentOf( *this );
+}
+
+inline bool T_FSPath::isUnder(
+		T_FSPath const& other ) const noexcept
+{
+	return other.isAbove( *this );
+}
+
+
+} // namespace ebcl
+#endif // _H_EBCL_INLINE_FILESYSTEM
diff --git a/src/Filesystem.cc b/src/Filesystem.cc
new file mode 100644
index 0000000..2148b6b
--- /dev/null
+++ b/src/Filesystem.cc
@@ -0,0 +1,387 @@
+/******************************************************************************/
+/* FILESYSTEM ABSTRACTION *****************************************************/
+/******************************************************************************/
+
+#include <ebcl/Filesystem.hh>
+using namespace ebcl;
+
+
+#ifdef _WIN32
+# define M_PATHSEP_ '\\'
+#else
+# define M_PATHSEP_ '/'
+# include <unistd.h>
+#endif
+
+
+/*= T_FSPath =================================================================*/
+
+bool T_FSPath::IsValidRoot(
+		T_String const& str ) noexcept
+{
+	if ( !str ) {
+		return true;
+	}
+
+#ifdef _WIN32
+	// TODO: support for network names (\\server\path)
+	if ( str.length( ) == 3 && ( str.endsWith( ":\\" )
+				|| str.endsWith( ":/" ) ) ) {
+		return str[ 0 ].isAlpha( );
+	} else {
+		return ( str == "/" || str == "\\" );
+	}
+#else
+	return ( str == "/" || str == "\\" );
+#endif
+}
+
+bool T_FSPath::IsValidComponent(
+		T_String const& str ) noexcept
+{
+	if ( !str ) {
+		return false;
+	}
+
+	T_StringIterator it{ str };
+	while ( !it.atEnd( ) ) {
+		const auto c{ it.character( ) };
+		it.next( );
+		if ( c.isControl( ) || c == '\\' || c == '/' || c == '"'
+				|| c == '<' || c == '>' || c == '|' || c == ':'
+				|| c == '*' || c == '?' ) {
+			return false;
+		}
+	}
+	return true;
+}
+
+/*----------------------------------------------------------------------------*/
+
+T_FSPath::T_FSPath(
+		T_String const& path ) noexcept
+{
+	if ( !path ) {
+		valid_ = true;
+		return;
+	}
+
+	// Find sequences of (back)slashes in the string. For each sequence
+	// we will store the positions of the first and last (back)slash.
+	T_AutoArray< uint32_t , 64 > slashPos;
+	T_StringIterator it{ path };
+	while ( !it.atEnd( ) ) {
+		const auto c{ it.character( ) };
+		const auto p{ it.index( ) };
+		it.next( );
+
+		if ( c != '/' && c != '\\' ) {
+			continue;
+		}
+
+		const auto ps{ slashPos.size( ) };
+		if ( ps == 0 || slashPos[ ps - 1 ] != p - 1 ) {
+			slashPos.add( p );
+			slashPos.add( p );
+		} else {
+			slashPos[ ps - 1 ] ++;
+		}
+	}
+
+	// No (back)slashes, this is a relative path
+	const auto sps{ slashPos.size( ) };
+	if ( sps == 0 ) {
+		components_.add( path );
+		valid_ = IsValidComponent( path );
+		return;
+	}
+
+	// Collect the first item, with the first (back)slash included,
+	// check if it's a root
+	const auto firstSlashPos{ slashPos[ 0 ] };
+	T_String firstItem{ path.substr( 0 , 1 + firstSlashPos ) };
+	valid_ = true;
+	if ( IsValidRoot( firstItem ) ) {
+		root_ = std::move( firstItem );
+	}
+
+	// Get and check the components
+	uint32_t pos{ root_ ? 2u : 0u };
+	while ( pos <= sps ) {
+		T_String item{ path.range(
+			pos ? ( 1 + slashPos[ pos - 1 ] ) : 0 ,
+			pos == sps ? path.length( ) : ( slashPos[ pos ] - 1 ) )
+		};
+		assert( item || pos == sps );
+		if ( item ) {
+			valid_ = valid_ && IsValidComponent( item );
+			components_.add( std::move( item ) );
+		}
+		pos += 2;
+	}
+}
+
+/*----------------------------------------------------------------------------*/
+
+uint32_t T_FSPath::computeHash( ) const noexcept
+{
+	uint32_t h{ ComputeHash( root_ ) };
+	const auto cs{ components_.size( ) };
+	h = ( ( h << 27 ) | ( h >> 5 ) ) ^ cs;
+	for ( auto i = 0u ; i < cs ; i ++ ) {
+		h = ( ( h << 27 ) | ( h >> 5 ) ) ^ ComputeHash( components_[ i ] );
+	}
+	return h;
+}
+
+void T_FSPath::appendTo(
+		T_StringBuilder& sb ) const noexcept
+{
+	const auto cs{ components_.size( ) };
+	if ( !( root_ || cs ) ) {
+		sb << '.';
+		return;
+	}
+
+	for ( auto i = 0u ; i < cs ; i ++ ) {
+		if ( i == 0 ) {
+			sb << root_;
+		} else {
+			sb << M_PATHSEP_;
+		}
+		sb << components_[ i ];
+	}
+}
+
+/*----------------------------------------------------------------------------*/
+
+#ifdef _WIN32
+# define M_CMPSTR_(A,B) (A).compareIgnoreCase( B )
+#else
+# define M_CMPSTR_(A,B) (A).compare( B )
+#endif
+
+int32_t T_FSPath::compareTo(
+		T_FSPath const& other ) const noexcept
+{
+	if ( &other == this ) {
+		return 0;
+	}
+
+	int32_t cmp{ M_CMPSTR_( root_ , other.root_ ) };
+	if ( cmp == 0 ) {
+		const auto nca{ components_.size( ) } ,
+			ncb{ other.components_.size( ) } ,
+			nc{ std::min( nca , ncb ) };
+		for ( auto i = 0u ; i < nc && cmp == 0 ; i ++ ) {
+			cmp = M_CMPSTR_( components_[ i ] , other.components_[ i ] );
+		}
+		if ( cmp == 0 ) {
+			cmp = T_Comparator< uint32_t >::compare( nca , ncb );
+		}
+	}
+	return cmp;
+}
+
+/*----------------------------------------------------------------------------*/
+
+T_FSPath T_FSPath::parent( ) const noexcept
+{
+	T_FSPath p{ *this };
+	if ( p.components_.size( ) ) {
+		p.components_.removeLast( );
+	}
+	return p;
+}
+
+T_FSPath T_FSPath::child(
+		T_String const& name ) const noexcept
+{
+	T_FSPath c{ *this };
+	c.components_.add( name );
+	c.valid_ = c.valid_ && IsValidComponent( name );
+	return c;
+}
+
+T_FSPath T_FSPath::operator +(
+		T_FSPath const& other ) const noexcept
+{
+	T_FSPath p{ *this };
+	if ( other.root_ ) {
+		p.valid_ = false;
+	} else {
+		p.valid_ = p.valid_ && other.valid_;
+	}
+	p.components_.addAll( other.components_ );
+	return p;
+}
+
+/*----------------------------------------------------------------------------*/
+
+bool T_FSPath::inDirectoryOf(
+		T_FSPath const& other ) const noexcept
+{
+	if ( &other == this ) {
+		return false;
+	}
+	if ( M_CMPSTR_( root_ , other.root_ ) != 0 ) {
+		return false;
+	}
+
+	const auto nc{ components_.size( ) };
+	if ( nc != other.components_.size( ) || nc == 0 ) {
+		return false;
+	}
+
+	for ( auto i = 0u ; i < nc - 1 ; i ++ ) {
+		if ( M_CMPSTR_( components_[ i ] , other.components_[ i ] ) != 0 ) {
+			return false;
+		}
+	}
+
+	return M_CMPSTR_( components_[ nc - 1 ] , other.components_[ nc - 1 ] ) != 0;
+}
+
+bool T_FSPath::isParentOf(
+		T_FSPath const& other ) const noexcept
+{
+	if ( &other == this ) {
+		return false;
+	}
+	if ( M_CMPSTR_( root_ , other.root_ ) != 0 ) {
+		return false;
+	}
+
+	const auto nc{ components_.size( ) };
+	if ( nc + 1 != other.components_.size( ) ) {
+		return false;
+	}
+
+	for ( auto i = 0u ; i < nc ; i ++ ) {
+		if ( M_CMPSTR_( components_[ i ] , other.components_[ i ] ) != 0 ) {
+			return false;
+		}
+	}
+	return true;
+}
+
+bool T_FSPath::isAbove(
+		T_FSPath const& other ) const noexcept
+{
+	if ( &other == this ) {
+		return false;
+	}
+	if ( M_CMPSTR_( root_ , other.root_ ) != 0 ) {
+		return false;
+	}
+
+	const auto nc{ components_.size( ) };
+	if ( nc >= other.components_.size( ) ) {
+		return false;
+	}
+
+	for ( auto i = 0u ; i < nc ; i ++ ) {
+		if ( M_CMPSTR_( components_[ i ] , other.components_[ i ] ) != 0 ) {
+			return false;
+		}
+	}
+	return true;
+}
+
+/*----------------------------------------------------------------------------*/
+
+T_FSPath T_FSPath::makeRelative(
+		T_FSPath const& relTo ) const noexcept
+{
+	assert( isValid( ) && relTo.isValid( ) );
+	assert( isCanonical( ) && relTo.isCanonical( ) );
+	if ( relTo.root_ != root_ ) {
+		return *this;
+	}
+
+	const auto nca{ components_.size( ) } ,
+		ncb{ relTo.components_.size( ) } ,
+		nc{ std::min( nca , ncb ) };
+	uint32_t nCommon{ 0u };
+	for ( auto i = 0u ; i < nc ; i ++ ) {
+		if ( M_CMPSTR_( components_[ i ] , relTo.components_[ i ] ) != 0 ) {
+			break;
+		}
+		nCommon ++;
+	}
+
+	T_FSPath np;
+	const T_String parent{ T_String::Pooled( ".." ) };
+	for ( auto i = ncb ; i > nCommon ; i -- ) {
+		np.components_.add( parent );
+	}
+	for ( auto i = nCommon ; i < nca ; i ++ ) {
+		np.components_.add( components_[ i ] );
+	}
+	return np;
+}
+
+/*----------------------------------------------------------------------------*/
+
+bool T_FSPath::isCanonical( ) const noexcept
+{
+	if ( !( valid_ && root_ ) ) {
+		return false;
+	}
+
+	auto const nc{ components_.size( ) };
+	for ( auto i = 0u ; i < nc ; i ++ ) {
+		if ( components_[ i ] == "." || components_[ i ] == ".." ) {
+			return false;
+		}
+	}
+	return true;
+}
+
+T_FSPath T_FSPath::canonical( ) const noexcept
+{
+	auto const nc{ components_.size( ) };
+	if ( !( valid_ && nc && root_ ) ) {
+		return *this;
+	}
+
+	T_FSPath np;
+	np.root_ = root_;
+	for ( auto i = 0u ; i < nc ; i ++ ) {
+		auto const& cmp{ components_[ i ] };
+		if ( cmp == ".." ) {
+			if ( np.components_.size( ) ) {
+				np.components_.removeLast( );
+			}
+		} else if ( cmp != "." ) {
+			np.components_.add( cmp );
+		}
+	}
+	return np;
+}
+
+
+/*= Filesystem ===============================================================*/
+
+T_FSPath Filesystem::Cwd( ) noexcept
+{
+	T_Buffer< char > buffer{ 256 };
+#if _WIN32
+	auto req{ buffer.bytes( ) };
+	do {
+		if ( req > buffer.bytes( ) ) {
+			buffer.resize( req + 1 );
+		}
+		req = GetCurrentDirectory( buffer.bytes( ) , &buffer[ 0 ] );
+	} while ( req > buffer.bytes( ) );
+#else
+	while ( getcwd( &buffer[ 0 ] , buffer.bytes( ) ) == nullptr ) {
+		assert( errno == ERANGE );
+		buffer.resize( buffer.size( ) + 256 );
+	}
+#endif
+
+	T_FSPath path{ &buffer[ 0 ] };
+	assert( path.isValid( ) );
+	return path;
+}
diff --git a/src/files.mk b/src/files.mk
index 28885db..7ad9b31 100644
--- a/src/files.mk
+++ b/src/files.mk
@@ -1,6 +1,7 @@
 LIB_SOURCES = \
 	src/DynLib.cc \
 	src/Files.cc \
+	src/Filesystem.cc \
 	src/HashIndex.cc \
 	src/MemoryStreams.cc \
 	src/Pointers.cc \