From e6e7d6dbd654b9936b04f27b8153fb4ed945b9d3 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Wed, 16 Dec 2015 11:05:33 +0100
Subject: [PATCH] fs/io/Reader: use C++ exceptions instead of class Error

---
 src/CommandLine.cxx                           | 17 ++---
 src/Main.cxx                                  |  7 +-
 src/PlaylistFile.cxx                          |  6 +-
 src/StateFile.cxx                             | 11 ++-
 src/config/ConfigFile.cxx                     | 44 +++++-------
 src/config/ConfigFile.hxx                     |  4 +-
 src/config/ConfigGlobal.cxx                   |  6 +-
 src/config/ConfigGlobal.hxx                   |  4 +-
 .../plugins/simple/SimpleDatabasePlugin.cxx   | 11 +--
 src/db/update/ExcludeList.cxx                 | 36 +++-------
 src/fs/FileInfo.hxx                           | 15 +++++
 src/fs/StandardDirectory.cxx                  |  9 +--
 src/fs/io/AutoGunzipReader.cxx                | 34 ++++------
 src/fs/io/AutoGunzipReader.hxx                |  4 +-
 src/fs/io/BufferedReader.cxx                  | 10 +--
 src/fs/io/BufferedReader.hxx                  | 17 -----
 src/fs/io/FileReader.cxx                      | 67 ++++++++-----------
 src/fs/io/FileReader.hxx                      | 11 +--
 src/fs/io/GunzipReader.cxx                    | 32 +++------
 src/fs/io/GunzipReader.hxx                    | 22 ++----
 src/fs/io/PeekReader.cxx                      |  8 +--
 src/fs/io/PeekReader.hxx                      |  4 +-
 src/fs/io/Reader.hxx                          |  6 +-
 src/fs/io/TextFile.cxx                        | 26 ++-----
 src/fs/io/TextFile.hxx                        | 19 +-----
 src/input/plugins/FileInputPlugin.cxx         | 34 +++++-----
 test/DumpDatabase.cxx                         |  5 +-
 test/dump_playlist.cxx                        |  5 +-
 test/read_conf.cxx                            |  7 +-
 test/run_filter.cxx                           |  4 +-
 test/run_gunzip.cxx                           | 31 ++++-----
 test/run_neighbor_explorer.cxx                |  5 +-
 test/run_output.cxx                           |  5 +-
 33 files changed, 192 insertions(+), 334 deletions(-)

diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx
index b59a39e40..ce0fa543b 100644
--- a/src/CommandLine.cxx
+++ b/src/CommandLine.cxx
@@ -277,13 +277,7 @@ static void help(void)
 
 class ConfigLoader
 {
-	Error &error;
-	bool result;
 public:
-	ConfigLoader(Error &_error) : error(_error), result(false) { }
-
-	bool GetResult() const { return result; }
-
 	bool TryFile(const Path path);
 	bool TryFile(const AllocatedPath &base_path,
 		     PathTraitsFS::const_pointer path);
@@ -292,7 +286,7 @@ public:
 bool ConfigLoader::TryFile(Path path)
 {
 	if (FileExists(path)) {
-		result = ReadConfigFile(path, error);
+		ReadConfigFile(path);
 		return true;
 	}
 	return false;
@@ -386,15 +380,16 @@ parse_cmdline(int argc, char **argv, struct options *options,
 			return false;
 		}
 
-		return ReadConfigFile(Path::FromFS(buffer), error);
+		ReadConfigFile(Path::FromFS(buffer));
 #else
-		return ReadConfigFile(Path::FromFS(config_file), error);
+		ReadConfigFile(Path::FromFS(config_file));
 #endif
+		return true;
 	}
 
 	/* use default configuration file path */
 
-	ConfigLoader loader(error);
+	ConfigLoader loader;
 
 	bool found =
 #ifdef WIN32
@@ -413,5 +408,5 @@ parse_cmdline(int argc, char **argv, struct options *options,
 		return false;
 	}
 
-	return loader.GetResult();
+	return true;
 }
diff --git a/src/Main.cxx b/src/Main.cxx
index de950c05a..bf0f82ccb 100644
--- a/src/Main.cxx
+++ b/src/Main.cxx
@@ -453,11 +453,8 @@ int mpd_main(int argc, char *argv[])
 		if (!sdcard.IsNull()) {
 			const auto config_path =
 				AllocatedPath::Build(sdcard, "mpd.conf");
-			if (FileExists(config_path) &&
-			    !ReadConfigFile(config_path, error)) {
-				LogError(error);
-				return EXIT_FAILURE;
-			}
+			if (FileExists(config_path))
+				ReadConfigFile(config_path);
 		}
 #else
 		if (!parse_cmdline(argc, argv, &options, error)) {
diff --git a/src/PlaylistFile.cxx b/src/PlaylistFile.cxx
index d0af1c7f5..8b86afcfc 100644
--- a/src/PlaylistFile.cxx
+++ b/src/PlaylistFile.cxx
@@ -254,11 +254,7 @@ LoadPlaylistFile(const char *utf8path, Error &error)
 	if (path_fs.IsNull())
 		return contents;
 
-	TextFile file(path_fs, error);
-	if (file.HasFailed()) {
-		TranslatePlaylistError(error);
-		return contents;
-	}
+	TextFile file(path_fs);
 
 	char *s;
 	while ((s = file.ReadLine()) != nullptr) {
diff --git a/src/StateFile.cxx b/src/StateFile.cxx
index fb031d04b..4de3d52f8 100644
--- a/src/StateFile.cxx
+++ b/src/StateFile.cxx
@@ -102,17 +102,12 @@ StateFile::Write()
 
 void
 StateFile::Read()
-{
+try {
 	bool success;
 
 	FormatDebug(state_file_domain, "Loading state file %s", path_utf8.c_str());
 
-	Error error;
-	TextFile file(path, error);
-	if (file.HasFailed()) {
-		LogError(error);
-		return;
-	}
+	TextFile file(path);
 
 #ifdef ENABLE_DATABASE
 	const SongLoader song_loader(partition.instance.database,
@@ -135,6 +130,8 @@ StateFile::Read()
 	}
 
 	RememberVersions();
+} catch (const std::exception &e) {
+	LogError(e);
 }
 
 void
diff --git a/src/config/ConfigFile.cxx b/src/config/ConfigFile.cxx
index 844dc5bb9..364e2dd46 100644
--- a/src/config/ConfigFile.cxx
+++ b/src/config/ConfigFile.cxx
@@ -25,7 +25,6 @@
 #include "ConfigTemplates.hxx"
 #include "util/Tokenizer.hxx"
 #include "util/StringUtil.hxx"
-#include "util/Error.hxx"
 #include "util/Domain.hxx"
 #include "util/RuntimeError.hxx"
 #include "fs/Path.hxx"
@@ -65,17 +64,14 @@ config_read_name_value(ConfigBlock &block, char *input, unsigned line)
 }
 
 static ConfigBlock *
-config_read_block(BufferedReader &reader, Error &error)
+config_read_block(BufferedReader &reader)
 try {
 	std::unique_ptr<ConfigBlock> block(new ConfigBlock(reader.GetLineNumber()));
 
 	while (true) {
 		char *line = reader.ReadLine();
-		if (line == nullptr) {
-			if (reader.Check(error))
-				throw std::runtime_error("Expected '}' before end-of-file");
-			return nullptr;
-		}
+		if (line == nullptr)
+			throw std::runtime_error("Expected '}' before end-of-file");
 
 		line = StripLeft(line);
 		if (*line == 0 || *line == CONF_COMMENT)
@@ -114,11 +110,10 @@ Append(ConfigBlock *&head, ConfigBlock *p)
 	*i = p;
 }
 
-static bool
+static void
 ReadConfigBlock(ConfigData &config_data, BufferedReader &reader,
 		const char *name, ConfigBlockOption o,
-		Tokenizer &tokenizer,
-		Error &error)
+		Tokenizer &tokenizer)
 {
 	const unsigned i = unsigned(o);
 	const ConfigTemplate &option = config_block_templates[i];
@@ -143,12 +138,9 @@ ReadConfigBlock(ConfigData &config_data, BufferedReader &reader,
 		throw FormatRuntimeError("line %u: Unknown tokens after '{'",
 					 reader.GetLineNumber());
 
-	auto *param = config_read_block(reader, error);
-	if (param == nullptr)
-		return false;
-
+	auto *param = config_read_block(reader);
+	assert(param != nullptr);
 	Append(head, param);
-	return true;
 }
 
 gcc_nonnull_all
@@ -196,13 +188,13 @@ ReadConfigParam(ConfigData &config_data, BufferedReader &reader,
 	Append(head, param);
 }
 
-static bool
-ReadConfigFile(ConfigData &config_data, BufferedReader &reader, Error &error)
+static void
+ReadConfigFile(ConfigData &config_data, BufferedReader &reader)
 {
 	while (true) {
 		char *line = reader.ReadLine();
 		if (line == nullptr)
-			return true;
+			return;
 
 		line = StripLeft(line);
 		if (*line == 0 || *line == CONF_COMMENT)
@@ -224,9 +216,8 @@ ReadConfigFile(ConfigData &config_data, BufferedReader &reader, Error &error)
 			ReadConfigParam(config_data, reader, name, o,
 					tokenizer);
 		} else if ((bo = ParseConfigBlockOptionName(name)) != ConfigBlockOption::MAX) {
-			if (!ReadConfigBlock(config_data, reader, name, bo,
-					     tokenizer, error))
-				return false;
+			ReadConfigBlock(config_data, reader, name, bo,
+					tokenizer);
 		} else {
 			throw FormatRuntimeError("unrecognized parameter in config file at "
 						 "line %u: %s\n",
@@ -235,19 +226,16 @@ ReadConfigFile(ConfigData &config_data, BufferedReader &reader, Error &error)
 	}
 }
 
-bool
-ReadConfigFile(ConfigData &config_data, Path path, Error &error)
+void
+ReadConfigFile(ConfigData &config_data, Path path)
 {
 	assert(!path.IsNull());
 	const std::string path_utf8 = path.ToUTF8();
 
 	FormatDebug(config_file_domain, "loading file %s", path_utf8.c_str());
 
-	FileReader file(path, error);
-	if (!file.IsDefined())
-		return false;
+	FileReader file(path);
 
 	BufferedReader reader(file);
-	return ReadConfigFile(config_data, reader, error) &&
-		reader.Check(error);
+	ReadConfigFile(config_data, reader);
 }
diff --git a/src/config/ConfigFile.hxx b/src/config/ConfigFile.hxx
index 30bee0614..8aeb055d7 100644
--- a/src/config/ConfigFile.hxx
+++ b/src/config/ConfigFile.hxx
@@ -24,7 +24,7 @@ class Error;
 class Path;
 struct ConfigData;
 
-bool
-ReadConfigFile(ConfigData &data, Path path, Error &error);
+void
+ReadConfigFile(ConfigData &data, Path path);
 
 #endif
diff --git a/src/config/ConfigGlobal.cxx b/src/config/ConfigGlobal.cxx
index 192baffec..657f871eb 100644
--- a/src/config/ConfigGlobal.cxx
+++ b/src/config/ConfigGlobal.cxx
@@ -45,10 +45,10 @@ void config_global_init(void)
 {
 }
 
-bool
-ReadConfigFile(Path path, Error &error)
+void
+ReadConfigFile(Path path)
 {
-	return ReadConfigFile(config_data, path, error);
+	return ReadConfigFile(config_data, path);
 }
 
 static void
diff --git a/src/config/ConfigGlobal.hxx b/src/config/ConfigGlobal.hxx
index b24b9aada..622105248 100644
--- a/src/config/ConfigGlobal.hxx
+++ b/src/config/ConfigGlobal.hxx
@@ -42,8 +42,8 @@ config_global_finish();
 void
 config_global_check();
 
-bool
-ReadConfigFile(Path path, Error &error);
+void
+ReadConfigFile(Path path);
 
 gcc_pure
 const config_param *
diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.cxx b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
index 113e9b321..ad266b0af 100644
--- a/src/db/plugins/simple/SimpleDatabasePlugin.cxx
+++ b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
@@ -184,11 +184,9 @@ SimpleDatabase::Load(Error &error)
 	assert(!path.IsNull());
 	assert(root != nullptr);
 
-	TextFile file(path, error);
-	if (file.HasFailed())
-		return false;
+	TextFile file(path);
 
-	if (!db_load_internal(file, *root, error) || !file.Check(error))
+	if (!db_load_internal(file, *root, error))
 		return false;
 
 	FileInfo fi;
@@ -200,7 +198,7 @@ SimpleDatabase::Load(Error &error)
 
 bool
 SimpleDatabase::Open(Error &error)
-{
+try {
 	assert(prefixed_light_song == nullptr);
 
 	root = Directory::NewRoot();
@@ -223,6 +221,9 @@ SimpleDatabase::Open(Error &error)
 	}
 
 	return true;
+} catch (const std::exception &e) {
+	error.Set(e);
+	return false;
 }
 
 void
diff --git a/src/db/update/ExcludeList.cxx b/src/db/update/ExcludeList.cxx
index b09f349ac..d117be412 100644
--- a/src/db/update/ExcludeList.cxx
+++ b/src/db/update/ExcludeList.cxx
@@ -27,41 +27,18 @@
 #include "fs/Path.hxx"
 #include "fs/NarrowPath.hxx"
 #include "fs/io/TextFile.hxx"
-#include "util/StringUtil.hxx"
-#include "util/Error.hxx"
+#include "system/Error.hxx"
 #include "Log.hxx"
 
 #include <assert.h>
 #include <string.h>
 #include <errno.h>
 
-#ifdef HAVE_CLASS_GLOB
-
-gcc_pure
-static bool
-IsFileNotFound(const Error &error)
-{
-#ifdef WIN32
-	return error.IsDomain(win32_domain) &&
-		error.GetCode() == ERROR_FILE_NOT_FOUND;
-#else
-	return error.IsDomain(errno_domain) && error.GetCode() == ENOENT;
-#endif
-}
-
-#endif
-
 bool
 ExcludeList::LoadFile(Path path_fs)
-{
+try {
 #ifdef HAVE_CLASS_GLOB
-	Error error;
-	TextFile file(path_fs, error);
-	if (file.HasFailed()) {
-		if (!IsFileNotFound(error))
-			LogError(error);
-		return false;
-	}
+	TextFile file(path_fs);
 
 	char *line;
 	while ((line = file.ReadLine()) != nullptr) {
@@ -79,6 +56,13 @@ ExcludeList::LoadFile(Path path_fs)
 #endif
 
 	return true;
+} catch (const std::system_error &e) {
+	if (!IsFileNotFound(e))
+		LogError(e);
+	return false;
+} catch (const std::exception &e) {
+	LogError(e);
+	return false;
 }
 
 bool
diff --git a/src/fs/FileInfo.hxx b/src/fs/FileInfo.hxx
index 7b272568f..5d8661be6 100644
--- a/src/fs/FileInfo.hxx
+++ b/src/fs/FileInfo.hxx
@@ -23,6 +23,7 @@
 #include "check.h"
 #include "Path.hxx"
 #include "util/Error.hxx"
+#include "system/Error.hxx"
 
 #include <stdint.h>
 
@@ -63,6 +64,20 @@ class FileInfo {
 #endif
 
 public:
+	FileInfo() = default;
+
+	FileInfo(Path path, bool follow_symlinks=true) {
+		if (!GetFileInfo(path, *this, follow_symlinks)) {
+#ifdef WIN32
+			throw FormatLastError("Failed to access %s",
+					      path.ToUTF8().c_str());
+#else
+			throw FormatErrno("Failed to access %s",
+					  path.ToUTF8().c_str());
+#endif
+		}
+	}
+
 	bool IsRegular() const {
 #ifdef WIN32
 		return (data.dwFileAttributes &
diff --git a/src/fs/StandardDirectory.cxx b/src/fs/StandardDirectory.cxx
index 009f02c37..e6e39f98b 100644
--- a/src/fs/StandardDirectory.cxx
+++ b/src/fs/StandardDirectory.cxx
@@ -200,20 +200,21 @@ ParseConfigLine(char *line, const char *dir_name, AllocatedPath &result_dir)
 }
 
 static AllocatedPath GetUserDir(const char *name)
-{
+try {
 	auto result = AllocatedPath::Null();
 	auto config_dir = GetUserConfigDir();
 	if (config_dir.IsNull())
 		return result;
 	auto dirs_file = AllocatedPath::Build(config_dir, "user-dirs.dirs");
-	TextFile input(dirs_file, IgnoreError());
-	if (input.HasFailed())
-		return result;
+
+	TextFile input(dirs_file);
 	char *line;
 	while ((line = input.ReadLine()) != nullptr)
 		if (ParseConfigLine(line, name, result))
 			return result;
 	return result;
+} catch (const std::exception &e) {
+	return AllocatedPath::Null();
 }
 
 #endif
diff --git a/src/fs/io/AutoGunzipReader.cxx b/src/fs/io/AutoGunzipReader.cxx
index b6d30dfd7..e23f69553 100644
--- a/src/fs/io/AutoGunzipReader.cxx
+++ b/src/fs/io/AutoGunzipReader.cxx
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "AutoGunzipReader.hxx"
 #include "GunzipReader.hxx"
-#include "util/Error.hxx"
 
 AutoGunzipReader::~AutoGunzipReader()
 {
@@ -35,36 +34,27 @@ IsGzip(const uint8_t data[4])
 		(data[3] & 0xe0) == 0;
 }
 
-inline bool
-AutoGunzipReader::Detect(Error &error)
+inline void
+AutoGunzipReader::Detect()
 {
-	const uint8_t *data = (const uint8_t *)peek.Peek(4, error);
+	const uint8_t *data = (const uint8_t *)peek.Peek(4);
 	if (data == nullptr) {
-		if (error.IsDefined())
-			return false;
-
 		next = &peek;
-		return true;
+		return;
 	}
 
-	if (IsGzip(data)) {
-		gunzip = new GunzipReader(peek, error);
-		if (!gunzip->IsDefined())
-			return false;
-
-
-		next = gunzip;
-	} else
+	if (IsGzip(data))
+		next = gunzip = new GunzipReader(peek);
+	else
 		next = &peek;
-
-	return true;
 }
 
 size_t
-AutoGunzipReader::Read(void *data, size_t size, Error &error)
+AutoGunzipReader::Read(void *data, size_t size)
 {
-	if (next == nullptr && !Detect(error))
-		return false;
+	if (next == nullptr)
+		Detect();
 
-	return next->Read(data, size, error);
+	assert(next != nullptr);
+	return next->Read(data, size);
 }
diff --git a/src/fs/io/AutoGunzipReader.hxx b/src/fs/io/AutoGunzipReader.hxx
index 29a794aed..783067b02 100644
--- a/src/fs/io/AutoGunzipReader.hxx
+++ b/src/fs/io/AutoGunzipReader.hxx
@@ -43,10 +43,10 @@ public:
 	~AutoGunzipReader();
 
 	/* virtual methods from class Reader */
-	virtual size_t Read(void *data, size_t size, Error &error) override;
+	virtual size_t Read(void *data, size_t size) override;
 
 private:
-	bool Detect(Error &error);
+	void Detect();
 };
 
 #endif
diff --git a/src/fs/io/BufferedReader.cxx b/src/fs/io/BufferedReader.cxx
index 9a296d815..c1de5e386 100644
--- a/src/fs/io/BufferedReader.cxx
+++ b/src/fs/io/BufferedReader.cxx
@@ -25,9 +25,6 @@
 bool
 BufferedReader::Fill(bool need_more)
 {
-	if (gcc_unlikely(last_error.IsDefined()))
-		return false;
-
 	if (eof)
 		return !need_more;
 
@@ -41,11 +38,8 @@ BufferedReader::Fill(bool need_more)
 		assert(!w.IsEmpty());
 	}
 
-	size_t nbytes = reader.Read(w.data, w.size, last_error);
+	size_t nbytes = reader.Read(w.data, w.size);
 	if (nbytes == 0) {
-		if (gcc_unlikely(last_error.IsDefined()))
-			return false;
-
 		eof = true;
 		return !need_more;
 	}
@@ -65,7 +59,7 @@ BufferedReader::ReadLine()
 		}
 	} while (Fill(true));
 
-	if (last_error.IsDefined() || !eof || buffer.IsEmpty())
+	if (!eof || buffer.IsEmpty())
 		return nullptr;
 
 	auto w = buffer.Write();
diff --git a/src/fs/io/BufferedReader.hxx b/src/fs/io/BufferedReader.hxx
index a0c42d23c..aa8232100 100644
--- a/src/fs/io/BufferedReader.hxx
+++ b/src/fs/io/BufferedReader.hxx
@@ -23,12 +23,10 @@
 #include "check.h"
 #include "Compiler.h"
 #include "util/DynamicFifoBuffer.hxx"
-#include "util/Error.hxx"
 
 #include <stddef.h>
 
 class Reader;
-class Error;
 
 class BufferedReader {
 	static constexpr size_t MAX_SIZE = 512 * 1024;
@@ -37,8 +35,6 @@ class BufferedReader {
 
 	DynamicFifoBuffer<char> buffer;
 
-	Error last_error;
-
 	bool eof;
 
 	unsigned line_number;
@@ -48,19 +44,6 @@ public:
 		:reader(_reader), buffer(4096), eof(false),
 		 line_number(0) {}
 
-	gcc_pure
-	bool Check() const {
-		return !last_error.IsDefined();
-	}
-
-	bool Check(Error &error) const {
-		if (last_error.IsDefined()) {
-			error.Set(last_error);
-			return false;
-		} else
-			return true;
-	}
-
 	bool Fill(bool need_more);
 
 	gcc_pure
diff --git a/src/fs/io/FileReader.cxx b/src/fs/io/FileReader.cxx
index e54f6f3a8..442ebed5d 100644
--- a/src/fs/io/FileReader.cxx
+++ b/src/fs/io/FileReader.cxx
@@ -20,57 +20,49 @@
 #include "config.h"
 #include "FileReader.hxx"
 #include "fs/FileInfo.hxx"
-#include "util/Error.hxx"
+#include "system/Error.hxx"
 
 #ifdef WIN32
 
-FileReader::FileReader(Path _path, Error &error)
+FileReader::FileReader(Path _path)
 	:path(_path),
 	 handle(CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ,
 			   nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
 			   nullptr))
 {
-	if (handle == INVALID_HANDLE_VALUE) {
-		const auto path_utf8 = path.ToUTF8();
-		error.FormatLastError("Failed to open %s", path_utf8.c_str());
-	}
+	if (handle == INVALID_HANDLE_VALUE)
+		throw FormatLastError("Failed to open %s", path.ToUTF8().c_str());
 }
 
-bool
-FileReader::GetFileInfo(FileInfo &info, Error &error) const
+FileInfo
+FileReader::GetFileInfo() const
 {
 	assert(IsDefined());
 
-	return ::GetFileInfo(path, info, error);
+	return FileInfo(path);
 }
 
 size_t
-FileReader::Read(void *data, size_t size, Error &error)
+FileReader::Read(void *data, size_t size)
 {
 	assert(IsDefined());
 
 	DWORD nbytes;
-	if (!ReadFile(handle, data, size, &nbytes, nullptr)) {
-		const auto path_utf8 = path.ToUTF8();
-		error.FormatLastError("Failed to read from %s",
-				      path_utf8.c_str());
-		nbytes = 0;
-	}
+	if (!ReadFile(handle, data, size, &nbytes, nullptr))
+		throw FormatLastError("Failed to read from %s",
+				      path.ToUTF8().c_str());
 
 	return nbytes;
 }
 
-bool
-FileReader::Seek(off_t offset, Error &error)
+void
+FileReader::Seek(off_t offset)
 {
 	assert(IsDefined());
 
 	auto result = SetFilePointer(handle, offset, nullptr, FILE_BEGIN);
-	const bool success = result != INVALID_SET_FILE_POINTER;
-	if (!success)
-		error.SetLastError("Failed to seek");
-
-	return success;
+	if (result == INVALID_SET_FILE_POINTER)
+		throw MakeLastError("Failed to seek");
 }
 
 void
@@ -83,52 +75,49 @@ FileReader::Close()
 
 #else
 
-FileReader::FileReader(Path _path, Error &error)
+FileReader::FileReader(Path _path)
 	:path(_path)
 {
 	fd.OpenReadOnly(path.c_str());
 	if (!fd.IsDefined())
-		error.FormatErrno("Failed to open %s", path.c_str());
+		throw FormatErrno("Failed to open %s", path.ToUTF8().c_str());
 }
 
-bool
-FileReader::GetFileInfo(FileInfo &info, Error &error) const
+FileInfo
+FileReader::GetFileInfo() const
 {
 	assert(IsDefined());
 
+	FileInfo info;
 	const bool success = fstat(fd.Get(), &info.st) == 0;
 	if (!success)
-		error.FormatErrno("Failed to access %s",
+		throw FormatErrno("Failed to access %s",
 				  path.ToUTF8().c_str());
 
-	return success;
+	return info;
 }
 
 size_t
-FileReader::Read(void *data, size_t size, Error &error)
+FileReader::Read(void *data, size_t size)
 {
 	assert(IsDefined());
 
 	ssize_t nbytes = fd.Read(data, size);
-	if (nbytes < 0) {
-		error.FormatErrno("Failed to read from %s", path.c_str());
-		nbytes = 0;
-	}
+	if (nbytes < 0)
+		throw FormatErrno("Failed to read from %s", path.ToUTF8().c_str());
 
 	return nbytes;
 }
 
-bool
-FileReader::Seek(off_t offset, Error &error)
+void
+FileReader::Seek(off_t offset)
 {
 	assert(IsDefined());
 
 	auto result = fd.Seek(offset);
 	const bool success = result >= 0;
 	if (!success)
-		error.SetErrno("Failed to seek");
-
-	return success;
+		throw MakeErrno("Failed to seek");
 }
 
 void
diff --git a/src/fs/io/FileReader.hxx b/src/fs/io/FileReader.hxx
index 642fc5ed1..ee931592b 100644
--- a/src/fs/io/FileReader.hxx
+++ b/src/fs/io/FileReader.hxx
@@ -48,7 +48,7 @@ class FileReader final : public Reader {
 #endif
 
 public:
-	FileReader(Path _path, Error &error);
+	FileReader(Path _path);
 
 #ifdef WIN32
 	FileReader(FileReader &&other)
@@ -70,6 +70,7 @@ public:
 	}
 
 
+protected:
 	bool IsDefined() const {
 #ifdef WIN32
 		return handle != INVALID_HANDLE_VALUE;
@@ -78,6 +79,7 @@ public:
 #endif
 	}
 
+public:
 #ifndef WIN32
 	FileDescriptor GetFD() const {
 		return fd;
@@ -86,12 +88,13 @@ public:
 
 	void Close();
 
-	bool GetFileInfo(FileInfo &info, Error &error) const;
+	gcc_pure
+	FileInfo GetFileInfo() const;
 
-	bool Seek(off_t offset, Error &error);
+	void Seek(off_t offset);
 
 	/* virtual methods from class Reader */
-	size_t Read(void *data, size_t size, Error &error) override;
+	size_t Read(void *data, size_t size) override;
 };
 
 #endif
diff --git a/src/fs/io/GunzipReader.cxx b/src/fs/io/GunzipReader.cxx
index 78f5b2c69..d7a0f6c60 100644
--- a/src/fs/io/GunzipReader.cxx
+++ b/src/fs/io/GunzipReader.cxx
@@ -20,10 +20,8 @@
 #include "config.h"
 #include "GunzipReader.hxx"
 #include "lib/zlib/Domain.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
 
-GunzipReader::GunzipReader(Reader &_next, Error &error)
+GunzipReader::GunzipReader(Reader &_next) throw(ZlibError)
 	:next(_next), eof(false)
 {
 	z.next_in = nullptr;
@@ -33,25 +31,17 @@ GunzipReader::GunzipReader(Reader &_next, Error &error)
 	z.opaque = Z_NULL;
 
 	int result = inflateInit2(&z, 16 + MAX_WBITS);
-	if (result != Z_OK) {
-		z.opaque = this;
-		error.Set(zlib_domain, result, zError(result));
-	}
-}
-
-GunzipReader::~GunzipReader()
-{
-	if (IsDefined())
-		inflateEnd(&z);
+	if (result != Z_OK)
+		throw ZlibError(result);
 }
 
 inline bool
-GunzipReader::FillBuffer(Error &error)
+GunzipReader::FillBuffer()
 {
 	auto w = buffer.Write();
 	assert(!w.IsEmpty());
 
-	size_t nbytes = next.Read(w.data, w.size, error);
+	size_t nbytes = next.Read(w.data, w.size);
 	if (nbytes == 0)
 		return false;
 
@@ -60,7 +50,7 @@ GunzipReader::FillBuffer(Error &error)
 }
 
 size_t
-GunzipReader::Read(void *data, size_t size, Error &error)
+GunzipReader::Read(void *data, size_t size)
 {
 	if (eof)
 		return 0;
@@ -73,10 +63,8 @@ GunzipReader::Read(void *data, size_t size, Error &error)
 
 		auto r = buffer.Read();
 		if (r.IsEmpty()) {
-			if (FillBuffer(error))
+			if (FillBuffer())
 				r = buffer.Read();
-			else if (error.IsDefined())
-				return 0;
 			else
 				flush = Z_FINISH;
 		}
@@ -88,10 +76,8 @@ GunzipReader::Read(void *data, size_t size, Error &error)
 		if (result == Z_STREAM_END) {
 			eof = true;
 			return size - z.avail_out;
-		} else if (result != Z_OK) {
-			error.Set(zlib_domain, result, zError(result));
-			return 0;
-		}
+		} else if (result != Z_OK)
+			throw ZlibError(result);
 
 		buffer.Consume(r.size - z.avail_in);
 
diff --git a/src/fs/io/GunzipReader.hxx b/src/fs/io/GunzipReader.hxx
index 381d1af5e..41d57f190 100644
--- a/src/fs/io/GunzipReader.hxx
+++ b/src/fs/io/GunzipReader.hxx
@@ -23,13 +23,11 @@
 #include "check.h"
 #include "Reader.hxx"
 #include "util/StaticFifoBuffer.hxx"
+#include "lib/zlib/Error.hxx"
 #include "Compiler.h"
 
 #include <zlib.h>
 
-class Error;
-class Domain;
-
 /**
  * A filter that decompresses data using zlib.
  */
@@ -44,25 +42,19 @@ class GunzipReader final : public Reader {
 
 public:
 	/**
-	 * Construct the filter.  Call IsDefined() to check whether
-	 * the constructor has succeeded.  If not, #error will hold
-	 * information about the failure.
+	 * Construct the filter.
 	 */
-	GunzipReader(Reader &_next, Error &error);
-	~GunzipReader();
+	GunzipReader(Reader &_next) throw(ZlibError);
 
-	/**
-	 * Check whether the constructor has succeeded.
-	 */
-	bool IsDefined() const {
-		return z.opaque == nullptr;
+	~GunzipReader() {
+		inflateEnd(&z);
 	}
 
 	/* virtual methods from class Reader */
-	virtual size_t Read(void *data, size_t size, Error &error) override;
+	size_t Read(void *data, size_t size) override;
 
 private:
-	bool FillBuffer(Error &error);
+	bool FillBuffer();
 };
 
 #endif
diff --git a/src/fs/io/PeekReader.cxx b/src/fs/io/PeekReader.cxx
index ec9520a37..b9b15bb5d 100644
--- a/src/fs/io/PeekReader.cxx
+++ b/src/fs/io/PeekReader.cxx
@@ -26,7 +26,7 @@
 #include <string.h>
 
 const void *
-PeekReader::Peek(size_t size, Error &error)
+PeekReader::Peek(size_t size)
 {
 	assert(size > 0);
 	assert(size < sizeof(buffer));
@@ -35,7 +35,7 @@ PeekReader::Peek(size_t size, Error &error)
 
 	do {
 		size_t nbytes = next.Read(buffer + buffer_size,
-					size - buffer_size, error);
+					  size - buffer_size);
 		if (nbytes == 0)
 			return nullptr;
 
@@ -46,7 +46,7 @@ PeekReader::Peek(size_t size, Error &error)
 }
 
 size_t
-PeekReader::Read(void *data, size_t size, Error &error)
+PeekReader::Read(void *data, size_t size)
 {
 	size_t buffer_remaining = buffer_size - buffer_position;
 	if (buffer_remaining > 0) {
@@ -56,5 +56,5 @@ PeekReader::Read(void *data, size_t size, Error &error)
 		return nbytes;
 	}
 
-	return next.Read(data, size, error);
+	return next.Read(data, size);
 }
diff --git a/src/fs/io/PeekReader.hxx b/src/fs/io/PeekReader.hxx
index c00ed66be..d8bc6d76a 100644
--- a/src/fs/io/PeekReader.hxx
+++ b/src/fs/io/PeekReader.hxx
@@ -44,10 +44,10 @@ public:
 	PeekReader(Reader &_next)
 		:next(_next), buffer_size(0), buffer_position(0) {}
 
-	const void *Peek(size_t size, Error &error);
+	const void *Peek(size_t size);
 
 	/* virtual methods from class Reader */
-	virtual size_t Read(void *data, size_t size, Error &error) override;
+	virtual size_t Read(void *data, size_t size) override;
 };
 
 #endif
diff --git a/src/fs/io/Reader.hxx b/src/fs/io/Reader.hxx
index 657f96ac2..d67e963ee 100644
--- a/src/fs/io/Reader.hxx
+++ b/src/fs/io/Reader.hxx
@@ -25,8 +25,6 @@
 
 #include <stddef.h>
 
-class Error;
-
 /**
  * An interface that can read bytes from a stream until the stream
  * ends.
@@ -43,10 +41,10 @@ public:
 	 * Read data from the stream.
 	 *
 	 * @return the number of bytes read into the given buffer or 0
-	 * on error/end-of-stream
+	 * on end-of-stream
 	 */
 	gcc_nonnull_all
-	virtual size_t Read(void *data, size_t size, Error &error) = 0;
+	virtual size_t Read(void *data, size_t size) = 0;
 };
 
 #endif
diff --git a/src/fs/io/TextFile.cxx b/src/fs/io/TextFile.cxx
index 9866da08a..edb3b26d8 100644
--- a/src/fs/io/TextFile.cxx
+++ b/src/fs/io/TextFile.cxx
@@ -26,22 +26,18 @@
 
 #include <assert.h>
 
-TextFile::TextFile(Path path_fs, Error &error)
-	:file_reader(new FileReader(path_fs, error)),
+TextFile::TextFile(Path path_fs)
+	:file_reader(new FileReader(path_fs)),
 #ifdef ENABLE_ZLIB
-	 gunzip_reader(file_reader->IsDefined()
-		       ? new AutoGunzipReader(*file_reader)
-		       : nullptr),
+	 gunzip_reader(new AutoGunzipReader(*file_reader)),
 #endif
-	 buffered_reader(file_reader->IsDefined()
-			 ? new BufferedReader(*
+	 buffered_reader(new BufferedReader(*
 #ifdef ENABLE_ZLIB
-					      gunzip_reader
+					    gunzip_reader
 #else
-					      file_reader
+					    file_reader
 #endif
-					      )
-			 : nullptr)
+					    ))
 {
 }
 
@@ -61,11 +57,3 @@ TextFile::ReadLine()
 
 	return buffered_reader->ReadLine();
 }
-
-bool
-TextFile::Check(Error &error) const
-{
-	assert(buffered_reader != nullptr);
-
-	return buffered_reader->Check(error);
-}
diff --git a/src/fs/io/TextFile.hxx b/src/fs/io/TextFile.hxx
index bee9e8c23..49ab2287a 100644
--- a/src/fs/io/TextFile.hxx
+++ b/src/fs/io/TextFile.hxx
@@ -23,10 +23,7 @@
 #include "check.h"
 #include "Compiler.h"
 
-#include <stddef.h>
-
 class Path;
-class Error;
 class FileReader;
 class AutoGunzipReader;
 class BufferedReader;
@@ -41,32 +38,20 @@ class TextFile {
 	BufferedReader *const buffered_reader;
 
 public:
-	TextFile(Path path_fs, Error &error);
+	TextFile(Path path_fs);
 
 	TextFile(const TextFile &other) = delete;
 
 	~TextFile();
 
-	bool HasFailed() const {
-		return gcc_unlikely(buffered_reader == nullptr);
-	}
-
 	/**
 	 * Reads a line from the input file, and strips trailing
 	 * space.  There is a reasonable maximum line length, only to
 	 * prevent denial of service.
 	 *
-	 * Use Check() after nullptr has been returned to check
-	 * whether an error occurred or end-of-file has been reached.
-	 *
-	 * @return a pointer to the line, or nullptr on end-of-file or error
+	 * @return a pointer to the line, or nullptr on end-of-file
 	 */
 	char *ReadLine();
-
-	/**
-	 * Check whether a ReadLine() call has thrown an error.
-	 */
-	bool Check(Error &error) const;
 };
 
 #endif
diff --git a/src/input/plugins/FileInputPlugin.cxx b/src/input/plugins/FileInputPlugin.cxx
index aa4676470..65fc830dd 100644
--- a/src/input/plugins/FileInputPlugin.cxx
+++ b/src/input/plugins/FileInputPlugin.cxx
@@ -61,14 +61,10 @@ InputStream *
 OpenFileInputStream(Path path,
 		    Mutex &mutex, Cond &cond,
 		    Error &error)
-{
-	FileReader reader(path, error);
-	if (!reader.IsDefined())
-		return nullptr;
+try {
+	FileReader reader(path);
 
-	FileInfo info;
-	if (!reader.GetFileInfo(info, error))
-		return nullptr;
+	const FileInfo info = reader.GetFileInfo();
 
 	if (!info.IsRegular()) {
 		error.Format(file_domain, "Not a regular file: %s",
@@ -84,6 +80,9 @@ OpenFileInputStream(Path path,
 	return new FileInputStream(path.ToUTF8().c_str(),
 				   std::move(reader), info.GetSize(),
 				   mutex, cond);
+} catch (const std::exception &e) {
+	error.Set(e);
+	return nullptr;
 }
 
 static InputStream *
@@ -98,23 +97,24 @@ input_file_open(gcc_unused const char *filename,
 
 bool
 FileInputStream::Seek(offset_type new_offset, Error &error)
-{
-	if (!reader.Seek((off_t)new_offset, error))
-		return false;
-
+try {
+	reader.Seek((off_t)new_offset);
 	offset = new_offset;
 	return true;
+} catch (const std::exception &e) {
+	error.Set(e);
+	return false;
 }
 
 size_t
 FileInputStream::Read(void *ptr, size_t read_size, Error &error)
-{
-	ssize_t nbytes = reader.Read(ptr, read_size, error);
-	if (nbytes < 0)
-		return 0;
-
+try {
+	size_t nbytes = reader.Read(ptr, read_size);
 	offset += nbytes;
-	return (size_t)nbytes;
+	return nbytes;
+} catch (const std::exception &e) {
+	error.Set(e);
+	return 0;
 }
 
 const InputPlugin input_plugin_file = {
diff --git a/test/DumpDatabase.cxx b/test/DumpDatabase.cxx
index 034428d88..f3d5454fc 100644
--- a/test/DumpDatabase.cxx
+++ b/test/DumpDatabase.cxx
@@ -110,10 +110,7 @@ try {
 	config_global_init();
 
 	Error error;
-	if (!ReadConfigFile(config_path, error)) {
-		cerr << error.GetMessage() << endl;
-		return EXIT_FAILURE;
-	}
+	ReadConfigFile(config_path);
 
 	TagLoadConfig();
 
diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx
index 65e980b21..b6659849a 100644
--- a/test/dump_playlist.cxx
+++ b/test/dump_playlist.cxx
@@ -65,10 +65,7 @@ try {
 	config_global_init();
 
 	Error error;
-	if (!ReadConfigFile(config_path, error)) {
-		LogError(error);
-		return EXIT_FAILURE;
-	}
+	ReadConfigFile(config_path);
 
 	const ScopeIOThread io_thread;
 
diff --git a/test/read_conf.cxx b/test/read_conf.cxx
index 4d0fadcc6..a62c20d2c 100644
--- a/test/read_conf.cxx
+++ b/test/read_conf.cxx
@@ -20,7 +20,6 @@
 #include "config.h"
 #include "config/ConfigGlobal.hxx"
 #include "fs/Path.hxx"
-#include "util/Error.hxx"
 #include "Log.hxx"
 
 #include <assert.h>
@@ -39,11 +38,7 @@ try {
 
 	config_global_init();
 
-	Error error;
-	if (!ReadConfigFile(config_path, error)) {
-		LogError(error);
-		return EXIT_FAILURE;
-	}
+	ReadConfigFile(config_path);
 
 	ConfigOption option = ParseConfigOptionName(name);
 	const char *value = option != ConfigOption::MAX
diff --git a/test/run_filter.cxx b/test/run_filter.cxx
index 912bc6470..367d474ae 100644
--- a/test/run_filter.cxx
+++ b/test/run_filter.cxx
@@ -69,7 +69,6 @@ load_filter(const char *name)
 int main(int argc, char **argv)
 try {
 	struct audio_format_string af_string;
-	Error error2;
 	char buffer[4096];
 
 	if (argc < 3 || argc > 4) {
@@ -84,8 +83,7 @@ try {
 	/* read configuration file (mpd.conf) */
 
 	config_global_init();
-	if (!ReadConfigFile(config_path, error2))
-		FatalError(error2);
+	ReadConfigFile(config_path);
 
 	/* parse the audio format */
 
diff --git a/test/run_gunzip.cxx b/test/run_gunzip.cxx
index 114fab3e5..469b68ba5 100644
--- a/test/run_gunzip.cxx
+++ b/test/run_gunzip.cxx
@@ -28,32 +28,32 @@
 #include <stdlib.h>
 #include <unistd.h>
 
-static bool
-Copy(OutputStream &dest, Reader &src, Error &error)
+static void
+Copy(OutputStream &dest, Reader &src)
 {
 	while (true) {
 		char buffer[4096];
-		size_t nbytes = src.Read(buffer, sizeof(buffer), error);
+		size_t nbytes = src.Read(buffer, sizeof(buffer));
 		if (nbytes == 0)
-			return !error.IsDefined();
+			break;
 
 		dest.Write(buffer, nbytes);
 	}
 }
 
-static bool
-CopyGunzip(OutputStream &dest, Reader &_src, Error &error)
+static void
+CopyGunzip(OutputStream &dest, Reader &_src)
 {
-	GunzipReader src(_src, error);
-	return src.IsDefined() && Copy(dest, src, error);
+	GunzipReader src(_src);
+	Copy(dest, src);
 }
 
-static bool
-CopyGunzip(FILE *_dest, Path src_path, Error &error)
+static void
+CopyGunzip(FILE *_dest, Path src_path)
 {
 	StdioOutputStream dest(_dest);
-	FileReader src(src_path, error);
-	return src.IsDefined() && CopyGunzip(dest, src, error);
+	FileReader src(src_path);
+	CopyGunzip(dest, src);
 }
 
 int
@@ -67,12 +67,7 @@ main(int argc, gcc_unused char **argv)
 	Path path = Path::FromFS(argv[1]);
 
 	try {
-		Error error;
-		if (!CopyGunzip(stdout, path, error)) {
-			fprintf(stderr, "%s\n", error.GetMessage());
-			return EXIT_FAILURE;
-		}
-
+		CopyGunzip(stdout, path);
 		return EXIT_SUCCESS;
 	} catch (const std::exception &e) {
 		LogError(e);
diff --git a/test/run_neighbor_explorer.cxx b/test/run_neighbor_explorer.cxx
index 3f48ee06f..29ea7b56e 100644
--- a/test/run_neighbor_explorer.cxx
+++ b/test/run_neighbor_explorer.cxx
@@ -59,10 +59,7 @@ try {
 	Error error;
 
 	config_global_init();
-	if (!ReadConfigFile(config_path, error)) {
-		LogError(error);
-		return EXIT_FAILURE;
-	}
+	ReadConfigFile(config_path);
 
 	/* initialize the core */
 
diff --git a/test/run_output.cxx b/test/run_output.cxx
index f8f959a07..b9fec7961 100644
--- a/test/run_output.cxx
+++ b/test/run_output.cxx
@@ -161,10 +161,7 @@ try {
 	/* read configuration file (mpd.conf) */
 
 	config_global_init();
-	if (!ReadConfigFile(config_path, error)) {
-		LogError(error);
-		return EXIT_FAILURE;
-	}
+	ReadConfigFile(config_path);
 
 	EventLoop event_loop;