fs/io/FileOutputStream: merge all classes into one, add enum Mode
Prepare to add more modes.
This commit is contained in:
		| @@ -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, | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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<typename P> | ||||
| 	BaseFileOutputStream(P &&_path) | ||||
| 		:path(std::forward<P>(_path)) {} | ||||
| #else | ||||
| 	template<typename P> | ||||
| 	BaseFileOutputStream(P &&_path) | ||||
| 		:path(std::forward<P>(_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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Max Kellermann
					Max Kellermann