diff options
Diffstat (limited to 'src/fs/io/FileOutputStream.cxx')
-rw-r--r-- | src/fs/io/FileOutputStream.cxx | 187 |
1 files changed, 154 insertions, 33 deletions
diff --git a/src/fs/io/FileOutputStream.cxx b/src/fs/io/FileOutputStream.cxx index 0eff8b5f0..a4ef8f6b4 100644 --- a/src/fs/io/FileOutputStream.cxx +++ b/src/fs/io/FileOutputStream.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,36 +20,61 @@ #include "config.h" #include "FileOutputStream.hxx" #include "fs/FileSystem.hxx" -#include "system/fd_util.h" #include "util/Error.hxx" +FileOutputStream * +FileOutputStream::Create(Path path, Error &error) +{ + FileOutputStream *f = new FileOutputStream(path, error); + if (!f->IsDefined()) { + delete f; + f = nullptr; + } + + return f; +} + #ifdef WIN32 FileOutputStream::FileOutputStream(Path _path, Error &error) - :path(_path), - handle(CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr, - TRUNCATE_EXISTING, - FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, - nullptr)) + :BaseFileOutputStream(_path) { - if (handle == INVALID_HANDLE_VALUE) - error.FormatLastError("Failed to create %s", path.c_str()); + SetHandle(CreateFile(_path.c_str(), GENERIC_WRITE, 0, nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, + nullptr)); + if (!IsDefined()) + error.FormatLastError("Failed to create %s", + GetPath().ToUTF8().c_str()); +} + +uint64_t +BaseFileOutputStream::Tell() const +{ + LONG high = 0; + DWORD low = SetFilePointer(handle, 0, &high, FILE_CURRENT); + if (low == 0xffffffff) + return 0; + + return uint64_t(high) << 32 | uint64_t(low); } bool -FileOutputStream::Write(const void *data, size_t size, Error &error) +BaseFileOutputStream::Write(const void *data, size_t size, Error &error) { assert(IsDefined()); DWORD nbytes; if (!WriteFile(handle, data, size, &nbytes, nullptr)) { - error.FormatLastError("Failed to write to %s", path.c_str()); + error.FormatLastError("Failed to write to %s", + path.ToUTF8().c_str()); return false; } if (size_t(nbytes) != size) { error.FormatLastError(ERROR_DISK_FULL, - "Failed to write to %s", path.c_str()); + "Failed to write to %s", + path.ToUTF8().c_str()); return false; } @@ -61,8 +86,7 @@ FileOutputStream::Commit(gcc_unused Error &error) { assert(IsDefined()); - CloseHandle(handle); - handle = INVALID_HANDLE_VALUE; + Close(); return true; } @@ -71,9 +95,8 @@ FileOutputStream::Cancel() { assert(IsDefined()); - CloseHandle(handle); - handle = INVALID_HANDLE_VALUE; - RemoveFile(path); + Close(); + RemoveFile(GetPath()); } #else @@ -82,28 +105,66 @@ 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 bool +OpenTempFile(FileDescriptor &fd, Path path) +{ + const auto directory = path.GetDirectoryName(); + if (directory.IsNull()) + return false; + + return fd.Open(directory.c_str(), O_TMPFILE|O_WRONLY, 0666); +} + +#endif /* HAVE_LINKAT */ + FileOutputStream::FileOutputStream(Path _path, Error &error) - :path(_path), - fd(open_cloexec(path.c_str(), - O_WRONLY|O_CREAT|O_TRUNC, - 0666)) + :BaseFileOutputStream(_path) +{ +#ifdef HAVE_LINKAT + /* try Linux's O_TMPFILE first */ + is_tmpfile = OpenTempFile(SetFD(), GetPath()); + if (!is_tmpfile) { +#endif + /* fall back to plain POSIX */ + if (!SetFD().Open(GetPath().c_str(), + O_WRONLY|O_CREAT|O_TRUNC, + 0666)) + error.FormatErrno("Failed to create %s", + GetPath().c_str()); +#ifdef HAVE_LINKAT + } +#endif +} + +uint64_t +BaseFileOutputStream::Tell() const { - if (fd < 0) - error.FormatErrno("Failed to create %s", path.c_str()); + return fd.Tell(); } bool -FileOutputStream::Write(const void *data, size_t size, Error &error) +BaseFileOutputStream::Write(const void *data, size_t size, Error &error) { assert(IsDefined()); - ssize_t nbytes = write(fd, data, size); + ssize_t nbytes = fd.Write(data, size); if (nbytes < 0) { - error.FormatErrno("Failed to write to %s", path.c_str()); + error.FormatErrno("Failed to write to %s", GetPath().c_str()); return false; } else if ((size_t)nbytes < size) { error.FormatErrno(ENOSPC, - "Failed to write to %s", path.c_str()); + "Failed to write to %s", GetPath().c_str()); return false; } @@ -115,10 +176,27 @@ FileOutputStream::Commit(Error &error) { assert(IsDefined()); - bool success = close(fd) == 0; - fd = -1; +#if HAVE_LINKAT + if (is_tmpfile) { + RemoveFile(GetPath()); + + /* hard-link the temporary file to the final path */ + char fd_path[64]; + snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", + GetFD().Get()); + if (linkat(AT_FDCWD, fd_path, AT_FDCWD, GetPath().c_str(), + AT_SYMLINK_FOLLOW) < 0) { + error.FormatErrno("Failed to commit %s", + GetPath().c_str()); + Close(); + return false; + } + } +#endif + + bool success = Close(); if (!success) - error.FormatErrno("Failed to commit %s", path.c_str()); + error.FormatErrno("Failed to commit %s", GetPath().c_str()); return success; } @@ -128,10 +206,53 @@ FileOutputStream::Cancel() { assert(IsDefined()); - close(fd); - fd = -1; + Close(); + +#ifdef HAVE_LINKAT + if (!is_tmpfile) +#endif + RemoveFile(GetPath()); +} + +#endif + +AppendFileOutputStream::AppendFileOutputStream(Path _path, Error &error) + :BaseFileOutputStream(_path) +{ +#ifdef WIN32 + SetHandle(CreateFile(GetPath().c_str(), GENERIC_WRITE, 0, nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, + nullptr)); + if (!IsDefined()) + error.FormatLastError("Failed to append to %s", + GetPath().ToUTF8().c_str()); - RemoveFile(path); + if (!SeekEOF()) { + error.FormatLastError("Failed seek end-of-file of %s", + GetPath().ToUTF8().c_str()); + Close(); + } +#else + if (!SetFD().Open(GetPath().c_str(), + O_WRONLY|O_APPEND)) + error.FormatErrno("Failed to append to %s", + GetPath().c_str()); +#endif } +bool +AppendFileOutputStream::Commit(gcc_unused Error &error) +{ + assert(IsDefined()); + +#ifdef WIN32 + return Close(); +#else + bool success = Close(); + if (!success) + error.FormatErrno("Failed to commit %s", GetPath().c_str()); + + return success; #endif +} |