| /* |
| * Copyright (C) 2007-2017 Apple Inc. All rights reserved. |
| * Copyright (C) 2021 Sony Interactive Entertainment Inc. |
| * |
| * 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> |
| |
| #if !HAVE(STD_FILESYSTEM) && !HAVE(STD_EXPERIMENTAL_FILESYSTEM) |
| |
| #include <dirent.h> |
| #include <libgen.h> |
| #include <sys/statvfs.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <wtf/SafeStrerror.h> |
| #include <wtf/text/StringBuilder.h> |
| |
| namespace WTF { |
| |
| namespace FileSystemImpl { |
| |
| enum class ShouldFollowSymbolicLinks { No, Yes }; |
| static std::optional<FileType> fileTypePotentiallyFollowingSymLinks(const String& path, ShouldFollowSymbolicLinks shouldFollowSymbolicLinks) |
| { |
| CString fsRep = fileSystemRepresentation(path); |
| |
| if (!fsRep.data() || fsRep.data()[0] == '\0') |
| return std::nullopt; |
| |
| auto statFunc = shouldFollowSymbolicLinks == ShouldFollowSymbolicLinks::Yes ? stat : lstat; |
| struct stat fileInfo; |
| if (statFunc(fsRep.data(), &fileInfo)) |
| return std::nullopt; |
| |
| if (S_ISDIR(fileInfo.st_mode)) |
| return FileType::Directory; |
| if (S_ISLNK(fileInfo.st_mode)) |
| return FileType::SymbolicLink; |
| return FileType::Regular; |
| } |
| |
| bool fileExists(const String& path) |
| { |
| if (path.isNull()) |
| return false; |
| |
| CString fsRep = fileSystemRepresentation(path); |
| |
| if (!fsRep.data() || fsRep.data()[0] == '\0') |
| return false; |
| |
| return access(fsRep.data(), F_OK) != -1; |
| } |
| |
| bool deleteFile(const String& path) |
| { |
| CString fsRep = fileSystemRepresentation(path); |
| |
| if (!fsRep.data() || fsRep.data()[0] == '\0') { |
| LOG_ERROR("File failed to delete. Failed to get filesystem representation to create CString from cfString or filesystem representation is a null value"); |
| return false; |
| } |
| |
| // unlink(...) returns 0 on successful deletion of the path and non-zero in any other case (including invalid permissions or non-existent file) |
| bool unlinked = !unlink(fsRep.data()); |
| if (!unlinked && errno != ENOENT) |
| LOG_ERROR("File failed to delete. Error message: %s", safeStrerror(errno).data()); |
| |
| return unlinked; |
| } |
| |
| bool deleteEmptyDirectory(const String& path) |
| { |
| CString fsRep = fileSystemRepresentation(path); |
| |
| if (!fsRep.data() || fsRep.data()[0] == '\0') |
| return false; |
| |
| // rmdir(...) returns 0 on successful deletion of the path and non-zero in any other case (including invalid permissions or non-existent file) |
| return !rmdir(fsRep.data()); |
| } |
| |
| bool moveFile(const String& oldPath, const String& newPath) |
| { |
| auto oldFilename = fileSystemRepresentation(oldPath); |
| if (oldFilename.isNull()) |
| return false; |
| |
| auto newFilename = fileSystemRepresentation(newPath); |
| if (newFilename.isNull()) |
| return false; |
| |
| return rename(oldFilename.data(), newFilename.data()) != -1; |
| } |
| |
| std::optional<uint64_t> fileSize(const String& path) |
| { |
| CString fsRep = fileSystemRepresentation(path); |
| |
| if (!fsRep.data() || fsRep.data()[0] == '\0') |
| return std::nullopt; |
| |
| struct stat fileInfo; |
| |
| if (stat(fsRep.data(), &fileInfo)) |
| return std::nullopt; |
| |
| return fileInfo.st_size; |
| } |
| |
| bool makeAllDirectories(const String& path) |
| { |
| CString fullPath = fileSystemRepresentation(path); |
| if (!access(fullPath.data(), F_OK)) |
| return true; |
| |
| char* p = fullPath.mutableData() + 1; |
| int length = fullPath.length(); |
| |
| if (p[length - 1] == '/') |
| p[length - 1] = '\0'; |
| for (; *p; ++p) { |
| if (*p == '/') { |
| *p = '\0'; |
| if (access(fullPath.data(), F_OK)) { |
| if (mkdir(fullPath.data(), S_IRWXU)) |
| return false; |
| } |
| *p = '/'; |
| } |
| } |
| if (access(fullPath.data(), F_OK)) { |
| if (mkdir(fullPath.data(), S_IRWXU)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| std::optional<uint64_t> volumeFreeSpace(const String& path) |
| { |
| struct statvfs fileSystemStat; |
| if (!statvfs(fileSystemRepresentation(path).data(), &fileSystemStat)) |
| return fileSystemStat.f_bavail * fileSystemStat.f_frsize; |
| return std::nullopt; |
| } |
| |
| bool createSymbolicLink(const String& targetPath, const String& symbolicLinkPath) |
| { |
| CString targetPathFSRep = fileSystemRepresentation(targetPath); |
| if (!targetPathFSRep.data() || targetPathFSRep.data()[0] == '\0') |
| return false; |
| |
| CString symbolicLinkPathFSRep = fileSystemRepresentation(symbolicLinkPath); |
| if (!symbolicLinkPathFSRep.data() || symbolicLinkPathFSRep.data()[0] == '\0') |
| return false; |
| |
| return !symlink(targetPathFSRep.data(), symbolicLinkPathFSRep.data()); |
| } |
| |
| bool hardLink(const String& source, const String& destination) |
| { |
| if (source.isEmpty() || destination.isEmpty()) |
| return false; |
| |
| auto fsSource = fileSystemRepresentation(source); |
| if (!fsSource.data()) |
| return false; |
| |
| auto fsDestination = fileSystemRepresentation(destination); |
| if (!fsDestination.data()) |
| return false; |
| |
| return !link(fsSource.data(), fsDestination.data()); |
| } |
| |
| bool hardLinkOrCopyFile(const String& source, const String& destination) |
| { |
| if (hardLink(source, destination)) |
| return true; |
| |
| // Hard link failed. Perform a copy instead. |
| if (source.isEmpty() || destination.isEmpty()) |
| return false; |
| |
| auto fsSource = fileSystemRepresentation(source); |
| if (!fsSource.data()) |
| return false; |
| |
| auto fsDestination = fileSystemRepresentation(destination); |
| if (!fsDestination.data()) |
| return false; |
| |
| auto handle = open(fsDestination.data(), O_WRONLY | O_CREAT | O_EXCL, 0666); |
| if (handle == -1) |
| return false; |
| |
| bool appendResult = appendFileContentsToFileHandle(source, handle); |
| close(handle); |
| |
| // If the copy failed, delete the unusable file. |
| if (!appendResult) |
| unlink(fsDestination.data()); |
| |
| return appendResult; |
| } |
| |
| std::optional<uint64_t> hardLinkCount(const String& path) |
| { |
| auto linkPath = fileSystemRepresentation(path); |
| struct stat stat; |
| if (::stat(linkPath.data(), &stat) < 0) |
| return std::nullopt; |
| |
| // Link count is 2 in the single client case (the blob file and a link). |
| return stat.st_nlink - 1; |
| } |
| |
| bool deleteNonEmptyDirectory(const String& path) |
| { |
| auto entries = listDirectory(path); |
| for (auto& entry : entries) { |
| if (fileTypePotentiallyFollowingSymLinks(entry, ShouldFollowSymbolicLinks::No) == FileType::Directory) |
| deleteNonEmptyDirectory(entry); |
| else |
| deleteFile(entry); |
| } |
| return deleteEmptyDirectory(path); |
| } |
| |
| std::optional<WallTime> fileModificationTime(const String& path) |
| { |
| CString fsRep = fileSystemRepresentation(path); |
| |
| if (!fsRep.data() || fsRep.data()[0] == '\0') |
| return std::nullopt; |
| |
| struct stat fileInfo; |
| |
| if (stat(fsRep.data(), &fileInfo)) |
| return std::nullopt; |
| |
| return WallTime::fromRawSeconds(fileInfo.st_mtime); |
| } |
| |
| bool updateFileModificationTime(const String& path) |
| { |
| CString fsRep = fileSystemRepresentation(path); |
| |
| if (!fsRep.data() || fsRep.data()[0] == '\0') |
| return false; |
| |
| // Passing in null sets the modification time to now |
| return !utimes(fsRep.data(), nullptr); |
| } |
| |
| bool isHiddenFile(const String& path) |
| { |
| auto filename = pathFileName(path); |
| |
| return !filename.isEmpty() && filename[0] == '.'; |
| } |
| |
| 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 path.substring(path.reverseFind('/') + 1); |
| } |
| |
| String parentPath(const String& path) |
| { |
| CString fsRep = fileSystemRepresentation(path); |
| |
| if (!fsRep.data() || fsRep.data()[0] == '\0') |
| return String(); |
| |
| return String::fromUTF8(dirname(fsRep.mutableData())); |
| } |
| |
| String realPath(const String& filePath) |
| { |
| CString fsRep = fileSystemRepresentation(filePath); |
| char resolvedName[PATH_MAX]; |
| const char* result = realpath(fsRep.data(), resolvedName); |
| return result ? String::fromUTF8(result) : filePath; |
| } |
| |
| String pathByAppendingComponent(const String& path, const String& component) |
| { |
| if (path.endsWith('/')) |
| return path + component; |
| return path + "/" + component; |
| } |
| |
| String pathByAppendingComponents(StringView path, const Vector<StringView>& components) |
| { |
| StringBuilder builder; |
| builder.append(path); |
| for (auto& component : components) |
| builder.append('/', component); |
| return builder.toString(); |
| } |
| |
| Vector<String> listDirectory(const String& path) |
| { |
| Vector<String> entries; |
| CString cpath = fileSystemRepresentation(path); |
| DIR* dir = opendir(cpath.data()); |
| if (dir) { |
| struct dirent* dp; |
| while ((dp = readdir(dir))) { |
| const char* name = dp->d_name; |
| if (!strcmp(name, ".") || !strcmp(name, "..")) |
| continue; |
| char filePath[PATH_MAX]; |
| if (static_cast<int>(sizeof(filePath) - 1) < snprintf(filePath, sizeof(filePath), "%s/%s", cpath.data(), name)) |
| continue; // buffer overflow |
| |
| auto string = stringFromFileSystemRepresentation(filePath); |
| |
| // Some file system representations cannot be represented as a UTF-16 string, |
| // so this string might be null. |
| if (!string.isNull()) |
| entries.append(WTFMove(string)); |
| } |
| closedir(dir); |
| } |
| return entries; |
| } |
| |
| } // namespace FileSystemImpl |
| } // namespace WTF |
| |
| #endif // !HAVE(STD_FILESYSTEM) && !HAVE(STD_EXPERIMENTAL_FILESYSTEM) |