diff --git a/src/PlaylistFile.cxx b/src/PlaylistFile.cxx index 9c73a6030..069b1fce8 100644 --- a/src/PlaylistFile.cxx +++ b/src/PlaylistFile.cxx @@ -199,7 +199,6 @@ SavePlaylistFile(const PlaylistFileContents &contents, const char *utf8path) assert(!path_fs.IsNull()); FileOutputStream fos(path_fs); - BufferedOutputStream bos(fos); for (const auto &uri_utf8 : contents) @@ -354,7 +353,7 @@ try { const auto path_fs = spl_map_to_fs(utf8path); assert(!path_fs.IsNull()); - AppendFileOutputStream fos(path_fs); + FileOutputStream fos(path_fs, FileOutputStream::Mode::APPEND_EXISTING); if (fos.Tell() / (MPD_PATH_MAX + 1) >= playlist_max_length) throw PlaylistError(PlaylistResult::TOO_LARGE, diff --git a/src/fs/io/FileOutputStream.cxx b/src/fs/io/FileOutputStream.cxx index d0375fe0a..7f9baf9f7 100644 --- a/src/fs/io/FileOutputStream.cxx +++ b/src/fs/io/FileOutputStream.cxx @@ -21,22 +21,56 @@ #include "FileOutputStream.hxx" #include "system/Error.hxx" +FileOutputStream::FileOutputStream(Path _path, Mode _mode) + :path(_path), mode(_mode) +{ + switch (mode) { + case Mode::CREATE: + OpenCreate(); + break; + + case Mode::APPEND_EXISTING: + OpenAppendExisting(); + break; + } +} + #ifdef WIN32 -FileOutputStream::FileOutputStream(Path _path) - :BaseFileOutputStream(_path) +inline void +FileOutputStream::OpenCreate() { - SetHandle(CreateFile(_path.c_str(), GENERIC_WRITE, 0, nullptr, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, - nullptr)); + handle = CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, + nullptr); if (!IsDefined()) throw FormatLastError("Failed to create %s", - GetPath().ToUTF8().c_str()); + path.ToUTF8().c_str()); +} + +inline void +FileOutputStream::OpenAppendExisting() +{ + handle = CreateFile(path.c_str(), GENERIC_WRITE, 0, nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, + nullptr); + if (!IsDefined()) + throw FormatLastError("Failed to append to %s", + path.ToUTF8().c_str()); + + if (!SeekEOF()) { + auto code = GetLastError(); + Close(); + throw FormatLastError(code, "Failed seek end-of-file of %s", + path.ToUTF8().c_str()); + } + } uint64_t -BaseFileOutputStream::Tell() const +FileOutputStream::Tell() const { LONG high = 0; DWORD low = SetFilePointer(handle, 0, &high, FILE_CURRENT); @@ -47,7 +81,7 @@ BaseFileOutputStream::Tell() const } void -BaseFileOutputStream::Write(const void *data, size_t size) +FileOutputStream::Write(const void *data, size_t size) { assert(IsDefined()); @@ -108,18 +142,18 @@ OpenTempFile(FileDescriptor &fd, Path path) #endif /* HAVE_LINKAT */ -FileOutputStream::FileOutputStream(Path _path) - :BaseFileOutputStream(_path) +inline void +FileOutputStream::OpenCreate() { #ifdef HAVE_LINKAT /* try Linux's O_TMPFILE first */ - is_tmpfile = OpenTempFile(SetFD(), GetPath()); + is_tmpfile = OpenTempFile(fd, GetPath()); if (!is_tmpfile) { #endif /* fall back to plain POSIX */ - if (!SetFD().Open(GetPath().c_str(), - O_WRONLY|O_CREAT|O_TRUNC, - 0666)) + if (!fd.Open(GetPath().c_str(), + O_WRONLY|O_CREAT|O_TRUNC, + 0666)) throw FormatErrno("Failed to create %s", GetPath().c_str()); #ifdef HAVE_LINKAT @@ -127,14 +161,22 @@ FileOutputStream::FileOutputStream(Path _path) #endif } +inline void +FileOutputStream::OpenAppendExisting() +{ + if (!fd.Open(path.c_str(), O_WRONLY|O_APPEND)) + throw FormatErrno("Failed to append to %s", + path.c_str()); +} + uint64_t -BaseFileOutputStream::Tell() const +FileOutputStream::Tell() const { return fd.Tell(); } void -BaseFileOutputStream::Write(const void *data, size_t size) +FileOutputStream::Write(const void *data, size_t size) { assert(IsDefined()); @@ -158,20 +200,20 @@ FileOutputStream::Commit() /* 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(), + fd.Get()); + if (linkat(AT_FDCWD, fd_path, AT_FDCWD, path.c_str(), AT_SYMLINK_FOLLOW) < 0) throw FormatErrno("Failed to commit %s", - GetPath().c_str()); + path.c_str()); } #endif if (!Close()) { #ifdef WIN32 throw FormatLastError("Failed to commit %s", - GetPath().ToUTF8().c_str()); + path.ToUTF8().c_str()); #else - throw FormatErrno("Failed to commit %s", GetPath().c_str()); + throw FormatErrno("Failed to commit %s", path.c_str()); #endif } } @@ -183,51 +225,18 @@ FileOutputStream::Cancel() Close(); + switch (mode) { + case Mode::CREATE: #ifdef HAVE_LINKAT - if (!is_tmpfile) + if (!is_tmpfile) #endif - unlink(GetPath().c_str()); -} + unlink(GetPath().c_str()); + break; -#endif - -AppendFileOutputStream::AppendFileOutputStream(Path _path) - :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()) - throw FormatLastError("Failed to append to %s", - GetPath().ToUTF8().c_str()); - - if (!SeekEOF()) { - auto code = GetLastError(); - Close(); - throw FormatLastError(code, "Failed seek end-of-file of %s", - GetPath().ToUTF8().c_str()); - } -#else - if (!SetFD().Open(GetPath().c_str(), - O_WRONLY|O_APPEND)) - throw FormatErrno("Failed to append to %s", - GetPath().c_str()); -#endif -} - -void -AppendFileOutputStream::Commit() -{ - assert(IsDefined()); - - if (!Close()) { -#ifdef WIN32 - throw FormatLastError("Failed to commit %s", - GetPath().ToUTF8().c_str()); -#else - throw FormatErrno("Failed to commit %s", GetPath().c_str()); -#endif + case Mode::APPEND_EXISTING: + /* can't roll this back */ + break; } } + +#endif diff --git a/src/fs/io/FileOutputStream.hxx b/src/fs/io/FileOutputStream.hxx index e9f148701..3857aa7a5 100644 --- a/src/fs/io/FileOutputStream.hxx +++ b/src/fs/io/FileOutputStream.hxx @@ -38,7 +38,7 @@ class Path; -class BaseFileOutputStream : public OutputStream { +class FileOutputStream final : public OutputStream { const AllocatedPath path; #ifdef WIN32 @@ -47,40 +47,58 @@ class BaseFileOutputStream : public OutputStream { FileDescriptor fd = FileDescriptor::Undefined(); #endif -protected: -#ifdef WIN32 - template - BaseFileOutputStream(P &&_path) - :path(std::forward

(_path)) {} -#else - template - BaseFileOutputStream(P &&_path) - :path(std::forward

(_path)) {} +#ifdef HAVE_LINKAT + /** + * Was O_TMPFILE used? If yes, then linkat() must be used to + * create a link to this file. + */ + bool is_tmpfile = false; #endif - ~BaseFileOutputStream() { - assert(!IsDefined()); +public: + enum class Mode : uint8_t { + /** + * Create a new file, or replace an existing file. + * File contents may not be visible until Commit() has + * been called. + */ + CREATE, + + /** + * Append to a file that already exists. If it does + * not, an exception is thrown. + */ + APPEND_EXISTING, + }; + +private: + Mode mode; + +public: + FileOutputStream(Path _path, Mode _mode=Mode::CREATE); + + ~FileOutputStream() { + if (IsDefined()) + Cancel(); } -#ifdef WIN32 - void SetHandle(HANDLE _handle) { - assert(!IsDefined()); - - handle = _handle; - - assert(IsDefined()); - } -#else - FileDescriptor &SetFD() { - assert(!IsDefined()); - - return fd; +public: + Path GetPath() const { + return path; } - const FileDescriptor &GetFD() const { - return fd; - } -#endif + gcc_pure + uint64_t Tell() const; + + /* virtual methods from class OutputStream */ + void Write(const void *data, size_t size) override; + + void Commit(); + void Cancel(); + +private: + void OpenCreate(); + void OpenAppendExisting(); bool Close() { assert(IsDefined()); @@ -108,50 +126,6 @@ protected: return fd.IsDefined(); #endif } - -public: - Path GetPath() const { - return path; - } - - gcc_pure - uint64_t Tell() const; - - /* virtual methods from class OutputStream */ - void Write(const void *data, size_t size) override; -}; - -class FileOutputStream final : public BaseFileOutputStream { -#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); - - ~FileOutputStream() { - if (IsDefined()) - Cancel(); - } - - void Commit(); - void Cancel(); -}; - -class AppendFileOutputStream final : public BaseFileOutputStream { -public: - AppendFileOutputStream(Path _path); - - ~AppendFileOutputStream() { - if (IsDefined()) - Close(); - } - - void Commit(); }; #endif