763 lines
22 KiB
C++
763 lines
22 KiB
C++
/*
|
|
* Copyright (C) 2007-2017 Apple Inc. All rights reserved.
|
|
* Copyright (C) 2015 Canon Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <wtf/FileSystem.h>
|
|
|
|
#include <wtf/CryptographicallyRandomNumber.h>
|
|
#include <wtf/HexNumber.h>
|
|
#include <wtf/Scope.h>
|
|
#include <wtf/text/CString.h>
|
|
#include <wtf/text/StringBuilder.h>
|
|
|
|
#if !OS(WINDOWS)
|
|
#include <sys/mman.h>
|
|
#include <sys/param.h>
|
|
#include <sys/stat.h>
|
|
#endif
|
|
|
|
#if PLATFORM(HAIKU)
|
|
#define MAP_FILE 0
|
|
#endif
|
|
|
|
#if USE(GLIB)
|
|
#include <gio/gfiledescriptorbased.h>
|
|
#include <gio/gio.h>
|
|
#endif
|
|
|
|
#if HAVE(STD_FILESYSTEM) || HAVE(STD_EXPERIMENTAL_FILESYSTEM)
|
|
#include <wtf/StdFilesystem.h>
|
|
#endif
|
|
|
|
namespace WTF {
|
|
|
|
namespace FileSystemImpl {
|
|
|
|
#if HAVE(STD_FILESYSTEM) || HAVE(STD_EXPERIMENTAL_FILESYSTEM)
|
|
|
|
static std::filesystem::path toStdFileSystemPath(StringView path)
|
|
{
|
|
return std::filesystem::u8path(path.utf8().data());
|
|
}
|
|
|
|
static String fromStdFileSystemPath(const std::filesystem::path& path)
|
|
{
|
|
return String::fromUTF8(path.u8string().c_str());
|
|
}
|
|
|
|
#endif // HAVE(STD_FILESYSTEM) || HAVE(STD_EXPERIMENTAL_FILESYSTEM)
|
|
|
|
// The following lower-ASCII characters need escaping to be used in a filename
|
|
// across all systems, including Windows:
|
|
// - Unprintable ASCII (00-1F)
|
|
// - Space (20)
|
|
// - Double quote (22)
|
|
// - Percent (25) (escaped because it is our escape character)
|
|
// - Asterisk (2A)
|
|
// - Slash (2F)
|
|
// - Colon (3A)
|
|
// - Less-than (3C)
|
|
// - Greater-than (3E)
|
|
// - Question Mark (3F)
|
|
// - Backslash (5C)
|
|
// - Pipe (7C)
|
|
// - Delete (7F)
|
|
|
|
static const bool needsEscaping[128] = {
|
|
true, true, true, true, true, true, true, true, /* 00-07 */
|
|
true, true, true, true, true, true, true, true, /* 08-0F */
|
|
|
|
true, true, true, true, true, true, true, true, /* 10-17 */
|
|
true, true, true, true, true, true, true, true, /* 18-1F */
|
|
|
|
true, false, true, false, false, true, false, false, /* 20-27 */
|
|
false, false, true, false, false, false, false, true, /* 28-2F */
|
|
|
|
false, false, false, false, false, false, false, false, /* 30-37 */
|
|
false, false, true, false, true, false, true, true, /* 38-3F */
|
|
|
|
false, false, false, false, false, false, false, false, /* 40-47 */
|
|
false, false, false, false, false, false, false, false, /* 48-4F */
|
|
|
|
false, false, false, false, false, false, false, false, /* 50-57 */
|
|
false, false, false, false, true, false, false, false, /* 58-5F */
|
|
|
|
false, false, false, false, false, false, false, false, /* 60-67 */
|
|
false, false, false, false, false, false, false, false, /* 68-6F */
|
|
|
|
false, false, false, false, false, false, false, false, /* 70-77 */
|
|
false, false, false, false, true, false, false, true, /* 78-7F */
|
|
};
|
|
|
|
static inline bool shouldEscapeUChar(UChar character, UChar previousCharacter, UChar nextCharacter)
|
|
{
|
|
if (character <= 127)
|
|
return needsEscaping[character];
|
|
|
|
if (U16_IS_LEAD(character) && !U16_IS_TRAIL(nextCharacter))
|
|
return true;
|
|
|
|
if (U16_IS_TRAIL(character) && !U16_IS_LEAD(previousCharacter))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
#if HAVE(STD_FILESYSTEM) || HAVE(STD_EXPERIMENTAL_FILESYSTEM)
|
|
|
|
template<typename ClockType, typename = void> struct has_to_time_t : std::false_type { };
|
|
template<typename ClockType> struct has_to_time_t<ClockType, std::void_t<
|
|
std::enable_if_t<std::is_same_v<std::time_t, decltype(ClockType::to_time_t(std::filesystem::file_time_type()))>>
|
|
>> : std::true_type { };
|
|
|
|
template <typename FileTimeType>
|
|
typename std::enable_if_t<has_to_time_t<typename FileTimeType::clock>::value, std::time_t> toTimeT(FileTimeType fileTime)
|
|
{
|
|
return decltype(fileTime)::clock::to_time_t(fileTime);
|
|
}
|
|
|
|
template <typename FileTimeType>
|
|
typename std::enable_if_t<!has_to_time_t<typename FileTimeType::clock>::value, std::time_t> toTimeT(FileTimeType fileTime)
|
|
{
|
|
return std::chrono::system_clock::to_time_t(std::chrono::time_point_cast<std::chrono::system_clock::duration>(fileTime - decltype(fileTime)::clock::now() + std::chrono::system_clock::now()));
|
|
}
|
|
|
|
static WallTime toWallTime(std::filesystem::file_time_type fileTime)
|
|
{
|
|
// FIXME: Use std::chrono::file_clock::to_sys() once we can use C++20.
|
|
return WallTime::fromRawSeconds(toTimeT(fileTime));
|
|
}
|
|
|
|
#endif // HAVE(STD_FILESYSTEM) || HAVE(STD_EXPERIMENTAL_FILESYSTEM)
|
|
|
|
String encodeForFileName(const String& inputString)
|
|
{
|
|
unsigned length = inputString.length();
|
|
if (!length)
|
|
return inputString;
|
|
|
|
StringBuilder result;
|
|
result.reserveCapacity(length);
|
|
|
|
UChar previousCharacter = 0;
|
|
UChar nextCharacter = inputString[0];
|
|
for (unsigned i = 0; i < length; ++i) {
|
|
auto character = std::exchange(nextCharacter, i + 1 < length ? inputString[i + 1] : 0);
|
|
if (shouldEscapeUChar(character, previousCharacter, nextCharacter)) {
|
|
if (character <= 0xFF)
|
|
result.append('%', hex(character, 2));
|
|
else
|
|
result.append("%+", hex(static_cast<uint8_t>(character >> 8), 2), hex(static_cast<uint8_t>(character), 2));
|
|
} else
|
|
result.append(character);
|
|
previousCharacter = character;
|
|
}
|
|
|
|
return result.toString();
|
|
}
|
|
|
|
String decodeFromFilename(const String& inputString)
|
|
{
|
|
unsigned length = inputString.length();
|
|
if (!length)
|
|
return inputString;
|
|
|
|
StringBuilder result;
|
|
result.reserveCapacity(length);
|
|
|
|
for (unsigned i = 0; i < length; ++i) {
|
|
if (inputString[i] != '%') {
|
|
result.append(inputString[i]);
|
|
continue;
|
|
}
|
|
|
|
// If the input string is a valid encoded filename, it must be at least 2 characters longer
|
|
// than the current index to account for this percent encoded value.
|
|
if (i + 2 >= length)
|
|
return { };
|
|
|
|
if (inputString[i+1] != '+') {
|
|
if (!isASCIIHexDigit(inputString[i + 1]))
|
|
return { };
|
|
if (!isASCIIHexDigit(inputString[i + 2]))
|
|
return { };
|
|
result.append(toASCIIHexValue(inputString[i + 1], inputString[i + 2]));
|
|
i += 2;
|
|
continue;
|
|
}
|
|
|
|
// If the input string is a valid encoded filename, it must be at least 5 characters longer
|
|
// than the current index to account for this percent encoded value.
|
|
if (i + 5 >= length)
|
|
return { };
|
|
|
|
if (!isASCIIHexDigit(inputString[i + 2]))
|
|
return { };
|
|
if (!isASCIIHexDigit(inputString[i + 3]))
|
|
return { };
|
|
if (!isASCIIHexDigit(inputString[i + 4]))
|
|
return { };
|
|
if (!isASCIIHexDigit(inputString[i + 5]))
|
|
return { };
|
|
|
|
UChar encodedCharacter = toASCIIHexValue(inputString[i + 2], inputString[i + 3]) << 8 | toASCIIHexValue(inputString[i + 4], inputString[i + 5]);
|
|
result.append(encodedCharacter);
|
|
i += 5;
|
|
}
|
|
|
|
return result.toString();
|
|
}
|
|
|
|
String lastComponentOfPathIgnoringTrailingSlash(const String& path)
|
|
{
|
|
#if OS(WINDOWS)
|
|
char pathSeparator = '\\';
|
|
#else
|
|
char pathSeparator = '/';
|
|
#endif
|
|
|
|
auto position = path.reverseFind(pathSeparator);
|
|
if (position == notFound)
|
|
return path;
|
|
|
|
size_t endOfSubstring = path.length() - 1;
|
|
if (position == endOfSubstring) {
|
|
--endOfSubstring;
|
|
position = path.reverseFind(pathSeparator, endOfSubstring);
|
|
}
|
|
|
|
return path.substring(position + 1, endOfSubstring - position);
|
|
}
|
|
|
|
bool appendFileContentsToFileHandle(const String& path, PlatformFileHandle& target)
|
|
{
|
|
auto source = openFile(path, FileOpenMode::Read);
|
|
|
|
if (!isHandleValid(source))
|
|
return false;
|
|
|
|
static int bufferSize = 1 << 19;
|
|
Vector<uint8_t> buffer(bufferSize);
|
|
|
|
auto fileCloser = WTF::makeScopeExit([source]() {
|
|
PlatformFileHandle handle = source;
|
|
closeFile(handle);
|
|
});
|
|
|
|
do {
|
|
int readBytes = readFromFile(source, buffer.data(), bufferSize);
|
|
|
|
if (readBytes < 0)
|
|
return false;
|
|
|
|
if (writeToFile(target, buffer.data(), readBytes) != readBytes)
|
|
return false;
|
|
|
|
if (readBytes < bufferSize)
|
|
return true;
|
|
} while (true);
|
|
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
|
|
bool filesHaveSameVolume(const String& fileA, const String& fileB)
|
|
{
|
|
auto fsRepFileA = fileSystemRepresentation(fileA);
|
|
auto fsRepFileB = fileSystemRepresentation(fileB);
|
|
|
|
if (fsRepFileA.isNull() || fsRepFileB.isNull())
|
|
return false;
|
|
|
|
bool result = false;
|
|
|
|
auto fileADev = getFileDeviceId(fsRepFileA);
|
|
auto fileBDev = getFileDeviceId(fsRepFileB);
|
|
|
|
if (fileADev && fileBDev)
|
|
result = (fileADev == fileBDev);
|
|
|
|
return result;
|
|
}
|
|
|
|
#if !PLATFORM(MAC)
|
|
|
|
void setMetadataURL(const String&, const String&, const String&)
|
|
{
|
|
}
|
|
|
|
bool canExcludeFromBackup()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool excludeFromBackup(const String&)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
|
|
MappedFileData::MappedFileData(const String& filePath, MappedFileMode mapMode, bool& success)
|
|
{
|
|
auto fd = openFile(filePath, FileSystem::FileOpenMode::Read);
|
|
|
|
success = mapFileHandle(fd, FileSystem::FileOpenMode::Read, mapMode);
|
|
closeFile(fd);
|
|
}
|
|
|
|
#if HAVE(MMAP)
|
|
|
|
MappedFileData::~MappedFileData()
|
|
{
|
|
if (!m_fileData)
|
|
return;
|
|
munmap(m_fileData, m_fileSize);
|
|
}
|
|
|
|
bool MappedFileData::mapFileHandle(PlatformFileHandle handle, FileOpenMode openMode, MappedFileMode mapMode)
|
|
{
|
|
if (!isHandleValid(handle))
|
|
return false;
|
|
|
|
int fd;
|
|
#if USE(GLIB)
|
|
auto* inputStream = g_io_stream_get_input_stream(G_IO_STREAM(handle));
|
|
fd = g_file_descriptor_based_get_fd(G_FILE_DESCRIPTOR_BASED(inputStream));
|
|
#else
|
|
fd = handle;
|
|
#endif
|
|
|
|
struct stat fileStat;
|
|
if (fstat(fd, &fileStat)) {
|
|
return false;
|
|
}
|
|
|
|
unsigned size;
|
|
if (!WTF::convertSafely(fileStat.st_size, size)) {
|
|
return false;
|
|
}
|
|
|
|
if (!size)
|
|
return true;
|
|
|
|
int pageProtection = PROT_READ;
|
|
switch (openMode) {
|
|
case FileOpenMode::Read:
|
|
pageProtection = PROT_READ;
|
|
break;
|
|
case FileOpenMode::Write:
|
|
pageProtection = PROT_WRITE;
|
|
break;
|
|
case FileOpenMode::ReadWrite:
|
|
pageProtection = PROT_READ | PROT_WRITE;
|
|
break;
|
|
#if OS(DARWIN)
|
|
case FileOpenMode::EventsOnly:
|
|
ASSERT_NOT_REACHED();
|
|
#endif
|
|
}
|
|
|
|
void* data = mmap(0, size, pageProtection, MAP_FILE | (mapMode == MappedFileMode::Shared ? MAP_SHARED : MAP_PRIVATE), fd, 0);
|
|
|
|
if (data == MAP_FAILED) {
|
|
return false;
|
|
}
|
|
|
|
m_fileData = data;
|
|
m_fileSize = size;
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
PlatformFileHandle openAndLockFile(const String& path, FileOpenMode openMode, OptionSet<FileLockMode> lockMode)
|
|
{
|
|
auto handle = openFile(path, openMode);
|
|
if (handle == invalidPlatformFileHandle)
|
|
return invalidPlatformFileHandle;
|
|
|
|
#if USE(FILE_LOCK)
|
|
bool locked = lockFile(handle, lockMode);
|
|
ASSERT_UNUSED(locked, locked);
|
|
#else
|
|
UNUSED_PARAM(lockMode);
|
|
#endif
|
|
|
|
return handle;
|
|
}
|
|
|
|
void unlockAndCloseFile(PlatformFileHandle handle)
|
|
{
|
|
#if USE(FILE_LOCK)
|
|
bool unlocked = unlockFile(handle);
|
|
ASSERT_UNUSED(unlocked, unlocked);
|
|
#endif
|
|
closeFile(handle);
|
|
}
|
|
|
|
#if !PLATFORM(IOS_FAMILY)
|
|
bool isSafeToUseMemoryMapForPath(const String&)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void makeSafeToUseMemoryMapForPath(const String&)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
#if !PLATFORM(COCOA)
|
|
String createTemporaryZipArchive(const String&)
|
|
{
|
|
return { };
|
|
}
|
|
#endif
|
|
|
|
MappedFileData mapToFile(const String& path, size_t bytesSize, Function<void(const Function<bool(Span<const uint8_t>)>&)>&& apply, PlatformFileHandle* outputHandle)
|
|
{
|
|
constexpr bool failIfFileExists = true;
|
|
auto handle = FileSystem::openFile(path, FileSystem::FileOpenMode::ReadWrite, FileSystem::FileAccessPermission::User, failIfFileExists);
|
|
if (!FileSystem::isHandleValid(handle) || !FileSystem::truncateFile(handle, bytesSize)) {
|
|
FileSystem::closeFile(handle);
|
|
return { };
|
|
}
|
|
|
|
FileSystem::makeSafeToUseMemoryMapForPath(path);
|
|
bool success;
|
|
FileSystem::MappedFileData mappedFile(handle, FileSystem::FileOpenMode::ReadWrite, FileSystem::MappedFileMode::Shared, success);
|
|
if (!success) {
|
|
FileSystem::closeFile(handle);
|
|
return { };
|
|
}
|
|
|
|
void* map = const_cast<void*>(mappedFile.data());
|
|
uint8_t* mapData = static_cast<uint8_t*>(map);
|
|
|
|
apply([&mapData](Span<const uint8_t> chunk) {
|
|
memcpy(mapData, chunk.data(), chunk.size());
|
|
mapData += chunk.size();
|
|
return true;
|
|
});
|
|
|
|
#if OS(WINDOWS)
|
|
DWORD oldProtection;
|
|
VirtualProtect(map, bytesSize, FILE_MAP_READ, &oldProtection);
|
|
FlushViewOfFile(map, bytesSize);
|
|
#else
|
|
// Drop the write permission.
|
|
mprotect(map, bytesSize, PROT_READ);
|
|
|
|
// Flush (asynchronously) to file, turning this into clean memory.
|
|
msync(map, bytesSize, MS_ASYNC);
|
|
#endif
|
|
|
|
if (outputHandle)
|
|
*outputHandle = handle;
|
|
else
|
|
FileSystem::closeFile(handle);
|
|
|
|
return mappedFile;
|
|
}
|
|
|
|
static Salt makeSalt()
|
|
{
|
|
Salt salt;
|
|
static_assert(salt.size() == 8, "Salt size");
|
|
*reinterpret_cast<uint32_t*>(&salt[0]) = cryptographicallyRandomNumber();
|
|
*reinterpret_cast<uint32_t*>(&salt[4]) = cryptographicallyRandomNumber();
|
|
return salt;
|
|
}
|
|
|
|
std::optional<Salt> readOrMakeSalt(const String& path)
|
|
{
|
|
if (FileSystem::fileExists(path)) {
|
|
auto file = FileSystem::openFile(path, FileSystem::FileOpenMode::Read);
|
|
Salt salt;
|
|
auto bytesRead = static_cast<std::size_t>(FileSystem::readFromFile(file, salt.data(), salt.size()));
|
|
FileSystem::closeFile(file);
|
|
if (bytesRead == salt.size())
|
|
return salt;
|
|
|
|
FileSystem::deleteFile(path);
|
|
}
|
|
|
|
Salt salt = makeSalt();
|
|
FileSystem::makeAllDirectories(FileSystem::parentPath(path));
|
|
auto file = FileSystem::openFile(path, FileSystem::FileOpenMode::Write, FileSystem::FileAccessPermission::User);
|
|
if (!FileSystem::isHandleValid(file))
|
|
return { };
|
|
|
|
bool success = static_cast<std::size_t>(FileSystem::writeToFile(file, salt.data(), salt.size())) == salt.size();
|
|
FileSystem::closeFile(file);
|
|
if (!success)
|
|
return { };
|
|
|
|
return salt;
|
|
}
|
|
|
|
#if HAVE(STD_FILESYSTEM) || HAVE(STD_EXPERIMENTAL_FILESYSTEM)
|
|
|
|
bool deleteEmptyDirectory(const String& path)
|
|
{
|
|
std::error_code ec;
|
|
auto fsPath = toStdFileSystemPath(path);
|
|
|
|
auto fileStatus = std::filesystem::symlink_status(fsPath, ec);
|
|
if (ec || fileStatus.type() != std::filesystem::file_type::directory)
|
|
return false;
|
|
|
|
#if PLATFORM(MAC)
|
|
bool containsSingleDSStoreFile = false;
|
|
auto entries = std::filesystem::directory_iterator(fsPath, ec);
|
|
for (auto it = std::filesystem::begin(entries), end = std::filesystem::end(entries); !ec && it != end; it.increment(ec)) {
|
|
if (it->path().filename() == ".DS_Store")
|
|
containsSingleDSStoreFile = true;
|
|
else {
|
|
containsSingleDSStoreFile = false;
|
|
break;
|
|
}
|
|
}
|
|
if (containsSingleDSStoreFile)
|
|
std::filesystem::remove(fsPath / ".DS_Store", ec);
|
|
#endif
|
|
|
|
// remove() returns false on error so no need to check ec.
|
|
return std::filesystem::remove(fsPath, ec);
|
|
}
|
|
|
|
bool moveFile(const String& oldPath, const String& newPath)
|
|
{
|
|
auto fsOldPath = toStdFileSystemPath(oldPath);
|
|
auto fsNewPath = toStdFileSystemPath(newPath);
|
|
|
|
std::error_code ec;
|
|
std::filesystem::rename(fsOldPath, fsNewPath, ec);
|
|
if (!ec)
|
|
return true;
|
|
|
|
// Fall back to copying and then deleting source as rename() does not work across volumes.
|
|
ec = { };
|
|
std::filesystem::copy(fsOldPath, fsNewPath, std::filesystem::copy_options::overwrite_existing | std::filesystem::copy_options::recursive, ec);
|
|
if (ec)
|
|
return false;
|
|
return std::filesystem::remove_all(fsOldPath, ec);
|
|
}
|
|
|
|
std::optional<uint64_t> fileSize(const String& path)
|
|
{
|
|
std::error_code ec;
|
|
auto size = std::filesystem::file_size(toStdFileSystemPath(path), ec);
|
|
if (ec)
|
|
return std::nullopt;
|
|
return size;
|
|
}
|
|
|
|
std::optional<uint64_t> volumeFreeSpace(const String& path)
|
|
{
|
|
std::error_code ec;
|
|
auto spaceInfo = std::filesystem::space(toStdFileSystemPath(path), ec);
|
|
if (ec)
|
|
return std::nullopt;
|
|
return spaceInfo.available;
|
|
}
|
|
|
|
bool createSymbolicLink(const String& targetPath, const String& symbolicLinkPath)
|
|
{
|
|
std::error_code ec;
|
|
std::filesystem::create_symlink(toStdFileSystemPath(targetPath), toStdFileSystemPath(symbolicLinkPath), ec);
|
|
return !ec;
|
|
}
|
|
|
|
bool hardLink(const String& targetPath, const String& linkPath)
|
|
{
|
|
std::error_code ec;
|
|
std::filesystem::create_hard_link(toStdFileSystemPath(targetPath), toStdFileSystemPath(linkPath), ec);
|
|
return !ec;
|
|
}
|
|
|
|
bool hardLinkOrCopyFile(const String& targetPath, const String& linkPath)
|
|
{
|
|
auto fsTargetPath = toStdFileSystemPath(targetPath);
|
|
auto fsLinkPath = toStdFileSystemPath(linkPath);
|
|
|
|
std::error_code ec;
|
|
std::filesystem::create_hard_link(fsTargetPath, fsLinkPath, ec);
|
|
if (!ec)
|
|
return true;
|
|
|
|
std::filesystem::copy_file(fsTargetPath, fsLinkPath, ec);
|
|
return !ec;
|
|
}
|
|
|
|
std::optional<uint64_t> hardLinkCount(const String& path)
|
|
{
|
|
std::error_code ec;
|
|
uint64_t linkCount = std::filesystem::hard_link_count(toStdFileSystemPath(path), ec);
|
|
return ec ? std::nullopt : std::make_optional(linkCount);
|
|
}
|
|
|
|
bool deleteNonEmptyDirectory(const String& path)
|
|
{
|
|
std::error_code ec;
|
|
std::filesystem::remove_all(toStdFileSystemPath(path), ec);
|
|
return !ec;
|
|
}
|
|
|
|
std::optional<WallTime> fileModificationTime(const String& path)
|
|
{
|
|
std::error_code ec;
|
|
auto modificationTime = std::filesystem::last_write_time(toStdFileSystemPath(path), ec);
|
|
if (ec)
|
|
return std::nullopt;
|
|
return toWallTime(modificationTime);
|
|
}
|
|
|
|
bool updateFileModificationTime(const String& path)
|
|
{
|
|
std::error_code ec;
|
|
std::filesystem::last_write_time(toStdFileSystemPath(path), std::filesystem::file_time_type::clock::now(), ec);
|
|
return !ec;
|
|
}
|
|
|
|
bool isHiddenFile(const String& path)
|
|
{
|
|
#if OS(UNIX)
|
|
auto fsPath = toStdFileSystemPath(path);
|
|
std::filesystem::path::string_type filename = fsPath.filename();
|
|
return !filename.empty() && filename[0] == '.';
|
|
#else
|
|
UNUSED_PARAM(path);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
enum class ShouldFollowSymbolicLinks { No, Yes };
|
|
static std::optional<FileType> fileTypePotentiallyFollowingSymLinks(const String& path, ShouldFollowSymbolicLinks shouldFollowSymbolicLinks)
|
|
{
|
|
std::error_code ec;
|
|
auto status = shouldFollowSymbolicLinks == ShouldFollowSymbolicLinks::Yes ? std::filesystem::status(toStdFileSystemPath(path), ec) : std::filesystem::symlink_status(toStdFileSystemPath(path), ec);
|
|
if (ec)
|
|
return std::nullopt;
|
|
switch (status.type()) {
|
|
case std::filesystem::file_type::directory:
|
|
return FileType::Directory;
|
|
case std::filesystem::file_type::symlink:
|
|
return FileType::SymbolicLink;
|
|
default:
|
|
break;
|
|
}
|
|
return FileType::Regular;
|
|
}
|
|
|
|
std::optional<FileType> fileType(const String& path)
|
|
{
|
|
return fileTypePotentiallyFollowingSymLinks(path, ShouldFollowSymbolicLinks::No);
|
|
}
|
|
|
|
std::optional<FileType> fileTypeFollowingSymlinks(const String& path)
|
|
{
|
|
return fileTypePotentiallyFollowingSymLinks(path, ShouldFollowSymbolicLinks::Yes);
|
|
}
|
|
|
|
String pathFileName(const String& path)
|
|
{
|
|
return fromStdFileSystemPath(toStdFileSystemPath(path).filename());
|
|
}
|
|
|
|
String parentPath(const String& path)
|
|
{
|
|
return fromStdFileSystemPath(toStdFileSystemPath(path).parent_path());
|
|
}
|
|
|
|
String realPath(const String& path)
|
|
{
|
|
std::error_code ec;
|
|
auto canonicalPath = std::filesystem::canonical(toStdFileSystemPath(path), ec);
|
|
return ec ? path : fromStdFileSystemPath(canonicalPath);
|
|
}
|
|
|
|
Vector<String> listDirectory(const String& path)
|
|
{
|
|
Vector<String> fileNames;
|
|
std::error_code ec;
|
|
auto entries = std::filesystem::directory_iterator(toStdFileSystemPath(path), ec);
|
|
for (auto it = std::filesystem::begin(entries), end = std::filesystem::end(entries); !ec && it != end; it.increment(ec)) {
|
|
auto fileName = fromStdFileSystemPath(it->path().filename());
|
|
if (!fileName.isNull())
|
|
fileNames.append(WTFMove(fileName));
|
|
}
|
|
return fileNames;
|
|
}
|
|
|
|
#if !ENABLE(FILESYSTEM_POSIX_FAST_PATH)
|
|
|
|
bool fileExists(const String& path)
|
|
{
|
|
std::error_code ec;
|
|
// exists() returns false on error so no need to check ec.
|
|
return std::filesystem::exists(toStdFileSystemPath(path), ec);
|
|
}
|
|
|
|
bool deleteFile(const String& path)
|
|
{
|
|
std::error_code ec;
|
|
auto fsPath = toStdFileSystemPath(path);
|
|
|
|
auto fileStatus = std::filesystem::symlink_status(fsPath, ec);
|
|
if (ec || fileStatus.type() == std::filesystem::file_type::directory)
|
|
return false;
|
|
|
|
// remove() returns false on error so no need to check ec.
|
|
return std::filesystem::remove(fsPath, ec);
|
|
}
|
|
|
|
bool makeAllDirectories(const String& path)
|
|
{
|
|
std::error_code ec;
|
|
std::filesystem::create_directories(toStdFileSystemPath(path), ec);
|
|
return !ec;
|
|
}
|
|
|
|
String pathByAppendingComponent(const String& path, const String& component)
|
|
{
|
|
return fromStdFileSystemPath(toStdFileSystemPath(path) / toStdFileSystemPath(component));
|
|
}
|
|
|
|
String pathByAppendingComponents(StringView path, const Vector<StringView>& components)
|
|
{
|
|
auto fsPath = toStdFileSystemPath(path);
|
|
for (auto& component : components)
|
|
fsPath /= toStdFileSystemPath(component);
|
|
return fromStdFileSystemPath(fsPath);
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif // HAVE(STD_FILESYSTEM) || HAVE(STD_EXPERIMENTAL_FILESYSTEM)
|
|
|
|
} // namespace FileSystemImpl
|
|
} // namespace WTF
|