From a76366386497fe8c44a77421fa735955f95a31a0 Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Sat, 6 Jul 2024 21:22:29 +0900 Subject: [PATCH] FileManagement: Trace symlink paths across rootfs Non-rootfs paths might contain a path component that links to the rootfs. This is the case with Wine's `dosdevices/z:` which links to /. To handle this, we need to trace the path component by component and resolve each potential symlink (except the last one, which is already handled by the existing logic if FollowSymlink is true). Signed-off-by: Asahi Lina --- .../LinuxSyscalls/FileManagement.cpp | 189 ++++++++++++++---- 1 file changed, 153 insertions(+), 36 deletions(-) diff --git a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp index e57c281586..9534bfeb66 100644 --- a/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp +++ b/Source/Tools/LinuxEmulation/LinuxSyscalls/FileManagement.cpp @@ -330,41 +330,37 @@ FileManager::~FileManager() { } fextl::string FileManager::GetEmulatedPath(const char* pathname, bool FollowSymlink) { - if (!pathname || // If no pathname - pathname[0] != '/' || // If relative - strcmp(pathname, "/") == 0) { // If we are getting root - return {}; - } - auto thunkOverlay = ThunkOverlays.find(pathname); - if (thunkOverlay != ThunkOverlays.end()) { - return thunkOverlay->second; - } + FDPathTmpData TmpFilename; const auto& RootFSPath = LDPath(); if (RootFSPath.empty()) { // If RootFS doesn't exist return {}; } - fextl::string Path = RootFSPath + pathname; - if (FollowSymlink) { - char Filename[PATH_MAX]; - while (FEX::HLE::IsSymlink(AT_FDCWD, Path.c_str())) { - auto SymlinkSize = FEX::HLE::GetSymlink(AT_FDCWD, Path.c_str(), Filename, PATH_MAX - 1); - if (SymlinkSize > 0 && Filename[0] == '/') { - Path = RootFSPath; - Path += std::string_view(Filename, SymlinkSize); - } else { - break; - } - } + auto FdPath = GetEmulatedFDPath(AT_FDCWD, pathname, FollowSymlink, TmpFilename); + + if (!FdPath.second) { + return {}; } + + fextl::string Path = RootFSPath + "/" + FdPath.second; + return Path; } std::pair FileManager::GetEmulatedFDPath(int dirfd, const char* pathname, bool FollowSymlink, FDPathTmpData& TmpFilename) { constexpr auto NoEntry = std::make_pair(-1, nullptr); + // The two temporary paths. + const std::array TmpPaths = { + TmpFilename[0], + TmpFilename[1], + }; + + // Current index for the temporary path to use. + uint32_t CurrentIndex {}; + if (!pathname) { // No pathname. return NoEntry; @@ -375,13 +371,138 @@ std::pair FileManager::GetEmulatedFDPath(int dirfd, const char dirfd = AT_FDCWD; } - if (pathname[0] != '/' || // If relative - pathname[1] == 0 || // If we are getting root - dirfd != AT_FDCWD) { // If dirfd isn't special FDCWD + if (dirfd != AT_FDCWD) { + auto Tmp = TmpPaths[1]; + auto PathLength = FEX::get_fdpath(dirfd, Tmp); + if (PathLength != -1) { + if (pathname) { + Tmp[PathLength] = '/'; + PathLength += 1; + strncpy(&Tmp[PathLength], pathname, PATH_MAX - PathLength); + } else { + Tmp[PathLength] = '\0'; + } + } else { + strncpy(TmpPaths[1], pathname, PATH_MAX); + } + dirfd = AT_FDCWD; + } else { + strncpy(TmpPaths[1], pathname, PATH_MAX); + } + + char *SubPath = TmpPaths[1]; + + if (!strcmp(SubPath, "/")) // If we are getting root return NoEntry; + + int tries = 40; +retry: + char *right = SubPath; + // If this is an absolute path, skip the first delimiter + while (*right == '/') + right++; + + for (; char *delim = strchr(right, '/'); ) { + char *cur_element = right; + while (delim[1] == '/') + delim++; + + if (!delim[1]) + break; + + *delim = 0; + right = delim + 1; + + struct stat Buffer {}; + + // Scan the rootFS if this is an absolute path + int Result = -1; + bool rootfs = true; + + errno = ENOENT; + if (SubPath[0] == '/') { + Result = fstatat(RootFSFD, &SubPath[1], &Buffer, AT_SYMLINK_NOFOLLOW); + } + + // Otherwise just scan the dirfd + if (Result != 0 && errno == ENOENT) { + rootfs = false; + Result = fstatat(dirfd, SubPath, &Buffer, AT_SYMLINK_NOFOLLOW); + } + + if (Result != 0) { + // Some path component does not exist (or other error) + return NoEntry; + } + + const bool IsLink = S_ISLNK(Buffer.st_mode); + + if (IsLink) { + // Choose the current temporary working path. + auto CurrentTmp = TmpPaths[CurrentIndex]; + + // Get the symlink of RootFS FD + stripped subpath. + int SymlinkSize; + if (rootfs) + SymlinkSize = FEX::HLE::GetSymlink(RootFSFD, &SubPath[1], CurrentTmp, PATH_MAX - 1); + else + SymlinkSize = FEX::HLE::GetSymlink(dirfd, SubPath, CurrentTmp, PATH_MAX - 1); + + if (SymlinkSize > 0 && CurrentTmp[0] == '/') { + // If the symlink is absolute: + // Strip leading redundant slashes + while (SymlinkSize > 1 && CurrentTmp[1] == '/') { + CurrentTmp++; + SymlinkSize--; + } + // If it's not just a link to the root, append a slash + if (SymlinkSize > 1) { + CurrentTmp[SymlinkSize] = '/'; + SymlinkSize++; + } + // Append the current right side + strncpy(&CurrentTmp[SymlinkSize], right, PATH_MAX - SymlinkSize); + SubPath = CurrentTmp; + // And switch buffers + CurrentIndex ^= 1; + if (!--tries) + return NoEntry; // Max depth exceeded + goto retry; + } else if (SymlinkSize > 0) { + // If the symlink is relative: + // Calculate the total path length + auto left_len = cur_element - SubPath; + auto right_len = strlen(right); + + if ((left_len + right_len + SymlinkSize + 1) >= PATH_MAX) + return NoEntry; // Max path exceeded + + // Move the symlink target to the right position in the buffer + memmove(&CurrentTmp[left_len], CurrentTmp, SymlinkSize); + // Insert the left side + memcpy(CurrentTmp, SubPath, left_len); + CurrentTmp[left_len + SymlinkSize] = '/'; + // Append the right side + memcpy(&CurrentTmp[left_len + SymlinkSize + 1], right, right_len); + // Terminate + CurrentTmp[left_len + SymlinkSize + 1 + right_len] = 0; + + // Continue processing starting at the symlink target + right = &CurrentTmp[left_len]; + + SubPath = CurrentTmp; + CurrentIndex ^= 1; + + if (!--tries) + return NoEntry; // Max depth exceeded + + continue; + } + } + *delim = '/'; } - auto thunkOverlay = ThunkOverlays.find(pathname); + auto thunkOverlay = ThunkOverlays.find(SubPath); if (thunkOverlay != ThunkOverlays.end()) { return std::make_pair(AT_FDCWD, thunkOverlay->second.c_str()); } @@ -391,17 +512,9 @@ std::pair FileManager::GetEmulatedFDPath(int dirfd, const char return NoEntry; } - // Starting subpath is the pathname passed in. - const char* SubPath = pathname; - - // Current index for the temporary path to use. - uint32_t CurrentIndex {}; - - // The two temporary paths. - const std::array TmpPaths = { - TmpFilename[0], - TmpFilename[1], - }; + // If relative or we're getting root + if (SubPath[0] != '/' || SubPath[1] == 0) + return NoEntry; if (FollowSymlink) { // Check if the combination of RootFS FD and subpath with the front '/' stripped off is a symlink. @@ -449,6 +562,10 @@ std::pair FileManager::GetEmulatedFDPath(int dirfd, const char } } + // If we wound up at the root again + if (!strcmp(SubPath, "/")) + return NoEntry; + // Return the pair of rootfs FD plus relative subpath by stripping off the front '/' return std::make_pair(RootFSFD, &SubPath[1]); }