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.
This commit is contained in:
Max Kellermann 2015-01-05 20:24:59 +01:00
parent ac62586bad
commit 8b217d5313
4 changed files with 70 additions and 9 deletions

1
NEWS
View File

@ -18,6 +18,7 @@ ver 0.20 (not yet released)
* mixer * mixer
- null: new plugin - null: new plugin
* reset song priority on playback * reset song priority on playback
* write database and state file atomically
* remove dependency on GLib * remove dependency on GLib
ver 0.19.8 (not yet released) ver 0.19.8 (not yet released)

View File

@ -219,7 +219,7 @@ AC_SEARCH_LIBS([socket], [socket])
AC_SEARCH_LIBS([gethostbyname], [nsl]) AC_SEARCH_LIBS([gethostbyname], [nsl])
if test x$host_is_linux = xyes; then if test x$host_is_linux = xyes; then
AC_CHECK_FUNCS(pipe2 accept4) AC_CHECK_FUNCS(pipe2 accept4 linkat)
fi fi
AC_CHECK_FUNCS(getpwnam_r getpwuid_r) AC_CHECK_FUNCS(getpwnam_r getpwuid_r)

View File

@ -79,14 +79,47 @@ FileOutputStream::Cancel()
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
FileOutputStream::FileOutputStream(Path _path, Error &error) #ifdef HAVE_LINKAT
:path(_path), #ifndef O_TMPFILE
fd(OpenFile(path, /* supported since Linux 3.11 */
O_WRONLY|O_CREAT|O_TRUNC, #define __O_TMPFILE 020000000
0666)) #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)
{ {
if (fd < 0) const auto directory = path.GetDirectoryName();
error.FormatErrno("Failed to create %s", path.c_str()); if (directory.IsNull())
return -1;
return OpenFile(directory, O_TMPFILE|O_WRONLY, 0666);
}
#endif /* HAVE_LINKAT */
FileOutputStream::FileOutputStream(Path _path, Error &error)
:path(_path)
{
#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 bool
@ -112,6 +145,22 @@ FileOutputStream::Commit(Error &error)
{ {
assert(IsDefined()); 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; bool success = close(fd) == 0;
fd = -1; fd = -1;
if (!success) if (!success)
@ -128,7 +177,10 @@ FileOutputStream::Cancel()
close(fd); close(fd);
fd = -1; fd = -1;
RemoveFile(path); #ifdef HAVE_LINKAT
if (!is_tmpfile)
#endif
RemoveFile(path);
} }
#endif #endif

View File

@ -42,6 +42,14 @@ class FileOutputStream final : public OutputStream {
int fd; int fd;
#endif #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: public:
FileOutputStream(Path _path, Error &error); FileOutputStream(Path _path, Error &error);