mirror of
https://github.com/nzbget/nzbget.git
synced 2025-12-24 06:37:44 -05:00
1225 lines
25 KiB
C++
1225 lines
25 KiB
C++
/*
|
|
* This file is part of nzbget. See <http://nzbget.net>.
|
|
*
|
|
* Copyright (C) 2007-2017 Andrey Prygunkov <hugbug@users.sourceforge.net>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
#include "nzbget.h"
|
|
#include "FileSystem.h"
|
|
#include "Util.h"
|
|
|
|
const char* RESERVED_DEVICE_NAMES[] = { "CON", "PRN", "AUX", "NUL",
|
|
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
|
|
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", NULL };
|
|
|
|
CString FileSystem::GetLastErrorMessage()
|
|
{
|
|
BString<1024> msg;
|
|
strerror_r(errno, msg, msg.Capacity());
|
|
|
|
#ifdef WIN32
|
|
if (!errno)
|
|
{
|
|
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
msg, 1024, nullptr);
|
|
}
|
|
#endif
|
|
|
|
return *msg;
|
|
}
|
|
|
|
void FileSystem::NormalizePathSeparators(char* path)
|
|
{
|
|
for (char* p = path; *p; p++)
|
|
{
|
|
if (*p == ALT_PATH_SEPARATOR)
|
|
{
|
|
*p = PATH_SEPARATOR;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef WIN32
|
|
bool FileSystem::ForceDirectories(const char* path, CString& errmsg)
|
|
{
|
|
errmsg.Clear();
|
|
BString<1024> normPath = path;
|
|
NormalizePathSeparators(normPath);
|
|
int len = strlen(normPath);
|
|
if (len > 3 && normPath[len - 1] == PATH_SEPARATOR)
|
|
{
|
|
normPath[len - 1] = '\0';
|
|
}
|
|
|
|
if (DirectoryExists(normPath))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (FileExists(normPath))
|
|
{
|
|
errmsg.Format("path %s is not a directory", *normPath);
|
|
return false;
|
|
}
|
|
|
|
if (strlen(normPath) > 2)
|
|
{
|
|
BString<1024> parentPath = *normPath;
|
|
char* p = (char*)strrchr(parentPath, PATH_SEPARATOR);
|
|
if (p)
|
|
{
|
|
if (p - parentPath == 2 && parentPath[1] == ':' && strlen(parentPath) > 2)
|
|
{
|
|
parentPath[3] = '\0';
|
|
}
|
|
else
|
|
{
|
|
*p = '\0';
|
|
}
|
|
if (strlen(parentPath) != strlen(path) && !ForceDirectories(parentPath, errmsg))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (_wmkdir(UtfPathToWidePath(normPath)) != 0 && errno != EEXIST)
|
|
{
|
|
errmsg.Format("could not create directory %s: %s", *normPath, *GetLastErrorMessage());
|
|
return false;
|
|
}
|
|
|
|
if (DirectoryExists(normPath))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (FileExists(normPath))
|
|
{
|
|
errmsg.Format("path %s is not a directory", *normPath);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
errmsg.Format("path %s does not exist and could not be created", *normPath);
|
|
return false;
|
|
}
|
|
#else
|
|
bool FileSystem::ForceDirectories(const char* path, CString& errmsg)
|
|
{
|
|
errmsg.Clear();
|
|
BString<1024> normPath = path;
|
|
NormalizePathSeparators(normPath);
|
|
int len = strlen(normPath);
|
|
if ((len > 0) && normPath[len - 1] == PATH_SEPARATOR)
|
|
{
|
|
normPath[len - 1] = '\0';
|
|
}
|
|
|
|
struct stat buffer;
|
|
bool ok = !stat(normPath, &buffer);
|
|
if (!ok && errno != ENOENT)
|
|
{
|
|
errmsg.Format("could not read information for directory %s: errno %i, %s",
|
|
*normPath, errno, *GetLastErrorMessage());
|
|
return false;
|
|
}
|
|
|
|
if (ok && !S_ISDIR(buffer.st_mode))
|
|
{
|
|
errmsg.Format("path %s is not a directory", *normPath);
|
|
return false;
|
|
}
|
|
|
|
if (!ok)
|
|
{
|
|
BString<1024> parentPath = *normPath;
|
|
char* p = (char*)strrchr(parentPath, PATH_SEPARATOR);
|
|
if (p)
|
|
{
|
|
*p = '\0';
|
|
if (strlen(parentPath) != strlen(path) && !ForceDirectories(parentPath, errmsg))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (mkdir(normPath, S_DIRMODE) != 0 && errno != EEXIST)
|
|
{
|
|
errmsg.Format("could not create directory %s: %s", *normPath, *GetLastErrorMessage());
|
|
return false;
|
|
}
|
|
|
|
if (stat(normPath, &buffer) != 0)
|
|
{
|
|
errmsg.Format("could not read information for directory %s: %s",
|
|
*normPath, *GetLastErrorMessage());
|
|
return false;
|
|
}
|
|
|
|
if (!S_ISDIR(buffer.st_mode))
|
|
{
|
|
errmsg.Format("path %s is not a directory", *normPath);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
CString FileSystem::GetCurrentDirectory()
|
|
{
|
|
#ifdef WIN32
|
|
wchar_t unistr[1024];
|
|
::GetCurrentDirectoryW(1024, unistr);
|
|
return WidePathToUtfPath(unistr);
|
|
#else
|
|
char str[1024];
|
|
getcwd(str, 1024);
|
|
return str;
|
|
#endif
|
|
}
|
|
|
|
bool FileSystem::SetCurrentDirectory(const char* dirFilename)
|
|
{
|
|
#ifdef WIN32
|
|
return ::SetCurrentDirectoryW(UtfPathToWidePath(dirFilename));
|
|
#else
|
|
return chdir(dirFilename) == 0;
|
|
#endif
|
|
}
|
|
|
|
bool FileSystem::DirEmpty(const char* dirFilename)
|
|
{
|
|
DirBrowser dir(dirFilename);
|
|
return dir.Next() == nullptr;
|
|
}
|
|
|
|
bool FileSystem::LoadFileIntoBuffer(const char* filename, CharBuffer& buffer, bool addTrailingNull)
|
|
{
|
|
DiskFile file;
|
|
if (!file.Open(filename, DiskFile::omRead))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// obtain file size.
|
|
file.Seek(0, DiskFile::soEnd);
|
|
int size = (int)file.Position();
|
|
file.Seek(0);
|
|
|
|
// allocate memory to contain the whole file.
|
|
buffer.Reserve(size + (addTrailingNull ? 1 : 0));
|
|
|
|
// copy the file into the buffer.
|
|
file.Read(buffer, size);
|
|
file.Close();
|
|
|
|
if (addTrailingNull)
|
|
{
|
|
buffer[size] = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::SaveBufferIntoFile(const char* filename, const char* buffer, int bufLen)
|
|
{
|
|
DiskFile file;
|
|
if (!file.Open(filename, DiskFile::omWrite))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int writtenBytes = (int)file.Write(buffer, bufLen);
|
|
file.Close();
|
|
|
|
return writtenBytes == bufLen;
|
|
}
|
|
|
|
bool FileSystem::AllocateFile(const char* filename, int64 size, bool sparse, CString& errmsg)
|
|
{
|
|
errmsg.Clear();
|
|
bool ok = false;
|
|
#ifdef WIN32
|
|
HANDLE hFile = CreateFileW(UtfPathToWidePath(filename), GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_NEW, 0, nullptr);
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
errno = 0; // wanting error message from WinAPI instead of C-lib
|
|
errmsg = GetLastErrorMessage();
|
|
return false;
|
|
}
|
|
|
|
if (sparse)
|
|
{
|
|
// try to create sparse file (supported only on NTFS partitions); it may fail but that's OK.
|
|
DWORD dwBytesReturned;
|
|
DeviceIoControl(hFile, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &dwBytesReturned, nullptr);
|
|
}
|
|
|
|
LARGE_INTEGER size64;
|
|
size64.QuadPart = size;
|
|
SetFilePointerEx(hFile, size64, nullptr, FILE_END);
|
|
SetEndOfFile(hFile);
|
|
CloseHandle(hFile);
|
|
ok = true;
|
|
#else
|
|
// create file
|
|
FILE* file = fopen(filename, FOPEN_AB);
|
|
if (!file)
|
|
{
|
|
errmsg = GetLastErrorMessage();
|
|
return false;
|
|
}
|
|
fclose(file);
|
|
|
|
// there are no reliable function to expand file on POSIX, so we must try different approaches,
|
|
// starting with the fastest one and hoping it will work
|
|
// 1) set file size using function "truncate" (this is fast, if it works)
|
|
truncate(filename, size);
|
|
// check if it worked
|
|
ok = FileSize(filename) == size;
|
|
if (!ok)
|
|
{
|
|
// 2) truncate did not work, expanding the file by writing to it (that's slow)
|
|
truncate(filename, 0);
|
|
file = fopen(filename, FOPEN_AB);
|
|
if (!file)
|
|
{
|
|
errmsg = GetLastErrorMessage();
|
|
return false;
|
|
}
|
|
|
|
// write zeros in 16K chunks
|
|
CharBuffer zeros(16 * 1024);
|
|
memset(zeros, 0, zeros.Size());
|
|
for (int64 remaining = size; remaining > 0;)
|
|
{
|
|
int64 needbytes = std::min(remaining, (int64)zeros.Size());
|
|
int64 written = fwrite(zeros, 1, needbytes, file);
|
|
if (written != needbytes)
|
|
{
|
|
errmsg = GetLastErrorMessage();
|
|
fclose(file);
|
|
return false;
|
|
}
|
|
remaining -= written;
|
|
}
|
|
fclose(file);
|
|
|
|
ok = FileSize(filename) == size;
|
|
if (!ok)
|
|
{
|
|
errmsg = "created file has wrong size";
|
|
}
|
|
}
|
|
#endif
|
|
return ok;
|
|
}
|
|
|
|
bool FileSystem::TruncateFile(const char* filename, int size)
|
|
{
|
|
#ifdef WIN32
|
|
FILE* file = _wfopen(UtfPathToWidePath(filename), WString(FOPEN_RBP));
|
|
fseek(file, size, SEEK_SET);
|
|
bool ok = SetEndOfFile((HANDLE)_get_osfhandle(_fileno(file))) != 0;
|
|
fclose(file);
|
|
return ok;
|
|
#else
|
|
return truncate(filename, size) == 0;
|
|
#endif
|
|
}
|
|
|
|
char* FileSystem::BaseFileName(const char* filename)
|
|
{
|
|
char* p = (char*)strrchr(filename, PATH_SEPARATOR);
|
|
char* p1 = (char*)strrchr(filename, ALT_PATH_SEPARATOR);
|
|
if (p1)
|
|
{
|
|
if ((p && p < p1) || !p)
|
|
{
|
|
p = p1;
|
|
}
|
|
}
|
|
if (p)
|
|
{
|
|
return p + 1;
|
|
}
|
|
else
|
|
{
|
|
return (char*)filename;
|
|
}
|
|
}
|
|
|
|
bool FileSystem::ReservedChar(char ch)
|
|
{
|
|
if ((unsigned char)ch < 32)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
switch (ch)
|
|
{
|
|
case '"':
|
|
case '*':
|
|
case '/':
|
|
case ':':
|
|
case '<':
|
|
case '>':
|
|
case '?':
|
|
case '\\':
|
|
case '|':
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//replace bad chars in filename
|
|
CString FileSystem::MakeValidFilename(const char* filename, bool allowSlashes)
|
|
{
|
|
CString result = filename;
|
|
|
|
for (char* p = result; *p; p++)
|
|
{
|
|
if (ReservedChar(*p))
|
|
{
|
|
if (allowSlashes && (*p == PATH_SEPARATOR || *p == ALT_PATH_SEPARATOR))
|
|
{
|
|
*p = PATH_SEPARATOR;
|
|
continue;
|
|
}
|
|
*p = '_';
|
|
}
|
|
}
|
|
|
|
// remove trailing dots and spaces. they are not allowed in directory names on windows,
|
|
// but we remove them on posix too, in a case the directory is accessed from windows via samba.
|
|
for (int len = strlen(result); len > 0 && (result[len - 1] == '.' || result[len - 1] == ' '); len--)
|
|
{
|
|
result[len - 1] = '\0';
|
|
}
|
|
|
|
// check if the filename starts with a reserved device name.
|
|
// although these names are reserved only on Windows we adjust them on posix too,
|
|
// in a case the directory is accessed from windows via samba.
|
|
for (const char** ptr = RESERVED_DEVICE_NAMES; const char* reserved = *ptr; ptr++)
|
|
{
|
|
int len = strlen(reserved);
|
|
if (!strncasecmp(result, reserved, len) && (result[len] == '.' || result[len] == '\0'))
|
|
{
|
|
result = CString::FormatStr("_%s", *result);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
CString FileSystem::MakeUniqueFilename(const char* destDir, const char* basename)
|
|
{
|
|
CString result;
|
|
result.Format("%s%c%s", destDir, PATH_SEPARATOR, basename);
|
|
|
|
int dupeNumber = 0;
|
|
while (FileExists(result))
|
|
{
|
|
dupeNumber++;
|
|
|
|
const char* extension = strrchr(basename, '.');
|
|
if (extension && extension != basename)
|
|
{
|
|
BString<1024> filenameWithoutExt = basename;
|
|
int end = (int)(extension - basename);
|
|
filenameWithoutExt[end < 1024 ? end : 1024-1] = '\0';
|
|
|
|
if (!strcasecmp(extension, ".par2"))
|
|
{
|
|
char* volExtension = strrchr(filenameWithoutExt, '.');
|
|
if (volExtension && volExtension != filenameWithoutExt &&
|
|
!strncasecmp(volExtension, ".vol", 4))
|
|
{
|
|
*volExtension = '\0';
|
|
extension = basename + (volExtension - filenameWithoutExt);
|
|
}
|
|
}
|
|
|
|
result.Format("%s%c%s.duplicate%d%s", destDir, PATH_SEPARATOR,
|
|
*filenameWithoutExt, dupeNumber, extension);
|
|
}
|
|
else
|
|
{
|
|
result.Format("%s%c%s.duplicate%d", destDir, PATH_SEPARATOR,
|
|
basename, dupeNumber);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool FileSystem::MoveFile(const char* srcFilename, const char* dstFilename)
|
|
{
|
|
#ifdef WIN32
|
|
return _wrename(UtfPathToWidePath(srcFilename), UtfPathToWidePath(dstFilename)) == 0;
|
|
#else
|
|
bool ok = rename(srcFilename, dstFilename) == 0;
|
|
if (!ok && errno == EXDEV)
|
|
{
|
|
ok = CopyFile(srcFilename, dstFilename) && DeleteFile(srcFilename);
|
|
}
|
|
return ok;
|
|
#endif
|
|
}
|
|
|
|
bool FileSystem::CopyFile(const char* srcFilename, const char* dstFilename)
|
|
{
|
|
DiskFile infile;
|
|
if (!infile.Open(srcFilename, DiskFile::omRead))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DiskFile outfile;
|
|
if (!outfile.Open(dstFilename, DiskFile::omWrite))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CharBuffer buffer(1024 * 50);
|
|
|
|
int cnt = buffer.Size();
|
|
while (cnt == buffer.Size())
|
|
{
|
|
cnt = (int)infile.Read(buffer, buffer.Size());
|
|
outfile.Write(buffer, cnt);
|
|
}
|
|
|
|
infile.Close();
|
|
outfile.Close();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::DeleteFile(const char* filename)
|
|
{
|
|
#ifdef WIN32
|
|
return _wremove(UtfPathToWidePath(filename)) == 0;
|
|
#else
|
|
return remove(filename) == 0;
|
|
#endif
|
|
}
|
|
|
|
bool FileSystem::FileExists(const char* filename)
|
|
{
|
|
#ifdef WIN32
|
|
// we use a native windows call because c-lib function "stat" fails on windows if file date is invalid
|
|
WIN32_FIND_DATAW findData;
|
|
HANDLE handle = FindFirstFileW(UtfPathToWidePath(filename), &findData);
|
|
if (handle != INVALID_HANDLE_VALUE)
|
|
{
|
|
bool exists = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
|
|
FindClose(handle);
|
|
return exists;
|
|
}
|
|
return false;
|
|
#else
|
|
struct stat buffer;
|
|
bool exists = !stat(filename, &buffer) && S_ISREG(buffer.st_mode);
|
|
return exists;
|
|
#endif
|
|
}
|
|
|
|
bool FileSystem::DirectoryExists(const char* dirFilename)
|
|
{
|
|
#ifdef WIN32
|
|
WIN32_FIND_DATAW findData;
|
|
HANDLE handle = FindFirstFileW(UtfPathToWidePath(
|
|
BString<1024>(dirFilename && dirFilename[strlen(dirFilename) - 1] == PATH_SEPARATOR ? "%s*" : "%s\\*", dirFilename)),
|
|
&findData);
|
|
if (handle != INVALID_HANDLE_VALUE)
|
|
{
|
|
bool exists = ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) ||
|
|
(dirFilename[0] != '\0' && dirFilename[1] == ':' && (dirFilename[2] == '\0' || dirFilename[3] == '\0'));
|
|
FindClose(handle);
|
|
return exists;
|
|
}
|
|
if (GetLastError() == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
// path exists but doesn't have any file/directory entries - possible only for root paths (e. g. "C:\")
|
|
return true;
|
|
}
|
|
return false;
|
|
#else
|
|
struct stat buffer;
|
|
bool exists = !stat(dirFilename, &buffer) && S_ISDIR(buffer.st_mode);
|
|
return exists;
|
|
#endif
|
|
}
|
|
|
|
bool FileSystem::CreateDirectory(const char* dirFilename)
|
|
{
|
|
#ifdef WIN32
|
|
_wmkdir(UtfPathToWidePath(dirFilename));
|
|
#else
|
|
mkdir(dirFilename, S_DIRMODE);
|
|
#endif
|
|
return DirectoryExists(dirFilename);
|
|
}
|
|
|
|
bool FileSystem::RemoveDirectory(const char* dirFilename)
|
|
{
|
|
#ifdef WIN32
|
|
return _wrmdir(UtfPathToWidePath(dirFilename)) == 0;
|
|
#else
|
|
return rmdir(dirFilename) == 0;
|
|
#endif
|
|
}
|
|
|
|
/* Delete directory which is empty or contains only hidden files or directories (whose names start with dot) */
|
|
bool FileSystem::DeleteDirectory(const char* dirFilename)
|
|
{
|
|
if (RemoveDirectory(dirFilename))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// check if directory contains only hidden files (whose names start with dot)
|
|
{
|
|
DirBrowser dir(dirFilename);
|
|
while (const char* filename = dir.Next())
|
|
{
|
|
if (*filename != '.')
|
|
{
|
|
// calling RemoveDirectory to set correct errno
|
|
return RemoveDirectory(dirFilename);
|
|
}
|
|
}
|
|
} // make sure "DirBrowser dir" is destroyed (and has closed its handle) before we trying to delete the directory
|
|
|
|
CString errmsg;
|
|
if (!DeleteDirectoryWithContent(dirFilename, errmsg))
|
|
{
|
|
// calling RemoveDirectory to set correct errno
|
|
return RemoveDirectory(dirFilename);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileSystem::DeleteDirectoryWithContent(const char* dirFilename, CString& errmsg)
|
|
{
|
|
errmsg.Clear();
|
|
|
|
bool del = false;
|
|
bool ok = true;
|
|
|
|
{
|
|
DirBrowser dir(dirFilename);
|
|
while (const char* filename = dir.Next())
|
|
{
|
|
BString<1024> fullFilename("%s%c%s", dirFilename, PATH_SEPARATOR, filename);
|
|
|
|
if (FileSystem::DirectoryExists(fullFilename))
|
|
{
|
|
del = DeleteDirectoryWithContent(fullFilename, errmsg);
|
|
}
|
|
else
|
|
{
|
|
del = DeleteFile(fullFilename);
|
|
}
|
|
ok &= del;
|
|
if (!del && errmsg.Empty())
|
|
{
|
|
errmsg.Format("could not delete %s: %s", *fullFilename, *GetLastErrorMessage());
|
|
}
|
|
}
|
|
} // make sure "DirBrowser dir" is destroyed (and has closed its handle) before we trying to delete the directory
|
|
|
|
del = RemoveDirectory(dirFilename);
|
|
ok &= del;
|
|
if (!del && errmsg.Empty())
|
|
{
|
|
errmsg = GetLastErrorMessage();
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
int64 FileSystem::FileSize(const char* filename)
|
|
{
|
|
#ifdef WIN32
|
|
// we use a native windows call because c-lib function "stat" fails on windows if file date is invalid
|
|
WIN32_FIND_DATAW findData;
|
|
HANDLE handle = FindFirstFileW(UtfPathToWidePath(filename), &findData);
|
|
if (handle != INVALID_HANDLE_VALUE)
|
|
{
|
|
int64 size = ((int64)(findData.nFileSizeHigh) << 32) + findData.nFileSizeLow;
|
|
FindClose(handle);
|
|
return size;
|
|
}
|
|
return -1;
|
|
#else
|
|
struct stat buffer;
|
|
buffer.st_size = -1;
|
|
stat(filename, &buffer);
|
|
return buffer.st_size;
|
|
#endif
|
|
}
|
|
|
|
int64 FileSystem::FreeDiskSize(const char* path)
|
|
{
|
|
#ifdef WIN32
|
|
ULARGE_INTEGER free, dummy;
|
|
if (GetDiskFreeSpaceEx(path, &free, &dummy, &dummy))
|
|
{
|
|
return free.QuadPart;
|
|
}
|
|
#else
|
|
struct statvfs diskdata;
|
|
if (!statvfs(path, &diskdata))
|
|
{
|
|
return (int64)diskdata.f_frsize * (int64)diskdata.f_bavail;
|
|
}
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
bool FileSystem::RenameBak(const char* filename, const char* bakPart, bool removeOldExtension, CString& newName)
|
|
{
|
|
BString<1024> changedFilename;
|
|
|
|
if (removeOldExtension)
|
|
{
|
|
changedFilename = filename;
|
|
char* extension = strrchr(changedFilename, '.');
|
|
if (extension)
|
|
{
|
|
*extension = '\0';
|
|
}
|
|
}
|
|
|
|
newName.Format("%s.%s", removeOldExtension ? *changedFilename : filename, bakPart);
|
|
|
|
int i = 2;
|
|
while (FileExists(newName) || DirectoryExists(newName))
|
|
{
|
|
newName.Format("%s.%i.%s", removeOldExtension ? *changedFilename : filename, i++, bakPart);
|
|
}
|
|
|
|
return MoveFile(filename, newName);
|
|
}
|
|
|
|
#ifndef WIN32
|
|
CString FileSystem::ExpandHomePath(const char* filename)
|
|
{
|
|
CString result;
|
|
|
|
if (filename && (filename[0] == '~') && (filename[1] == '/'))
|
|
{
|
|
// expand home-dir
|
|
|
|
char* home = getenv("HOME");
|
|
if (!home)
|
|
{
|
|
struct passwd *pw = getpwuid(getuid());
|
|
if (pw)
|
|
{
|
|
home = pw->pw_dir;
|
|
}
|
|
}
|
|
|
|
if (!home)
|
|
{
|
|
return filename;
|
|
}
|
|
|
|
if (home[strlen(home)-1] == '/')
|
|
{
|
|
result.Format("%s%s", home, filename + 2);
|
|
}
|
|
else
|
|
{
|
|
result.Format("%s/%s", home, filename + 2);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.Append(filename ? filename : "");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
CString FileSystem::ExpandFileName(const char* filename)
|
|
{
|
|
#ifdef WIN32
|
|
wchar_t unistr[1024];
|
|
_wfullpath(unistr, UtfPathToWidePath(filename), 1024);
|
|
return WidePathToUtfPath(unistr);
|
|
#else
|
|
CString result;
|
|
result.Reserve(1024 - 1);
|
|
if (filename[0] != '\0' && filename[0] != '/')
|
|
{
|
|
char curDir[MAX_PATH + 1];
|
|
getcwd(curDir, sizeof(curDir) - 1); // 1 char reserved for adding backslash
|
|
int offset = 0;
|
|
if (filename[0] == '.' && filename[1] == '/')
|
|
{
|
|
offset += 2;
|
|
}
|
|
result.Format("%s/%s", curDir, filename + offset);
|
|
}
|
|
else
|
|
{
|
|
result = filename;
|
|
}
|
|
return result;
|
|
#endif
|
|
}
|
|
|
|
CString FileSystem::GetExeFileName(const char* argv0)
|
|
{
|
|
CString exename;
|
|
exename.Reserve(1024 - 1);
|
|
exename[1024 - 1] = '\0';
|
|
|
|
#ifdef WIN32
|
|
GetModuleFileName(nullptr, exename, 1024);
|
|
#else
|
|
// Linux
|
|
int r = readlink("/proc/self/exe", exename, 1024 - 1);
|
|
if (r > 0)
|
|
{
|
|
exename[r] = '\0';
|
|
return exename;
|
|
}
|
|
// FreeBSD
|
|
r = readlink("/proc/curproc/file", exename, 1024 - 1);
|
|
if (r > 0)
|
|
{
|
|
exename[r] = '\0';
|
|
return exename;
|
|
}
|
|
|
|
exename = ExpandFileName(argv0);
|
|
#endif
|
|
|
|
return exename;
|
|
}
|
|
|
|
bool FileSystem::SameFilename(const char* filename1, const char* filename2)
|
|
{
|
|
#ifdef WIN32
|
|
return strcasecmp(filename1, filename2) == 0;
|
|
#else
|
|
return strcmp(filename1, filename2) == 0;
|
|
#endif
|
|
}
|
|
|
|
#ifdef WIN32
|
|
CString FileSystem::MakeCanonicalPath(const char* path)
|
|
{
|
|
int len = strlen(path);
|
|
|
|
if (!strncmp("\\\\?\\", path, 4) || len == 0)
|
|
{
|
|
return path;
|
|
}
|
|
|
|
std::vector<CString> components = Util::SplitStr(path, "\\/");
|
|
for (uint32 i = 1; i < components.size(); i++)
|
|
{
|
|
if (!strcmp(components[i], ".."))
|
|
{
|
|
components.erase(components.begin() + i - 1, components.begin() + i + 1);
|
|
i -= 2;
|
|
}
|
|
else if (!strcmp(components[i], "."))
|
|
{
|
|
components.erase(components.begin() + i);
|
|
i--;
|
|
}
|
|
}
|
|
|
|
StringBuilder result;
|
|
result.Reserve(strlen(path));
|
|
|
|
if (!strncmp("\\\\", path, 2))
|
|
{
|
|
result.Append("\\\\");
|
|
}
|
|
|
|
bool first = true;
|
|
for (CString& comp : components)
|
|
{
|
|
if (comp.Length() > 0)
|
|
{
|
|
if (!first)
|
|
{
|
|
result.Append("\\");
|
|
}
|
|
result.Append(comp);
|
|
first = false;
|
|
}
|
|
}
|
|
|
|
if ((path[len - 1] == '\\' || path[len - 1] == '/' ||
|
|
(len > 3 && !strcmp(path + len - 3, "\\..")) ||
|
|
(len > 2 && !strcmp(path + len - 2, "\\.")))
|
|
&&
|
|
result[result.Length() - 1] != '\\')
|
|
{
|
|
result.Append("\\");
|
|
}
|
|
|
|
return *result;
|
|
}
|
|
#endif
|
|
|
|
bool FileSystem::FlushFileBuffers(int fileDescriptor, CString& errmsg)
|
|
{
|
|
#ifdef WIN32
|
|
BOOL ok = ::FlushFileBuffers((HANDLE)_get_osfhandle(fileDescriptor));
|
|
if (!ok)
|
|
{
|
|
errno = 0; // wanting error message from WinAPI instead of C-lib
|
|
errmsg = GetLastErrorMessage();
|
|
}
|
|
return ok;
|
|
#else
|
|
#ifdef HAVE_FULLFSYNC
|
|
int ret = fcntl(fileDescriptor, F_FULLFSYNC) == -1 ? 1 : 0;
|
|
#elif HAVE_FDATASYNC
|
|
int ret = fdatasync(fileDescriptor);
|
|
#else
|
|
int ret = fsync(fileDescriptor);
|
|
#endif
|
|
if (ret != 0)
|
|
{
|
|
errmsg = GetLastErrorMessage();
|
|
}
|
|
return ret == 0;
|
|
#endif
|
|
}
|
|
|
|
bool FileSystem::FlushDirBuffers(const char* filename, CString& errmsg)
|
|
{
|
|
#ifdef WIN32
|
|
FILE* file = _wfopen(UtfPathToWidePath(filename), WString(FOPEN_RBP));
|
|
#else
|
|
BString<1024> parentPath = filename;
|
|
char* p = (char*)strrchr(parentPath, PATH_SEPARATOR);
|
|
if (p)
|
|
{
|
|
*p = '\0';
|
|
}
|
|
FILE* file = fopen(parentPath, FOPEN_RB);
|
|
#endif
|
|
|
|
if (!file)
|
|
{
|
|
errmsg = GetLastErrorMessage();
|
|
return false;
|
|
}
|
|
bool ok = FlushFileBuffers(fileno(file), errmsg);
|
|
fclose(file);
|
|
return ok;
|
|
}
|
|
|
|
#ifndef WIN32
|
|
void FileSystem::FixExecPermission(const char* filename)
|
|
{
|
|
struct stat buffer;
|
|
bool ok = !stat(filename, &buffer);
|
|
if (ok)
|
|
{
|
|
buffer.st_mode = buffer.st_mode | S_IXUSR | S_IXGRP | S_IXOTH;
|
|
chmod(filename, buffer.st_mode);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef WIN32
|
|
bool FileSystem::NeedLongPath(const char* path)
|
|
{
|
|
bool alreadyLongPath = !strncmp(path, "\\\\?\\", 4);
|
|
if (alreadyLongPath)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (strlen(path) > 260 - 14)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
Tokenizer tok(path, "\\/");
|
|
for (int partNo = 0; const char* part = tok.Next(); partNo++)
|
|
{
|
|
// check if the file part starts with a reserved device name
|
|
for (const char** ptr = RESERVED_DEVICE_NAMES; const char* reserved = *ptr; ptr++)
|
|
{
|
|
int len = strlen(reserved);
|
|
if (!strncasecmp(part, reserved, len) && (part[len] == '.' || part[len] == '\0'))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// check if the file part contains reserved characters
|
|
for (const char* p = part; *p; p++)
|
|
{
|
|
if (ReservedChar(*p) && !(partNo == 0 && p == part + 1 && *p == ':'))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
CString FileSystem::MakeExtendedPath(const char* path, bool force)
|
|
{
|
|
#ifdef WIN32
|
|
if (force || NeedLongPath(path))
|
|
{
|
|
CString canonicalPath = MakeCanonicalPath(path);
|
|
BString<1024> longpath;
|
|
if (canonicalPath[0] == '\\' && canonicalPath[1] == '\\')
|
|
{
|
|
// UNC path
|
|
longpath.Format("\\\\?\\UNC\\%s", canonicalPath + 2);
|
|
}
|
|
else
|
|
{
|
|
// local path
|
|
longpath.Format("\\\\?\\%s", canonicalPath);
|
|
}
|
|
return *longpath;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
return path;
|
|
}
|
|
}
|
|
|
|
#ifdef WIN32
|
|
WString FileSystem::UtfPathToWidePath(const char* utfpath)
|
|
{
|
|
return *FileSystem::MakeExtendedPath(utfpath, false);
|
|
}
|
|
|
|
CString FileSystem::WidePathToUtfPath(const wchar_t* wpath)
|
|
{
|
|
char utfstr[1024];
|
|
int copied = WideCharToMultiByte(CP_UTF8, 0, wpath, -1, utfstr, 1024, nullptr, nullptr);
|
|
return utfstr;
|
|
}
|
|
#endif
|
|
|
|
|
|
DirBrowser::DirBrowser(const char* path, bool snapshot) :
|
|
m_snapshot(snapshot)
|
|
{
|
|
if (m_snapshot)
|
|
{
|
|
DirBrowser dir(path, false);
|
|
while (const char* filename = dir.Next())
|
|
{
|
|
m_snapshotFiles.emplace_back(filename);
|
|
}
|
|
m_snapshotIter = m_snapshotFiles.begin();
|
|
}
|
|
else
|
|
{
|
|
#ifdef WIN32
|
|
BString<1024> mask("%s%c*.*", path, PATH_SEPARATOR);
|
|
m_file = FindFirstFileW(FileSystem::UtfPathToWidePath(mask), &m_findData);
|
|
m_first = true;
|
|
#else
|
|
m_dir = opendir(path);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
DirBrowser::~DirBrowser()
|
|
{
|
|
#ifdef WIN32
|
|
if (m_file != INVALID_HANDLE_VALUE)
|
|
{
|
|
FindClose(m_file);
|
|
}
|
|
#else
|
|
if (m_dir)
|
|
{
|
|
closedir(m_dir);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
const char* DirBrowser::InternNext()
|
|
{
|
|
if (m_snapshot)
|
|
{
|
|
return m_snapshotIter == m_snapshotFiles.end() ? nullptr : **m_snapshotIter++;
|
|
}
|
|
else
|
|
{
|
|
#ifdef WIN32
|
|
bool ok = false;
|
|
if (m_first)
|
|
{
|
|
ok = m_file != INVALID_HANDLE_VALUE;
|
|
m_first = false;
|
|
}
|
|
else
|
|
{
|
|
ok = FindNextFileW(m_file, &m_findData) != 0;
|
|
}
|
|
if (ok)
|
|
{
|
|
m_filename = FileSystem::WidePathToUtfPath(m_findData.cFileName);
|
|
return m_filename;
|
|
}
|
|
#else
|
|
if (m_dir)
|
|
{
|
|
m_findData = readdir(m_dir);
|
|
if (m_findData)
|
|
{
|
|
return m_findData->d_name;
|
|
}
|
|
}
|
|
#endif
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
const char* DirBrowser::Next()
|
|
{
|
|
const char* filename = nullptr;
|
|
for (filename = InternNext(); filename && (!strcmp(filename, ".") || !strcmp(filename, "..")); )
|
|
{
|
|
filename = InternNext();
|
|
}
|
|
return filename;
|
|
}
|
|
|
|
|
|
DiskFile::~DiskFile()
|
|
{
|
|
if (m_file)
|
|
{
|
|
Close();
|
|
}
|
|
}
|
|
|
|
bool DiskFile::Open(const char* filename, EOpenMode mode)
|
|
{
|
|
const char* strmode = mode == omRead ? FOPEN_RB : mode == omReadWrite ?
|
|
FOPEN_RBP : mode == omWrite ? FOPEN_WB : FOPEN_AB;
|
|
#ifdef WIN32
|
|
m_file = _wfopen(FileSystem::UtfPathToWidePath(filename), WString(strmode));
|
|
#else
|
|
m_file = fopen(filename, strmode);
|
|
#endif
|
|
return m_file;
|
|
}
|
|
|
|
bool DiskFile::Close()
|
|
{
|
|
if (m_file)
|
|
{
|
|
int ret = fclose(m_file);
|
|
m_file = nullptr;
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int64 DiskFile::Read(void* buffer, int64 size)
|
|
{
|
|
return fread(buffer, 1, (size_t)size, m_file);
|
|
}
|
|
|
|
int64 DiskFile::Write(const void* buffer, int64 size)
|
|
{
|
|
return fwrite(buffer, 1, (size_t)size, m_file);
|
|
}
|
|
|
|
int64 DiskFile::Print(const char* format, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, format);
|
|
int ret = vfprintf(m_file, format, ap);
|
|
va_end(ap);
|
|
return ret;
|
|
}
|
|
|
|
char* DiskFile::ReadLine(char* buffer, int64 size)
|
|
{
|
|
return fgets(buffer, (int)size, m_file);
|
|
}
|
|
|
|
int64 DiskFile::Position()
|
|
{
|
|
return ftell(m_file);
|
|
}
|
|
|
|
bool DiskFile::Seek(int64 position, ESeekOrigin origin)
|
|
{
|
|
return fseek(m_file, position,
|
|
origin == soCur ? SEEK_CUR :
|
|
origin == soEnd ? SEEK_END : SEEK_SET) == 0;
|
|
}
|
|
|
|
bool DiskFile::Eof()
|
|
{
|
|
return feof(m_file) != 0;
|
|
}
|
|
|
|
bool DiskFile::Error()
|
|
{
|
|
return ferror(m_file) != 0;
|
|
}
|
|
|
|
bool DiskFile::SetWriteBuffer(int size)
|
|
{
|
|
return setvbuf(m_file, nullptr, _IOFBF, size) == 0;
|
|
}
|
|
|
|
bool DiskFile::Flush()
|
|
{
|
|
return fflush(m_file) == 0;
|
|
}
|
|
|
|
bool DiskFile::Sync(CString& errmsg)
|
|
{
|
|
return FileSystem::FlushFileBuffers(fileno(m_file), errmsg);
|
|
}
|
|
|