#include "files.h" #include "common.h" #include "handles.h" #include "strutil.h" #include #include #include #include #include #include #include #include #include #include #include #include kernel32::FsObject::~FsObject() { std::lock_guard lk(m); int fd = std::exchange(this->fd, -1); if (fd >= 0 && closeOnDestroy) { close(fd); } if (deletePending && !canonicalPath.empty()) { if (unlink(canonicalPath.c_str()) != 0) { perror("Failed to delete file on close"); } } } namespace files { static std::vector splitList(const std::string &value, char delimiter) { std::vector entries; size_t start = 0; while (start <= value.size()) { size_t end = value.find(delimiter, start); if (end == std::string::npos) { end = value.size(); } entries.emplace_back(value.substr(start, end - start)); if (end == value.size()) { break; } start = end + 1; } return entries; } static std::string toWindowsPathEntry(const std::string &entry) { if (entry.empty()) { return {}; } bool looksWindows = entry.find('\\') != std::string::npos || (entry.size() >= 2 && entry[1] == ':' && entry[0] != '/'); if (looksWindows) { std::string normalized = entry; std::replace(normalized.begin(), normalized.end(), '/', '\\'); return normalized; } return pathToWindows(std::filesystem::path(entry)); } static std::string toHostPathEntry(const std::string &entry) { if (entry.empty()) { return {}; } auto converted = pathFromWindows(entry.c_str()); if (!converted.empty()) { return converted.string(); } std::string normalized = entry; std::replace(normalized.begin(), normalized.end(), '\\', '/'); return normalized; } static HANDLE stdinHandle; static HANDLE stdoutHandle; static HANDLE stderrHandle; std::filesystem::path pathFromWindows(const char *inStr) { // Convert to forward slashes std::string str = inStr; std::replace(str.begin(), str.end(), '\\', '/'); // Remove "//?/" prefix if (str.rfind("//?/", 0) == 0) { str.erase(0, 4); } // Remove the drive letter if (str.rfind("z:/", 0) == 0 || str.rfind("Z:/", 0) == 0 || str.rfind("c:/", 0) == 0 || str.rfind("C:/", 0) == 0) { str.erase(0, 2); } // Return as-is if it exists, else traverse the filesystem looking for // a path that matches case insensitively std::filesystem::path path = std::filesystem::path(str).lexically_normal(); if (std::filesystem::exists(path)) { return path; } std::filesystem::path newPath = "."; bool followingExisting = true; for (const auto &component : path) { std::filesystem::path newPath2 = newPath / component; if (followingExisting && !std::filesystem::exists(newPath2) && (component != ".." && component != "." && component != "")) { followingExisting = false; try { for (std::filesystem::path entry : std::filesystem::directory_iterator{newPath}) { if (strcasecmp(entry.filename().c_str(), component.c_str()) == 0) { followingExisting = true; newPath2 = entry; break; } } } catch (const std::filesystem::filesystem_error &) { // not a directory } } newPath = newPath2; } if (followingExisting) { DEBUG_LOG("Resolved case-insensitive path: %s\n", newPath.c_str()); } else { DEBUG_LOG("Failed to resolve path: %s\n", newPath.c_str()); } return newPath; } std::string pathToWindows(const std::filesystem::path &path) { std::string str = path.lexically_normal(); if (path.is_absolute()) { str.insert(0, "Z:"); } std::replace(str.begin(), str.end(), '/', '\\'); return str; } IOResult read(FileObject *file, void *buffer, size_t bytesToRead, const std::optional &offset, bool updateFilePointer) { IOResult result{}; if (!file || !file->valid()) { result.unixError = EBADF; return result; } if (bytesToRead == 0) { return result; } // Sanity check: if no offset is given, we must update the file pointer assert(offset.has_value() || updateFilePointer); const auto doRead = [&](off64_t pos) { size_t total = 0; size_t remaining = bytesToRead; uint8_t *in = static_cast(buffer); while (remaining > 0) { size_t chunk = remaining > SSIZE_MAX ? SSIZE_MAX : remaining; ssize_t rc = pread64(file->fd, in + total, chunk, pos); if (rc == -1) { if (errno == EINTR) { continue; } result.bytesTransferred = total; result.unixError = errno ? errno : EIO; return; } if (rc == 0) { result.bytesTransferred = total; result.reachedEnd = true; return; } total += static_cast(rc); remaining -= static_cast(rc); pos += rc; } result.bytesTransferred = total; }; if (updateFilePointer || !offset.has_value()) { std::lock_guard lk(file->m); off64_t pos = offset.value_or(file->filePos); doRead(pos); if (updateFilePointer) { file->filePos = pos + static_cast(result.bytesTransferred); } } else { doRead(*offset); } return result; } IOResult write(FileObject *file, const void *buffer, size_t bytesToWrite, const std::optional &offset, bool updateFilePointer) { IOResult result{}; if (!file || !file->valid()) { result.unixError = EBADF; return result; } if (bytesToWrite == 0) { return result; } // Sanity check: if no offset is given, we must update the file pointer assert(offset.has_value() || updateFilePointer); if (file->appendOnly || !file->seekable) { std::lock_guard lk(file->m); size_t total = 0; size_t remaining = bytesToWrite; const uint8_t *in = static_cast(buffer); while (remaining > 0) { size_t chunk = remaining > SSIZE_MAX ? SSIZE_MAX : remaining; ssize_t rc = ::write(file->fd, in + total, chunk); if (rc == -1) { if (errno == EINTR) { continue; } result.unixError = errno ? errno : EIO; break; } if (rc == 0) { break; } total += static_cast(rc); remaining -= static_cast(rc); } result.bytesTransferred = total; if (updateFilePointer) { off64_t pos = file->seekable ? lseek64(file->fd, 0, SEEK_CUR) : 0; if (pos >= 0) { file->filePos = pos; } else if (result.unixError == 0) { result.unixError = errno ? errno : EIO; } } return result; } auto doWrite = [&](off64_t pos) { size_t total = 0; size_t remaining = bytesToWrite; const uint8_t *in = static_cast(buffer); while (remaining > 0) { size_t chunk = remaining > SSIZE_MAX ? SSIZE_MAX : remaining; ssize_t rc = pwrite64(file->fd, in + total, chunk, pos); if (rc == -1) { if (errno == EINTR) { continue; } result.unixError = errno ? errno : EIO; break; } if (rc == 0) { break; } total += static_cast(rc); remaining -= static_cast(rc); pos += rc; } result.bytesTransferred = total; }; if (updateFilePointer || !offset.has_value()) { std::lock_guard lk(file->m); const off64_t pos = offset.value_or(file->filePos); doWrite(pos); if (updateFilePointer) { file->filePos = pos + static_cast(result.bytesTransferred); } } else { doWrite(*offset); } return result; } HANDLE getStdHandle(DWORD nStdHandle) { switch (nStdHandle) { case STD_INPUT_HANDLE: return stdinHandle; case STD_OUTPUT_HANDLE: return stdoutHandle; case STD_ERROR_HANDLE: return stderrHandle; default: return (void *)0xFFFFFFFF; } } BOOL setStdHandle(DWORD nStdHandle, HANDLE hHandle) { switch (nStdHandle) { case STD_INPUT_HANDLE: stdinHandle = hHandle; break; case STD_OUTPUT_HANDLE: stdoutHandle = hHandle; break; case STD_ERROR_HANDLE: stderrHandle = hHandle; break; default: return 0; // fail } return 1; // success } void init() { auto &handles = wibo::handles(); auto stdinObject = make_pin(STDIN_FILENO); stdinObject->closeOnDestroy = false; stdinHandle = handles.alloc(std::move(stdinObject), FILE_GENERIC_READ, 0); auto stdoutObject = make_pin(STDOUT_FILENO); stdoutObject->closeOnDestroy = false; stdoutHandle = handles.alloc(std::move(stdoutObject), FILE_GENERIC_WRITE, 0); auto stderrObject = make_pin(STDERR_FILENO); stderrObject->closeOnDestroy = false; stderrHandle = handles.alloc(std::move(stderrObject), FILE_GENERIC_WRITE, 0); } std::optional findCaseInsensitiveFile(const std::filesystem::path &directory, const std::string &filename) { std::error_code ec; if (directory.empty()) { return std::nullopt; } if (!std::filesystem::exists(directory, ec) || !std::filesystem::is_directory(directory, ec)) { return std::nullopt; } std::string needle = filename; toLowerInPlace(needle); for (const auto &entry : std::filesystem::directory_iterator(directory, ec)) { if (ec) { break; } std::string candidate = entry.path().filename().string(); toLowerInPlace(candidate); if (candidate == needle) { return canonicalPath(entry.path()); } } auto direct = directory / filename; if (std::filesystem::exists(direct, ec)) { return canonicalPath(direct); } return std::nullopt; } std::filesystem::path canonicalPath(const std::filesystem::path &path) { std::error_code ec; auto canonical = std::filesystem::weakly_canonical(path, ec); if (!ec) { return canonical; } return std::filesystem::absolute(path); } std::string hostPathListToWindows(const std::string &value) { if (value.empty()) { return value; } char delimiter = value.find(';') != std::string::npos ? ';' : ':'; auto entries = splitList(value, delimiter); std::string result; for (size_t i = 0; i < entries.size(); ++i) { if (i != 0) { result.push_back(';'); } if (!entries[i].empty()) { result += toWindowsPathEntry(entries[i]); } } return result; } std::string windowsPathListToHost(const std::string &value) { if (value.empty()) { return value; } auto entries = splitList(value, ';'); std::string result; for (size_t i = 0; i < entries.size(); ++i) { if (i != 0) { result.push_back(':'); } if (!entries[i].empty()) { result += toHostPathEntry(entries[i]); } } return result; } } // namespace files