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:
parent
ac62586bad
commit
8b217d5313
1
NEWS
1
NEWS
|
@ -18,6 +18,7 @@ ver 0.20 (not yet released)
|
|||
* mixer
|
||||
- null: new plugin
|
||||
* reset song priority on playback
|
||||
* write database and state file atomically
|
||||
* remove dependency on GLib
|
||||
|
||||
ver 0.19.8 (not yet released)
|
||||
|
|
|
@ -219,7 +219,7 @@ AC_SEARCH_LIBS([socket], [socket])
|
|||
AC_SEARCH_LIBS([gethostbyname], [nsl])
|
||||
|
||||
if test x$host_is_linux = xyes; then
|
||||
AC_CHECK_FUNCS(pipe2 accept4)
|
||||
AC_CHECK_FUNCS(pipe2 accept4 linkat)
|
||||
fi
|
||||
|
||||
AC_CHECK_FUNCS(getpwnam_r getpwuid_r)
|
||||
|
|
|
@ -79,14 +79,47 @@ FileOutputStream::Cancel()
|
|||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
FileOutputStream::FileOutputStream(Path _path, Error &error)
|
||||
:path(_path),
|
||||
fd(OpenFile(path,
|
||||
O_WRONLY|O_CREAT|O_TRUNC,
|
||||
0666))
|
||||
#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)
|
||||
{
|
||||
if (fd < 0)
|
||||
error.FormatErrno("Failed to create %s", path.c_str());
|
||||
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)
|
||||
{
|
||||
#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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue