text: // license:BSD-3-Clause
  // copyright-holders:Olivier Galibert, R. Belmont, Vas Crabb
  //============================================================
  //
  //  sdldir.c - SDL core directory access functions
  //
  //  SDLMAME by Olivier Galibert and R. Belmont
  //
  //============================================================
  
  
  #ifndef _LARGEFILE64_SOURCE
  #define _LARGEFILE64_SOURCE
  #endif
  
  #ifdef __linux__
  #ifndef __USE_LARGEFILE64
  #define __USE_LARGEFILE64
  #endif
  #ifndef __USE_BSD
  #define __USE_BSD
  #endif
  #endif
  
  #ifdef WIN32
  #define _FILE_OFFSET_BITS 64
  #endif
  
  #if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__bsdi__) && !defined(__DragonFly__)
  #ifdef _XOPEN_SOURCE
  #if _XOPEN_SOURCE < 500
  #undef _XOPEN_SOURCE
  #endif
  #endif
  #ifndef _XOPEN_SOURCE
  #define _XOPEN_SOURCE 500
  #endif
  #endif
  
  #define _DARWIN_C_SOURCE  // to get DT_xxx on OS X
  
  #include "osdcore.h"
  #include "modules/lib/osdlib.h"
  #include "util/strformat.h"
  
  #include <cctype>
  #include <cstdlib>
  #include <cstring>
  #include <memory>
  #include <string>
  #include <utility>
  
  #include <dirent.h>
  #include <sys/stat.h>
  #include <sys/types.h>
  
  
  
  namespace osd {
  namespace {
  //============================================================
  //  CONSTANTS
  //============================================================
  
  #if defined(WIN32)
  constexpr char PATHSEPCH = '\\';
  #else
  constexpr char PATHSEPCH = '/';
  #endif
  
  #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) || defined(EMSCRIPTEN) || defined(__ANDROID__) || defined(WIN32) || defined(SDLMAME_NO64BITIO)
  using sdl_dirent = struct dirent;
  using sdl_stat = struct stat;
  #define sdl_readdir readdir
  #define sdl_stat_fn stat
  #else
  using sdl_dirent = struct dirent64;
  using sdl_stat = struct stat64;
  #define sdl_readdir readdir64
  #define sdl_stat_fn stat64
  #endif
  
  #if (defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__))
  #define HAS_DT_XXX 1
  #else
  #define HAS_DT_XXX 0
  #endif
  
  
  
  //============================================================
  //  TYPE DEFINITIONS
  //============================================================
  
  class posix_directory : public osd::directory
  {
  public:
  	posix_directory();
  	virtual ~posix_directory() override;
  
  	virtual const entry *read() override;
  
  	bool open_impl(std::string const &dirname);
  
  private:
  	typedef std::unique_ptr<DIR, int (*)(DIR *)>    dir_ptr;
  
  	entry       m_entry;
  	sdl_dirent  *m_data;
  	dir_ptr     m_fd;
  	std::string m_path;
  };
  
  
  //============================================================
  //  posix_directory::~posix_directory
  //============================================================
  
  posix_directory::posix_directory()
  	: m_entry()
  	, m_data(nullptr)
  	, m_fd(nullptr, &::closedir)
  	, m_path()
  {
  }
  
  
  //============================================================
  //  posix_directory::~posix_directory
  //============================================================
  
  posix_directory::~posix_directory()
  {
  }
  
  
  //============================================================
  //  posix_directory::read
  //============================================================
  
  const osd::directory::entry *posix_directory::read()
  {
  	m_data = sdl_readdir(m_fd.get());
  	if (!m_data)
  		return nullptr;
  
  	m_entry.name = m_data->d_name;
  
  	sdl_stat st;
  	bool stat_err(0 > sdl_stat_fn(util::string_format("%s%c%s", m_path, PATHSEPCH, m_data->d_name).c_str(), &st));
  
  #if HAS_DT_XXX
  	switch (m_data->d_type)
  	{
  	case DT_DIR:
  		m_entry.type = entry::entry_type::DIR;
  		break;
  	case DT_REG:
  		m_entry.type = entry::entry_type::FILE;
  		break;
  	case DT_UNKNOWN:
  	case DT_LNK:
  		if (stat_err)
  			m_entry.type = entry::entry_type::OTHER;
  		else if (S_ISDIR(st.st_mode))
  			m_entry.type = entry::entry_type::DIR;
  		else
  			m_entry.type = entry::entry_type::FILE;
  		break;
  	default:
  		m_entry.type = entry::entry_type::OTHER;
  	}
  #else
  	if (stat_err)
  		m_entry.type = entry::entry_type::OTHER;
  	else if (S_ISDIR(st.st_mode))
  		m_entry.type = entry::entry_type::DIR;
  	else
  		m_entry.type = entry::entry_type::FILE;
  #endif
  	m_entry.size = stat_err ? 0 : std::uint64_t(std::make_unsigned_t<decltype(st.st_size)>(st.st_size));
  	m_entry.last_modified = std::chrono::system_clock::from_time_t(st.st_mtime);
  	return &m_entry;
  }
  
  
  //============================================================
  //  posix_directory::open_impl
  //============================================================
  
  bool posix_directory::open_impl(std::string const &dirname)
  {
  	assert(!m_fd);
  
  	osd_subst_env(m_path, dirname);
  	m_fd.reset(::opendir(m_path.c_str()));
  	return bool(m_fd);
  }
  
  } // anonymous namespace
  
  
  //============================================================
  //  osd::directory::open
  //============================================================
  
  directory::ptr directory::open(std::string const &dirname)
  {
  	std::unique_ptr<posix_directory> dir;
  	try { dir.reset(new posix_directory); }
  	catch (...) { return nullptr; }
  
  	if (!dir->open_impl(dirname))
  		return nullptr;
  
  	return ptr(std::move(dir));
  }
  
  } // namespace osd
  
  
  //============================================================
  //  osd_subst_env
  //============================================================
  
  void osd_subst_env(std::string &dst, std::string const &src)
  {
  	std::string result, var;
  	auto start = src.begin();
  
  	// a leading tilde expands as $HOME
  	if ((src.end() != start) && ('~' == *start))
  	{
  		char const *const home = std::getenv("HOME");
  		if (home)
  		{
  			++start;
  			if ((src.end() == start) || (osd::PATHSEPCH == *start))
  				result.append(home);
  			else
  				result.push_back('~');
  		}
  	}
  
  	while (src.end() != start)
  	{
  		// find $ marking start of environment variable or end of string
  		auto it = start;
  		while ((src.end() != it) && ('$' != *it)) ++it;
  		if (start != it) result.append(start, it);
  		start = it;
  
  		if (src.end() != start)
  		{
  			start = ++it;
  			if ((src.end() != start) && ('{' == *start))
  			{
  				start = ++it;
  				for (++it; (src.end() != it) && ('}' != *it); ++it) { }
  				if (src.end() == it)
  				{
  					result.append("${").append(start, it);
  					start = it;
  				}
  				else
  				{
  					var.assign(start, it);
  					start = ++it;
  					const char *const exp = std::getenv(var.c_str());
  					if (exp)
  						result.append(exp);
  					else
  						fprintf(stderr, "Warning: osd_subst_env variable %s not found.\n", var.c_str());
  				}
  			}
  			else if ((src.end() != start) && (('_' == *start) || std::isalnum(*start)))
  			{
  				for (++it; (src.end() != it) && (('_' == *it) || std::isalnum(*it)); ++it) { }
  				var.assign(start, it);
  				start = it;
  				const char *const exp = std::getenv(var.c_str());
  				if (exp)
  					result.append(exp);
  				else
  					fprintf(stderr, "Warning: osd_subst_env variable %s not found.\n", var.c_str());
  			}
  			else
  			{
  				result.push_back('$');
  			}
  		}
  	}
  
  	dst = std::move(result);
  }
  