aboutsummaryrefslogtreecommitdiffstats
path: root/src/fs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/fs/io/FileOutputStream.cxx66
-rw-r--r--src/fs/io/FileOutputStream.hxx8
2 files changed, 67 insertions, 7 deletions
diff --git a/src/fs/io/FileOutputStream.cxx b/src/fs/io/FileOutputStream.cxx
index 2951149b9..7a6416557 100644
--- a/src/fs/io/FileOutputStream.cxx
+++ b/src/fs/io/FileOutputStream.cxx
@@ -79,14 +79,47 @@ FileOutputStream::Cancel()
#include <unistd.h>
#include <errno.h>
+#ifdef HAVE_LINKAT
+#ifndef O_TMPFILE
+/* supported since Linux 3.11 */
+#define __O_TMPFILE 020000000
+#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)
+#include <stdio.h>
+#endif
+
+/**
+ * Open a file using Linux's O_TMPFILE for writing the given file.
+ */
+static int
+OpenTempFile(Path path)
+{
+ const auto directory = path.GetDirectoryName();
+ if (directory.IsNull())
+ return -1;
+
+ return OpenFile(directory, O_TMPFILE|O_WRONLY, 0666);
+}
+
+#endif /* HAVE_LINKAT */
+
FileOutputStream::FileOutputStream(Path _path, Error &error)
- :path(_path),
- fd(OpenFile(path,
- O_WRONLY|O_CREAT|O_TRUNC,
- 0666))
+ :path(_path)
{
- if (fd < 0)
- error.FormatErrno("Failed to create %s", path.c_str());
+#ifdef HAVE_LINKAT
+ /* try Linux's O_TMPFILE first */
+ fd = OpenTempFile(path);
+ is_tmpfile = fd >= 0;
+ if (!is_tmpfile) {
+#endif
+ /* fall back to plain POSIX */
+ fd = OpenFile(path,
+ O_WRONLY|O_CREAT|O_TRUNC,
+ 0666);
+ if (fd < 0)
+ error.FormatErrno("Failed to create %s", path.c_str());
+#ifdef HAVE_LINKAT
+ }
+#endif
}
bool
@@ -112,6 +145,22 @@ FileOutputStream::Commit(Error &error)
{
assert(IsDefined());
+#if HAVE_LINKAT
+ if (is_tmpfile) {
+ RemoveFile(path);
+
+ /* hard-link the temporary file to the final path */
+ char fd_path[64];
+ snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", fd);
+ if (linkat(AT_FDCWD, fd_path, AT_FDCWD, path.c_str(),
+ AT_SYMLINK_FOLLOW) < 0) {
+ error.FormatErrno("Failed to commit %s", path.c_str());
+ close(fd);
+ return false;
+ }
+ }
+#endif
+
bool success = close(fd) == 0;
fd = -1;
if (!success)
@@ -128,7 +177,10 @@ FileOutputStream::Cancel()
close(fd);
fd = -1;
- RemoveFile(path);
+#ifdef HAVE_LINKAT
+ if (!is_tmpfile)
+#endif
+ RemoveFile(path);
}
#endif
diff --git a/src/fs/io/FileOutputStream.hxx b/src/fs/io/FileOutputStream.hxx
index 5ac2f9e15..03d062134 100644
--- a/src/fs/io/FileOutputStream.hxx
+++ b/src/fs/io/FileOutputStream.hxx
@@ -42,6 +42,14 @@ class FileOutputStream final : public OutputStream {
int fd;
#endif
+#ifdef HAVE_LINKAT
+ /**
+ * Was O_TMPFILE used? If yes, then linkat() must be used to
+ * create a link to this file.
+ */
+ bool is_tmpfile;
+#endif
+
public:
FileOutputStream(Path _path, Error &error);