mirror of
https://github.com/bkaradzic/bx.git
synced 2026-02-18 13:03:06 +01:00
557 lines
9.7 KiB
C++
557 lines
9.7 KiB
C++
/*
|
|
* Copyright 2010-2018 Branimir Karadzic. All rights reserved.
|
|
* License: https://github.com/bkaradzic/bx#license-bsd-2-clause
|
|
*/
|
|
|
|
#include "bx_p.h"
|
|
#include <bx/file.h>
|
|
#include <bx/os.h>
|
|
#include <bx/readerwriter.h>
|
|
|
|
#include <stdio.h> // remove
|
|
#include <dirent.h> // opendir
|
|
|
|
#if BX_CRT_MSVC
|
|
# include <direct.h> // _getcwd
|
|
#else
|
|
# include <sys/stat.h> // mkdir
|
|
# include <unistd.h> // getcwd
|
|
#endif // BX_CRT_MSVC
|
|
|
|
#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: Lexical File Names in Plan 9 or Getting Dot-Dot Right
|
|
// 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(const char* _name, FileInfo::Enum _type, char* _out, uint32_t* _inOutSize)
|
|
{
|
|
uint32_t len = *_inOutSize;
|
|
*_out = '\0';
|
|
|
|
if (getenv(_name, _out, &len) )
|
|
{
|
|
FileInfo fi;
|
|
if (stat(_out, fi)
|
|
&& _type == fi.m_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_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;
|
|
pwd(_out, len);
|
|
*_inOutSize = strLen(_out);
|
|
return true;
|
|
}
|
|
|
|
static bool getHomePath(char* _out, uint32_t* _inOutSize)
|
|
{
|
|
return false
|
|
#if BX_PLATFORM_WINDOWS
|
|
|| getEnv("USERPROFILE", FileInfo::Directory, _out, _inOutSize)
|
|
#endif // BX_PLATFORM_WINDOWS
|
|
|| getEnv("HOME", FileInfo::Directory, _out, _inOutSize)
|
|
;
|
|
}
|
|
|
|
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 char* s_tmp[] =
|
|
{
|
|
"TMPDIR",
|
|
"TMP",
|
|
"TEMP",
|
|
"TEMPDIR",
|
|
|
|
NULL
|
|
};
|
|
|
|
for (const char** tmp = s_tmp; *tmp != NULL; ++tmp)
|
|
{
|
|
uint32_t len = *_inOutSize;
|
|
*_out = '\0';
|
|
bool ok = getEnv(*tmp, FileInfo::Directory, _out, &len);
|
|
|
|
if (ok
|
|
&& len != 0
|
|
&& len < *_inOutSize)
|
|
{
|
|
*_inOutSize = len;
|
|
return ok;
|
|
}
|
|
}
|
|
|
|
FileInfo fi;
|
|
if (stat("/tmp", fi)
|
|
&& FileInfo::Directory == fi.m_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);
|
|
}
|
|
|
|
const char* FilePath::get() const
|
|
{
|
|
return m_filePath;
|
|
}
|
|
|
|
const StringView FilePath::getPath() const
|
|
{
|
|
const char* end = strRFind(m_filePath, '/');
|
|
if (NULL != end)
|
|
{
|
|
return StringView(m_filePath, end+1);
|
|
}
|
|
|
|
return StringView();
|
|
}
|
|
|
|
const StringView FilePath::getFileName() const
|
|
{
|
|
const char* fileName = strRFind(m_filePath, '/');
|
|
if (NULL != fileName)
|
|
{
|
|
return StringView(fileName+1);
|
|
}
|
|
|
|
return get();
|
|
}
|
|
|
|
const StringView FilePath::getBaseName() const
|
|
{
|
|
const StringView fileName = getFileName();
|
|
if (!fileName.isEmpty() )
|
|
{
|
|
const char* ext = strFind(fileName, '.');
|
|
if (ext != NULL)
|
|
{
|
|
return StringView(fileName.getPtr(), ext);
|
|
}
|
|
|
|
return fileName;
|
|
}
|
|
|
|
return StringView();
|
|
}
|
|
|
|
const StringView FilePath::getExt() const
|
|
{
|
|
const StringView fileName = getFileName();
|
|
if (!fileName.isEmpty() )
|
|
{
|
|
const char* ext = strFind(fileName, '.');
|
|
return StringView(ext);
|
|
}
|
|
|
|
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, ".");
|
|
}
|
|
|
|
bool make(const FilePath& _filePath, Error* _err)
|
|
{
|
|
BX_ERROR_SCOPE(_err);
|
|
|
|
if (!_err->isOk() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#if BX_CRT_MSVC
|
|
int32_t result = ::_mkdir(_filePath.get() );
|
|
#elif BX_CRT_MINGW
|
|
int32_t result = ::mkdir(_filePath.get());
|
|
#else
|
|
int32_t result = ::mkdir(_filePath.get(), 0700);
|
|
#endif // BX_CRT_MSVC
|
|
|
|
if (0 != result)
|
|
{
|
|
BX_ERROR_SET(_err, BX_ERROR_ACCESS, "The parent directory does not allow write permission to the process.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool makeAll(const FilePath& _filePath, Error* _err)
|
|
{
|
|
BX_ERROR_SCOPE(_err);
|
|
|
|
if (!_err->isOk() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FileInfo fi;
|
|
|
|
if (stat(_filePath, fi) )
|
|
{
|
|
if (FileInfo::Directory == fi.m_type)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
BX_ERROR_SET(_err, BX_ERROR_NOT_DIRECTORY, "File already exist, and is not directory.");
|
|
return false;
|
|
}
|
|
|
|
const StringView dir = strRTrim(_filePath.get(), "/");
|
|
const char* slash = strRFind(dir, '/');
|
|
|
|
if (NULL != slash
|
|
&& slash - dir.getPtr() > 1)
|
|
{
|
|
if (!makeAll(StringView(dir.getPtr(), slash), _err) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
FilePath path(dir);
|
|
return make(path, _err);
|
|
}
|
|
|
|
bool remove(const FilePath& _filePath, Error* _err)
|
|
{
|
|
BX_ERROR_SCOPE(_err);
|
|
|
|
if (!_err->isOk() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#if BX_CRT_MSVC
|
|
int32_t result = -1;
|
|
FileInfo fi;
|
|
if (stat(_filePath, fi) )
|
|
{
|
|
if (FileInfo::Directory == fi.m_type)
|
|
{
|
|
result = ::_rmdir(_filePath.get() );
|
|
}
|
|
else
|
|
{
|
|
result = ::remove(_filePath.get() );
|
|
}
|
|
}
|
|
#else
|
|
int32_t result = ::remove(_filePath.get() );
|
|
#endif // BX_CRT_MSVC
|
|
|
|
if (0 != result)
|
|
{
|
|
BX_ERROR_SET(_err, BX_ERROR_ACCESS, "The parent directory does not allow write permission to the process.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool removeAll(const FilePath& _filePath, Error* _err)
|
|
{
|
|
BX_ERROR_SCOPE(_err);
|
|
|
|
if (remove(_filePath, _err) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
_err->reset();
|
|
|
|
FileInfo fi;
|
|
|
|
if (!stat(_filePath, fi) )
|
|
{
|
|
BX_ERROR_SET(_err, BX_ERROR_ACCESS, "The parent directory does not allow write permission to the process.");
|
|
return false;
|
|
}
|
|
|
|
if (FileInfo::Directory != fi.m_type)
|
|
{
|
|
BX_ERROR_SET(_err, BX_ERROR_NOT_DIRECTORY, "File already exist, and is not directory.");
|
|
return false;
|
|
}
|
|
|
|
#if BX_PLATFORM_WINDOWS || BX_PLATFORM_LINUX || BX_PLATFORM_OSX
|
|
DIR* dir = opendir(_filePath.get() );
|
|
if (NULL == dir)
|
|
{
|
|
BX_ERROR_SET(_err, BX_ERROR_NOT_DIRECTORY, "File already exist, and is not directory.");
|
|
return false;
|
|
}
|
|
|
|
for (dirent* item = readdir(dir); NULL != item; item = readdir(dir) )
|
|
{
|
|
if (0 == strCmp(item->d_name, ".")
|
|
|| 0 == strCmp(item->d_name, "..") )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FilePath path(_filePath);
|
|
path.join(item->d_name);
|
|
if (!removeAll(path, _err) )
|
|
{
|
|
_err->reset();
|
|
break;
|
|
}
|
|
}
|
|
|
|
closedir(dir);
|
|
#endif // BX_PLATFORM_WINDOWS || BX_PLATFORM_LINUX || BX_PLATFORM_OSX
|
|
|
|
return remove(_filePath, _err);
|
|
}
|
|
|
|
} // namespace bx
|