diff options
Diffstat (limited to 'src/fs')
41 files changed, 2498 insertions, 139 deletions
diff --git a/src/fs/AllocatedPath.cxx b/src/fs/AllocatedPath.cxx index 37b79a685..30ce7e3a9 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,29 +24,32 @@ #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 return AllocatedPath(Donate(), ::PathFromUTF8(path_utf8)); +#else + return FromFS(path_utf8); +#endif } AllocatedPath @@ -64,7 +67,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 @@ -82,14 +85,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; @@ -101,7 +104,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..4fb217547 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,6 +53,12 @@ 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. @@ -67,6 +70,8 @@ public: */ 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,6 +138,15 @@ public: } /** + * 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 a #AllocatedPath instance. * Returns return a "nulled" instance on error. */ @@ -215,7 +245,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 dad5779f9..2d289c3b8 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,11 +22,14 @@ #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> @@ -41,6 +44,7 @@ */ static constexpr size_t MPD_PATH_MAX_UTF8 = (MPD_PATH_MAX - 1) * 4 + 1; +#ifdef HAVE_GLIB static std::string fs_charset; gcc_pure @@ -70,10 +74,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 @@ -81,8 +104,14 @@ PathToUTF8(const char *path_fs) { assert(path_fs != nullptr); - 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)) @@ -103,9 +132,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) { @@ -118,3 +152,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 2c3ce075b..d62987087 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,24 +22,136 @@ #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) { assert(p != nullptr); - 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) { assert(p != nullptr); - 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..88715c3e8 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,153 @@ #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; #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) { + assert(p != nullptr); #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) { - assert(p != nullptr); +#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) { + assert(p != nullptr); #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 char *pointer; + typedef const char *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) { assert(p != nullptr); + 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) { + assert(p != nullptr); +#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 +178,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 +186,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..0efd9d56c --- /dev/null +++ b/src/fs/io/AutoGunzipReader.hxx @@ -0,0 +1,51 @@ +/* + * 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 <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..dc4456d1f --- /dev/null +++ b/src/fs/io/FileOutputStream.cxx @@ -0,0 +1,135 @@ +/* + * 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); + return true; +} + +void +FileOutputStream::Cancel() +{ + assert(IsDefined()); + + CloseHandle(handle); + 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..68174ec83 --- /dev/null +++ b/src/fs/io/FileOutputStream.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_OUTPUT_STREAM_HXX +#define MPD_FILE_OUTPUT_STREAM_HXX + +#include "check.h" +#include "OutputStream.hxx" +#include "fs/AllocatedPath.hxx" + +#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..34b43943c --- /dev/null +++ b/src/fs/io/FileReader.hxx @@ -0,0 +1,67 @@ +/* + * 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 <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..34fc653fa --- /dev/null +++ b/src/fs/io/GunzipReader.hxx @@ -0,0 +1,67 @@ +/* + * 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 <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..e23835de8 --- /dev/null +++ b/src/fs/io/GzipOutputStream.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_GZIP_OUTPUT_STREAM_HXX +#define MPD_GZIP_OUTPUT_STREAM_HXX + +#include "check.h" +#include "OutputStream.hxx" + +#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..27e7ff7c1 --- /dev/null +++ b/src/fs/io/PeekReader.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_PEEK_READER_HXX +#define MPD_PEEK_READER_HXX + +#include "check.h" +#include "Reader.hxx" + +#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..e00db922f --- /dev/null +++ b/src/fs/io/StdioOutputStream.hxx @@ -0,0 +1,45 @@ +/* + * 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 <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 |