blob: 659dfd52727dc9ef699b7f924d5d2e72d84e0ae7 [file] [log] [blame]
/*
* Copyright (C) 2007-2017 Apple Inc. All rights reserved.
* Copyright (C) 2008 Collabora, Ltd. 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.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 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 <io.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <sys/stat.h>
#include <windows.h>
#include <wtf/CryptographicallyRandomNumber.h>
#include <wtf/FileMetadata.h>
#include <wtf/HashMap.h>
#include <wtf/text/CString.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/win/WCharStringExtras.h>
#include <wtf/win/PathWalker.h>
namespace WTF {
namespace FileSystemImpl {
static const ULONGLONG kSecondsFromFileTimeToTimet = 11644473600;
static bool getFindData(String path, WIN32_FIND_DATAW& findData)
{
HANDLE handle = FindFirstFileW(path.wideCharacters().data(), &findData);
if (handle == INVALID_HANDLE_VALUE)
return false;
FindClose(handle);
return true;
}
static bool getFileSizeFromFindData(const WIN32_FIND_DATAW& findData, long long& size)
{
ULARGE_INTEGER fileSize;
fileSize.HighPart = findData.nFileSizeHigh;
fileSize.LowPart = findData.nFileSizeLow;
if (fileSize.QuadPart > static_cast<ULONGLONG>(std::numeric_limits<long long>::max()))
return false;
size = fileSize.QuadPart;
return true;
}
static bool getFileSizeFromByHandleFileInformationStructure(const BY_HANDLE_FILE_INFORMATION& fileInformation, long long& size)
{
ULARGE_INTEGER fileSize;
fileSize.HighPart = fileInformation.nFileSizeHigh;
fileSize.LowPart = fileInformation.nFileSizeLow;
if (fileSize.QuadPart > static_cast<ULONGLONG>(std::numeric_limits<long long>::max()))
return false;
size = fileSize.QuadPart;
return true;
}
static void getFileCreationTimeFromFindData(const WIN32_FIND_DATAW& findData, time_t& time)
{
ULARGE_INTEGER fileTime;
fileTime.HighPart = findData.ftCreationTime.dwHighDateTime;
fileTime.LowPart = findData.ftCreationTime.dwLowDateTime;
// Information about converting time_t to FileTime is available at http://msdn.microsoft.com/en-us/library/ms724228%28v=vs.85%29.aspx
time = fileTime.QuadPart / 10000000 - kSecondsFromFileTimeToTimet;
}
static void getFileModificationTimeFromFindData(const WIN32_FIND_DATAW& findData, time_t& time)
{
ULARGE_INTEGER fileTime;
fileTime.HighPart = findData.ftLastWriteTime.dwHighDateTime;
fileTime.LowPart = findData.ftLastWriteTime.dwLowDateTime;
// Information about converting time_t to FileTime is available at http://msdn.microsoft.com/en-us/library/ms724228%28v=vs.85%29.aspx
time = fileTime.QuadPart / 10000000 - kSecondsFromFileTimeToTimet;
}
bool getFileSize(const String& path, long long& size)
{
WIN32_FIND_DATAW findData;
if (!getFindData(path, findData))
return false;
return getFileSizeFromFindData(findData, size);
}
bool getFileSize(PlatformFileHandle fileHandle, long long& size)
{
BY_HANDLE_FILE_INFORMATION fileInformation;
if (!::GetFileInformationByHandle(fileHandle, &fileInformation))
return false;
return getFileSizeFromByHandleFileInformationStructure(fileInformation, size);
}
Optional<WallTime> getFileModificationTime(const String& path)
{
WIN32_FIND_DATAW findData;
if (!getFindData(path, findData))
return WTF::nullopt;
time_t time = 0;
getFileModificationTimeFromFindData(findData, time);
return WallTime::fromRawSeconds(time);
}
Optional<WallTime> getFileCreationTime(const String& path)
{
WIN32_FIND_DATAW findData;
if (!getFindData(path, findData))
return WTF::nullopt;
time_t time = 0;
getFileCreationTimeFromFindData(findData, time);
return WallTime::fromRawSeconds(time);
}
static String getFinalPathName(const String& path)
{
auto handle = openFile(path, FileOpenMode::Read);
if (!isHandleValid(handle))
return String();
// VOLUME_NAME_DOS can return a \\?\ prefixed path, so it can be longer than MAX_PATH
Vector<UChar> buffer(32768);
if (::GetFinalPathNameByHandleW(handle, wcharFrom(buffer.data()), buffer.size(), VOLUME_NAME_DOS) >= 32768) {
closeFile(handle);
return String();
}
closeFile(handle);
buffer.shrink(wcslen(wcharFrom(buffer.data())));
return String::adopt(WTFMove(buffer));
}
static inline bool isSymbolicLink(WIN32_FIND_DATAW findData)
{
return findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK;
}
static FileMetadata::Type toFileMetadataType(WIN32_FIND_DATAW findData)
{
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
return FileMetadata::Type::Directory;
if (isSymbolicLink(findData))
return FileMetadata::Type::SymbolicLink;
return FileMetadata::Type::File;
}
static Optional<FileMetadata> findDataToFileMetadata(WIN32_FIND_DATAW findData)
{
long long length;
if (!getFileSizeFromFindData(findData, length))
return WTF::nullopt;
time_t modificationTime;
getFileModificationTimeFromFindData(findData, modificationTime);
return FileMetadata {
WallTime::fromRawSeconds(modificationTime),
length,
static_cast<bool>(findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN),
toFileMetadataType(findData)
};
}
Optional<FileMetadata> fileMetadata(const String& path)
{
WIN32_FIND_DATAW findData;
if (!getFindData(path, findData))
return WTF::nullopt;
return findDataToFileMetadata(findData);
}
Optional<FileMetadata> fileMetadataFollowingSymlinks(const String& path)
{
WIN32_FIND_DATAW findData;
if (!getFindData(path, findData))
return WTF::nullopt;
if (isSymbolicLink(findData)) {
String targetPath = getFinalPathName(path);
if (targetPath.isNull())
return WTF::nullopt;
if (!getFindData(targetPath, findData))
return WTF::nullopt;
}
return findDataToFileMetadata(findData);
}
bool createSymbolicLink(const String& targetPath, const String& symbolicLinkPath)
{
return ::CreateSymbolicLinkW(symbolicLinkPath.wideCharacters().data(), targetPath.wideCharacters().data(), 0);
}
bool fileExists(const String& path)
{
WIN32_FIND_DATAW findData;
return getFindData(path, findData);
}
bool deleteFile(const String& path)
{
String filename = path;
return !!DeleteFileW(filename.wideCharacters().data());
}
bool deleteEmptyDirectory(const String& path)
{
String filename = path;
return !!RemoveDirectoryW(filename.wideCharacters().data());
}
bool moveFile(const String& oldPath, const String& newPath)
{
String oldFilename = oldPath;
String newFilename = newPath;
return !!::MoveFileEx(oldFilename.wideCharacters().data(), newFilename.wideCharacters().data(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING);
}
String pathByAppendingComponent(const String& path, const String& component)
{
Vector<UChar> buffer(MAX_PATH);
if (path.length() + 1 > buffer.size())
return String();
StringView(path).getCharactersWithUpconvert(buffer.data());
buffer[path.length()] = '\0';
if (!PathAppendW(wcharFrom(buffer.data()), component.wideCharacters().data()))
return String();
buffer.shrink(wcslen(wcharFrom(buffer.data())));
return String::adopt(WTFMove(buffer));
}
String pathByAppendingComponents(StringView path, const Vector<StringView>& components)
{
String result = path.toString();
for (auto& component : components)
result = pathByAppendingComponent(result, component.toString());
return result;
}
#if !USE(CF)
CString fileSystemRepresentation(const String& path)
{
auto characters = wcharFrom(StringView(path).upconvertedCharacters());
int size = WideCharToMultiByte(CP_ACP, 0, characters, path.length(), 0, 0, 0, 0) - 1;
char* buffer;
CString string = CString::newUninitialized(size, buffer);
WideCharToMultiByte(CP_ACP, 0, characters, path.length(), buffer, size, 0, 0);
return string;
}
#endif // !USE(CF)
bool makeAllDirectories(const String& path)
{
String fullPath = path;
if (SHCreateDirectoryEx(nullptr, fullPath.wideCharacters().data(), nullptr) != ERROR_SUCCESS) {
DWORD error = GetLastError();
if (error != ERROR_FILE_EXISTS && error != ERROR_ALREADY_EXISTS) {
LOG_ERROR("Failed to create path %s", path.ascii().data());
return false;
}
}
return true;
}
String homeDirectoryPath()
{
return "";
}
String pathGetFileName(const String& path)
{
return String(::PathFindFileName(path.wideCharacters().data()));
}
String directoryName(const String& path)
{
String name = path.left(path.length() - pathGetFileName(path).length());
if (name.characterStartingAt(name.length() - 1) == '\\'
|| name.characterStartingAt(name.length() - 1) == '/') {
// Remove any trailing "\" or "/"
name.truncate(name.length() - 1);
}
return name;
}
static String bundleName()
{
static const NeverDestroyed<String> name = [] {
String name { "WebKit"_s };
#if USE(CF)
if (CFBundleRef bundle = CFBundleGetMainBundle()) {
if (CFTypeRef bundleExecutable = CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleExecutableKey)) {
if (CFGetTypeID(bundleExecutable) == CFStringGetTypeID())
name = reinterpret_cast<CFStringRef>(bundleExecutable);
}
}
#endif
return name;
}();
return name;
}
static String storageDirectory(DWORD pathIdentifier)
{
Vector<UChar> buffer(MAX_PATH);
if (FAILED(SHGetFolderPathW(nullptr, pathIdentifier | CSIDL_FLAG_CREATE, nullptr, 0, wcharFrom(buffer.data()))))
return String();
buffer.shrink(wcslen(wcharFrom(buffer.data())));
String directory = String::adopt(WTFMove(buffer));
directory = pathByAppendingComponent(directory, "Apple Computer\\" + bundleName());
if (!makeAllDirectories(directory))
return String();
return directory;
}
static String cachedStorageDirectory(DWORD pathIdentifier)
{
static HashMap<DWORD, String> directories;
HashMap<DWORD, String>::iterator it = directories.find(pathIdentifier);
if (it != directories.end())
return it->value;
String directory = storageDirectory(pathIdentifier);
directories.add(pathIdentifier, directory);
return directory;
}
static String generateTemporaryPath(const Function<bool(const String&)>& action)
{
wchar_t tempPath[MAX_PATH];
int tempPathLength = ::GetTempPathW(WTF_ARRAY_LENGTH(tempPath), tempPath);
if (tempPathLength <= 0 || tempPathLength > WTF_ARRAY_LENGTH(tempPath))
return String();
String proposedPath;
do {
wchar_t tempFile[] = L"XXXXXXXX.tmp"; // Use 8.3 style name (more characters aren't helpful due to 8.3 short file names)
const int randomPartLength = 8;
cryptographicallyRandomValues(tempFile, randomPartLength * sizeof(wchar_t));
// Limit to valid filesystem characters, also excluding others that could be problematic, like punctuation.
// don't include both upper and lowercase since Windows file systems are typically not case sensitive.
const char validChars[] = "0123456789abcdefghijklmnopqrstuvwxyz";
for (int i = 0; i < randomPartLength; ++i)
tempFile[i] = validChars[tempFile[i] % (sizeof(validChars) - 1)];
ASSERT(wcslen(tempFile) == WTF_ARRAY_LENGTH(tempFile) - 1);
proposedPath = pathByAppendingComponent(tempPath, tempFile);
if (proposedPath.isEmpty())
break;
} while (!action(proposedPath));
return proposedPath;
}
String openTemporaryFile(const String&, PlatformFileHandle& handle)
{
handle = INVALID_HANDLE_VALUE;
String proposedPath = generateTemporaryPath([&handle](const String& proposedPath) {
// use CREATE_NEW to avoid overwriting an existing file with the same name
handle = ::CreateFileW(proposedPath.wideCharacters().data(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
return isHandleValid(handle) || GetLastError() == ERROR_ALREADY_EXISTS;
});
if (!isHandleValid(handle))
return String();
return proposedPath;
}
PlatformFileHandle openFile(const String& path, FileOpenMode mode, FileAccessPermission, bool failIfFileExists)
{
DWORD desiredAccess = 0;
DWORD creationDisposition = 0;
DWORD shareMode = 0;
switch (mode) {
case FileOpenMode::Read:
desiredAccess = GENERIC_READ;
creationDisposition = OPEN_EXISTING;
shareMode = FILE_SHARE_READ;
break;
case FileOpenMode::Write:
desiredAccess = GENERIC_WRITE;
creationDisposition = CREATE_ALWAYS;
break;
case FileOpenMode::ReadWrite:
desiredAccess = GENERIC_READ | GENERIC_WRITE;
creationDisposition = OPEN_ALWAYS;
break;
}
if (failIfFileExists)
creationDisposition = CREATE_NEW;
String destination = path;
return CreateFile(destination.wideCharacters().data(), desiredAccess, shareMode, nullptr, creationDisposition, FILE_ATTRIBUTE_NORMAL, nullptr);
}
void closeFile(PlatformFileHandle& handle)
{
if (isHandleValid(handle)) {
::CloseHandle(handle);
handle = invalidPlatformFileHandle;
}
}
long long seekFile(PlatformFileHandle handle, long long offset, FileSeekOrigin origin)
{
DWORD moveMethod = FILE_BEGIN;
if (origin == FileSeekOrigin::Current)
moveMethod = FILE_CURRENT;
else if (origin == FileSeekOrigin::End)
moveMethod = FILE_END;
LARGE_INTEGER largeOffset;
largeOffset.QuadPart = offset;
largeOffset.LowPart = SetFilePointer(handle, largeOffset.LowPart, &largeOffset.HighPart, moveMethod);
if (largeOffset.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
return -1;
return largeOffset.QuadPart;
}
bool truncateFile(PlatformFileHandle handle, long long offset)
{
FILE_END_OF_FILE_INFO eofInfo;
eofInfo.EndOfFile.QuadPart = offset;
return SetFileInformationByHandle(handle, FileEndOfFileInfo, &eofInfo, sizeof(FILE_END_OF_FILE_INFO));
}
int writeToFile(PlatformFileHandle handle, const char* data, int length)
{
if (!isHandleValid(handle))
return -1;
DWORD bytesWritten;
bool success = WriteFile(handle, data, length, &bytesWritten, nullptr);
if (!success)
return -1;
return static_cast<int>(bytesWritten);
}
int readFromFile(PlatformFileHandle handle, char* data, int length)
{
if (!isHandleValid(handle))
return -1;
DWORD bytesRead;
bool success = ::ReadFile(handle, data, length, &bytesRead, nullptr);
if (!success)
return -1;
return static_cast<int>(bytesRead);
}
bool hardLink(const String& source, const String& destination)
{
return CreateHardLink(destination.wideCharacters().data(), source.wideCharacters().data(), nullptr);
}
bool hardLinkOrCopyFile(const String& source, const String& destination)
{
if (hardLink(source, destination))
return true;
// Hard link failed. Perform a copy instead.
return !!::CopyFile(source.wideCharacters().data(), destination.wideCharacters().data(), TRUE);
}
String localUserSpecificStorageDirectory()
{
return cachedStorageDirectory(CSIDL_LOCAL_APPDATA);
}
String roamingUserSpecificStorageDirectory()
{
return cachedStorageDirectory(CSIDL_APPDATA);
}
Vector<String> listDirectory(const String& directory, const String& filter)
{
Vector<String> entries;
PathWalker walker(directory, filter);
if (!walker.isValid())
return entries;
do {
if (walker.data().dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
&& (!wcscmp(walker.data().cFileName, L".") || !wcscmp(walker.data().cFileName, L"..")))
continue;
entries.append(directory + "\\" + reinterpret_cast<const UChar*>(walker.data().cFileName));
} while (walker.step());
return entries;
}
bool getVolumeFreeSpace(const String& path, uint64_t& freeSpace)
{
ULARGE_INTEGER freeBytesAvailableToCaller;
if (!GetDiskFreeSpaceExW(path.wideCharacters().data(), &freeBytesAvailableToCaller, nullptr, nullptr))
return false;
freeSpace = freeBytesAvailableToCaller.QuadPart;
return true;
}
Optional<int32_t> getFileDeviceId(const CString& fsFile)
{
auto handle = openFile(fsFile.data(), FileOpenMode::Read);
if (!isHandleValid(handle))
return WTF::nullopt;
BY_HANDLE_FILE_INFORMATION fileInformation = { };
if (!::GetFileInformationByHandle(handle, &fileInformation)) {
closeFile(handle);
return WTF::nullopt;
}
closeFile(handle);
return fileInformation.dwVolumeSerialNumber;
}
String realPath(const String& filePath)
{
return getFinalPathName(filePath);
}
String createTemporaryDirectory()
{
return generateTemporaryPath([](const String& proposedPath) {
return makeAllDirectories(proposedPath);
});
}
bool deleteNonEmptyDirectory(const String& directoryPath)
{
SHFILEOPSTRUCT deleteOperation = {
nullptr,
FO_DELETE,
directoryPath.wideCharacters().data(),
L"",
FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT,
false,
nullptr,
L""
};
return !SHFileOperation(&deleteOperation);
}
bool unmapViewOfFile(void* buffer, size_t)
{
return UnmapViewOfFile(buffer);
}
bool MappedFileData::mapFileHandle(PlatformFileHandle handle, FileOpenMode openMode, MappedFileMode)
{
if (!isHandleValid(handle))
return false;
long long size;
if (!getFileSize(handle, size) || size > std::numeric_limits<size_t>::max() || size > std::numeric_limits<decltype(m_fileSize)>::max()) {
return false;
}
if (!size) {
return true;
}
DWORD pageProtection = PAGE_READONLY;
DWORD desiredAccess = FILE_MAP_READ;
switch (openMode) {
case FileOpenMode::Read:
pageProtection = PAGE_READONLY;
desiredAccess = FILE_MAP_READ;
break;
case FileOpenMode::Write:
pageProtection = PAGE_READWRITE;
desiredAccess = FILE_MAP_WRITE;
break;
case FileOpenMode::ReadWrite:
pageProtection = PAGE_READWRITE;
desiredAccess = FILE_MAP_WRITE | FILE_MAP_READ;
break;
}
auto mapping = CreateFileMapping(handle, nullptr, pageProtection, 0, 0, nullptr);
if (!mapping)
return false;
m_fileData = MapViewOfFile(mapping, desiredAccess, 0, 0, size);
CloseHandle(mapping);
if (!m_fileData)
return false;
m_fileSize = size;
return true;
}
} // namespace FileSystemImpl
} // namespace WTF