/* * Copyright 2010-2019 Branimir Karadzic. All rights reserved. * License: https://github.com/bkaradzic/bx#license-bsd-2-clause */ #include "bx_p.h" #include #include #include #if !BX_CRT_NONE # if BX_CRT_MSVC # include // _getcwd # else # include // getcwd # endif // BX_CRT_MSVC #endif // !BX_CRT_NONE #if BX_PLATFORM_WINDOWS extern "C" __declspec(dllimport) unsigned long __stdcall GetTempPathA(unsigned long _max, char* _ptr); #endif // BX_PLATFORM_WINDOWS namespace bx { static bool isPathSeparator(char _ch) { return false || '/' == _ch || '\\' == _ch ; } static int32_t normalizeFilePath(char* _dst, int32_t _dstSize, const char* _src, int32_t _num) { // Reference(s): // - Lexical File Names in Plan 9 or Getting Dot-Dot Right // https://web.archive.org/web/20180629044444/https://9p.io/sys/doc/lexnames.html const int32_t num = strLen(_src, _num); if (0 == num) { return strCopy(_dst, _dstSize, "."); } int32_t size = 0; StaticMemoryBlockWriter writer(_dst, _dstSize); Error err; int32_t idx = 0; int32_t dotdot = 0; if (2 <= num && ':' == _src[1]) { size += write(&writer, toUpper(_src[idx]), &err); size += write(&writer, ':', &err); idx += 2; dotdot = size; } const int32_t slashIdx = idx; bool rooted = isPathSeparator(_src[idx]); if (rooted) { size += write(&writer, '/', &err); ++idx; dotdot = size; } bool trailingSlash = false; while (idx < num && err.isOk() ) { switch (_src[idx]) { case '/': case '\\': ++idx; trailingSlash = idx == num; break; case '.': if (idx+1 == num || isPathSeparator(_src[idx+1]) ) { ++idx; break; } if ('.' == _src[idx+1] && (idx+2 == num || isPathSeparator(_src[idx+2]) ) ) { idx += 2; if (dotdot < size) { for (--size ; dotdot < size && !isPathSeparator(_dst[size]) ; --size) { } seek(&writer, size, Whence::Begin); } else if (!rooted) { if (0 < size) { size += write(&writer, '/', &err); } size += write(&writer, "..", &err); dotdot = size; } break; } BX_FALLTHROUGH; default: if ( ( rooted && slashIdx+1 != size) || (!rooted && 0 != size) ) { size += write(&writer, '/', &err); } for (; idx < num && !isPathSeparator(_src[idx]); ++idx) { size += write(&writer, _src[idx], &err); } break; } } if (0 == size) { size += write(&writer, '.', &err); } if (trailingSlash) { size += write(&writer, '/', &err); } write(&writer, '\0', &err); return size; } static bool getEnv(char* _out, uint32_t* _inOutSize, const StringView& _name, FileType::Enum _type) { uint32_t len = *_inOutSize; *_out = '\0'; if (getEnv(_out, &len, _name) ) { FileInfo fi; if (stat(fi, _out) && _type == fi.type) { *_inOutSize = len; return true; } } return false; } static char* pwd(char* _buffer, uint32_t _size) { #if BX_PLATFORM_PS4 \ || BX_PLATFORM_XBOXONE \ || BX_PLATFORM_WINRT \ || BX_CRT_NONE BX_UNUSED(_buffer, _size); return NULL; #elif BX_CRT_MSVC return ::_getcwd(_buffer, (int32_t)_size); #else return ::getcwd(_buffer, _size); #endif // BX_COMPILER_ } static bool getCurrentPath(char* _out, uint32_t* _inOutSize) { uint32_t len = *_inOutSize; if (NULL != pwd(_out, len)) { *_inOutSize = strLen(_out); return true; } return false; } static bool getHomePath(char* _out, uint32_t* _inOutSize) { return false #if BX_PLATFORM_WINDOWS || getEnv(_out, _inOutSize, "USERPROFILE", FileType::Dir) #endif // BX_PLATFORM_WINDOWS || getEnv(_out, _inOutSize, "HOME", FileType::Dir) ; } static bool getTempPath(char* _out, uint32_t* _inOutSize) { #if BX_PLATFORM_WINDOWS uint32_t len = ::GetTempPathA(*_inOutSize, _out); bool result = len != 0 && len < *_inOutSize; *_inOutSize = len; return result; #else static const StringView s_tmp[] = { "TMPDIR", "TMP", "TEMP", "TEMPDIR", "" }; for (const StringView* tmp = s_tmp; !tmp->isEmpty(); ++tmp) { uint32_t len = *_inOutSize; *_out = '\0'; bool ok = getEnv(_out, &len, *tmp, FileType::Dir); if (ok && len != 0 && len < *_inOutSize) { *_inOutSize = len; return ok; } } FileInfo fi; if (stat(fi, "/tmp") && FileType::Dir == fi.type) { strCopy(_out, *_inOutSize, "/tmp"); *_inOutSize = 4; return true; } return false; #endif // BX_PLATFORM_* } FilePath::FilePath() { set(""); } FilePath::FilePath(Dir::Enum _dir) { set(_dir); } FilePath::FilePath(const char* _rhs) { set(_rhs); } FilePath::FilePath(const StringView& _filePath) { set(_filePath); } FilePath& FilePath::operator=(const StringView& _rhs) { set(_rhs); return *this; } void FilePath::clear() { if (!isEmpty() ) { set(""); } } void FilePath::set(Dir::Enum _dir) { char tmp[kMaxFilePath]; uint32_t len = BX_COUNTOF(tmp); switch (_dir) { case Dir::Current: getCurrentPath(tmp, &len); break; case Dir::Temp: getTempPath(tmp, &len); break; case Dir::Home: getHomePath(tmp, &len); break; default: len = 0; break; } set(StringView(tmp, len) ); } void FilePath::set(const StringView& _filePath) { normalizeFilePath( m_filePath , BX_COUNTOF(m_filePath) , _filePath.getPtr() , _filePath.getLength() ); } void FilePath::join(const StringView& _str) { char tmp[kMaxFilePath]; strCopy(tmp, BX_COUNTOF(tmp), m_filePath); strCat(tmp, BX_COUNTOF(tmp), "/"); strCat(tmp, BX_COUNTOF(tmp), _str); set(tmp); } FilePath::operator StringView() const { return StringView(m_filePath, strLen(m_filePath) ); } const char* FilePath::getCPtr() const { return m_filePath; } StringView FilePath::getPath() const { StringView end = strRFind(m_filePath, '/'); if (!end.isEmpty() ) { return StringView(m_filePath, end.getPtr()+1); } return StringView(); } StringView FilePath::getFileName() const { StringView fileName = strRFind(m_filePath, '/'); if (!fileName.isEmpty() ) { return StringView(fileName.getPtr()+1); } return getCPtr(); } StringView FilePath::getBaseName() const { const StringView fileName = getFileName(); if (!fileName.isEmpty() ) { StringView ext = strFind(fileName, '.'); if (!ext.isEmpty() ) { return StringView(fileName.getPtr(), ext.getPtr() ); } return fileName; } return StringView(); } StringView FilePath::getExt() const { const StringView fileName = getFileName(); if (!fileName.isEmpty() ) { const StringView dot = strFind(fileName, '.'); return StringView(dot.getPtr(), fileName.getTerm() ); } return StringView(); } bool FilePath::isAbsolute() const { return '/' == m_filePath[0] // no drive letter || (':' == m_filePath[1] && '/' == m_filePath[2]) // with drive letter ; } bool FilePath::isEmpty() const { return 0 == strCmp(m_filePath, "."); } } // namespace bx