aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-02-25 22:02:36 +0100
committerStefan Boberg <[email protected]>2026-02-25 22:02:36 +0100
commitc4940918859111a4e115535d074ef4bb9e6f7ebe (patch)
tree9309a728008c739726f3dd9d2cf46959ae1d8ece /src
parentadd tentative block cloning support for macOS / Linux (diff)
downloadzen-sb/block-clone.tar.xz
zen-sb/block-clone.zip
promoted ScopeFd to file scope and applied it to tests and other codesb/block-clone
fixes potential file handle leak in ScanFile (if callback throws) and generally makes code more maintainable and aligned with how handles are typically managed on Windows
Diffstat (limited to 'src')
-rw-r--r--src/zencore/filesystem.cpp186
1 files changed, 94 insertions, 92 deletions
diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp
index 14ecfe8df..a64fb6d66 100644
--- a/src/zencore/filesystem.cpp
+++ b/src/zencore/filesystem.cpp
@@ -69,6 +69,53 @@ namespace zen {
using namespace std::literals;
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+struct ScopedFd
+{
+ int Fd = -1;
+
+ ScopedFd() = default;
+ explicit ScopedFd(int InFd) : Fd(InFd) {}
+
+ ~ScopedFd()
+ {
+ if (Fd >= 0)
+ {
+ close(Fd);
+ }
+ }
+
+ ScopedFd(const ScopedFd&) = delete;
+ ScopedFd& operator=(const ScopedFd&) = delete;
+
+ ScopedFd(ScopedFd&& Other) noexcept : Fd(Other.Fd) { Other.Fd = -1; }
+
+ ScopedFd& operator=(ScopedFd&& Other) noexcept
+ {
+ if (this != &Other)
+ {
+ if (Fd >= 0)
+ {
+ close(Fd);
+ }
+ Fd = Other.Fd;
+ Other.Fd = -1;
+ }
+ return *this;
+ }
+
+ // Release ownership of the file descriptor, returning it without closing
+ int Release()
+ {
+ int Result = Fd;
+ Fd = -1;
+ return Result;
+ }
+
+ explicit operator bool() const { return Fd >= 0; }
+};
+#endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+
#if ZEN_PLATFORM_WINDOWS
static bool
@@ -1173,43 +1220,28 @@ TryCloneFile(const std::filesystem::path& FromPath, const std::filesystem::path&
return TryCloneFile((void*)FromFile.m_Handle, (void*)TargetFile.m_Handle);
#elif ZEN_PLATFORM_LINUX
- struct ScopedFd
- {
- ~ScopedFd()
- {
- if (Fd >= 0)
- {
- close(Fd);
- }
- }
- int Fd;
- };
-
// The 'from' file
- int FromFd = open(FromPath.c_str(), O_RDONLY | O_CLOEXEC);
- if (FromFd < 0)
+ ScopedFd FromFd(open(FromPath.c_str(), O_RDONLY | O_CLOEXEC));
+ if (!FromFd)
{
return false;
}
- ScopedFd $From = {FromFd};
// Remove any existing target so we can create a fresh clone
unlink(ToPath.c_str());
// The 'to' file
- int ToFd = open(ToPath.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0666);
- if (ToFd < 0)
+ ScopedFd ToFd(open(ToPath.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0666));
+ if (!ToFd)
{
return false;
}
- ScopedFd $To = {ToFd};
- if (ioctl(ToFd, FICLONE, FromFd) != 0)
+ if (ioctl(ToFd.Fd, FICLONE, FromFd.Fd) != 0)
{
// Clone not supported by this filesystem or files are on different volumes.
// Remove the empty target file we created.
- close(ToFd);
- $To.Fd = -1;
+ ToFd = ScopedFd();
unlink(ToPath.c_str());
return false;
}
@@ -1274,49 +1306,41 @@ CopyFile(const std::filesystem::path& FromPath, const std::filesystem::path& ToP
&CancelFlag,
/* dwCopyFlags */ 0);
#else
- struct ScopedFd
- {
- ~ScopedFd() { close(Fd); }
- int Fd;
- };
-
// From file
- int FromFd = open(FromPath.c_str(), O_RDONLY | O_CLOEXEC);
- if (FromFd < 0)
+ ScopedFd FromFd(open(FromPath.c_str(), O_RDONLY | O_CLOEXEC));
+ if (!FromFd)
{
ThrowLastError(fmt::format("failed to open file {}", FromPath));
}
- ScopedFd $From = {FromFd};
// To file
- int ToFd = open(ToPath.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0666);
- if (ToFd < 0)
+ ScopedFd ToFd(open(ToPath.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, 0666));
+ if (!ToFd)
{
ThrowLastError(fmt::format("failed to create file {}", ToPath));
}
- fchmod(ToFd, 0666);
- ScopedFd $To = {ToFd};
+ fchmod(ToFd.Fd, 0666);
struct stat Stat;
- fstat(FromFd, &Stat);
+ fstat(FromFd.Fd, &Stat);
size_t FileSizeBytes = Stat.st_size;
- fchown(ToFd, Stat.st_uid, Stat.st_gid);
+ fchown(ToFd.Fd, Stat.st_uid, Stat.st_gid);
// Copy impl
const size_t BufferSize = Min(FileSizeBytes, 64u << 10);
void* Buffer = malloc(BufferSize);
while (true)
{
- int BytesRead = read(FromFd, Buffer, BufferSize);
+ int BytesRead = read(FromFd.Fd, Buffer, BufferSize);
if (BytesRead <= 0)
{
Success = (BytesRead == 0);
break;
}
- if (write(ToFd, Buffer, BytesRead) != BytesRead)
+ if (write(ToFd.Fd, Buffer, BytesRead) != BytesRead)
{
Success = false;
break;
@@ -1560,20 +1584,20 @@ WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t Buffer
}
#else
- int OpenFlags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC;
- int Fd = open(Path.c_str(), OpenFlags, 0666);
- if (Fd < 0)
+ int OpenFlags = O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC;
+ ScopedFd OutFd(open(Path.c_str(), OpenFlags, 0666));
+ if (!OutFd)
{
zen::CreateDirectories(Path.parent_path());
- Fd = open(Path.c_str(), OpenFlags, 0666);
+ OutFd = ScopedFd(open(Path.c_str(), OpenFlags, 0666));
}
- if (Fd < 0)
+ if (!OutFd)
{
ThrowLastError(fmt::format("File open failed for '{}'", Path));
}
- fchmod(Fd, 0666);
+ fchmod(OutFd.Fd, 0666);
#endif
// TODO: this should be block-enlightened
@@ -1597,9 +1621,9 @@ WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t Buffer
ThrowSystemException(hRes, fmt::format("File write failed for '{}'", Path).c_str());
}
#else
- if (write(Fd, DataPtr, WriteSize) != int64_t(WriteSize))
+ if (write(OutFd.Fd, DataPtr, WriteSize) != int64_t(WriteSize))
{
- close(Fd);
+ OutFd = ScopedFd();
std::error_code DummyEc;
RemoveFile(Path, DummyEc);
ThrowLastError(fmt::format("File write failed for '{}'", Path));
@@ -1613,8 +1637,6 @@ WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t Buffer
#if ZEN_PLATFORM_WINDOWS
Outfile.Close();
-#else
- close(Fd);
#endif
}
@@ -1896,8 +1918,8 @@ ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function<voi
ProcessFunc(ReadBuffer.data(), dwBytesRead);
}
#else
- int Fd = open(Path.c_str(), O_RDONLY | O_CLOEXEC);
- if (Fd < 0)
+ ScopedFd InFd(open(Path.c_str(), O_RDONLY | O_CLOEXEC));
+ if (!InFd)
{
return false;
}
@@ -1907,7 +1929,7 @@ ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function<voi
void* Buffer = malloc(ChunkSize);
while (true)
{
- int BytesRead = read(Fd, Buffer, ChunkSize);
+ int BytesRead = read(InFd.Fd, Buffer, ChunkSize);
if (BytesRead < 0)
{
Success = false;
@@ -1923,7 +1945,6 @@ ScanFile(std::filesystem::path Path, const uint64_t ChunkSize, std::function<voi
}
free(Buffer);
- close(Fd);
if (!Success)
{
@@ -3312,28 +3333,26 @@ public:
ZEN_UNUSED(SystemGlobal);
std::string InstanceMapName = fmt::format("/{}", Name);
- int Fd = shm_open(InstanceMapName.c_str(), O_RDWR, 0666);
- if (Fd < 0)
+ ScopedFd FdGuard(shm_open(InstanceMapName.c_str(), O_RDWR, 0666));
+ if (!FdGuard)
{
return {};
}
- void* hMap = (void*)intptr_t(Fd);
struct stat Stat;
- fstat(Fd, &Stat);
+ fstat(FdGuard.Fd, &Stat);
if (size_t(Stat.st_size) < Size)
{
- close(Fd);
return {};
}
- void* pBuf = mmap(nullptr, Size, PROT_READ | PROT_WRITE, MAP_SHARED, Fd, 0);
+ void* pBuf = mmap(nullptr, Size, PROT_READ | PROT_WRITE, MAP_SHARED, FdGuard.Fd, 0);
if (pBuf == MAP_FAILED)
{
- close(Fd);
return {};
}
+ void* hMap = (void*)intptr_t(FdGuard.Release());
return Data{.Handle = hMap, .DataPtr = pBuf, .Size = Size, .Name = std::string(Name)};
#endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
}
@@ -3388,23 +3407,22 @@ public:
ZEN_UNUSED(SystemGlobal);
std::string InstanceMapName = fmt::format("/{}", Name);
- int Fd = shm_open(InstanceMapName.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666);
- if (Fd < 0)
+ ScopedFd FdGuard(shm_open(InstanceMapName.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666));
+ if (!FdGuard)
{
return {};
}
- fchmod(Fd, 0666);
- void* hMap = (void*)intptr_t(Fd);
+ fchmod(FdGuard.Fd, 0666);
- int Result = ftruncate(Fd, Size);
+ int Result = ftruncate(FdGuard.Fd, Size);
ZEN_UNUSED(Result);
- void* pBuf = mmap(nullptr, Size, PROT_READ | PROT_WRITE, MAP_SHARED, Fd, 0);
+ void* pBuf = mmap(nullptr, Size, PROT_READ | PROT_WRITE, MAP_SHARED, FdGuard.Fd, 0);
if (pBuf == MAP_FAILED)
{
- close(Fd);
return {};
}
+ void* hMap = (void*)intptr_t(FdGuard.Release());
return Data{.Handle = hMap, .DataPtr = pBuf, .Size = Size, .Name = std::string(Name)};
#endif // ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
}
@@ -3945,13 +3963,13 @@ TEST_CASE("CloneQueryInterface")
CHECK(DstHandle != INVALID_HANDLE_VALUE);
void* DstNativeHandle = (void*)DstHandle.m_Handle;
# else
- int SrcFd = open(SrcPath.c_str(), O_RDONLY | O_CLOEXEC);
- CHECK(SrcFd >= 0);
- void* SrcNativeHandle = (void*)uintptr_t(SrcFd);
+ ScopedFd SrcFd(open(SrcPath.c_str(), O_RDONLY | O_CLOEXEC));
+ CHECK(bool(SrcFd));
+ void* SrcNativeHandle = (void*)uintptr_t(SrcFd.Fd);
- int DstFd = open(DstPath.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666);
- CHECK(DstFd >= 0);
- void* DstNativeHandle = (void*)uintptr_t(DstFd);
+ ScopedFd DstFd(open(DstPath.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666));
+ CHECK(bool(DstFd));
+ void* DstNativeHandle = (void*)uintptr_t(DstFd.Fd);
# endif
SUBCASE("CanClone returns true for same volume") { CHECK(CloneQuery->CanClone(SrcNativeHandle)); }
@@ -3983,19 +4001,17 @@ TEST_CASE("CloneQueryInterface")
Ov.OffsetHigh = (DWORD)(Clonable >> 32);
::WriteFile(DstHandle, SrcData, (DWORD)PostBytes, &Written, &Ov);
# else
- pwrite(DstFd, SrcData, PostBytes, Clonable);
+ pwrite(DstFd.Fd, SrcData, PostBytes, Clonable);
# endif
}
- // Verify the cloned file content matches
+ // Close handles before reading back the file for verification
# if ZEN_PLATFORM_WINDOWS
SrcHandle.Close();
DstHandle.Close();
# else
- close(SrcFd);
- close(DstFd);
- SrcFd = -1;
- DstFd = -1;
+ SrcFd = ScopedFd();
+ DstFd = ScopedFd();
# endif
FileContents DstContents = ReadFile(DstPath);
@@ -4006,20 +4022,6 @@ TEST_CASE("CloneQueryInterface")
}
}
}
-
- // Cleanup handles if still open
-# if ZEN_PLATFORM_WINDOWS
- // windows::Handle auto-closes
-# else
- if (SrcFd >= 0)
- {
- close(SrcFd);
- }
- if (DstFd >= 0)
- {
- close(DstFd);
- }
-# endif
}
else
{