summaryrefslogtreecommitdiff
path: root/indra/llcommon/llfile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon/llfile.cpp')
-rw-r--r--[-rwxr-xr-x]indra/llcommon/llfile.cpp1134
1 files changed, 286 insertions, 848 deletions
diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp
index a1d41cdf73..a539e4fe28 100755..100644
--- a/indra/llcommon/llfile.cpp
+++ b/indra/llcommon/llfile.cpp
@@ -29,17 +29,22 @@
#include "linden_common.h"
#include "llfile.h"
+#include "llstring.h"
#include "llerror.h"
#include "stringize.h"
#if LL_WINDOWS
-#include <fcntl.h>
+#include "llwin32headers.h"
+#include <vector>
#else
#include <errno.h>
-#include <sys/file.h>
#endif
-// Some of the methods below use OS-level functions that mess with errno. Wrap
+using namespace std;
+
+static std::string empty;
+
+// Many of the methods below use OS-level functions that mess with errno. Wrap
// variants of strerror() to report errors.
#if LL_WINDOWS
@@ -74,7 +79,6 @@ static errentry const errtable[]
{ ERROR_CURRENT_DIRECTORY, EACCES }, // 16
{ ERROR_NOT_SAME_DEVICE, EXDEV }, // 17
{ ERROR_NO_MORE_FILES, ENOENT }, // 18
- { ERROR_SHARING_VIOLATION, EACCES }, // 32
{ ERROR_LOCK_VIOLATION, EACCES }, // 33
{ ERROR_BAD_NETPATH, ENOENT }, // 53
{ ERROR_NETWORK_ACCESS_DENIED, EACCES }, // 65
@@ -105,25 +109,22 @@ static errentry const errtable[]
{ ERROR_NOT_ENOUGH_QUOTA, ENOMEM } // 1816
};
-static int get_errno_from_oserror(int oserr)
+static int set_errno_from_oserror(unsigned long oserr)
{
if (!oserr)
return 0;
// Check the table for the Windows OS error code
- for (const struct errentry& entry : errtable)
+ for (const struct errentry &entry : errtable)
{
if (oserr == entry.oserr)
{
- return entry.errcode;
+ _set_errno(entry.errcode);
+ return -1;
}
}
- return EINVAL;
-}
-static int set_errno_from_oserror(unsigned long oserr)
-{
- _set_errno(get_errno_from_oserror(oserr));
+ _set_errno(EINVAL);
return -1;
}
@@ -135,8 +136,69 @@ std::string strerr(int errn)
return buffer;
}
-#else
+inline bool is_slash(wchar_t const c)
+{
+ return c == L'\\' || c == L'/';
+}
+static std::wstring utf8path_to_wstring(const std::string& utf8path)
+{
+ if (utf8path.size() >= MAX_PATH)
+ {
+ // By prepending "\\?\" to a path, Windows widechar file APIs will not fail on long path names
+ std::wstring utf16path = L"\\\\?\\" + ll_convert<std::wstring>(utf8path);
+ // We need to make sure that the path does not contain forward slashes as above
+ // prefix does bypass the path normalization that replaces slashes with backslashes
+ // before passing the path to kernel mode APIs
+ std::replace(utf16path.begin(), utf16path.end(), L'/', L'\\');
+ return utf16path;
+ }
+ return ll_convert<std::wstring>(utf8path);
+}
+
+static unsigned short get_fileattr(const std::wstring& utf16path, bool dontFollowSymLink = false)
+{
+ unsigned long flags = FILE_FLAG_BACKUP_SEMANTICS;
+ if (dontFollowSymLink)
+ {
+ flags |= FILE_FLAG_OPEN_REPARSE_POINT;
+ }
+ HANDLE file_handle = CreateFileW(utf16path.c_str(), FILE_READ_ATTRIBUTES,
+ FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+ nullptr, OPEN_EXISTING, flags, nullptr);
+ if (file_handle != INVALID_HANDLE_VALUE)
+ {
+ FILE_ATTRIBUTE_TAG_INFO attribute_info;
+ if (GetFileInformationByHandleEx(file_handle, FileAttributeTagInfo, &attribute_info, sizeof(attribute_info)))
+ {
+ // A volume path alone (only drive letter) is not recognized as directory while it technically is
+ bool is_directory = (attribute_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
+ (iswalpha(utf16path[0]) && utf16path[1] == ':' &&
+ (!utf16path[2] || (is_slash(utf16path[2]) && !utf16path[3])));
+ unsigned short st_mode = is_directory ? S_IFDIR :
+ (attribute_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT ? S_IFLNK : S_IFREG);
+ st_mode |= (attribute_info.FileAttributes & FILE_ATTRIBUTE_READONLY) ? S_IREAD : S_IREAD | S_IWRITE;
+ // we do not try to guess executable flag
+
+ // propagate user bits to group/other fields:
+ st_mode |= (st_mode & 0700) >> 3;
+ st_mode |= (st_mode & 0700) >> 6;
+
+ CloseHandle(file_handle);
+ return st_mode;
+ }
+ }
+ // Retrieve last error and set errno before calling CloseHandle()
+ set_errno_from_oserror(GetLastError());
+
+ if (file_handle != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(file_handle);
+ }
+ return 0;
+}
+
+#else
// On Posix we want to call strerror_r(), but alarmingly, there are two
// different variants. The one that returns int always populates the passed
// buffer (except in case of error), whereas the other one always returns a
@@ -183,50 +245,9 @@ std::string strerr(int errn)
return message_from(errn, buffer, sizeof(buffer),
strerror_r(errn, buffer, sizeof(buffer)));
}
-
#endif // ! LL_WINDOWS
-#if LL_WINDOWS && 0 // turn on to debug file-locking problems
-#define PROCESS_LOCKING_CHECK 1
-static void find_locking_process(const std::string& filename)
-{
- // Only do any of this stuff (before LL_ENDL) if it will be logged.
- LL_DEBUGS("LLFile") << "";
- // wrong way
- std::string TEMP = LLFile::tmpdir();
- if (TEMP.empty())
- {
- LL_CONT << "No $TEMP, not running 'handle'";
- }
- else
- {
- std::string tf(TEMP);
- tf += "\\handle.tmp";
- // http://technet.microsoft.com/en-us/sysinternals/bb896655
- std::string cmd(STRINGIZE("handle \"" << filename
- // "openfiles /query /v | fgrep -i \"" << filename
- << "\" > \"" << tf << '"'));
- LL_CONT << cmd;
- if (system(cmd.c_str()) != 0)
- {
- LL_CONT << "\nDownload 'handle.exe' from http://technet.microsoft.com/en-us/sysinternals/bb896655";
- }
- else
- {
- std::ifstream inf(tf);
- std::string line;
- while (std::getline(inf, line))
- {
- LL_CONT << '\n' << line;
- }
- }
- LLFile::remove(tf);
- }
- LL_CONT << LL_ENDL;
-}
-#endif // LL_WINDOWS hack to identify processes holding file open
-
-static int warnif(const std::string& desc, const std::string& filename, int rc, int suppress_warning = 0)
+static int warnif(const std::string& desc, const std::string& filename, int rc, int accept = 0)
{
if (rc < 0)
{
@@ -235,930 +256,319 @@ static int warnif(const std::string& desc, const std::string& filename, int rc,
// For certain operations, a particular errno value might be
// acceptable -- e.g. stat() could permit ENOENT, mkdir() could permit
- // EEXIST. Don't log a warning if caller explicitly says this errno is okay.
- if (errn != suppress_warning)
+ // EEXIST. Don't warn if caller explicitly says this errno is okay.
+ if (errn != accept)
{
- LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename << "' (errno " << errn << "): " << strerr(errn) << LL_ENDL;
+ LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename
+ << "' (errno " << errn << "): " << strerr(errn) << LL_ENDL;
}
-#if PROCESS_LOCKING_CHECK
+#if 0 && LL_WINDOWS // turn on to debug file-locking problems
// If the problem is "Permission denied," maybe it's because another
// process has the file open. Try to find out.
- if (errn == EACCES) // *not* EPERM
+ if (errn == EACCES) // *not* EPERM
{
- find_locking_process(filename);
+ // Only do any of this stuff (before LL_ENDL) if it will be logged.
+ LL_DEBUGS("LLFile") << empty;
+ // would be nice to use LLDir for this, but dependency goes the
+ // wrong way
+ const char* TEMP = LLFile::tmpdir();
+ if (! (TEMP && *TEMP))
+ {
+ LL_CONT << "No $TEMP, not running 'handle'";
+ }
+ else
+ {
+ std::string tf(TEMP);
+ tf += "\\handle.tmp";
+ // http://technet.microsoft.com/en-us/sysinternals/bb896655
+ std::string cmd(STRINGIZE("handle \"" << filename
+ // "openfiles /query /v | fgrep -i \"" << filename
+ << "\" > \"" << tf << '"'));
+ LL_CONT << cmd;
+ if (system(cmd.c_str()) != 0)
+ {
+ LL_CONT << "\nDownload 'handle.exe' from http://technet.microsoft.com/en-us/sysinternals/bb896655";
+ }
+ else
+ {
+ std::ifstream inf(tf);
+ std::string line;
+ while (std::getline(inf, line))
+ {
+ LL_CONT << '\n' << line;
+ }
+ }
+ LLFile::remove(tf);
+ }
+ LL_CONT << LL_ENDL;
}
-#endif
+#endif // LL_WINDOWS hack to identify processes holding file open
}
return rc;
}
-static int warnif(const std::string& desc, const std::string& filename, const std::error_code& ec, int suppress_warning = 0)
+// static
+int LLFile::mkdir(const std::string& dirname, int perms)
{
- if (ec)
- {
- // get Posix errno from the std::error_code so we can compare it to the suppress_warning parameter
- // to see when a caller wants us to not generate a warning for a particular error code
-#if LL_WINDOWS
- int errn = get_errno_from_oserror(ec.value());
-#else
- int errn = ec.value();
-#endif
- // For certain operations, a particular errno value might be acceptable
- // Don't warn if caller explicitly says this errno is okay.
- if (errn != suppress_warning)
- {
- LL_WARNS("LLFile") << "Couldn't " << desc << " '" << filename << "' (errno " << errn << "): " << ec.message() << LL_ENDL;
- }
-#if PROCESS_LOCKING_CHECK
- // Try to detect locked files by other processes
- if (ec.value() == ERROR_SHARING_VIOLATION || ec.value() == ERROR_LOCK_VIOLATION)
- {
- find_locking_process(filename);
- }
-#endif
- return -1;
- }
- return 0;
-}
-
+ // We often use mkdir() to ensure the existence of a directory that might
+ // already exist. There is no known case in which we want to call out as
+ // an error the requested directory already existing.
#if LL_WINDOWS
-
-inline int set_ec_from_system_error(std::error_code& ec, DWORD error)
-{
- ec.assign(error, std::system_category());
- return -1;
-}
-
-static int set_ec_from_system_error(std::error_code& ec)
-{
- return set_ec_from_system_error(ec, GetLastError());
-}
-
-inline int set_ec_to_parameter_error(std::error_code& ec)
-{
- return set_ec_from_system_error(ec, ERROR_INVALID_PARAMETER);
-}
-
-inline int set_ec_to_outofmemory_error(std::error_code& ec)
-{
- return set_ec_from_system_error(ec, ERROR_NOT_ENOUGH_MEMORY);
-}
-
-inline DWORD decode_access_mode(std::ios_base::openmode omode)
-{
- switch (omode & (LLFile::in | LLFile::out))
- {
- case LLFile::in:
- return GENERIC_READ;
- case LLFile::out:
- return GENERIC_WRITE;
- case LLFile::in | LLFile::out:
- return GENERIC_READ | GENERIC_WRITE;
- }
- if (omode & LLFile::app)
- {
- return GENERIC_WRITE;
- }
- return 0;
-}
-
-inline DWORD decode_open_create_flags(std::ios_base::openmode omode)
-{
- if (omode & LLFile::noreplace)
+ // permissions are ignored on Windows
+ int rc = 0;
+ std::wstring utf16dirname = utf8path_to_wstring(dirname);
+ if (!CreateDirectoryW(utf16dirname.c_str(), nullptr))
{
- return CREATE_NEW; // create if it does not exist, otherwise fail
- }
- if (omode & LLFile::trunc)
- {
- if (!(omode & LLFile::out))
+ // Only treat other errors than an already existing file as a real error
+ unsigned long oserr = GetLastError();
+ if (oserr != ERROR_ALREADY_EXISTS)
{
- return TRUNCATE_EXISTING; // open and truncate if it exists, otherwise fail
+ rc = set_errno_from_oserror(oserr);
}
- return CREATE_ALWAYS; // open and truncate if it exists, otherwise create it
- }
- if (!(omode & LLFile::out))
- {
- return OPEN_EXISTING; // open if it exists, otherwise fail
- }
- // LLFile::app or (LLFile::out and (!LLFile::trunc or !LLFile::noreplace))
- return OPEN_ALWAYS; // open if it exists, otherwise create it
-}
-
-inline DWORD decode_share_mode(int omode)
-{
- if (omode & LLFile::exclusive)
- {
- return 0; // allow no other access
- }
- if (omode & LLFile::shared)
- {
- return FILE_SHARE_READ; // allow read access
- }
- return FILE_SHARE_READ | FILE_SHARE_WRITE; // allow read and write access to others
-}
-
-inline DWORD decode_attributes(std::ios_base::openmode omode, int perm)
-{
- return (perm & S_IWRITE) ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_READONLY;
-}
-
-// Under Windows the values for the std::ios_base::seekdir constants match the according FILE_BEGIN
-// and other constants but we do a programmatic translation for now to be sure
-static DWORD seek_mode_from_dir(std::ios_base::seekdir seekdir)
-{
- switch (seekdir)
- {
- case LLFile::beg:
- return FILE_BEGIN;
- case LLFile::cur:
- return FILE_CURRENT;
- case LLFile::end:
- return FILE_END;
}
- return FILE_BEGIN;
-}
-
#else
-
-inline int set_ec_from_system_error(std::error_code& ec, int error)
-{
- ec.assign(error, std::system_category());
- return -1;
-}
-
-static int set_ec_from_system_error(std::error_code& ec)
-{
- return set_ec_from_system_error(ec, errno);
-}
-
-inline int set_ec_to_parameter_error(std::error_code& ec)
-{
- return set_ec_from_system_error(ec, EINVAL);
-}
-
-inline int set_ec_to_outofmemory_error(std::error_code& ec)
-{
- return set_ec_from_system_error(ec, ENOMEM);
-}
-
-inline int decode_access_mode(std::ios_base::openmode omode)
-{
- switch (omode & (LLFile::in | LLFile::out))
+ int rc = ::mkdir(dirname.c_str(), (mode_t)perms);
+ if (rc < 0 && errno == EEXIST)
{
- case LLFile::out:
- return O_WRONLY;
- case LLFile::in | LLFile::out:
- return O_RDWR;
- }
- return O_RDONLY;
-}
-
-inline int decode_open_mode(std::ios_base::openmode omode)
-{
- int flags = O_CREAT | decode_access_mode(omode);
- if (omode & LLFile::app)
- {
- flags |= O_APPEND;
- }
- if (omode & LLFile::trunc)
- {
- flags |= O_TRUNC;
- }
- if (omode & LLFile::binary)
- {
- // Not a thing under *nix
- }
- if (omode & LLFile::noreplace)
- {
- flags |= O_EXCL;
- }
- return flags;
-}
-
-inline int decode_lock_mode(std::ios_base::openmode omode)
-{
- int lmode = omode & LLFile::noblock ? LOCK_NB : 0;
- if (omode & LLFile::lock_mask)
- {
- if (omode & LLFile::exclusive)
- {
- return lmode | LOCK_EX;
- }
- return lmode | LOCK_SH;
- }
- return lmode | LOCK_UN;
-}
-
-// Under Linux and Mac the values for the std::ios_base::seekdir constants match the according SEEK_SET
-// and other constants but we do a programmatic translation for now to be sure
-inline int seek_mode_from_dir(std::ios_base::seekdir seekdir)
-{
- switch (seekdir)
- {
- case LLFile::beg:
- return SEEK_SET;
- case LLFile::cur:
- return SEEK_CUR;
- case LLFile::end:
- return SEEK_END;
+ // this is not the error you want, move along
+ return 0;
}
- return SEEK_SET;
-}
-
#endif
-
-inline int clear_error(std::error_code& ec)
-{
- ec.clear();
- return 0;
-}
-
-inline bool are_open_mode_flags_invalid(std::ios_base::openmode omode)
-{
- // at least one of input or output needs to be specified
- if (!(omode & (LLFile::in | LLFile::out)))
- {
- return true;
- }
- // output must be possible for any of the extra options
- if (!(omode & LLFile::out) && (omode & (LLFile::trunc | LLFile::app | LLFile::noreplace)))
- {
- return true;
- }
- // invalid combination, mutually exclusive
- if ((omode & LLFile::app) && (omode & (LLFile::trunc | LLFile::noreplace)))
- {
- return true;
- }
- return false;
+ // anything else might be a problem
+ return warnif("mkdir", dirname, rc);
}
-//----------------------------------------------------------------------------------------
-// class member functions
-//----------------------------------------------------------------------------------------
-int LLFile::open(const std::string& filename, std::ios_base::openmode omode, std::error_code& ec, int perm)
+// static
+int LLFile::rmdir(const std::string& dirname, int suppress_error)
{
- close(ec);
- if (are_open_mode_flags_invalid(omode))
- {
- return set_ec_to_parameter_error(ec);
- }
#if LL_WINDOWS
- DWORD access = decode_access_mode(omode),
- share = decode_share_mode(omode),
- create = decode_open_create_flags(omode),
- attributes = decode_attributes(omode, perm);
-
- std::wstring file_path = utf8StringToWstring(filename);
- mHandle = CreateFileW(file_path.c_str(), access, share, nullptr, create, attributes, nullptr);
- // The dwShareMode = share parameter takes care of locking the file for other processes if indicated,
- // no need to do anything else for file locking here
+ std::wstring utf16dirname = utf8path_to_wstring(dirname);
+ int rc = _wrmdir(utf16dirname.c_str());
#else
- int oflags = decode_open_mode(omode);
- int lmode = omode & LLFile::lock_mask;
- mHandle = ::open(filename.c_str(), oflags, perm);
- if (mHandle != InvalidHandle && lmode && lock(lmode | LLFile::noblock, ec) != 0)
- {
- close();
- return -1;
- }
+ int rc = ::rmdir(dirname.c_str());
#endif
- if (mHandle == InvalidHandle)
- {
- return set_ec_from_system_error(ec);
- }
-
- if (omode & LLFile::ate && seek(0, LLFile::end, ec) != 0)
- {
- close();
- return -1;
- }
- mOpen = omode;
- return clear_error(ec);
+ return warnif("rmdir", dirname, rc, suppress_error);
}
-S64 LLFile::size(std::error_code& ec)
+// static
+LLFILE* LLFile::fopen(const std::string& filename, const char* mode)
{
#if LL_WINDOWS
- LARGE_INTEGER value = { 0 };
- if (GetFileSizeEx(mHandle, &value))
- {
- clear_error(ec);
- return value.QuadPart;
- }
+ std::wstring utf16filename = utf8path_to_wstring(filename);
+ std::wstring utf16mode = ll_convert<std::wstring>(std::string(mode));
+ return _wfopen(utf16filename.c_str(), utf16mode.c_str());
#else
- struct stat statval;
- if (fstat(mHandle, &statval) == 0)
- {
- clear_error(ec);
- return statval.st_size;
- }
+ return ::fopen(filename.c_str(),mode);
#endif
- return set_ec_from_system_error(ec);
}
-S64 LLFile::tell(std::error_code& ec)
+// static
+int LLFile::close(LLFILE * file)
{
-#if LL_WINDOWS
- LARGE_INTEGER value = { 0 };
- if (SetFilePointerEx(mHandle, value, &value, FILE_CURRENT))
- {
- clear_error(ec);
- return value.QuadPart;
- }
-#else
- off_t offset = lseek(mHandle, 0, SEEK_CUR);
- if (offset != -1)
+ int ret_value = 0;
+ if (file)
{
- clear_error(ec);
- return offset;
+ ret_value = fclose(file);
}
-#endif
- return set_ec_from_system_error(ec);
-}
-
-int LLFile::seek(S64 pos, std::error_code& ec)
-{
- return seek(pos, LLFile::beg, ec);
+ return ret_value;
}
-int LLFile::seek(S64 offset, std::ios_base::seekdir dir, std::error_code& ec)
+// static
+std::string LLFile::getContents(const std::string& filename)
{
- S64 newOffset = 0;
-#if LL_WINDOWS
- DWORD seekdir = seek_mode_from_dir(dir);
- LARGE_INTEGER value;
- value.QuadPart = offset;
- if (SetFilePointerEx(mHandle, value, (PLARGE_INTEGER)&newOffset, seekdir))
-#else
- newOffset = lseek(mHandle, offset, seek_mode_from_dir(dir));
- if (newOffset != -1)
-#endif
+ LLFILE* fp = LLFile::fopen(filename, "rb");
+ if (fp)
{
- return clear_error(ec);
- }
- return set_ec_from_system_error(ec);
-}
+ fseek(fp, 0, SEEK_END);
+ U32 length = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
-#if LL_WINDOWS
-inline DWORD next_buffer_size(S64 nbytes)
-{
- return nbytes > 0x80000000 ? 0x80000000 : (DWORD)nbytes;
-}
-#endif
+ std::vector<char> buffer(length);
+ size_t nread = fread(buffer.data(), 1, length, fp);
+ fclose(fp);
-S64 LLFile::read(void* buffer, S64 nbytes, std::error_code& ec)
-{
- if (nbytes == 0)
- {
- // Nothing to do
- return clear_error(ec);
+ return std::string(buffer.data(), nread);
}
- else if (!buffer || nbytes < 0)
- {
- return set_ec_to_parameter_error(ec);
- }
-#if LL_WINDOWS
- S64 totalBytes = 0;
- char *ptr = (char*)buffer;
- DWORD bytesRead, bytesToRead = next_buffer_size(nbytes);
- // Read in chunks to support >4GB which the S64 nbytes value makes possible
- while (ReadFile(mHandle, ptr, bytesToRead, &bytesRead, nullptr))
- {
- totalBytes += bytesRead;
- if (nbytes <= totalBytes || // requested amount read
- bytesRead < bytesToRead) // ReadFile encountered eof
- {
- clear_error(ec);
- return totalBytes;
- }
- ptr += bytesRead;
- bytesToRead = next_buffer_size(nbytes - totalBytes);
- }
-#else
- ssize_t bytesRead = ::read(mHandle, buffer, nbytes);
- if (bytesRead != -1)
- {
- clear_error(ec);
- return bytesRead;
- }
-#endif
- return set_ec_from_system_error(ec);
+ return LLStringUtil::null;
}
-S64 LLFile::write(const void* buffer, S64 nbytes, std::error_code& ec)
+// static
+int LLFile::remove(const std::string& filename, int suppress_error)
{
- if (nbytes == 0)
- {
- // Nothing to do here
- return clear_error(ec);
- }
- else if (!buffer || nbytes < 0)
- {
- return set_ec_to_parameter_error(ec);
- }
#if LL_WINDOWS
- // If this was opened in append mode, we emulate it on Windows
- if (mOpen & LLFile::app && seek(0, LLFile::end, ec) != 0)
+ // Posix remove() works on both files and directories although on Windows
+ // remove() and its wide char variant _wremove() only removes files just
+ // as its siblings unlink() and _wunlink().
+ // If we really only want to support files we should instead use
+ // unlink() in the non-Windows part below too
+ int rc = -1;
+ std::wstring utf16filename = utf8path_to_wstring(filename);
+ unsigned short st_mode = get_fileattr(utf16filename);
+ if (S_ISDIR(st_mode))
{
- return -1;
+ rc = _wrmdir(utf16filename.c_str());
}
-
- S64 totalBytes = 0;
- char* ptr = (char*)buffer;
- DWORD bytesWritten, bytesToWrite = next_buffer_size(nbytes);
-
- // Write in chunks to support >4GB which the S64 nbytes value makes possible
- while (WriteFile(mHandle, ptr, bytesToWrite, &bytesWritten, nullptr))
+ else if (S_ISREG(st_mode))
{
- totalBytes += bytesWritten;
- if (nbytes <= totalBytes)
- {
- clear_error(ec);
- return totalBytes;
- }
- ptr += bytesWritten;
- bytesToWrite = next_buffer_size(nbytes - totalBytes);
+ rc = _wunlink(utf16filename.c_str());
}
-#else
- ssize_t bytesWritten = ::write(mHandle, buffer, nbytes);
- if (bytesWritten != -1)
- {
- clear_error(ec);
- return bytesWritten;
- }
-#endif
- return set_ec_from_system_error(ec);
-}
-
-S64 LLFile::printf(const char* fmt, ...)
-{
- va_list args1;
- va_start(args1, fmt);
- va_list args2;
- va_copy(args2, args1);
- int length = vsnprintf(nullptr, 0, fmt, args1);
- va_end(args1);
- if (length < 0)
+ else if (st_mode)
{
- va_end(args2);
- return -1;
- }
- void* buffer = malloc(length + 1);
- if (!buffer)
- {
- va_end(args2);
- return -1;
- }
- length = vsnprintf((char*)buffer, length + 1, fmt, args2);
- va_end(args2);
- std::error_code ec;
- S64 written = write(buffer, length, ec);
- free(buffer);
- return written;
-}
-
-int LLFile::lock(int mode, std::error_code& ec)
-{
-#if LL_WINDOWS
- if (!(mode & LLFile::lock_mask))
- {
- if (UnlockFile(mHandle, 0, 0, MAXDWORD, MAXDWORD))
- {
- return clear_error(ec);
- }
+ // it is something else than a file or directory
+ // this should not really happen as long as we do not allow for symlink
+ // detection in the optional parameter to get_fileattr()
+ rc = set_errno_from_oserror(ERROR_INVALID_PARAMETER);
}
else
{
- OVERLAPPED overlapped = { 0 };
- DWORD flags = (mode & LLFile::noblock) ? LOCKFILE_FAIL_IMMEDIATELY : 0;
- if (mode & LLFile::exclusive)
- {
- flags |= LOCKFILE_EXCLUSIVE_LOCK;
- }
- // We lock the maximum range, since flock only supports locking the entire file too
- if (LockFileEx(mHandle, flags, 0, MAXDWORD, MAXDWORD, &overlapped))
- {
- return clear_error(ec);
- }
- }
-#else
- if (flock(mHandle, decode_lock_mode(mode)) == 0)
- {
- return clear_error(ec);
+ // get_fileattr() failed and already set errno, preserve it for correct error reporting
}
-#endif
- return set_ec_from_system_error(ec);
-}
-
-int LLFile::close(std::error_code& ec)
-{
- if (mHandle != InvalidHandle)
- {
- llfile_handle_t handle = InvalidHandle;
- std::swap(handle, mHandle);
-#if LL_WINDOWS
- if (!CloseHandle(handle))
#else
- if (::close(handle))
+ int rc = ::remove(filename.c_str());
#endif
- {
- return set_ec_from_system_error(ec);
- }
- }
- return clear_error(ec);
+ return warnif("remove", filename, rc, suppress_error);
}
-int LLFile::close()
-{
- std::error_code ec;
- return close(ec);
-}
-
-//----------------------------------------------------------------------------------------
-// static member functions
-//----------------------------------------------------------------------------------------
-
// static
-LLFILE* LLFile::fopen(const std::string& filename, const char* mode, int lmode)
+int LLFile::rename(const std::string& filename, const std::string& newname, int suppress_error)
{
- LLFILE* file;
#if LL_WINDOWS
- int shflag = _SH_DENYNO;
- switch (lmode)
+ // Posix rename() will gladly overwrite a file at newname if it exists, the Windows
+ // rename(), respectively _wrename(), will bark on that. Instead call directly the Windows
+ // API MoveFileEx() and use its flags to specify that overwrite is allowed.
+ std::wstring utf16filename = utf8path_to_wstring(filename);
+ std::wstring utf16newname = utf8path_to_wstring(newname);
+ int rc = 0;
+ if (!MoveFileExW(utf16filename.c_str(), utf16newname.c_str(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
{
- case LLFile::exclusive:
- shflag = _SH_DENYRW;
- break;
- case LLFile::shared:
- shflag = _SH_DENYWR;
- break;
+ rc = set_errno_from_oserror(GetLastError());
}
- std::wstring file_path = utf8StringToWstring(filename);
- std::wstring utf16mode = ll_convert<std::wstring>(std::string(mode));
- file = _wfsopen(file_path.c_str(), utf16mode.c_str(), shflag);
#else
- file = ::fopen(filename.c_str(), mode);
- if (file && (lmode & (LLFile::lock_mask)))
- {
- // Rather fail on a sharing conflict than block
- if (flock(fileno(file), decode_lock_mode(lmode | LLFile::noblock)))
- {
- ::fclose(file);
- file = nullptr;
- }
- }
+ int rc = ::rename(filename.c_str(),newname.c_str());
#endif
- return file;
+ return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, rc, suppress_error);
}
-// static
-int LLFile::close(LLFILE* file)
-{
- int ret_value = 0;
- if (file)
- {
- ret_value = ::fclose(file);
- }
- return ret_value;
-}
-
-// static
-std::string LLFile::getContents(const std::string& filename)
-{
- std::error_code ec;
- return getContents(filename, ec);
-}
-
-// static
-std::string LLFile::getContents(const std::string& filename, std::error_code& ec)
-{
- std::string buffer;
- LLFile file(filename, LLFile::in | LLFile::binary, ec);
- if (file)
- {
- S64 length = file.size(ec);
- if (!ec && length > 0)
- {
- buffer = std::string(length, 0);
- file.read(&buffer[0], length, ec);
- if (ec)
- {
- buffer.clear();
- }
- }
- }
- return buffer;
-}
-
-// static
-int LLFile::mkdir(const std::string& dirname)
-{
- std::error_code ec;
- std::filesystem::path file_path = utf8StringToPath(dirname);
- // We often use mkdir() to ensure the existence of a directory that might
- // already exist. There is no known case in which we want to call out as
- // an error the requested directory already existing.
- std::filesystem::create_directory(file_path, ec);
- // The return value is only true if the directory was actually created.
- // But if it already existed, ec still indicates success.
- return warnif("mkdir", dirname, ec);
-}
-
-// static
-int LLFile::remove(const std::string& filename, int suppress_warning)
-{
- std::error_code ec;
- std::filesystem::path file_path = utf8StringToPath(filename);
- std::filesystem::remove(file_path, ec);
- return warnif("remove", filename, ec, suppress_warning);
-}
-
-// static
-int LLFile::rename(const std::string& filename, const std::string& newname, int suppress_warning)
-{
- std::error_code ec;
- std::filesystem::path file_path = utf8StringToPath(filename);
- std::filesystem::path new_path = utf8StringToPath(newname);
- std::filesystem::rename(file_path, new_path, ec);
- return warnif(STRINGIZE("rename to '" << newname << "' from"), filename, ec, suppress_warning);
-}
-
-// static
-S64 LLFile::read(const std::string& filename, void* buf, S64 offset, S64 nbytes)
-{
- std::error_code ec;
- return read(filename, buf, offset, nbytes, ec);
-}
+// Make this a define rather than using magic numbers multiple times in the code
+#define LLFILE_COPY_BUFFER_SIZE 16384
// static
-S64 LLFile::read(const std::string& filename, void* buf, S64 offset, S64 nbytes, std::error_code& ec)
+bool LLFile::copy(const std::string& from, const std::string& to)
{
- // if number of bytes is 0 or less there is nothing to do here
- if (nbytes <= 0)
+ bool copied = false;
+ LLFILE* in = LLFile::fopen(from, "rb");
+ if (in)
{
- clear_error(ec);
- return 0;
- }
-
- if (!buf || offset < 0)
- {
- set_ec_to_parameter_error(ec);
- }
- else
- {
- std::ios_base::openmode omode = LLFile::in | LLFile::binary;
-
- LLFile file(filename, omode, ec);
- if (!ec && (bool)file)
+ LLFILE* out = LLFile::fopen(to, "wb");
+ if (out)
{
- if (offset > 0)
- {
- file.seek(offset, ec);
- }
- // else (offset == 0) file was just opened and should already be at 0.
- if (!ec)
+ char buf[LLFILE_COPY_BUFFER_SIZE];
+ size_t readbytes;
+ bool write_ok = true;
+ while (write_ok && (readbytes = fread(buf, 1, LLFILE_COPY_BUFFER_SIZE, in)))
{
- S64 bytes_read = file.read(buf, nbytes, ec);
- if (!ec)
+ if (fwrite(buf, 1, readbytes, out) != readbytes)
{
- return bytes_read;
+ LL_WARNS("LLFile") << "Short write" << LL_ENDL;
+ write_ok = false;
}
}
- }
- }
- return warnif("read from file failed", filename, ec);
-}
-
-// static
-S64 LLFile::write(const std::string& filename, const void* buf, S64 offset, S64 nbytes)
-{
- std::error_code ec;
- return write(filename, buf, offset, nbytes, ec);
-}
-
-// static
-S64 LLFile::write(const std::string& filename, const void* buf, S64 offset, S64 nbytes, std::error_code& ec)
-{
- // if number of bytes is 0 or less there is nothing to do here
- if (nbytes <= 0)
- {
- clear_error(ec);
- return 0;
- }
-
- if (!buf)
- {
- set_ec_to_parameter_error(ec);
- }
- else
- {
- std::ios_base::openmode omode = LLFile::out | LLFile::binary;
- if (offset < 0)
- {
- omode |= LLFile::app;
- }
-
- LLFile file(filename, omode, ec);
- if (!ec && (bool)file)
- {
- if (offset > 0)
+ if ( write_ok )
{
- file.seek(offset, ec);
- }
- // else (offset == 0) we are not appending, file was just opened and should already be at 0.
- if (!ec)
- {
- S64 bytes_written = file.write(buf, nbytes, ec);
- if (!ec)
- {
- return bytes_written;
- }
+ copied = true;
}
+ fclose(out);
}
- }
- return warnif("write to file failed", filename, ec);
-}
-
-// static
-bool LLFile::copy(const std::string& source, const std::string& target)
-{
- std::error_code ec;
- return copy(source, target, std::filesystem::copy_options::overwrite_existing, ec);
-}
-
-// static
-bool LLFile::copy(const std::string& source, const std::string& target, std::filesystem::copy_options options, std::error_code& ec)
-{
- std::filesystem::path source_path = utf8StringToPath(source);
- std::filesystem::path target_path = utf8StringToPath(target);
- bool copied = std::filesystem::copy_file(source_path, target_path, options, ec);
- if (!copied)
- {
- warnif(STRINGIZE("copy failed, to '" << target << "' from"), source, ec);
+ fclose(in);
}
return copied;
}
// static
-int LLFile::stat(const std::string& filename, llstat* filestatus, const char *fname, int suppress_warning)
+int LLFile::stat(const std::string& filename, llstat* filestatus, int suppress_error)
{
#if LL_WINDOWS
- std::wstring file_path = utf8StringToWstring(filename);
- int rc = _wstat64(file_path.c_str(), filestatus);
+ std::wstring utf16filename = utf8path_to_wstring(filename);
+ int rc = _wstat64(utf16filename.c_str(), filestatus);
#else
int rc = ::stat(filename.c_str(), filestatus);
#endif
- return warnif(fname ? fname : "stat", filename, rc, suppress_warning);
+ return warnif("stat", filename, rc, suppress_error);
}
// static
-std::time_t LLFile::getCreationTime(const std::string& filename, int suppress_warning)
+unsigned short LLFile::getattr(const std::string& filename, bool dontFollowSymLink, int suppress_error)
{
- // As of C++20 there is no functionality in std::filesystem to retrieve this information
- llstat filestat;
- int rc = stat(filename, &filestat, "getCreationTime", suppress_warning);
- if (rc == 0)
+#if LL_WINDOWS
+ // _wstat64() is a bit heavyweight on Windows, use a more lightweight API
+ // to just get the attributes
+ int rc = -1;
+ std::wstring utf16filename = utf8path_to_wstring(filename);
+ unsigned short st_mode = get_fileattr(utf16filename, dontFollowSymLink);
+ if (st_mode)
{
-#if LL_DARWIN
- return filestat.st_birthtime;
-#else
- // Linux stat() doesn't have a creation/birth time (st_ctime really is the last status
- // change or inode attributes change) unless we would use statx() instead. But that is
- // a major effort, which would require Linux specific changes to LLFile::stat() above
- // and possibly adaptions for other platforms that we leave for a later exercise if it
- // is ever desired.
- return filestat.st_ctime;
-#endif
+ return st_mode;
}
- return 0;
-}
-
-// static
-std::time_t LLFile::getModificationTime(const std::string& filename, int suppress_warning)
-{
- // tried to use std::filesystem::last_write_time() but the whole std::chrono infrastructure is as of
- // C++20 still not fully implemented on all platforms. Specifically MacOS C++20 seems lacking here,
- // and Windows requires a roundabout through std::chrono::utc_clock to then get a
- // std::chrono::system_clock that can return a more useful time_t.
- // So we take the easy way out in a similar way as with getCreationTime().
- llstat filestat;
- int rc = stat(filename, &filestat, "getModificationTime", suppress_warning);
+#else
+ llstat filestatus;
+ int rc = dontFollowSymLink ? ::lstat(filename.c_str(), &filestatus) : ::stat(filename.c_str(), &filestatus);
if (rc == 0)
{
- return filestat.st_mtime;
+ return filestatus.st_mode;
}
+#endif
+ warnif("getattr", filename, rc, suppress_error);
return 0;
}
// static
-S64 LLFile::size(const std::string& filename, int suppress_warning)
-{
- std::error_code ec;
- std::filesystem::path file_path = utf8StringToPath(filename);
- std::intmax_t size = static_cast<std::intmax_t>(std::filesystem::file_size(file_path, ec));
- if (ec)
- {
- return warnif("size", filename, ec, suppress_warning);
- }
- return size;
-}
-
-// static
-std::filesystem::file_status LLFile::getStatus(const std::string& filename, bool dontFollowSymLink, int suppress_warning)
-{
- std::error_code ec;
- std::filesystem::path file_path = utf8StringToPath(filename);
- std::filesystem::file_status status;
- if (dontFollowSymLink)
- {
- status = std::filesystem::symlink_status(file_path, ec);
- }
- else
- {
- status = std::filesystem::status(file_path, ec);
- }
- warnif("getStatus()", filename, ec, suppress_warning);
- return status;
-}
-
-// static
-bool LLFile::exists(const std::string& filename)
-{
- std::filesystem::file_status status = getStatus(filename);
- return std::filesystem::exists(status);
-}
-
-// static
bool LLFile::isdir(const std::string& filename)
{
- std::filesystem::file_status status = getStatus(filename);
- return std::filesystem::is_directory(status);
+ return S_ISDIR(getattr(filename));
}
// static
bool LLFile::isfile(const std::string& filename)
{
- std::filesystem::file_status status = getStatus(filename);
- return std::filesystem::is_regular_file(status);
+ return S_ISREG(getattr(filename));
}
// static
bool LLFile::islink(const std::string& filename)
{
- std::filesystem::file_status status = getStatus(filename, true);
- return std::filesystem::is_symlink(status);
+ return S_ISLNK(getattr(filename, true));
}
// static
-const std::string& LLFile::tmpdir()
+const char *LLFile::tmpdir()
{
- static std::string temppath;
- if (temppath.empty())
- {
- temppath = std::filesystem::temp_directory_path().string();
- }
- return temppath;
-}
+ static std::string utf8path;
-// static
-std::filesystem::path LLFile::utf8StringToPath(const std::string& pathname)
-{
-#if LL_WINDOWS
- return ll_convert<std::wstring>(pathname);
-#else
- return pathname;
-#endif
-}
-
-#if LL_WINDOWS
-
-// static
-std::wstring LLFile::utf8StringToWstring(const std::string& pathname)
-{
- std::wstring utf16string(ll_convert<std::wstring>(pathname));
- if (utf16string.size() >= MAX_PATH)
+ if (utf8path.empty())
{
- // By going through std::filesystem::path we get a lot of path sanitation done for us that
- // is needed when passing a path with a kernel object space prefix to Windows API functions
- // since this prefix disables the kernel32 path normalization
- std::filesystem::path utf16path(utf16string);
+ char sep;
+#if LL_WINDOWS
+ sep = '\\';
- // By prepending "\\?\" to a path, Windows widechar file APIs will not fail on long path names
- utf16string.assign(L"\\\\?\\").append(utf16path);
+ std::vector<wchar_t> utf16path(MAX_PATH + 1);
+ GetTempPathW(static_cast<DWORD>(utf16path.size()), &utf16path[0]);
+ utf8path = ll_convert_wide_to_string(&utf16path[0]);
+#else
+ sep = '/';
- /* remove trailing spaces and dots (yes, Windows really does that) */
- size_t last_valid = utf16string.find_last_not_of(L" \t.");
- if (last_valid == std::wstring::npos)
+ utf8path = LLStringUtil::getenv("TMPDIR", "/tmp/");
+#endif
+ if (utf8path[utf8path.size() - 1] != sep)
{
- return std::wstring();
+ utf8path += sep;
}
- return utf16string.substr(0, last_valid + 1);
}
- return utf16string;
+ return utf8path.c_str();
}
+#if LL_WINDOWS
+
/************** input file stream ********************************/
llifstream::llifstream() {}
@@ -1176,8 +586,10 @@ void llifstream::open(const std::string& _Filename, ios_base::openmode _Mode)
_Mode | ios_base::in);
}
+
/************** output file stream ********************************/
+
llofstream::llofstream() {}
// explicit
@@ -1193,4 +605,30 @@ void llofstream::open(const std::string& _Filename, ios_base::openmode _Mode)
_Mode | ios_base::out);
}
+/************** helper functions ********************************/
+
+std::streamsize llifstream_size(llifstream& ifstr)
+{
+ if(!ifstr.is_open()) return 0;
+ std::streampos pos_old = ifstr.tellg();
+ ifstr.seekg(0, ios_base::beg);
+ std::streampos pos_beg = ifstr.tellg();
+ ifstr.seekg(0, ios_base::end);
+ std::streampos pos_end = ifstr.tellg();
+ ifstr.seekg(pos_old, ios_base::beg);
+ return pos_end - pos_beg;
+}
+
+std::streamsize llofstream_size(llofstream& ofstr)
+{
+ if(!ofstr.is_open()) return 0;
+ std::streampos pos_old = ofstr.tellp();
+ ofstr.seekp(0, ios_base::beg);
+ std::streampos pos_beg = ofstr.tellp();
+ ofstr.seekp(0, ios_base::end);
+ std::streampos pos_end = ofstr.tellp();
+ ofstr.seekp(pos_old, ios_base::beg);
+ return pos_end - pos_beg;
+}
+
#endif // LL_WINDOWS