fs/io/FileOutputStream: add constructor with directory fd

This commit is contained in:
Max Kellermann 2019-01-21 21:07:34 +01:00
parent dee8872395
commit cf23fd8774
2 changed files with 52 additions and 8 deletions

View File

@ -31,8 +31,27 @@
#include "system/Error.hxx" #include "system/Error.hxx"
#include "util/StringFormat.hxx" #include "util/StringFormat.hxx"
#ifdef __linux__
#include <fcntl.h>
#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) FileOutputStream::FileOutputStream(Path _path, Mode _mode)
:path(_path), mode(_mode) :path(_path),
#ifdef __linux__
directory_fd(AT_FDCWD),
#endif
mode(_mode)
{ {
Open(); Open();
} }
@ -155,8 +174,12 @@ FileOutputStream::Cancel() noexcept
* Open a file using Linux's O_TMPFILE for writing the given file. * Open a file using Linux's O_TMPFILE for writing the given file.
*/ */
static bool 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(); const auto directory = path.GetDirectoryName();
if (directory.IsNull()) if (directory.IsNull())
return false; return false;
@ -171,11 +194,15 @@ FileOutputStream::OpenCreate(bool visible)
{ {
#ifdef HAVE_O_TMPFILE #ifdef HAVE_O_TMPFILE
/* try Linux's O_TMPFILE first */ /* try Linux's O_TMPFILE first */
is_tmpfile = !visible && OpenTempFile(fd, GetPath()); is_tmpfile = !visible && OpenTempFile(directory_fd, fd, GetPath());
if (!is_tmpfile) { if (!is_tmpfile) {
#endif #endif
/* fall back to plain POSIX */ /* 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, O_WRONLY|O_CREAT|O_TRUNC,
0666)) 0666))
throw FormatErrno("Failed to create %s", throw FormatErrno("Failed to create %s",
@ -194,7 +221,11 @@ FileOutputStream::OpenAppend(bool create)
if (create) if (create)
flags |= O_CREAT; 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", throw FormatErrno("Failed to append to %s",
path.c_str()); path.c_str());
} }
@ -225,12 +256,12 @@ FileOutputStream::Commit()
#ifdef HAVE_O_TMPFILE #ifdef HAVE_O_TMPFILE
if (is_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 */ /* hard-link the temporary file to the final path */
if (linkat(AT_FDCWD, if (linkat(AT_FDCWD,
StringFormat<64>("/proc/self/fd/%d", fd.Get()), StringFormat<64>("/proc/self/fd/%d", fd.Get()),
AT_FDCWD, path.c_str(), directory_fd.Get(), path.c_str(),
AT_SYMLINK_FOLLOW) < 0) AT_SYMLINK_FOLLOW) < 0)
throw FormatErrno("Failed to commit %s", throw FormatErrno("Failed to commit %s",
path.c_str()); path.c_str());
@ -259,7 +290,11 @@ FileOutputStream::Cancel() noexcept
#ifdef HAVE_O_TMPFILE #ifdef HAVE_O_TMPFILE
if (!is_tmpfile) if (!is_tmpfile)
#endif #endif
#ifdef __linux__
unlinkat(directory_fd.Get(), GetPath().c_str(), 0);
#else
unlink(GetPath().c_str()); unlink(GetPath().c_str());
#endif
break; break;
case Mode::CREATE_VISIBLE: case Mode::CREATE_VISIBLE:

View File

@ -59,6 +59,10 @@ class Path;
class FileOutputStream final : public OutputStream { class FileOutputStream final : public OutputStream {
const AllocatedPath path; const AllocatedPath path;
#ifdef __linux__
const FileDescriptor directory_fd;
#endif
#ifdef _WIN32 #ifdef _WIN32
HANDLE handle = INVALID_HANDLE_VALUE; HANDLE handle = INVALID_HANDLE_VALUE;
#else #else
@ -108,6 +112,11 @@ private:
public: public:
explicit FileOutputStream(Path _path, Mode _mode=Mode::CREATE); explicit FileOutputStream(Path _path, Mode _mode=Mode::CREATE);
#ifdef __linux__
FileOutputStream(FileDescriptor _directory_fd, Path _path,
Mode _mode=Mode::CREATE);
#endif
~FileOutputStream() noexcept { ~FileOutputStream() noexcept {
if (IsDefined()) if (IsDefined())
Cancel(); Cancel();