aboutsummaryrefslogtreecommitdiffstats
path: root/src/fs/io/FileOutputStream.cxx
diff options
context:
space:
mode:
authorMax Kellermann <max@duempel.org>2015-01-05 20:24:59 +0100
committerMax Kellermann <max@duempel.org>2015-01-06 19:38:55 +0100
commit8b217d531305433df01c789dab4dc81d58f05eba (patch)
treee81465cd429cfeaf12d49a4e128a8bcc703a4e9d /src/fs/io/FileOutputStream.cxx
parentac62586badd439dfcc43df6a895f2988eb784e6d (diff)
downloadmpd-8b217d531305433df01c789dab4dc81d58f05eba.tar.gz
mpd-8b217d531305433df01c789dab4dc81d58f05eba.tar.xz
mpd-8b217d531305433df01c789dab4dc81d58f05eba.zip
fs/io/FileOutputStream: use O_TMPFILE if available
The Linux feature allows writing new files to an invisible file, and then replace the old file. This preserves the old file if we get interrupted by some event.
Diffstat (limited to 'src/fs/io/FileOutputStream.cxx')
-rw-r--r--src/fs/io/FileOutputStream.cxx66
1 files changed, 59 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