diff --git a/src/fs/io/FileOutputStream.cxx b/src/fs/io/FileOutputStream.cxx index 0ee62a092..83518e177 100644 --- a/src/fs/io/FileOutputStream.cxx +++ b/src/fs/io/FileOutputStream.cxx @@ -31,8 +31,27 @@ #include "system/Error.hxx" #include "util/StringFormat.hxx" +#ifdef __linux__ +#include +#endif + +#ifdef __linux__ +FileOutputStream::FileOutputStream(FileDescriptor _directory_fd, + Path _path, Mode _mode) + :path(_path), + directory_fd(_directory_fd), + mode(_mode) +{ + Open(); +} +#endif + FileOutputStream::FileOutputStream(Path _path, Mode _mode) - :path(_path), mode(_mode) + :path(_path), +#ifdef __linux__ + directory_fd(AT_FDCWD), +#endif + mode(_mode) { Open(); } @@ -155,8 +174,12 @@ FileOutputStream::Cancel() noexcept * Open a file using Linux's O_TMPFILE for writing the given file. */ static bool -OpenTempFile(FileDescriptor &fd, Path path) +OpenTempFile(FileDescriptor directory_fd, + FileDescriptor &fd, Path path) { + if (directory_fd != FileDescriptor(AT_FDCWD)) + return fd.Open(directory_fd, ".", O_TMPFILE|O_WRONLY, 0666); + const auto directory = path.GetDirectoryName(); if (directory.IsNull()) return false; @@ -171,11 +194,15 @@ FileOutputStream::OpenCreate(bool visible) { #ifdef HAVE_O_TMPFILE /* try Linux's O_TMPFILE first */ - is_tmpfile = !visible && OpenTempFile(fd, GetPath()); + is_tmpfile = !visible && OpenTempFile(directory_fd, fd, GetPath()); if (!is_tmpfile) { #endif /* fall back to plain POSIX */ - if (!fd.Open(GetPath().c_str(), + if (!fd.Open( +#ifdef __linux__ + directory_fd, +#endif + GetPath().c_str(), O_WRONLY|O_CREAT|O_TRUNC, 0666)) throw FormatErrno("Failed to create %s", @@ -194,7 +221,11 @@ FileOutputStream::OpenAppend(bool create) if (create) flags |= O_CREAT; - if (!fd.Open(path.c_str(), flags)) + if (!fd.Open( +#ifdef __linux__ + directory_fd, +#endif + path.c_str(), flags)) throw FormatErrno("Failed to append to %s", path.c_str()); } @@ -225,12 +256,12 @@ FileOutputStream::Commit() #ifdef HAVE_O_TMPFILE if (is_tmpfile) { - unlink(GetPath().c_str()); + unlinkat(directory_fd.Get(), GetPath().c_str(), 0); /* hard-link the temporary file to the final path */ if (linkat(AT_FDCWD, StringFormat<64>("/proc/self/fd/%d", fd.Get()), - AT_FDCWD, path.c_str(), + directory_fd.Get(), path.c_str(), AT_SYMLINK_FOLLOW) < 0) throw FormatErrno("Failed to commit %s", path.c_str()); @@ -259,7 +290,11 @@ FileOutputStream::Cancel() noexcept #ifdef HAVE_O_TMPFILE if (!is_tmpfile) #endif - unlink(GetPath().c_str()); +#ifdef __linux__ + unlinkat(directory_fd.Get(), GetPath().c_str(), 0); +#else + unlink(GetPath().c_str()); +#endif break; case Mode::CREATE_VISIBLE: diff --git a/src/fs/io/FileOutputStream.hxx b/src/fs/io/FileOutputStream.hxx index a17350adc..73ac4f6f8 100644 --- a/src/fs/io/FileOutputStream.hxx +++ b/src/fs/io/FileOutputStream.hxx @@ -59,6 +59,10 @@ class Path; class FileOutputStream final : public OutputStream { const AllocatedPath path; +#ifdef __linux__ + const FileDescriptor directory_fd; +#endif + #ifdef _WIN32 HANDLE handle = INVALID_HANDLE_VALUE; #else @@ -108,6 +112,11 @@ private: public: explicit FileOutputStream(Path _path, Mode _mode=Mode::CREATE); +#ifdef __linux__ + FileOutputStream(FileDescriptor _directory_fd, Path _path, + Mode _mode=Mode::CREATE); +#endif + ~FileOutputStream() noexcept { if (IsDefined()) Cancel();