Skip to content

Commit

Permalink
fs/io/FileOutputStream: use O_TMPFILE if available
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
MaxKellermann committed Jan 6, 2015
1 parent ac62586 commit 8b217d5
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 8 deletions.
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
66 changes: 59 additions & 7 deletions src/fs/io/FileOutputStream.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -128,7 +177,10 @@ FileOutputStream::Cancel()
close(fd);
fd = -1;

RemoveFile(path);
#ifdef HAVE_LINKAT
if (!is_tmpfile)
#endif
RemoveFile(path);
}

#endif
8 changes: 8 additions & 0 deletions src/fs/io/FileOutputStream.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down

0 comments on commit 8b217d5

Please sign in to comment.