aboutsummaryrefslogtreecommitdiffstats
path: root/src/fs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/fs/AllocatedPath.cxx27
-rw-r--r--src/fs/AllocatedPath.hxx74
-rw-r--r--src/fs/Charset.cxx51
-rw-r--r--src/fs/Charset.hxx2
-rw-r--r--src/fs/CheckFile.cxx62
-rw-r--r--src/fs/CheckFile.hxx34
-rw-r--r--src/fs/Config.cxx16
-rw-r--r--src/fs/Config.hxx2
-rw-r--r--src/fs/DirectoryReader.hxx8
-rw-r--r--src/fs/Domain.cxx2
-rw-r--r--src/fs/Domain.hxx2
-rw-r--r--src/fs/FileSystem.cxx2
-rw-r--r--src/fs/FileSystem.hxx37
-rw-r--r--src/fs/Limits.hxx2
-rw-r--r--src/fs/Path.cxx24
-rw-r--r--src/fs/Path.hxx16
-rw-r--r--src/fs/StandardDirectory.cxx327
-rw-r--r--src/fs/StandardDirectory.hxx71
-rw-r--r--src/fs/Traits.cxx136
-rw-r--r--src/fs/Traits.hxx186
-rw-r--r--src/fs/io/AutoGunzipReader.cxx70
-rw-r--r--src/fs/io/AutoGunzipReader.hxx52
-rw-r--r--src/fs/io/BufferedOutputStream.cxx143
-rw-r--r--src/fs/io/BufferedOutputStream.hxx71
-rw-r--r--src/fs/io/BufferedReader.cxx82
-rw-r--r--src/fs/io/BufferedReader.hxx75
-rw-r--r--src/fs/io/FileOutputStream.cxx137
-rw-r--r--src/fs/io/FileOutputStream.hxx69
-rw-r--r--src/fs/io/FileReader.cxx98
-rw-r--r--src/fs/io/FileReader.hxx68
-rw-r--r--src/fs/io/GunzipReader.cxx101
-rw-r--r--src/fs/io/GunzipReader.hxx68
-rw-r--r--src/fs/io/GzipOutputStream.cxx105
-rw-r--r--src/fs/io/GzipOutputStream.hxx70
-rw-r--r--src/fs/io/OutputStream.hxx38
-rw-r--r--src/fs/io/PeekReader.cxx60
-rw-r--r--src/fs/io/PeekReader.hxx53
-rw-r--r--src/fs/io/Reader.hxx52
-rw-r--r--src/fs/io/StdioOutputStream.hxx46
-rw-r--r--src/fs/io/TextFile.cxx71
-rw-r--r--src/fs/io/TextFile.hxx73
41 files changed, 2538 insertions, 145 deletions
diff --git a/src/fs/AllocatedPath.cxx b/src/fs/AllocatedPath.cxx
index 4651ea625..ceaad73ea 100644
--- a/src/fs/AllocatedPath.cxx
+++ b/src/fs/AllocatedPath.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,33 +24,36 @@
#include "util/Error.hxx"
#include "Compiler.h"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
-#include <assert.h>
#include <string.h>
+#ifdef HAVE_GLIB
+
inline AllocatedPath::AllocatedPath(Donate, pointer _value)
:value(_value) {
g_free(_value);
}
+#endif
+
/* no inlining, please */
AllocatedPath::~AllocatedPath() {}
AllocatedPath
-AllocatedPath::Build(const_pointer a, const_pointer b)
-{
- return AllocatedPath(Donate(), g_build_filename(a, b, nullptr));
-}
-
-AllocatedPath
AllocatedPath::FromUTF8(const char *path_utf8)
{
+#ifdef HAVE_GLIB
char *path = ::PathFromUTF8(path_utf8);
if (path == nullptr)
return AllocatedPath::Null();
return AllocatedPath(Donate(), path);
+#else
+ return FromFS(path_utf8);
+#endif
}
AllocatedPath
@@ -68,7 +71,7 @@ AllocatedPath::FromUTF8(const char *path_utf8, Error &error)
AllocatedPath
AllocatedPath::GetDirectoryName() const
{
- return AllocatedPath(Donate(), g_path_get_dirname(c_str()));
+ return FromFS(PathTraitsFS::GetParent(c_str()));
}
std::string
@@ -86,14 +89,14 @@ AllocatedPath::RelativeFS(const char *other_fs) const
other_fs += l;
if (*other_fs != 0) {
- if (!PathTraits::IsSeparatorFS(*other_fs))
+ if (!PathTraitsFS::IsSeparator(*other_fs))
/* mismatch */
return nullptr;
/* skip remaining path separators */
do {
++other_fs;
- } while (PathTraits::IsSeparatorFS(*other_fs));
+ } while (PathTraitsFS::IsSeparator(*other_fs));
}
return other_fs;
@@ -105,7 +108,7 @@ AllocatedPath::ChopSeparators()
size_t l = length();
const char *p = data();
- while (l >= 2 && PathTraits::IsSeparatorFS(p[l - 1])) {
+ while (l >= 2 && PathTraitsFS::IsSeparator(p[l - 1])) {
--l;
#if GCC_CHECK_VERSION(4,7) && !defined(__clang__)
diff --git a/src/fs/AllocatedPath.hxx b/src/fs/AllocatedPath.hxx
index 36d8a1598..c345470c8 100644
--- a/src/fs/AllocatedPath.hxx
+++ b/src/fs/AllocatedPath.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,8 +28,6 @@
#include <utility>
#include <string>
-#include <assert.h>
-
class Error;
/**
@@ -39,11 +37,10 @@ class Error;
* stored.
*/
class AllocatedPath {
- typedef std::string string;
-
- typedef PathTraits::value_type value_type;
- typedef PathTraits::pointer pointer;
- typedef PathTraits::const_pointer const_pointer;
+ typedef PathTraitsFS::string string;
+ typedef PathTraitsFS::value_type value_type;
+ typedef PathTraitsFS::pointer pointer;
+ typedef PathTraitsFS::const_pointer const_pointer;
string value;
@@ -56,17 +53,25 @@ class AllocatedPath {
AllocatedPath(const_pointer _value):value(_value) {}
+ AllocatedPath(string &&_value):value(std::move(_value)) {}
+
+ static AllocatedPath Build(const_pointer a, size_t a_size,
+ const_pointer b, size_t b_size) {
+ return AllocatedPath(PathTraitsFS::Build(a, a_size, b, b_size));
+ }
public:
/**
- * Copy a #AllocatedPath object.
+ * Copy an #AllocatedPath object.
*/
AllocatedPath(const AllocatedPath &) = default;
/**
- * Move a #AllocatedPath object.
+ * Move an #AllocatedPath object.
*/
AllocatedPath(AllocatedPath &&other):value(std::move(other.value)) {}
+ explicit AllocatedPath(Path other):value(other.c_str()) {}
+
~AllocatedPath();
/**
@@ -89,22 +94,38 @@ public:
* Join two path components with the path separator.
*/
gcc_pure gcc_nonnull_all
- static AllocatedPath Build(const_pointer a, const_pointer b);
+ static AllocatedPath Build(const_pointer a, const_pointer b) {
+ return Build(a, PathTraitsFS::GetLength(a),
+ b, PathTraitsFS::GetLength(b));
+ }
gcc_pure gcc_nonnull_all
- static AllocatedPath Build(const_pointer a, const AllocatedPath &b) {
+ static AllocatedPath Build(Path a, const_pointer b) {
+ return Build(a.c_str(), b);
+ }
+
+ gcc_pure gcc_nonnull_all
+ static AllocatedPath Build(Path a, Path b) {
return Build(a, b.c_str());
}
gcc_pure gcc_nonnull_all
+ static AllocatedPath Build(const_pointer a, const AllocatedPath &b) {
+ return Build(a, PathTraitsFS::GetLength(a),
+ b.value.c_str(), b.value.size());
+ }
+
+ gcc_pure gcc_nonnull_all
static AllocatedPath Build(const AllocatedPath &a, const_pointer b) {
- return Build(a.c_str(), b);
+ return Build(a.value.c_str(), a.value.size(),
+ b, PathTraitsFS::GetLength(b));
}
gcc_pure
static AllocatedPath Build(const AllocatedPath &a,
const AllocatedPath &b) {
- return Build(a.c_str(), b.c_str());
+ return Build(a.value.c_str(), a.value.size(),
+ b.value.c_str(), b.value.size());
}
/**
@@ -117,7 +138,16 @@ public:
}
/**
- * Convert a UTF-8 C string to a #AllocatedPath instance.
+ * Convert a C++ string that is already in the filesystem
+ * character set to a #Path instance.
+ */
+ gcc_pure
+ static AllocatedPath FromFS(string &&fs) {
+ return AllocatedPath(std::move(fs));
+ }
+
+ /**
+ * Convert a UTF-8 C string to an #AllocatedPath instance.
* Returns return a "nulled" instance on error.
*/
gcc_pure gcc_nonnull_all
@@ -127,12 +157,12 @@ public:
static AllocatedPath FromUTF8(const char *path_utf8, Error &error);
/**
- * Copy a #AllocatedPath object.
+ * Copy an #AllocatedPath object.
*/
AllocatedPath &operator=(const AllocatedPath &) = default;
/**
- * Move a #AllocatedPath object.
+ * Move an #AllocatedPath object.
*/
AllocatedPath &operator=(AllocatedPath &&other) {
value = std::move(other.value);
@@ -140,6 +170,14 @@ public:
}
/**
+ * Allows the caller to "steal" the internal value by
+ * providing a rvalue reference to the std::string attribute.
+ */
+ std::string &&Steal() {
+ return std::move(value);
+ }
+
+ /**
* Check if this is a "nulled" instance. A "nulled" instance
* must not be used.
*/
@@ -215,7 +253,7 @@ public:
gcc_pure
bool IsAbsolute() {
- return PathTraits::IsAbsoluteFS(c_str());
+ return PathTraitsFS::IsAbsolute(c_str());
}
};
diff --git a/src/fs/Charset.cxx b/src/fs/Charset.cxx
index 0b598ef46..c634c9340 100644
--- a/src/fs/Charset.cxx
+++ b/src/fs/Charset.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,15 +22,20 @@
#include "Domain.hxx"
#include "Limits.hxx"
#include "system/FatalError.hxx"
-#include "util/Error.hxx"
-#include "util/Domain.hxx"
#include "Log.hxx"
+#include "Traits.hxx"
+#ifdef HAVE_GLIB
#include <glib.h>
+#endif
+
+#include <algorithm>
#include <assert.h>
#include <string.h>
+#ifdef HAVE_GLIB
+
/**
* Maximal number of bytes required to represent path name in UTF-8
* (including nul-terminator).
@@ -70,10 +75,29 @@ SetFSCharset(const char *charset)
"SetFSCharset: fs charset is: %s", fs_charset.c_str());
}
+#endif
+
const char *
GetFSCharset()
{
- return fs_charset.empty() ? "utf-8" : fs_charset.c_str();
+#ifdef HAVE_GLIB
+ return fs_charset.empty() ? "UTF-8" : fs_charset.c_str();
+#else
+ return "UTF-8";
+#endif
+}
+
+static inline void FixSeparators(std::string &s)
+{
+#ifdef WIN32
+ // For whatever reason GCC can't convert constexpr to value reference.
+ // This leads to link errors when passing separators directly.
+ auto from = PathTraitsFS::SEPARATOR;
+ auto to = PathTraitsUTF8::SEPARATOR;
+ std::replace(s.begin(), s.end(), from, to);
+#else
+ (void)s;
+#endif
}
std::string
@@ -84,8 +108,14 @@ PathToUTF8(const char *path_fs)
assert(path_fs != nullptr);
#endif
- if (fs_charset.empty())
- return std::string(path_fs);
+#ifdef HAVE_GLIB
+ if (fs_charset.empty()) {
+#endif
+ auto result = std::string(path_fs);
+ FixSeparators(result);
+ return result;
+#ifdef HAVE_GLIB
+ }
GIConv conv = g_iconv_open("utf-8", fs_charset.c_str());
if (conv == reinterpret_cast<GIConv>(-1))
@@ -106,9 +136,14 @@ PathToUTF8(const char *path_fs)
if (ret == static_cast<size_t>(-1) || in_left > 0)
return std::string();
- return std::string(path_utf8, sizeof(path_utf8) - out_left);
+ auto result_path = std::string(path_utf8, sizeof(path_utf8) - out_left);
+ FixSeparators(result_path);
+ return result_path;
+#endif
}
+#ifdef HAVE_GLIB
+
char *
PathFromUTF8(const char *path_utf8)
{
@@ -124,3 +159,5 @@ PathFromUTF8(const char *path_utf8)
fs_charset.c_str(), "utf-8",
nullptr, nullptr, nullptr);
}
+
+#endif
diff --git a/src/fs/Charset.hxx b/src/fs/Charset.hxx
index a89cb0459..0a71d7c58 100644
--- a/src/fs/Charset.hxx
+++ b/src/fs/Charset.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/fs/CheckFile.cxx b/src/fs/CheckFile.cxx
new file mode 100644
index 000000000..a35443674
--- /dev/null
+++ b/src/fs/CheckFile.cxx
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "CheckFile.hxx"
+#include "Log.hxx"
+#include "config/ConfigError.hxx"
+#include "FileSystem.hxx"
+#include "Path.hxx"
+#include "AllocatedPath.hxx"
+#include "DirectoryReader.hxx"
+
+#include <errno.h>
+#include <sys/stat.h>
+
+void
+CheckDirectoryReadable(Path path_fs)
+{
+ struct stat st;
+ if (!StatFile(path_fs, st)) {
+ FormatErrno(config_domain,
+ "Failed to stat directory \"%s\"",
+ path_fs.c_str());
+ return;
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ FormatError(config_domain,
+ "Not a directory: %s", path_fs.c_str());
+ return;
+ }
+
+#ifndef WIN32
+ const auto x = AllocatedPath::Build(path_fs, ".");
+ if (!StatFile(x, st) && errno == EACCES)
+ FormatError(config_domain,
+ "No permission to traverse (\"execute\") directory: %s",
+ path_fs.c_str());
+#endif
+
+ const DirectoryReader reader(path_fs);
+ if (reader.HasFailed() && errno == EACCES)
+ FormatError(config_domain,
+ "No permission to read directory: %s", path_fs.c_str());
+
+}
diff --git a/src/fs/CheckFile.hxx b/src/fs/CheckFile.hxx
new file mode 100644
index 000000000..00559647d
--- /dev/null
+++ b/src/fs/CheckFile.hxx
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FS_CHECK_FILE_HXX
+#define MPD_FS_CHECK_FILE_HXX
+
+#include "check.h"
+
+class Path;
+
+/**
+ * Check whether the directory is readable and usable. Logs a warning
+ * if there is a problem.
+ */
+void
+CheckDirectoryReadable(Path path_fs);
+
+#endif
diff --git a/src/fs/Config.cxx b/src/fs/Config.cxx
index 63e64ef99..6aa23005c 100644
--- a/src/fs/Config.cxx
+++ b/src/fs/Config.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,24 +20,19 @@
#include "config.h"
#include "Config.hxx"
#include "Charset.hxx"
-#include "Domain.hxx"
-#include "ConfigGlobal.hxx"
-#include "Log.hxx"
-#include "Compiler.h"
-
-#include <glib.h>
-
-#include <assert.h>
-#include <string.h>
+#include "config/ConfigGlobal.hxx"
#ifdef WIN32
#include <windows.h> // for GetACP()
#include <stdio.h> // for sprintf()
+#elif defined(HAVE_GLIB)
+#include <glib.h>
#endif
void
ConfigureFS()
{
+#if defined(HAVE_GLIB) || defined(WIN32)
const char *charset = nullptr;
charset = config_get_string(CONF_FS_CHARSET, nullptr);
@@ -62,4 +57,5 @@ ConfigureFS()
if (charset != nullptr)
SetFSCharset(charset);
+#endif
}
diff --git a/src/fs/Config.hxx b/src/fs/Config.hxx
index 9d7035706..d4f1709f5 100644
--- a/src/fs/Config.hxx
+++ b/src/fs/Config.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/fs/DirectoryReader.hxx b/src/fs/DirectoryReader.hxx
index c9d2c04b8..f77c0629f 100644
--- a/src/fs/DirectoryReader.hxx
+++ b/src/fs/DirectoryReader.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -21,7 +21,7 @@
#define MPD_FS_DIRECTORY_READER_HXX
#include "check.h"
-#include "AllocatedPath.hxx"
+#include "Path.hxx"
#include <dirent.h>
@@ -78,9 +78,9 @@ public:
/**
* Extracts directory entry that was previously read by #ReadEntry.
*/
- AllocatedPath GetEntry() const {
+ Path GetEntry() const {
assert(HasEntry());
- return AllocatedPath::FromFS(ent->d_name);
+ return Path::FromFS(ent->d_name);
}
};
diff --git a/src/fs/Domain.cxx b/src/fs/Domain.cxx
index 0877bca4c..4f3129219 100644
--- a/src/fs/Domain.cxx
+++ b/src/fs/Domain.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/fs/Domain.hxx b/src/fs/Domain.hxx
index b303570fc..1fd17b37f 100644
--- a/src/fs/Domain.hxx
+++ b/src/fs/Domain.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/fs/FileSystem.cxx b/src/fs/FileSystem.cxx
index 4cd9f33b2..4e7c87415 100644
--- a/src/fs/FileSystem.cxx
+++ b/src/fs/FileSystem.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/fs/FileSystem.hxx b/src/fs/FileSystem.hxx
index cb2f82d22..4dbb064cb 100644
--- a/src/fs/FileSystem.hxx
+++ b/src/fs/FileSystem.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -28,7 +28,6 @@
#include <sys/stat.h>
#include <unistd.h>
-#include <assert.h>
#include <stdio.h>
class AllocatedPath;
@@ -37,39 +36,39 @@ namespace FOpenMode {
/**
* Open mode for reading text files.
*/
- constexpr PathTraits::const_pointer ReadText = "r";
+ constexpr PathTraitsFS::const_pointer ReadText = "r";
/**
* Open mode for reading binary files.
*/
- constexpr PathTraits::const_pointer ReadBinary = "rb";
+ constexpr PathTraitsFS::const_pointer ReadBinary = "rb";
/**
* Open mode for writing text files.
*/
- constexpr PathTraits::const_pointer WriteText = "w";
+ constexpr PathTraitsFS::const_pointer WriteText = "w";
/**
* Open mode for writing binary files.
*/
- constexpr PathTraits::const_pointer WriteBinary = "wb";
+ constexpr PathTraitsFS::const_pointer WriteBinary = "wb";
/**
* Open mode for appending text files.
*/
- constexpr PathTraits::const_pointer AppendText = "a";
+ constexpr PathTraitsFS::const_pointer AppendText = "a";
/**
* Open mode for appending binary files.
*/
- constexpr PathTraits::const_pointer AppendBinary = "ab";
+ constexpr PathTraitsFS::const_pointer AppendBinary = "ab";
}
/**
* Wrapper for fopen() that uses #Path names.
*/
static inline FILE *
-FOpen(Path file, PathTraits::const_pointer mode)
+FOpen(Path file, PathTraitsFS::const_pointer mode)
{
return fopen(file.c_str(), mode);
}
@@ -132,20 +131,28 @@ MakeFifo(Path path, mode_t mode)
return mkfifo(path.c_str(), mode) == 0;
}
-#endif
-
/**
* Wrapper for access() that uses #Path names.
*/
static inline bool
CheckAccess(Path path, int mode)
{
+ return access(path.c_str(), mode) == 0;
+}
+
+#endif
+
+/**
+ * Checks is specified path exists and accessible.
+ */
+static inline bool
+CheckAccess(Path path)
+{
#ifdef WIN32
- (void)path;
- (void)mode;
- return true;
+ struct stat buf;
+ return StatFile(path, buf);
#else
- return access(path.c_str(), mode) == 0;
+ return CheckAccess(path, F_OK);
#endif
}
diff --git a/src/fs/Limits.hxx b/src/fs/Limits.hxx
index 480b08851..432897a69 100644
--- a/src/fs/Limits.hxx
+++ b/src/fs/Limits.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/fs/Path.cxx b/src/fs/Path.cxx
index 0ff0591fb..8288a4fec 100644
--- a/src/fs/Path.cxx
+++ b/src/fs/Path.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -26,25 +26,3 @@ Path::ToUTF8() const
{
return ::PathToUTF8(c_str());
}
-
-const char *
-Path::RelativeFS(const char *other_fs) const
-{
- const size_t l = length();
- if (memcmp(data(), other_fs, l) != 0)
- return nullptr;
-
- other_fs += l;
- if (*other_fs != 0) {
- if (!PathTraits::IsSeparatorFS(*other_fs))
- /* mismatch */
- return nullptr;
-
- /* skip remaining path separators */
- do {
- ++other_fs;
- } while (PathTraits::IsSeparatorFS(*other_fs));
- }
-
- return other_fs;
-}
diff --git a/src/fs/Path.hxx b/src/fs/Path.hxx
index 6ea954577..9e0fa5aeb 100644
--- a/src/fs/Path.hxx
+++ b/src/fs/Path.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -29,8 +29,6 @@
#include <assert.h>
#include <string.h>
-class Error;
-
/**
* A path name in the native file system character set.
*
@@ -38,9 +36,9 @@ class Error;
* instance lives, the string must not be invalidated.
*/
class Path {
- typedef PathTraits::value_type value_type;
- typedef PathTraits::pointer pointer;
- typedef PathTraits::const_pointer const_pointer;
+ typedef PathTraitsFS::value_type value_type;
+ typedef PathTraitsFS::pointer pointer;
+ typedef PathTraitsFS::const_pointer const_pointer;
const char *value;
@@ -137,11 +135,13 @@ public:
* nullptr on mismatch.
*/
gcc_pure
- const char *RelativeFS(const char *other_fs) const;
+ const char *RelativeFS(const char *other_fs) const {
+ return PathTraitsFS::Relative(value, other_fs);
+ }
gcc_pure
bool IsAbsolute() {
- return PathTraits::IsAbsoluteFS(c_str());
+ return PathTraitsFS::IsAbsolute(c_str());
}
};
diff --git a/src/fs/StandardDirectory.cxx b/src/fs/StandardDirectory.cxx
new file mode 100644
index 000000000..7a836f906
--- /dev/null
+++ b/src/fs/StandardDirectory.cxx
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+// Use X Desktop guidelines where applicable
+#if !defined(__APPLE__) && !defined(WIN32) && !defined(ANDROID)
+#define USE_XDG
+#endif
+
+#include "StandardDirectory.hxx"
+#include "FileSystem.hxx"
+
+#include <array>
+
+#ifdef WIN32
+#include <windows.h>
+#include <shlobj.h>
+#else
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <pwd.h>
+#endif
+
+#ifdef USE_XDG
+#include "util/Error.hxx"
+#include "util/StringUtil.hxx"
+#include "io/TextFile.hxx"
+#include <string.h>
+#include <utility>
+#endif
+
+#ifdef ANDROID
+#include "java/Global.hxx"
+#include "android/Environment.hxx"
+#include "android/Context.hxx"
+#include "Main.hxx"
+#endif
+
+#ifndef WIN32
+class PasswdEntry
+{
+#if defined(HAVE_GETPWNAM_R) || defined(HAVE_GETPWUID_R)
+ std::array<char, 16 * 1024> buf;
+ passwd pw;
+#endif
+
+ passwd *result;
+public:
+ PasswdEntry() : result(nullptr) { }
+
+ bool ReadByName(const char *name) {
+#ifdef HAVE_GETPWNAM_R
+ getpwnam_r(name, &pw, buf.data(), buf.size(), &result);
+#else
+ result = getpwnam(name);
+#endif
+ return result != nullptr;
+ }
+
+ bool ReadByUid(uid_t uid) {
+#ifdef HAVE_GETPWUID_R
+ getpwuid_r(uid, &pw, buf.data(), buf.size(), &result);
+#else
+ result = getpwuid(uid);
+#endif
+ return result != nullptr;
+ }
+
+ const passwd *operator->() {
+ assert(result != nullptr);
+ return result;
+ }
+};
+#endif
+
+static inline bool IsValidPathString(PathTraitsFS::const_pointer path)
+{
+ return path != nullptr && *path != '\0';
+}
+
+static inline bool IsValidDir(PathTraitsFS::const_pointer dir)
+{
+ return PathTraitsFS::IsAbsolute(dir) &&
+ DirectoryExists(Path::FromFS(dir));
+}
+
+static inline AllocatedPath SafePathFromFS(PathTraitsFS::const_pointer dir)
+{
+ if (IsValidPathString(dir) && IsValidDir(dir))
+ return AllocatedPath::FromFS(dir);
+ return AllocatedPath::Null();
+}
+
+#ifdef WIN32
+static AllocatedPath GetStandardDir(int folder_id)
+{
+ std::array<char, MAX_PATH> dir;
+ auto ret = SHGetFolderPath(nullptr, folder_id | CSIDL_FLAG_DONT_VERIFY,
+ nullptr, SHGFP_TYPE_CURRENT, dir.data());
+ if (FAILED(ret))
+ return AllocatedPath::Null();
+ return SafePathFromFS(dir.data());
+}
+#endif
+
+#ifdef USE_XDG
+
+static const char home_prefix[] = "$HOME/";
+
+static bool
+ParseConfigLine(char *line, const char *dir_name, AllocatedPath &result_dir)
+{
+ // strip leading white space
+ line = StripLeft(line);
+
+ // check for end-of-line or comment
+ if (*line == '\0' || *line == '#')
+ return false;
+
+ // check if current setting is for requested dir
+ if (!StringStartsWith(line, dir_name))
+ return false;
+ line += strlen(dir_name);
+
+ // strip equals sign and spaces around it
+ line = StripLeft(line);
+ if (*line != '=')
+ return false;
+ ++line;
+ line = StripLeft(line);
+
+ // check if path is quoted
+ bool quoted = false;
+ if (*line == '"') {
+ ++line;
+ quoted = true;
+ }
+
+ // check if path is relative to $HOME
+ bool home_relative = false;
+ if (StringStartsWith(line, home_prefix)) {
+ line += strlen(home_prefix);
+ home_relative = true;
+ }
+
+
+ char *line_end;
+ // find end of the string
+ if (quoted) {
+ line_end = strrchr(line, '"');
+ if (line_end == nullptr)
+ return true;
+ } else {
+ line_end = StripRight(line, line + strlen(line));
+ }
+
+ // check for empty result
+ if (line == line_end)
+ return true;
+
+ *line_end = 0;
+
+ // build the result path
+ const char *path = line;
+
+ auto result = AllocatedPath::Null();
+ if (home_relative) {
+ auto home = GetHomeDir();
+ if (home.IsNull())
+ return true;
+ result = AllocatedPath::Build(home, path);
+ } else {
+ result = AllocatedPath::FromFS(path);
+ }
+
+ if (IsValidDir(result.c_str())) {
+ result_dir = std::move(result);
+ return true;
+ }
+ return true;
+}
+
+static AllocatedPath GetUserDir(const char *name)
+{
+ 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;
+ char *line;
+ while ((line = input.ReadLine()) != nullptr)
+ if (ParseConfigLine(line, name, result))
+ return result;
+ return result;
+}
+
+#endif
+
+AllocatedPath GetUserConfigDir()
+{
+#if defined(WIN32)
+ return GetStandardDir(CSIDL_LOCAL_APPDATA);
+#elif defined(USE_XDG)
+ // Check for $XDG_CONFIG_HOME
+ auto config_home = getenv("XDG_CONFIG_HOME");
+ if (IsValidPathString(config_home) && IsValidDir(config_home))
+ return AllocatedPath::FromFS(config_home);
+
+ // Check for $HOME/.config
+ auto home = GetHomeDir();
+ if (!home.IsNull()) {
+ AllocatedPath fallback = AllocatedPath::Build(home, ".config");
+ if (IsValidDir(fallback.c_str()))
+ return fallback;
+ }
+
+ return AllocatedPath::Null();
+#else
+ return AllocatedPath::Null();
+#endif
+}
+
+AllocatedPath GetUserMusicDir()
+{
+#if defined(WIN32)
+ return GetStandardDir(CSIDL_MYMUSIC);
+#elif defined(USE_XDG)
+ return GetUserDir("XDG_MUSIC_DIR");
+#elif defined(ANDROID)
+ return Environment::getExternalStoragePublicDirectory("Music");
+#else
+ return AllocatedPath::Null();
+#endif
+}
+
+AllocatedPath GetUserCacheDir()
+{
+#ifdef USE_XDG
+ // Check for $XDG_CACHE_HOME
+ auto cache_home = getenv("XDG_CACHE_HOME");
+ if (IsValidPathString(cache_home) && IsValidDir(cache_home))
+ return AllocatedPath::FromFS(cache_home);
+
+ // Check for $HOME/.cache
+ auto home = GetHomeDir();
+ if (!home.IsNull()) {
+ AllocatedPath fallback = AllocatedPath::Build(home, ".cache");
+ if (IsValidDir(fallback.c_str()))
+ return fallback;
+ }
+
+ return AllocatedPath::Null();
+#elif defined(ANDROID)
+ return context->GetCacheDir(Java::GetEnv());
+#else
+ return AllocatedPath::Null();
+#endif
+}
+
+#ifdef WIN32
+
+AllocatedPath GetSystemConfigDir()
+{
+ return GetStandardDir(CSIDL_COMMON_APPDATA);
+}
+
+AllocatedPath GetAppBaseDir()
+{
+ std::array<char, MAX_PATH> app;
+ auto ret = GetModuleFileName(nullptr, app.data(), app.size());
+
+ // Check for error
+ if (ret == 0)
+ return AllocatedPath::Null();
+
+ // Check for truncation
+ if (ret == app.size() && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
+ return AllocatedPath::Null();
+
+ auto app_path = AllocatedPath::FromFS(app.data());
+ return app_path.GetDirectoryName().GetDirectoryName();
+}
+
+#else
+
+AllocatedPath GetHomeDir()
+{
+ auto home = getenv("HOME");
+ if (IsValidPathString(home) && IsValidDir(home))
+ return AllocatedPath::FromFS(home);
+ PasswdEntry pw;
+ if (pw.ReadByUid(getuid()))
+ return SafePathFromFS(pw->pw_dir);
+ return AllocatedPath::Null();
+}
+
+AllocatedPath GetHomeDir(const char *user_name)
+{
+ assert(user_name != nullptr);
+ PasswdEntry pw;
+ if (pw.ReadByName(user_name))
+ return SafePathFromFS(pw->pw_dir);
+ return AllocatedPath::Null();
+}
+
+#endif
diff --git a/src/fs/StandardDirectory.hxx b/src/fs/StandardDirectory.hxx
new file mode 100644
index 000000000..e3fba375a
--- /dev/null
+++ b/src/fs/StandardDirectory.hxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FS_STANDARD_DIRECTORY_HXX
+#define MPD_FS_STANDARD_DIRECTORY_HXX
+
+#include "check.h"
+#include "AllocatedPath.hxx"
+
+/**
+ * Obtains configuration directory for the current user.
+ */
+AllocatedPath GetUserConfigDir();
+
+/**
+ * Obtains music directory for the current user.
+ */
+AllocatedPath GetUserMusicDir();
+
+/**
+ * Obtains cache directory for the current user.
+ */
+gcc_pure
+AllocatedPath
+GetUserCacheDir();
+
+#ifdef WIN32
+
+/**
+ * Obtains system configuration directory.
+ */
+AllocatedPath GetSystemConfigDir();
+
+/**
+ * Obtains application application base directory.
+ * Application base directory is a directory that contains 'bin' folder
+ * for current executable.
+ */
+AllocatedPath GetAppBaseDir();
+
+#else
+
+/**
+ * Obtains home directory for the current user.
+ */
+AllocatedPath GetHomeDir();
+
+/**
+ * Obtains home directory for the specified user.
+ */
+AllocatedPath GetHomeDir(const char *user_name);
+
+#endif
+
+#endif
diff --git a/src/fs/Traits.cxx b/src/fs/Traits.cxx
index 47cb5aee3..166b31f4e 100644
--- a/src/fs/Traits.cxx
+++ b/src/fs/Traits.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -22,30 +22,142 @@
#include <string.h>
-const char *
-PathTraits::GetBaseUTF8(const char *p)
+template<typename Traits>
+typename Traits::string
+BuildPathImpl(typename Traits::const_pointer a, size_t a_size,
+ typename Traits::const_pointer b, size_t b_size)
+{
+ assert(a != nullptr);
+ assert(b != nullptr);
+
+ if (a_size == 0)
+ return typename Traits::string(b, b_size);
+ if (b_size == 0)
+ return typename Traits::string(a, a_size);
+
+ typename Traits::string result(a, a_size);
+
+ if (!Traits::IsSeparator(a[a_size - 1]))
+ result.push_back(Traits::SEPARATOR);
+
+ if (Traits::IsSeparator(b[0]))
+ result.append(b + 1, b_size - 1);
+ else
+ result.append(b, b_size);
+
+ return result;
+}
+
+template<typename Traits>
+typename Traits::const_pointer
+GetBasePathImpl(typename Traits::const_pointer p)
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(p != nullptr);
#endif
- const char *slash = strrchr(p, SEPARATOR_UTF8);
- return slash != nullptr
- ? slash + 1
+ typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
+ return sep != nullptr
+ ? sep + 1
: p;
}
-std::string
-PathTraits::GetParentUTF8(const char *p)
+template<typename Traits>
+typename Traits::string
+GetParentPathImpl(typename Traits::const_pointer p)
{
#if !CLANG_CHECK_VERSION(3,6)
/* disabled on clang due to -Wtautological-pointer-compare */
assert(p != nullptr);
#endif
- const char *slash = strrchr(p, SEPARATOR_UTF8);
- return slash != nullptr
- ? std::string(p, slash)
- : std::string(".");
+ typename Traits::const_pointer sep = Traits::FindLastSeparator(p);
+ if (sep == nullptr)
+ return typename Traits::string(".");
+ if (sep == p)
+ return typename Traits::string(p, p + 1);
+#ifdef WIN32
+ if (Traits::IsDrive(p) && sep == p + 2)
+ return typename Traits::string(p, p + 3);
+#endif
+ return typename Traits::string(p, sep);
+}
+
+template<typename Traits>
+typename Traits::const_pointer
+RelativePathImpl(typename Traits::const_pointer base,
+ typename Traits::const_pointer other)
+{
+ assert(base != nullptr);
+ assert(other != nullptr);
+
+ const auto base_length = Traits::GetLength(base);
+ if (memcmp(base, other, base_length * sizeof(*base)) != 0)
+ /* mismatch */
+ return nullptr;
+
+ other += base_length;
+ if (other != 0) {
+ if (!Traits::IsSeparator(*other))
+ /* mismatch */
+ return nullptr;
+
+ /* skip remaining path separators */
+ do {
+ ++other;
+ } while (Traits::IsSeparator(*other));
+ }
+
+ return other;
+}
+
+PathTraitsFS::string
+PathTraitsFS::Build(PathTraitsFS::const_pointer a, size_t a_size,
+ PathTraitsFS::const_pointer b, size_t b_size)
+{
+ return BuildPathImpl<PathTraitsFS>(a, a_size, b, b_size);
+}
+
+PathTraitsFS::const_pointer
+PathTraitsFS::GetBase(PathTraitsFS::const_pointer p)
+{
+ return GetBasePathImpl<PathTraitsFS>(p);
+}
+
+PathTraitsFS::string
+PathTraitsFS::GetParent(PathTraitsFS::const_pointer p)
+{
+ return GetParentPathImpl<PathTraitsFS>(p);
+}
+
+PathTraitsFS::const_pointer
+PathTraitsFS::Relative(const_pointer base, const_pointer other)
+{
+ return RelativePathImpl<PathTraitsFS>(base, other);
+}
+
+PathTraitsUTF8::string
+PathTraitsUTF8::Build(PathTraitsUTF8::const_pointer a, size_t a_size,
+ PathTraitsUTF8::const_pointer b, size_t b_size)
+{
+ return BuildPathImpl<PathTraitsUTF8>(a, a_size, b, b_size);
+}
+
+PathTraitsUTF8::const_pointer
+PathTraitsUTF8::GetBase(PathTraitsUTF8::const_pointer p)
+{
+ return GetBasePathImpl<PathTraitsUTF8>(p);
+}
+
+PathTraitsUTF8::string
+PathTraitsUTF8::GetParent(PathTraitsUTF8::const_pointer p)
+{
+ return GetParentPathImpl<PathTraitsUTF8>(p);
+}
+
+PathTraitsUTF8::const_pointer
+PathTraitsUTF8::Relative(const_pointer base, const_pointer other)
+{
+ return RelativePathImpl<PathTraitsUTF8>(base, other);
}
diff --git a/src/fs/Traits.hxx b/src/fs/Traits.hxx
index 244ab8b5c..1af8f8672 100644
--- a/src/fs/Traits.hxx
+++ b/src/fs/Traits.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -24,67 +24,169 @@
#include "Compiler.h"
#ifdef WIN32
-#include <glib.h>
+#include "util/CharUtil.hxx"
#endif
#include <string>
+#include <string.h>
#include <assert.h>
-class Error;
-
/**
- * This class describes the nature of a filesystem path.
+ * This class describes the nature of a native filesystem path.
*/
-struct PathTraits {
+struct PathTraitsFS {
+ typedef std::string string;
typedef char value_type;
- typedef char *pointer;
- typedef const char *const_pointer;
+ typedef value_type *pointer;
+ typedef const value_type *const_pointer;
#ifdef WIN32
- static constexpr value_type SEPARATOR_FS = '\\';
- static constexpr char SEPARATOR_UTF8 = '/';
+ static constexpr value_type SEPARATOR = '\\';
#else
- static constexpr value_type SEPARATOR_FS = '/';
- static constexpr char SEPARATOR_UTF8 = '/';
+ static constexpr value_type SEPARATOR = '/';
#endif
- static constexpr bool IsSeparatorFS(value_type ch) {
+ static constexpr bool IsSeparator(value_type ch) {
return
#ifdef WIN32
ch == '/' ||
#endif
- ch == SEPARATOR_FS;
+ ch == SEPARATOR;
}
- static constexpr bool IsSeparatorUTF8(char ch) {
- return
+ gcc_pure gcc_nonnull_all
+ static const_pointer FindLastSeparator(const_pointer p) {
+#if !CLANG_CHECK_VERSION(3,6)
+ /* disabled on clang due to -Wtautological-pointer-compare */
+ assert(p != nullptr);
+#endif
+
#ifdef WIN32
- ch == '/' ||
+ const_pointer pos = p + GetLength(p);
+ while (p != pos && !IsSeparator(*pos))
+ --pos;
+ return IsSeparator(*pos) ? pos : nullptr;
+#else
+ return strrchr(p, SEPARATOR);
#endif
- ch == SEPARATOR_UTF8;
}
- gcc_pure
- static bool IsAbsoluteFS(const_pointer p) {
+#ifdef WIN32
+ gcc_pure gcc_nonnull_all
+ static constexpr bool IsDrive(const_pointer p) {
+ return IsAlphaASCII(p[0]) && p[1] == ':';
+ }
+#endif
+
+ gcc_pure gcc_nonnull_all
+ static bool IsAbsolute(const_pointer p) {
+#if !CLANG_CHECK_VERSION(3,6)
+ /* disabled on clang due to -Wtautological-pointer-compare */
assert(p != nullptr);
+#endif
#ifdef WIN32
- return g_path_is_absolute(p);
-#else
- return IsSeparatorFS(*p);
+ if (IsDrive(p) && IsSeparator(p[2]))
+ return true;
#endif
+ return IsSeparator(*p);
}
- gcc_pure
- static bool IsAbsoluteUTF8(const char *p) {
+ gcc_pure gcc_nonnull_all
+ static size_t GetLength(const_pointer p) {
+ return strlen(p);
+ }
+
+ /**
+ * Determine the "base" file name of the given native path.
+ * The return value points inside the given string.
+ */
+ gcc_pure gcc_nonnull_all
+ static const_pointer GetBase(const_pointer p);
+
+ /**
+ * Determine the "parent" file name of the given native path.
+ * As a special case, returns the string "." if there is no
+ * separator in the given input string.
+ */
+ gcc_pure gcc_nonnull_all
+ static string GetParent(const_pointer p);
+
+ /**
+ * Determine the relative part of the given path to this
+ * object, not including the directory separator. Returns an
+ * empty string if the given path equals this object or
+ * nullptr on mismatch.
+ */
+ gcc_pure gcc_nonnull_all
+ static const_pointer Relative(const_pointer base, const_pointer other);
+
+ /**
+ * Constructs the path from the given components.
+ * If either of the components is empty string,
+ * remaining component is returned unchanged.
+ * If both components are empty strings, empty string is returned.
+ */
+ gcc_pure gcc_nonnull_all
+ static string Build(const_pointer a, size_t a_size,
+ const_pointer b, size_t b_size);
+
+ gcc_pure gcc_nonnull_all
+ static string Build(const_pointer a, const_pointer b) {
+ return Build(a, GetLength(a), b, GetLength(b));
+ }
+};
+
+/**
+ * This class describes the nature of a MPD internal filesystem path.
+ */
+struct PathTraitsUTF8 {
+ typedef std::string string;
+ typedef char value_type;
+ typedef value_type *pointer;
+ typedef const value_type *const_pointer;
+
+ static constexpr value_type SEPARATOR = '/';
+
+ static constexpr bool IsSeparator(value_type ch) {
+ return ch == SEPARATOR;
+ }
+
+ gcc_pure gcc_nonnull_all
+ static const_pointer FindLastSeparator(const_pointer p) {
+#if !CLANG_CHECK_VERSION(3,6)
+ /* disabled on clang due to -Wtautological-pointer-compare */
assert(p != nullptr);
+#endif
+
+ return strrchr(p, SEPARATOR);
+ }
#ifdef WIN32
- return g_path_is_absolute(p);
-#else
- return IsSeparatorUTF8(*p);
+ gcc_pure gcc_nonnull_all
+ static constexpr bool IsDrive(const_pointer p) {
+ return IsAlphaASCII(p[0]) && p[1] == ':';
+ }
+#endif
+
+ gcc_pure gcc_nonnull_all
+ static bool IsAbsolute(const_pointer p) {
+#if !CLANG_CHECK_VERSION(3,6)
+ /* disabled on clang due to -Wtautological-pointer-compare */
+ assert(p != nullptr);
+#endif
+
+#ifdef WIN32
+ if (IsDrive(p) && IsSeparator(p[2]))
+ return true;
#endif
+ return IsSeparator(*p);
+ }
+
+ gcc_pure gcc_nonnull_all
+ static size_t GetLength(const_pointer p) {
+ return strlen(p);
}
/**
@@ -92,7 +194,7 @@ struct PathTraits {
* The return value points inside the given string.
*/
gcc_pure gcc_nonnull_all
- static const char *GetBaseUTF8(const char *p);
+ static const_pointer GetBase(const_pointer p);
/**
* Determine the "parent" file name of the given UTF-8 path.
@@ -100,7 +202,31 @@ struct PathTraits {
* separator in the given input string.
*/
gcc_pure gcc_nonnull_all
- static std::string GetParentUTF8(const char *p);
+ static string GetParent(const_pointer p);
+
+ /**
+ * Determine the relative part of the given path to this
+ * object, not including the directory separator. Returns an
+ * empty string if the given path equals this object or
+ * nullptr on mismatch.
+ */
+ gcc_pure gcc_nonnull_all
+ static const_pointer Relative(const_pointer base, const_pointer other);
+
+ /**
+ * Constructs the path from the given components.
+ * If either of the components is empty string,
+ * remaining component is returned unchanged.
+ * If both components are empty strings, empty string is returned.
+ */
+ gcc_pure gcc_nonnull_all
+ static string Build(const_pointer a, size_t a_size,
+ const_pointer b, size_t b_size);
+
+ gcc_pure gcc_nonnull_all
+ static string Build(const_pointer a, const_pointer b) {
+ return Build(a, GetLength(a), b, GetLength(b));
+ }
};
#endif
diff --git a/src/fs/io/AutoGunzipReader.cxx b/src/fs/io/AutoGunzipReader.cxx
new file mode 100644
index 000000000..2552f7b99
--- /dev/null
+++ b/src/fs/io/AutoGunzipReader.cxx
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "AutoGunzipReader.hxx"
+#include "GunzipReader.hxx"
+#include "util/Error.hxx"
+
+AutoGunzipReader::~AutoGunzipReader()
+{
+ delete gunzip;
+}
+
+gcc_pure
+static bool
+IsGzip(const uint8_t data[4])
+{
+ return data[0] == 0x1f && data[1] == 0x8b && data[2] == 0x08 &&
+ (data[3] & 0xe0) == 0;
+}
+
+inline bool
+AutoGunzipReader::Detect(Error &error)
+{
+ const uint8_t *data = (const uint8_t *)peek.Peek(4, error);
+ if (data == nullptr) {
+ if (error.IsDefined())
+ return false;
+
+ next = &peek;
+ return true;
+ }
+
+ if (IsGzip(data)) {
+ gunzip = new GunzipReader(peek, error);
+ if (!gunzip->IsDefined())
+ return false;
+
+
+ next = gunzip;
+ } else
+ next = &peek;
+
+ return true;
+}
+
+size_t
+AutoGunzipReader::Read(void *data, size_t size, Error &error)
+{
+ if (next == nullptr && !Detect(error))
+ return false;
+
+ return next->Read(data, size, error);
+}
diff --git a/src/fs/io/AutoGunzipReader.hxx b/src/fs/io/AutoGunzipReader.hxx
new file mode 100644
index 000000000..9f031e0f5
--- /dev/null
+++ b/src/fs/io/AutoGunzipReader.hxx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_AUTO_GUNZIP_READER_HXX
+#define MPD_AUTO_GUNZIP_READER_HXX
+
+#include "check.h"
+#include "PeekReader.hxx"
+#include "Compiler.h"
+
+#include <stdint.h>
+
+class GunzipReader;
+
+/**
+ * A filter that detects gzip compression and optionally inserts a
+ * #GunzipReader.
+ */
+class AutoGunzipReader final : public Reader {
+ Reader *next;
+ PeekReader peek;
+ GunzipReader *gunzip;
+
+public:
+ AutoGunzipReader(Reader &_next)
+ :next(nullptr), peek(_next), gunzip(nullptr) {}
+ ~AutoGunzipReader();
+
+ /* virtual methods from class Reader */
+ virtual size_t Read(void *data, size_t size, Error &error) override;
+
+private:
+ bool Detect(Error &error);
+};
+
+#endif
diff --git a/src/fs/io/BufferedOutputStream.cxx b/src/fs/io/BufferedOutputStream.cxx
new file mode 100644
index 000000000..088a3e279
--- /dev/null
+++ b/src/fs/io/BufferedOutputStream.cxx
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "BufferedOutputStream.hxx"
+#include "OutputStream.hxx"
+
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+bool
+BufferedOutputStream::AppendToBuffer(const void *data, size_t size)
+{
+ auto r = buffer.Write();
+ if (r.size < size)
+ return false;
+
+ memcpy(r.data, data, size);
+ buffer.Append(size);
+ return true;
+}
+
+bool
+BufferedOutputStream::Write(const void *data, size_t size)
+{
+ if (gcc_unlikely(last_error.IsDefined()))
+ return false;
+
+ if (AppendToBuffer(data, size))
+ return true;
+
+ if (!Flush())
+ return false;
+
+ if (AppendToBuffer(data, size))
+ return true;
+
+ return os.Write(data, size, last_error);
+}
+
+bool
+BufferedOutputStream::Write(const char *p)
+{
+ return Write(p, strlen(p));
+}
+
+bool
+BufferedOutputStream::Format(const char *fmt, ...)
+{
+ if (gcc_unlikely(last_error.IsDefined()))
+ return false;
+
+ auto r = buffer.Write();
+ if (r.IsEmpty()) {
+ if (!Flush())
+ return false;
+
+ r = buffer.Write();
+ }
+
+ /* format into the buffer */
+ va_list ap;
+ va_start(ap, fmt);
+ size_t size = vsnprintf(r.data, r.size, fmt, ap);
+ va_end(ap);
+
+ if (gcc_unlikely(size >= r.size)) {
+ /* buffer was not large enough; flush it and try
+ again */
+
+ if (!Flush())
+ return false;
+
+ r = buffer.Write();
+
+ if (gcc_unlikely(size >= r.size)) {
+ /* still not enough space: grow the buffer and
+ try again */
+ r.size = size + 1;
+ r.data = buffer.Write(r.size);
+ }
+
+ /* format into the new buffer */
+ va_start(ap, fmt);
+ size = vsnprintf(r.data, r.size, fmt, ap);
+ va_end(ap);
+
+ /* this time, it must fit */
+ assert(size < r.size);
+ }
+
+ buffer.Append(size);
+ return true;
+}
+
+bool
+BufferedOutputStream::Flush()
+{
+ if (!Check())
+ return false;
+
+ auto r = buffer.Read();
+ if (r.IsEmpty())
+ return true;
+
+ bool success = os.Write(r.data, r.size, last_error);
+ if (gcc_likely(success))
+ buffer.Consume(r.size);
+ return success;
+}
+
+bool
+BufferedOutputStream::Flush(Error &error)
+{
+ if (!Check(error))
+ return false;
+
+ auto r = buffer.Read();
+ if (r.IsEmpty())
+ return true;
+
+ bool success = os.Write(r.data, r.size, error);
+ if (gcc_likely(success))
+ buffer.Consume(r.size);
+ return success;
+}
diff --git a/src/fs/io/BufferedOutputStream.hxx b/src/fs/io/BufferedOutputStream.hxx
new file mode 100644
index 000000000..f2de758a2
--- /dev/null
+++ b/src/fs/io/BufferedOutputStream.hxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_BUFFERED_OUTPUT_STREAM_HXX
+#define MPD_BUFFERED_OUTPUT_STREAM_HXX
+
+#include "check.h"
+#include "Compiler.h"
+#include "util/DynamicFifoBuffer.hxx"
+#include "util/Error.hxx"
+
+#include <stddef.h>
+
+class OutputStream;
+class Error;
+
+class BufferedOutputStream {
+ OutputStream &os;
+
+ DynamicFifoBuffer<char> buffer;
+
+ Error last_error;
+
+public:
+ BufferedOutputStream(OutputStream &_os)
+ :os(_os), buffer(32768) {}
+
+ bool Write(const void *data, size_t size);
+ bool Write(const char *p);
+
+ gcc_printf(2,3)
+ bool Format(const char *fmt, ...);
+
+ 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 Flush();
+
+ bool Flush(Error &error);
+
+private:
+ bool AppendToBuffer(const void *data, size_t size);
+};
+
+#endif
diff --git a/src/fs/io/BufferedReader.cxx b/src/fs/io/BufferedReader.cxx
new file mode 100644
index 000000000..ba2f17dcf
--- /dev/null
+++ b/src/fs/io/BufferedReader.cxx
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "BufferedReader.hxx"
+#include "Reader.hxx"
+#include "util/TextFile.hxx"
+
+bool
+BufferedReader::Fill(bool need_more)
+{
+ if (gcc_unlikely(last_error.IsDefined()))
+ return false;
+
+ if (eof)
+ return !need_more;
+
+ auto w = buffer.Write();
+ if (w.IsEmpty()) {
+ if (buffer.GetCapacity() >= MAX_SIZE)
+ return !need_more;
+
+ buffer.Grow(buffer.GetCapacity() * 2);
+ w = buffer.Write();
+ assert(!w.IsEmpty());
+ }
+
+ size_t nbytes = reader.Read(w.data, w.size, last_error);
+ if (nbytes == 0) {
+ if (gcc_unlikely(last_error.IsDefined()))
+ return false;
+
+ eof = true;
+ return !need_more;
+ }
+
+ buffer.Append(nbytes);
+ return true;
+}
+
+char *
+BufferedReader::ReadLine()
+{
+ do {
+ char *line = ReadBufferedLine(buffer);
+ if (line != nullptr)
+ return line;
+ } while (Fill(true));
+
+ if (last_error.IsDefined() || !eof || buffer.IsEmpty())
+ return nullptr;
+
+ auto w = buffer.Write();
+ if (w.IsEmpty()) {
+ buffer.Grow(buffer.GetCapacity() + 1);
+ w = buffer.Write();
+ assert(!w.IsEmpty());
+ }
+
+ /* terminate the last line */
+ w[0] = 0;
+
+ char *line = buffer.Read().data;
+ buffer.Clear();
+ return line;
+}
diff --git a/src/fs/io/BufferedReader.hxx b/src/fs/io/BufferedReader.hxx
new file mode 100644
index 000000000..61cc8df83
--- /dev/null
+++ b/src/fs/io/BufferedReader.hxx
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_BUFFERED_READER_HXX
+#define MPD_BUFFERED_READER_HXX
+
+#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;
+
+ Reader &reader;
+
+ DynamicFifoBuffer<char> buffer;
+
+ Error last_error;
+
+ bool eof;
+
+public:
+ BufferedReader(Reader &_reader)
+ :reader(_reader), buffer(4096), eof(false) {}
+
+ 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
+ WritableBuffer<void> Read() const {
+ return buffer.Read().ToVoid();
+ }
+
+ void Consume(size_t n) {
+ buffer.Consume(n);
+ }
+
+ char *ReadLine();
+};
+
+#endif
diff --git a/src/fs/io/FileOutputStream.cxx b/src/fs/io/FileOutputStream.cxx
new file mode 100644
index 000000000..0eff8b5f0
--- /dev/null
+++ b/src/fs/io/FileOutputStream.cxx
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FileOutputStream.hxx"
+#include "fs/FileSystem.hxx"
+#include "system/fd_util.h"
+#include "util/Error.hxx"
+
+#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))
+{
+ if (handle == INVALID_HANDLE_VALUE)
+ error.FormatLastError("Failed to create %s", path.c_str());
+}
+
+bool
+FileOutputStream::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());
+ return false;
+ }
+
+ if (size_t(nbytes) != size) {
+ error.FormatLastError(ERROR_DISK_FULL,
+ "Failed to write to %s", path.c_str());
+ return false;
+ }
+
+ return true;
+}
+
+bool
+FileOutputStream::Commit(gcc_unused Error &error)
+{
+ assert(IsDefined());
+
+ CloseHandle(handle);
+ handle = INVALID_HANDLE_VALUE;
+ return true;
+}
+
+void
+FileOutputStream::Cancel()
+{
+ assert(IsDefined());
+
+ CloseHandle(handle);
+ handle = INVALID_HANDLE_VALUE;
+ RemoveFile(path);
+}
+
+#else
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+FileOutputStream::FileOutputStream(Path _path, Error &error)
+ :path(_path),
+ fd(open_cloexec(path.c_str(),
+ O_WRONLY|O_CREAT|O_TRUNC,
+ 0666))
+{
+ if (fd < 0)
+ error.FormatErrno("Failed to create %s", path.c_str());
+}
+
+bool
+FileOutputStream::Write(const void *data, size_t size, Error &error)
+{
+ assert(IsDefined());
+
+ ssize_t nbytes = write(fd, data, size);
+ if (nbytes < 0) {
+ error.FormatErrno("Failed to write to %s", path.c_str());
+ return false;
+ } else if ((size_t)nbytes < size) {
+ error.FormatErrno(ENOSPC,
+ "Failed to write to %s", path.c_str());
+ return false;
+ }
+
+ return true;
+}
+
+bool
+FileOutputStream::Commit(Error &error)
+{
+ assert(IsDefined());
+
+ bool success = close(fd) == 0;
+ fd = -1;
+ if (!success)
+ error.FormatErrno("Failed to commit %s", path.c_str());
+
+ return success;
+}
+
+void
+FileOutputStream::Cancel()
+{
+ assert(IsDefined());
+
+ close(fd);
+ fd = -1;
+
+ RemoveFile(path);
+}
+
+#endif
diff --git a/src/fs/io/FileOutputStream.hxx b/src/fs/io/FileOutputStream.hxx
new file mode 100644
index 000000000..5b6309957
--- /dev/null
+++ b/src/fs/io/FileOutputStream.hxx
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FILE_OUTPUT_STREAM_HXX
+#define MPD_FILE_OUTPUT_STREAM_HXX
+
+#include "check.h"
+#include "OutputStream.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "Compiler.h"
+
+#include <assert.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+class Path;
+
+class FileOutputStream final : public OutputStream {
+ AllocatedPath path;
+
+#ifdef WIN32
+ HANDLE handle;
+#else
+ int fd;
+#endif
+
+public:
+ FileOutputStream(Path _path, Error &error);
+
+ ~FileOutputStream() {
+ if (IsDefined())
+ Cancel();
+ }
+
+
+ bool IsDefined() const {
+#ifdef WIN32
+ return handle != INVALID_HANDLE_VALUE;
+#else
+ return fd >= 0;
+#endif
+ }
+
+ bool Commit(Error &error);
+ void Cancel();
+
+ /* virtual methods from class OutputStream */
+ bool Write(const void *data, size_t size, Error &error) override;
+};
+
+#endif
diff --git a/src/fs/io/FileReader.cxx b/src/fs/io/FileReader.cxx
new file mode 100644
index 000000000..d63cd8ab0
--- /dev/null
+++ b/src/fs/io/FileReader.cxx
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "FileReader.hxx"
+#include "system/fd_util.h"
+#include "util/Error.hxx"
+
+#ifdef WIN32
+
+FileReader::FileReader(Path _path, Error &error)
+ :path(_path),
+ handle(CreateFile(path.c_str(), GENERIC_READ, FILE_SHARE_READ,
+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
+ nullptr))
+{
+ if (handle == INVALID_HANDLE_VALUE)
+ error.FormatLastError("Failed to open %s", path.c_str());
+}
+
+size_t
+FileReader::Read(void *data, size_t size, Error &error)
+{
+ assert(IsDefined());
+
+ DWORD nbytes;
+ if (!ReadFile(handle, data, size, &nbytes, nullptr)) {
+ error.FormatLastError("Failed to read from %s", path.c_str());
+ nbytes = 0;
+ }
+
+ return nbytes;
+}
+
+void
+FileReader::Close()
+{
+ assert(IsDefined());
+
+ CloseHandle(handle);
+}
+
+#else
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+FileReader::FileReader(Path _path, Error &error)
+ :path(_path),
+ fd(open_cloexec(path.c_str(),
+ O_RDONLY,
+ 0))
+{
+ if (fd < 0)
+ error.FormatErrno("Failed to open %s", path.c_str());
+}
+
+size_t
+FileReader::Read(void *data, size_t size, Error &error)
+{
+ assert(IsDefined());
+
+ ssize_t nbytes = read(fd, data, size);
+ if (nbytes < 0) {
+ error.FormatErrno("Failed to read from %s", path.c_str());
+ nbytes = 0;
+ }
+
+ return nbytes;
+}
+
+void
+FileReader::Close()
+{
+ assert(IsDefined());
+
+ close(fd);
+ fd = -1;
+}
+
+#endif
diff --git a/src/fs/io/FileReader.hxx b/src/fs/io/FileReader.hxx
new file mode 100644
index 000000000..9f459aee2
--- /dev/null
+++ b/src/fs/io/FileReader.hxx
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_FILE_READER_HXX
+#define MPD_FILE_READER_HXX
+
+#include "check.h"
+#include "Reader.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "Compiler.h"
+
+#include <assert.h>
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+class Path;
+
+class FileReader final : public Reader {
+ AllocatedPath path;
+
+#ifdef WIN32
+ HANDLE handle;
+#else
+ int fd;
+#endif
+
+public:
+ FileReader(Path _path, Error &error);
+
+ ~FileReader() {
+ if (IsDefined())
+ Close();
+ }
+
+
+ bool IsDefined() const {
+#ifdef WIN32
+ return handle != INVALID_HANDLE_VALUE;
+#else
+ return fd >= 0;
+#endif
+ }
+
+ void Close();
+
+ /* virtual methods from class Reader */
+ size_t Read(void *data, size_t size, Error &error) override;
+};
+
+#endif
diff --git a/src/fs/io/GunzipReader.cxx b/src/fs/io/GunzipReader.cxx
new file mode 100644
index 000000000..ad5e41784
--- /dev/null
+++ b/src/fs/io/GunzipReader.cxx
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#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)
+ :next(_next), eof(false)
+{
+ z.next_in = nullptr;
+ z.avail_in = 0;
+ z.zalloc = Z_NULL;
+ z.zfree = Z_NULL;
+ 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);
+}
+
+inline bool
+GunzipReader::FillBuffer(Error &error)
+{
+ auto w = buffer.Write();
+ assert(!w.IsEmpty());
+
+ size_t nbytes = next.Read(w.data, w.size, error);
+ if (nbytes == 0)
+ return false;
+
+ buffer.Append(nbytes);
+ return true;
+}
+
+size_t
+GunzipReader::Read(void *data, size_t size, Error &error)
+{
+ if (eof)
+ return 0;
+
+ z.next_out = (Bytef *)data;
+ z.avail_out = size;
+
+ while (true) {
+ int flush = Z_NO_FLUSH;
+
+ auto r = buffer.Read();
+ if (r.IsEmpty()) {
+ if (FillBuffer(error))
+ r = buffer.Read();
+ else if (error.IsDefined())
+ return 0;
+ else
+ flush = Z_FINISH;
+ }
+
+ z.next_in = r.data;
+ z.avail_in = r.size;
+
+ int result = inflate(&z, flush);
+ 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;
+ }
+
+ buffer.Consume(r.size - z.avail_in);
+
+ if (z.avail_out < size)
+ return size - z.avail_out;
+ }
+}
diff --git a/src/fs/io/GunzipReader.hxx b/src/fs/io/GunzipReader.hxx
new file mode 100644
index 000000000..06c44bad6
--- /dev/null
+++ b/src/fs/io/GunzipReader.hxx
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_GUNZIP_READER_HXX
+#define MPD_GUNZIP_READER_HXX
+
+#include "check.h"
+#include "Reader.hxx"
+#include "util/StaticFifoBuffer.hxx"
+#include "Compiler.h"
+
+#include <zlib.h>
+
+class Error;
+class Domain;
+
+/**
+ * A filter that decompresses data using zlib.
+ */
+class GunzipReader final : public Reader {
+ Reader &next;
+
+ bool eof;
+
+ z_stream z;
+
+ StaticFifoBuffer<Bytef, 4096> buffer;
+
+public:
+ /**
+ * Construct the filter. Call IsDefined() to check whether
+ * the constructor has succeeded. If not, #error will hold
+ * information about the failure.
+ */
+ GunzipReader(Reader &_next, Error &error);
+ ~GunzipReader();
+
+ /**
+ * Check whether the constructor has succeeded.
+ */
+ bool IsDefined() const {
+ return z.opaque == nullptr;
+ }
+
+ /* virtual methods from class Reader */
+ virtual size_t Read(void *data, size_t size, Error &error) override;
+
+private:
+ bool FillBuffer(Error &error);
+};
+
+#endif
diff --git a/src/fs/io/GzipOutputStream.cxx b/src/fs/io/GzipOutputStream.cxx
new file mode 100644
index 000000000..27ae6b2ad
--- /dev/null
+++ b/src/fs/io/GzipOutputStream.cxx
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "GzipOutputStream.hxx"
+#include "lib/zlib/Domain.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+
+GzipOutputStream::GzipOutputStream(OutputStream &_next, Error &error)
+ :next(_next)
+{
+ z.next_in = nullptr;
+ z.avail_in = 0;
+ z.zalloc = Z_NULL;
+ z.zfree = Z_NULL;
+ z.opaque = Z_NULL;
+
+ constexpr int windowBits = 15;
+ constexpr int gzip_encoding = 16;
+
+ int result = deflateInit2(&z, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ windowBits | gzip_encoding,
+ 8, Z_DEFAULT_STRATEGY);
+ if (result != Z_OK) {
+ z.opaque = this;
+ error.Set(zlib_domain, result, zError(result));
+ }
+}
+
+GzipOutputStream::~GzipOutputStream()
+{
+ if (IsDefined())
+ deflateEnd(&z);
+}
+
+bool
+GzipOutputStream::Flush(Error &error)
+{
+ /* no more input */
+ z.next_in = nullptr;
+ z.avail_in = 0;
+
+ while (true) {
+ Bytef output[4096];
+ z.next_out = output;
+ z.avail_out = sizeof(output);
+
+ int result = deflate(&z, Z_FINISH);
+ if (z.next_out > output &&
+ !next.Write(output, z.next_out - output, error))
+ return false;
+
+ if (result == Z_STREAM_END)
+ return true;
+ else if (result != Z_OK) {
+ error.Set(zlib_domain, result, zError(result));
+ return false;
+ }
+ }
+}
+
+bool
+GzipOutputStream::Write(const void *_data, size_t size, Error &error)
+{
+ /* zlib's API requires non-const input pointer */
+ void *data = const_cast<void *>(_data);
+
+ z.next_in = reinterpret_cast<Bytef *>(data);
+ z.avail_in = size;
+
+ while (z.avail_in > 0) {
+ Bytef output[4096];
+ z.next_out = output;
+ z.avail_out = sizeof(output);
+
+ int result = deflate(&z, Z_NO_FLUSH);
+ if (result != Z_OK) {
+ error.Set(zlib_domain, result, zError(result));
+ return false;
+ }
+
+ if (z.next_out > output &&
+ !next.Write(output, z.next_out - output, error))
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/fs/io/GzipOutputStream.hxx b/src/fs/io/GzipOutputStream.hxx
new file mode 100644
index 000000000..27ee2dd24
--- /dev/null
+++ b/src/fs/io/GzipOutputStream.hxx
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_GZIP_OUTPUT_STREAM_HXX
+#define MPD_GZIP_OUTPUT_STREAM_HXX
+
+#include "check.h"
+#include "OutputStream.hxx"
+#include "Compiler.h"
+
+#include <assert.h>
+#include <zlib.h>
+
+class Error;
+class Domain;
+
+/**
+ * A filter that compresses data written to it using zlib, forwarding
+ * compressed data in the "gzip" format.
+ *
+ * Don't forget to call Flush() before destructing this object.
+ */
+class GzipOutputStream final : public OutputStream {
+ OutputStream &next;
+
+ z_stream z;
+
+public:
+ /**
+ * Construct the filter. Call IsDefined() to check whether
+ * the constructor has succeeded. If not, #error will hold
+ * information about the failure.
+ */
+ GzipOutputStream(OutputStream &_next, Error &error);
+ ~GzipOutputStream();
+
+ /**
+ * Check whether the constructor has succeeded.
+ */
+ bool IsDefined() const {
+ return z.opaque == nullptr;
+ }
+
+ /**
+ * Finish the file and write all data remaining in zlib's
+ * output buffer.
+ */
+ bool Flush(Error &error);
+
+ /* virtual methods from class OutputStream */
+ bool Write(const void *data, size_t size, Error &error) override;
+};
+
+#endif
diff --git a/src/fs/io/OutputStream.hxx b/src/fs/io/OutputStream.hxx
new file mode 100644
index 000000000..71311c71f
--- /dev/null
+++ b/src/fs/io/OutputStream.hxx
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_OUTPUT_STREAM_HXX
+#define MPD_OUTPUT_STREAM_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <stddef.h>
+
+class Error;
+
+class OutputStream {
+public:
+ OutputStream() = default;
+ OutputStream(const OutputStream &) = delete;
+
+ virtual bool Write(const void *data, size_t size, Error &error) = 0;
+};
+
+#endif
diff --git a/src/fs/io/PeekReader.cxx b/src/fs/io/PeekReader.cxx
new file mode 100644
index 000000000..2e8042ab6
--- /dev/null
+++ b/src/fs/io/PeekReader.cxx
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "PeekReader.hxx"
+
+#include <algorithm>
+
+#include <assert.h>
+#include <string.h>
+
+const void *
+PeekReader::Peek(size_t size, Error &error)
+{
+ assert(size > 0);
+ assert(size < sizeof(buffer));
+ assert(buffer_size == 0);
+ assert(buffer_position == 0);
+
+ do {
+ size_t nbytes = next.Read(buffer + buffer_size,
+ size - buffer_size, error);
+ if (nbytes == 0)
+ return nullptr;
+
+ buffer_size += nbytes;
+ } while (buffer_size < size);
+
+ return buffer;
+}
+
+size_t
+PeekReader::Read(void *data, size_t size, Error &error)
+{
+ size_t buffer_remaining = buffer_size - buffer_position;
+ if (buffer_remaining > 0) {
+ size_t nbytes = std::min(buffer_remaining, size);
+ memcpy(data, buffer + buffer_position, nbytes);
+ buffer_position += nbytes;
+ return nbytes;
+ }
+
+ return next.Read(data, size, error);
+}
diff --git a/src/fs/io/PeekReader.hxx b/src/fs/io/PeekReader.hxx
new file mode 100644
index 000000000..32180b0a8
--- /dev/null
+++ b/src/fs/io/PeekReader.hxx
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_PEEK_READER_HXX
+#define MPD_PEEK_READER_HXX
+
+#include "check.h"
+#include "Reader.hxx"
+#include "Compiler.h"
+
+#include <stdint.h>
+
+class AutoGunzipReader;
+
+/**
+ * A filter that allows the caller to peek the first few bytes without
+ * consuming them. The first call must be Peek(), and the following
+ * Read() will deliver the same bytes again.
+ */
+class PeekReader final : public Reader {
+ Reader &next;
+
+ size_t buffer_size, buffer_position;
+
+ uint8_t buffer[64];
+
+public:
+ PeekReader(Reader &_next)
+ :next(_next), buffer_size(0), buffer_position(0) {}
+
+ const void *Peek(size_t size, Error &error);
+
+ /* virtual methods from class Reader */
+ virtual size_t Read(void *data, size_t size, Error &error) override;
+};
+
+#endif
diff --git a/src/fs/io/Reader.hxx b/src/fs/io/Reader.hxx
new file mode 100644
index 000000000..d41e92dd0
--- /dev/null
+++ b/src/fs/io/Reader.hxx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_READER_HXX
+#define MPD_READER_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <stddef.h>
+
+class Error;
+
+/**
+ * An interface that can read bytes from a stream until the stream
+ * ends.
+ *
+ * This interface is simpler and less cumbersome to use than
+ * #InputStream.
+ */
+class Reader {
+public:
+ Reader() = default;
+ Reader(const Reader &) = delete;
+
+ /**
+ * Read data from the stream.
+ *
+ * @return the number of bytes read into the given buffer or 0
+ * on error/end-of-stream
+ */
+ gcc_nonnull_all
+ virtual size_t Read(void *data, size_t size, Error &error) = 0;
+};
+
+#endif
diff --git a/src/fs/io/StdioOutputStream.hxx b/src/fs/io/StdioOutputStream.hxx
new file mode 100644
index 000000000..c1c0a00bd
--- /dev/null
+++ b/src/fs/io/StdioOutputStream.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STDIO_OUTPUT_STREAM_HXX
+#define MPD_STDIO_OUTPUT_STREAM_HXX
+
+#include "check.h"
+#include "OutputStream.hxx"
+#include "fs/AllocatedPath.hxx"
+#include "Compiler.h"
+
+#include <stdio.h>
+
+class StdioOutputStream final : public OutputStream {
+ FILE *const file;
+
+public:
+ StdioOutputStream(FILE *_file):file(_file) {}
+
+ /* virtual methods from class OutputStream */
+ bool Write(const void *data, size_t size,
+ gcc_unused Error &error) override {
+ fwrite(data, 1, size, file);
+
+ /* this class is debug-only and ignores errors */
+ return true;
+ }
+};
+
+#endif
diff --git a/src/fs/io/TextFile.cxx b/src/fs/io/TextFile.cxx
new file mode 100644
index 000000000..28d6dabcb
--- /dev/null
+++ b/src/fs/io/TextFile.cxx
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "TextFile.hxx"
+#include "FileReader.hxx"
+#include "AutoGunzipReader.hxx"
+#include "BufferedReader.hxx"
+#include "fs/Path.hxx"
+
+#include <assert.h>
+
+TextFile::TextFile(Path path_fs, Error &error)
+ :file_reader(new FileReader(path_fs, error)),
+#ifdef HAVE_ZLIB
+ gunzip_reader(file_reader->IsDefined()
+ ? new AutoGunzipReader(*file_reader)
+ : nullptr),
+#endif
+ buffered_reader(file_reader->IsDefined()
+ ? new BufferedReader(*
+#ifdef HAVE_ZLIB
+ gunzip_reader
+#else
+ file_reader
+#endif
+ )
+ : nullptr)
+{
+}
+
+TextFile::~TextFile()
+{
+ delete buffered_reader;
+#ifdef HAVE_ZLIB
+ delete gunzip_reader;
+#endif
+ delete file_reader;
+}
+
+char *
+TextFile::ReadLine()
+{
+ assert(buffered_reader != nullptr);
+
+ 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
new file mode 100644
index 000000000..5577363e7
--- /dev/null
+++ b/src/fs/io/TextFile.hxx
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TEXT_FILE_HXX
+#define MPD_TEXT_FILE_HXX
+
+#include "check.h"
+#include "Compiler.h"
+
+#include <stddef.h>
+
+class Path;
+class Error;
+class FileReader;
+class AutoGunzipReader;
+class BufferedReader;
+
+class TextFile {
+ FileReader *const file_reader;
+
+#ifdef HAVE_ZLIB
+ AutoGunzipReader *const gunzip_reader;
+#endif
+
+ BufferedReader *const buffered_reader;
+
+public:
+ TextFile(Path path_fs, Error &error);
+
+ 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.
+ *
+ * @param file the source file, opened in text mode
+ * @return a pointer to the line, or nullptr on end-of-file or error
+ */
+ char *ReadLine();
+
+ /**
+ * Check whether a ReadLine() call has thrown an error.
+ */
+ bool Check(Error &error) const;
+};
+
+#endif