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 "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)
: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:

View File

@ -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();