diff options
Diffstat (limited to 'src/fs')
44 files changed, 1070 insertions, 338 deletions
diff --git a/src/fs/AllocatedPath.cxx b/src/fs/AllocatedPath.cxx index ceaad73ea..8b03ed2f1 100644 --- a/src/fs/AllocatedPath.cxx +++ b/src/fs/AllocatedPath.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,33 +24,14 @@ #include "util/Error.hxx" #include "Compiler.h" -#ifdef HAVE_GLIB -#include <glib.h> -#endif - -#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::FromUTF8(const char *path_utf8) { -#ifdef HAVE_GLIB - char *path = ::PathFromUTF8(path_utf8); - if (path == nullptr) - return AllocatedPath::Null(); - - return AllocatedPath(Donate(), path); +#if defined(HAVE_FS_CHARSET) || defined(WIN32) + return AllocatedPath(::PathFromUTF8(path_utf8)); #else return FromFS(path_utf8); #endif @@ -80,38 +61,16 @@ AllocatedPath::ToUTF8() const return ::PathToUTF8(c_str()); } -const char * -AllocatedPath::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 (!PathTraitsFS::IsSeparator(*other_fs)) - /* mismatch */ - return nullptr; - - /* skip remaining path separators */ - do { - ++other_fs; - } while (PathTraitsFS::IsSeparator(*other_fs)); - } - - return other_fs; -} - void AllocatedPath::ChopSeparators() { size_t l = length(); - const char *p = data(); + const auto *p = data(); while (l >= 2 && PathTraitsFS::IsSeparator(p[l - 1])) { --l; -#if GCC_CHECK_VERSION(4,7) && !defined(__clang__) +#if GCC_CHECK_VERSION(4,7) value.pop_back(); #else value.erase(value.end() - 1, value.end()); diff --git a/src/fs/AllocatedPath.hxx b/src/fs/AllocatedPath.hxx index c345470c8..e733c00a9 100644 --- a/src/fs/AllocatedPath.hxx +++ b/src/fs/AllocatedPath.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,7 @@ #include "Traits.hxx" #include "Path.hxx" +#include <cstddef> #include <utility> #include <string> @@ -44,13 +45,7 @@ class AllocatedPath { string value; - struct Donate {}; - - /** - * Donate the allocated pointer to a new #AllocatedPath object. - */ - AllocatedPath(Donate, pointer _value); - + AllocatedPath(std::nullptr_t):value() {} AllocatedPath(const_pointer _value):value(_value) {} AllocatedPath(string &&_value):value(std::move(_value)) {} @@ -82,7 +77,7 @@ public: */ gcc_const static AllocatedPath Null() { - return AllocatedPath(""); + return AllocatedPath(nullptr); } gcc_pure @@ -169,11 +164,21 @@ public: return *this; } + gcc_pure + bool operator==(const AllocatedPath &other) const { + return value == other.value; + } + + gcc_pure + bool operator!=(const AllocatedPath &other) const { + return value != other.value; + } + /** * Allows the caller to "steal" the internal value by * providing a rvalue reference to the std::string attribute. */ - std::string &&Steal() { + string &&Steal() { return std::move(value); } @@ -244,7 +249,9 @@ public: * nullptr on mismatch. */ gcc_pure - const char *RelativeFS(const char *other_fs) const; + const_pointer Relative(Path other_fs) const { + return PathTraitsFS::Relative(c_str(), other_fs.c_str()); + } /** * Chop trailing directory separators. @@ -252,7 +259,7 @@ public: void ChopSeparators(); gcc_pure - bool IsAbsolute() { + bool IsAbsolute() const { return PathTraitsFS::IsAbsolute(c_str()); } }; diff --git a/src/fs/Charset.cxx b/src/fs/Charset.cxx index c634c9340..b25615d42 100644 --- a/src/fs/Charset.cxx +++ b/src/fs/Charset.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,12 +21,12 @@ #include "Charset.hxx" #include "Domain.hxx" #include "Limits.hxx" -#include "system/FatalError.hxx" #include "Log.hxx" -#include "Traits.hxx" +#include "lib/icu/Converter.hxx" +#include "util/Error.hxx" -#ifdef HAVE_GLIB -#include <glib.h> +#ifdef WIN32 +#include <windows.h> #endif #include <algorithm> @@ -34,130 +34,133 @@ #include <assert.h> #include <string.h> -#ifdef HAVE_GLIB - -/** - * Maximal number of bytes required to represent path name in UTF-8 - * (including nul-terminator). - * This value is a rought estimate of upper bound. - * It's based on path name limit in bytes (MPD_PATH_MAX) - * and assumption that some weird encoding could represent some UTF-8 4 byte - * sequences with single byte. - */ -static constexpr size_t MPD_PATH_MAX_UTF8 = (MPD_PATH_MAX - 1) * 4 + 1; +#ifdef HAVE_FS_CHARSET static std::string fs_charset; -gcc_pure -static bool -IsSupportedCharset(const char *charset) -{ - /* convert a space to check if the charset is valid */ - char *test = g_convert(" ", 1, charset, "UTF-8", nullptr, nullptr, nullptr); - if (test == nullptr) - return false; +static IcuConverter *fs_converter; - g_free(test); - return true; -} - -void -SetFSCharset(const char *charset) +bool +SetFSCharset(const char *charset, Error &error) { assert(charset != nullptr); + assert(fs_converter == nullptr); - if (!IsSupportedCharset(charset)) - FormatFatalError("invalid filesystem charset: %s", charset); - - fs_charset = charset; + fs_converter = IcuConverter::Create(charset, error); + if (fs_converter == nullptr) + return false; FormatDebug(path_domain, "SetFSCharset: fs charset is: %s", fs_charset.c_str()); + return true; } #endif +void +DeinitFSCharset() +{ +#ifdef HAVE_ICU_CONVERTER + delete fs_converter; + fs_converter = nullptr; +#endif +} + const char * GetFSCharset() { -#ifdef HAVE_GLIB +#ifdef HAVE_FS_CHARSET return fs_charset.empty() ? "UTF-8" : fs_charset.c_str(); #else return "UTF-8"; #endif } -static inline void FixSeparators(std::string &s) +static inline PathTraitsUTF8::string && +FixSeparators(PathTraitsUTF8::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 + decltype(to) from = PathTraitsFS::SEPARATOR; + + if (from != to) + /* convert backslash to slash on WIN32 */ + std::replace(s.begin(), s.end(), from, to); + + return std::move(s); } -std::string -PathToUTF8(const char *path_fs) +PathTraitsUTF8::string +PathToUTF8(PathTraitsFS::const_pointer path_fs) { #if !CLANG_CHECK_VERSION(3,6) /* disabled on clang due to -Wtautological-pointer-compare */ assert(path_fs != nullptr); #endif -#ifdef HAVE_GLIB - if (fs_charset.empty()) { -#endif - auto result = std::string(path_fs); - FixSeparators(result); - return result; -#ifdef HAVE_GLIB +#ifdef WIN32 + int length = WideCharToMultiByte(CP_UTF8, 0, path_fs, -1, nullptr, 0, + nullptr, nullptr); + if (length <= 0) + return PathTraitsUTF8::string(); + + char *buffer = new char[length]; + length = WideCharToMultiByte(CP_UTF8, 0, path_fs, -1, buffer, length, + nullptr, nullptr); + if (length <= 0) { + delete[] buffer; + return PathTraitsUTF8::string(); } - GIConv conv = g_iconv_open("utf-8", fs_charset.c_str()); - if (conv == reinterpret_cast<GIConv>(-1)) - return std::string(); - - // g_iconv() does not need nul-terminator, - // std::string could be created without it too. - char path_utf8[MPD_PATH_MAX_UTF8 - 1]; - char *in = const_cast<char *>(path_fs); - char *out = path_utf8; - size_t in_left = strlen(path_fs); - size_t out_left = sizeof(path_utf8); - - size_t ret = g_iconv(conv, &in, &in_left, &out, &out_left); - - g_iconv_close(conv); - - if (ret == static_cast<size_t>(-1) || in_left > 0) - return std::string(); + PathTraitsUTF8::string result(buffer); + delete[] buffer; + return FixSeparators(std::move(result)); +#else +#ifdef HAVE_FS_CHARSET + if (fs_converter == nullptr) +#endif + return FixSeparators(path_fs); +#ifdef HAVE_FS_CHARSET - auto result_path = std::string(path_utf8, sizeof(path_utf8) - out_left); - FixSeparators(result_path); - return result_path; + return FixSeparators(fs_converter->ToUTF8(path_fs)); +#endif #endif } -#ifdef HAVE_GLIB +#if defined(HAVE_FS_CHARSET) || defined(WIN32) -char * -PathFromUTF8(const char *path_utf8) +PathTraitsFS::string +PathFromUTF8(PathTraitsUTF8::const_pointer path_utf8) { #if !CLANG_CHECK_VERSION(3,6) /* disabled on clang due to -Wtautological-pointer-compare */ assert(path_utf8 != nullptr); #endif - if (fs_charset.empty()) - return g_strdup(path_utf8); +#ifdef WIN32 + int length = MultiByteToWideChar(CP_UTF8, 0, path_utf8, -1, + nullptr, 0); + if (length <= 0) + return PathTraitsFS::string(); + + wchar_t *buffer = new wchar_t[length]; + length = MultiByteToWideChar(CP_UTF8, 0, path_utf8, -1, + buffer, length); + if (length <= 0) { + delete[] buffer; + return PathTraitsFS::string(); + } + + PathTraitsFS::string result(buffer); + delete[] buffer; + return std::move(result); +#else + if (fs_converter == nullptr) + return path_utf8; - return g_convert(path_utf8, -1, - fs_charset.c_str(), "utf-8", - nullptr, nullptr, nullptr); + return fs_converter->FromUTF8(path_utf8); +#endif } #endif diff --git a/src/fs/Charset.hxx b/src/fs/Charset.hxx index 0a71d7c58..49ad07820 100644 --- a/src/fs/Charset.hxx +++ b/src/fs/Charset.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,8 +22,13 @@ #include "check.h" #include "Compiler.h" +#include "Traits.hxx" -#include <string> +#if defined(HAVE_ICU) || defined(HAVE_GLIB) +#define HAVE_FS_CHARSET +#endif + +class Error; /** * Gets file system character set name. @@ -32,19 +37,26 @@ gcc_const const char * GetFSCharset(); +bool +SetFSCharset(const char *charset, Error &error); + void -SetFSCharset(const char *charset); +DeinitFSCharset(); /** * Convert the path to UTF-8. * Returns empty string on error. */ gcc_pure gcc_nonnull_all -std::string -PathToUTF8(const char *path_fs); +PathTraitsUTF8::string +PathToUTF8(PathTraitsFS::const_pointer path_fs); -gcc_malloc gcc_nonnull_all -char * -PathFromUTF8(const char *path_utf8); +/** + * Convert the path from UTF-8. + * Returns empty string on error. + */ +gcc_pure gcc_nonnull_all +PathTraitsFS::string +PathFromUTF8(PathTraitsUTF8::const_pointer path_utf8); #endif diff --git a/src/fs/CheckFile.cxx b/src/fs/CheckFile.cxx index a35443674..e900fe9af 100644 --- a/src/fs/CheckFile.cxx +++ b/src/fs/CheckFile.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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 @@ #include "CheckFile.hxx" #include "Log.hxx" #include "config/ConfigError.hxx" -#include "FileSystem.hxx" +#include "FileInfo.hxx" #include "Path.hxx" #include "AllocatedPath.hxx" #include "DirectoryReader.hxx" @@ -32,31 +32,37 @@ 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()); + Error error; + + FileInfo fi; + if (!GetFileInfo(path_fs, fi, error)) { + LogError(error); return; } - if (!S_ISDIR(st.st_mode)) { + if (!fi.IsDirectory()) { + const auto path_utf8 = path_fs.ToUTF8(); FormatError(config_domain, - "Not a directory: %s", path_fs.c_str()); + "Not a directory: %s", path_utf8.c_str()); return; } #ifndef WIN32 - const auto x = AllocatedPath::Build(path_fs, "."); - if (!StatFile(x, st) && errno == EACCES) + const auto x = AllocatedPath::Build(path_fs, + PathTraitsFS::CURRENT_DIRECTORY); + if (!GetFileInfo(x, fi) && errno == EACCES) { + const auto path_utf8 = path_fs.ToUTF8(); FormatError(config_domain, "No permission to traverse (\"execute\") directory: %s", - path_fs.c_str()); + path_utf8.c_str()); + } #endif const DirectoryReader reader(path_fs); - if (reader.HasFailed() && errno == EACCES) + if (reader.HasFailed() && errno == EACCES) { + const auto path_utf8 = path_fs.ToUTF8(); FormatError(config_domain, - "No permission to read directory: %s", path_fs.c_str()); - + "No permission to read directory: %s", + path_utf8.c_str()); + } } diff --git a/src/fs/CheckFile.hxx b/src/fs/CheckFile.hxx index 00559647d..52fe45f0a 100644 --- a/src/fs/CheckFile.hxx +++ b/src/fs/CheckFile.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/Config.cxx b/src/fs/Config.cxx index 6aa23005c..623736c54 100644 --- a/src/fs/Config.cxx +++ b/src/fs/Config.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,21 +29,15 @@ #include <glib.h> #endif -void -ConfigureFS() +bool +ConfigureFS(Error &error) { -#if defined(HAVE_GLIB) || defined(WIN32) +#ifdef HAVE_FS_CHARSET const char *charset = nullptr; - charset = config_get_string(CONF_FS_CHARSET, nullptr); + charset = config_get_string(ConfigOption::FS_CHARSET, nullptr); if (charset == nullptr) { -#ifndef WIN32 - const gchar **encodings; - g_get_filename_charsets(&encodings); - - if (encodings[0] != nullptr && *encodings[0] != '\0') - charset = encodings[0]; -#else +#ifdef WIN32 /* Glib claims that file system encoding is always utf-8 * on native Win32 (i.e. not Cygwin). * However this is true only if <gstdio.h> helpers are used. @@ -52,10 +46,26 @@ ConfigureFS() static char win_charset[13]; sprintf(win_charset, "cp%u", GetACP()); charset = win_charset; +#elif defined(HAVE_GLIB) + const gchar **encodings; + g_get_filename_charsets(&encodings); + + if (encodings[0] != nullptr && *encodings[0] != '\0') + charset = encodings[0]; #endif } - if (charset != nullptr) - SetFSCharset(charset); + return charset == nullptr || SetFSCharset(charset, error); +#else + (void)error; + return true; +#endif +} + +void +DeinitFS() +{ +#ifdef HAVE_FS_CHARSET + DeinitFSCharset(); #endif } diff --git a/src/fs/Config.hxx b/src/fs/Config.hxx index d4f1709f5..1db710551 100644 --- a/src/fs/Config.hxx +++ b/src/fs/Config.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,10 +22,15 @@ #include "check.h" +class Error; + /** * Performs global one-time initialization of this class. */ +bool +ConfigureFS(Error &error); + void -ConfigureFS(); +DeinitFS(); #endif diff --git a/src/fs/DirectoryReader.hxx b/src/fs/DirectoryReader.hxx index f77c0629f..ce27d2b9b 100644 --- a/src/fs/DirectoryReader.hxx +++ b/src/fs/DirectoryReader.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,6 +23,89 @@ #include "check.h" #include "Path.hxx" +#ifdef WIN32 + +#include <windows.h> +#include <tchar.h> + +/** + * Reader for directory entries. + */ +class DirectoryReader { + const HANDLE handle; + WIN32_FIND_DATA data; + bool first; + + class MakeWildcardPath { + PathTraitsFS::pointer path; + + public: + MakeWildcardPath(PathTraitsFS::const_pointer _path) { + auto l = _tcslen(_path); + path = new PathTraitsFS::value_type[l + 3]; + _tcscpy(path, _path); + path[l] = _T('\\'); + path[l + 1] = _T('*'); + path[l + 2] = 0; + } + + ~MakeWildcardPath() { + delete[] path; + } + + operator PathTraitsFS::const_pointer() const { + return path; + } + }; + +public: + /** + * Creates new directory reader for the specified #dir. + */ + explicit DirectoryReader(Path dir) + :handle(FindFirstFile(MakeWildcardPath(dir.c_str()), &data)), + first(true) {} + + DirectoryReader(const DirectoryReader &other) = delete; + DirectoryReader &operator=(const DirectoryReader &other) = delete; + + /** + * Destroys this instance. + */ + ~DirectoryReader() { + if (!HasFailed()) + FindClose(handle); + } + + /** + * Checks if directory failed to open. + */ + bool HasFailed() const { + return handle == INVALID_HANDLE_VALUE; + } + + /** + * Reads next directory entry. + */ + bool ReadEntry() { + if (first) { + first = false; + return true; + } + + return FindNextFile(handle, &data) != 0; + } + + /** + * Extracts directory entry that was previously read by #ReadEntry. + */ + Path GetEntry() const { + return Path::FromFS(data.cFileName); + } +}; + +#else + #include <dirent.h> /** @@ -85,3 +168,5 @@ public: }; #endif + +#endif diff --git a/src/fs/Domain.cxx b/src/fs/Domain.cxx index 4f3129219..d278ba1bf 100644 --- a/src/fs/Domain.cxx +++ b/src/fs/Domain.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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 1fd17b37f..77ca64549 100644 --- a/src/fs/Domain.hxx +++ b/src/fs/Domain.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/FileInfo.hxx b/src/fs/FileInfo.hxx new file mode 100644 index 000000000..7b272568f --- /dev/null +++ b/src/fs/FileInfo.hxx @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2003-2015 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_FILE_INFO_HXX +#define MPD_FS_FILE_INFO_HXX + +#include "check.h" +#include "Path.hxx" +#include "util/Error.hxx" + +#include <stdint.h> + +#ifdef WIN32 +#include <fileapi.h> +#else +#include <sys/stat.h> +#endif + +#ifdef WIN32 + +static inline constexpr uint64_t +ConstructUint64(DWORD lo, DWORD hi) +{ + return uint64_t(lo) | (uint64_t(hi) << 32); +} + +static constexpr time_t +FileTimeToTimeT(FILETIME ft) +{ + return (ConstructUint64(ft.dwLowDateTime, ft.dwHighDateTime) + - 116444736000000000) / 10000000; +} + +#endif + +class FileInfo { + friend bool GetFileInfo(Path path, FileInfo &info, + bool follow_symlinks); + friend bool GetFileInfo(Path path, FileInfo &info, + Error &error); + friend class FileReader; + +#ifdef WIN32 + WIN32_FILE_ATTRIBUTE_DATA data; +#else + struct stat st; +#endif + +public: + bool IsRegular() const { +#ifdef WIN32 + return (data.dwFileAttributes & + (FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_DEVICE)) == 0; +#else + return S_ISREG(st.st_mode); +#endif + } + + bool IsDirectory() const { +#ifdef WIN32 + return data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; +#else + return S_ISDIR(st.st_mode); +#endif + } + + uint64_t GetSize() const { +#ifdef WIN32 + return ConstructUint64(data.nFileSizeLow, data.nFileSizeHigh); +#else + return st.st_size; +#endif + } + + time_t GetModificationTime() const { +#ifdef WIN32 + return FileTimeToTimeT(data.ftLastWriteTime); +#else + return st.st_mtime; +#endif + } + +#ifndef WIN32 + uid_t GetUid() const { + return st.st_uid; + } + + mode_t GetMode() const { + return st.st_mode; + } + + dev_t GetDevice() const { + return st.st_dev; + } + + ino_t GetInode() const { + return st.st_ino; + } +#endif +}; + +inline bool +GetFileInfo(Path path, FileInfo &info, bool follow_symlinks=true) +{ +#ifdef WIN32 + (void)follow_symlinks; + return GetFileAttributesEx(path.c_str(), GetFileExInfoStandard, + &info.data); +#else + int ret = follow_symlinks + ? stat(path.c_str(), &info.st) + : lstat(path.c_str(), &info.st); + return ret == 0; +#endif +} + +inline bool +GetFileInfo(Path path, FileInfo &info, bool follow_symlinks, Error &error) +{ + bool success = GetFileInfo(path, info, follow_symlinks); + if (!success) { + const auto path_utf8 = path.ToUTF8(); +#ifdef WIN32 + error.FormatLastError("Failed to access %s", + path_utf8.c_str()); +#else + error.FormatErrno("Failed to access %s", path_utf8.c_str()); +#endif + } + + return success; +} + +inline bool +GetFileInfo(Path path, FileInfo &info, Error &error) +{ + return GetFileInfo(path, info, true, error); +} + +#endif diff --git a/src/fs/FileSystem.cxx b/src/fs/FileSystem.cxx index 4e7c87415..554915b61 100644 --- a/src/fs/FileSystem.cxx +++ b/src/fs/FileSystem.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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 4dbb064cb..309c0cdf6 100644 --- a/src/fs/FileSystem.hxx +++ b/src/fs/FileSystem.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -26,42 +26,27 @@ #include "Path.hxx" +#ifdef WIN32 +#include <fileapi.h> +#endif + #include <sys/stat.h> #include <unistd.h> #include <stdio.h> + class AllocatedPath; namespace FOpenMode { /** - * Open mode for reading text files. - */ - constexpr PathTraitsFS::const_pointer ReadText = "r"; - - /** - * Open mode for reading binary files. - */ - constexpr PathTraitsFS::const_pointer ReadBinary = "rb"; - - /** * Open mode for writing text files. */ - constexpr PathTraitsFS::const_pointer WriteText = "w"; - - /** - * Open mode for writing binary files. - */ - constexpr PathTraitsFS::const_pointer WriteBinary = "wb"; + constexpr PathTraitsFS::const_pointer WriteText = PATH_LITERAL("w"); /** * Open mode for appending text files. */ - constexpr PathTraitsFS::const_pointer AppendText = "a"; - - /** - * Open mode for appending binary files. - */ - constexpr PathTraitsFS::const_pointer AppendBinary = "ab"; + constexpr PathTraitsFS::const_pointer AppendText = PATH_LITERAL("a"); } /** @@ -70,7 +55,11 @@ namespace FOpenMode { static inline FILE * FOpen(Path file, PathTraitsFS::const_pointer mode) { +#ifdef WIN32 + return _tfopen(file.c_str(), mode); +#else return fopen(file.c_str(), mode); +#endif } /** @@ -79,7 +68,11 @@ FOpen(Path file, PathTraitsFS::const_pointer mode) static inline int OpenFile(Path file, int flags, int mode) { +#ifdef WIN32 + return _topen(file.c_str(), flags, mode); +#else return open_cloexec(file.c_str(), flags, mode); +#endif } /** @@ -88,33 +81,40 @@ OpenFile(Path file, int flags, int mode) static inline bool RenameFile(Path oldpath, Path newpath) { +#ifdef WIN32 + return _trename(oldpath.c_str(), newpath.c_str()) == 0; +#else return rename(oldpath.c_str(), newpath.c_str()) == 0; +#endif } +#ifndef WIN32 + /** * Wrapper for stat() that uses #Path names. */ static inline bool StatFile(Path file, struct stat &buf, bool follow_symlinks = true) { -#ifdef WIN32 - (void)follow_symlinks; - return stat(file.c_str(), &buf) == 0; -#else int ret = follow_symlinks ? stat(file.c_str(), &buf) : lstat(file.c_str(), &buf); return ret == 0; -#endif } +#endif + /** * Wrapper for unlink() that uses #Path names. */ static inline bool RemoveFile(Path file) { +#ifdef WIN32 + return _tunlink(file.c_str()) == 0; +#else return unlink(file.c_str()) == 0; +#endif } /** @@ -143,27 +143,21 @@ CheckAccess(Path path, int mode) #endif /** - * Checks is specified path exists and accessible. - */ -static inline bool -CheckAccess(Path path) -{ -#ifdef WIN32 - struct stat buf; - return StatFile(path, buf); -#else - return CheckAccess(path, F_OK); -#endif -} - -/** * Checks if #Path exists and is a regular file. */ static inline bool FileExists(Path path, bool follow_symlinks = true) { +#ifdef WIN32 + (void)follow_symlinks; + + const auto a = GetFileAttributes(path.c_str()); + return a != INVALID_FILE_ATTRIBUTES && + (a & (FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_DEVICE)) == 0; +#else struct stat buf; return StatFile(path, buf, follow_symlinks) && S_ISREG(buf.st_mode); +#endif } /** @@ -172,18 +166,28 @@ FileExists(Path path, bool follow_symlinks = true) static inline bool DirectoryExists(Path path, bool follow_symlinks = true) { +#ifdef WIN32 + (void)follow_symlinks; + + const auto a = GetFileAttributes(path.c_str()); + return a != INVALID_FILE_ATTRIBUTES && (a & FILE_ATTRIBUTE_DIRECTORY); +#else struct stat buf; return StatFile(path, buf, follow_symlinks) && S_ISDIR(buf.st_mode); +#endif } /** * Checks if #Path exists. */ static inline bool -PathExists(Path path, bool follow_symlinks = true) +PathExists(Path path) { - struct stat buf; - return StatFile(path, buf, follow_symlinks); +#ifdef WIN32 + return GetFileAttributes(path.c_str()) != INVALID_FILE_ATTRIBUTES; +#else + return CheckAccess(path, F_OK); +#endif } #endif diff --git a/src/fs/Limits.hxx b/src/fs/Limits.hxx index 432897a69..b574a9c9a 100644 --- a/src/fs/Limits.hxx +++ b/src/fs/Limits.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/NarrowPath.hxx b/src/fs/NarrowPath.hxx new file mode 100644 index 000000000..ad310cd5c --- /dev/null +++ b/src/fs/NarrowPath.hxx @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2015 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_NARROW_PATH_HXX +#define MPD_FS_NARROW_PATH_HXX + +#include "check.h" +#include "Path.hxx" +#include "util/Macros.hxx" + +#ifdef _UNICODE +#include <windows.h> +#endif + +/** + * A path name that uses the regular (narrow) "char". This is used to + * pass a #Path (which may be represented by wchar_t) to a library + * that accepts only "const char *". + */ +class NarrowPath { + typedef char value_type; + typedef const char *const_pointer; + +#ifdef _UNICODE + char value[PATH_MAX]; +#else + const_pointer value; +#endif + +public: +#ifdef _UNICODE + explicit NarrowPath(Path _path) { + auto result = WideCharToMultiByte(CP_ACP, 0, + _path.c_str(), -1, + value, ARRAY_SIZE(value), + nullptr, nullptr); + if (result < 0) + value[0] = 0; + } +#else + explicit NarrowPath(Path _path):value(_path.c_str()) {} +#endif + + operator const_pointer() const { + return value; + } + + const_pointer c_str() const { + return value; + } +}; + +#endif diff --git a/src/fs/Path.cxx b/src/fs/Path.cxx index 8288a4fec..99a4ffb61 100644 --- a/src/fs/Path.cxx +++ b/src/fs/Path.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,9 +20,22 @@ #include "config.h" #include "Path.hxx" #include "Charset.hxx" +#include "util/UriUtil.hxx" +#include "util/StringUtil.hxx" std::string Path::ToUTF8() const { return ::PathToUTF8(c_str()); } + +Path::const_pointer +Path::GetSuffix() const +{ + const auto base = GetBase().c_str(); + const auto *dot = StringFindLast(base, '.'); + if (dot == nullptr || dot == base) + return nullptr; + + return dot + 1; +} diff --git a/src/fs/Path.hxx b/src/fs/Path.hxx index 9e0fa5aeb..5e78ea507 100644 --- a/src/fs/Path.hxx +++ b/src/fs/Path.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -29,6 +29,8 @@ #include <assert.h> #include <string.h> +class AllocatedPath; + /** * A path name in the native file system character set. * @@ -40,7 +42,7 @@ class Path { typedef PathTraitsFS::pointer pointer; typedef PathTraitsFS::const_pointer const_pointer; - const char *value; + const_pointer value; constexpr Path(const_pointer _value):value(_value) {} @@ -98,7 +100,7 @@ public: size_t length() const { assert(value != nullptr); - return strlen(value); + return PathTraitsFS::GetLength(value); } /** @@ -121,6 +123,16 @@ public: } /** + * Does the path contain a newline character? (Which is + * usually rejected by MPD because its protocol cannot + * transfer newline characters). + */ + gcc_pure + bool HasNewline() const { + return PathTraitsFS::Find(value, '\n') != nullptr; + } + + /** * Convert the path to UTF-8. * Returns empty string on error or if this instance is "nulled" * (#IsNull returns true). @@ -129,20 +141,39 @@ public: std::string ToUTF8() const; /** + * Determine the "base" file name. + * The return value points inside this object. + */ + gcc_pure + Path GetBase() const { + return FromFS(PathTraitsFS::GetBase(value)); + } + + /** + * Gets directory name of this path. + * Returns a "nulled" instance on error. + */ + gcc_pure + AllocatedPath GetDirectoryName() const; + + /** * 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 - const char *RelativeFS(const char *other_fs) const { - return PathTraitsFS::Relative(value, other_fs); + const_pointer Relative(Path other_fs) const { + return PathTraitsFS::Relative(c_str(), other_fs.c_str()); } gcc_pure - bool IsAbsolute() { + bool IsAbsolute() const { return PathTraitsFS::IsAbsolute(c_str()); } + + gcc_pure + const_pointer GetSuffix() const; }; #endif diff --git a/src/fs/Path2.cxx b/src/fs/Path2.cxx new file mode 100644 index 000000000..b85909f79 --- /dev/null +++ b/src/fs/Path2.cxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2015 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 "Path.hxx" +#include "AllocatedPath.hxx" + +AllocatedPath +Path::GetDirectoryName() const +{ + return AllocatedPath::FromFS(PathTraitsFS::GetParent(c_str())); +} diff --git a/src/fs/StandardDirectory.cxx b/src/fs/StandardDirectory.cxx index 7a836f906..38ea804a0 100644 --- a/src/fs/StandardDirectory.cxx +++ b/src/fs/StandardDirectory.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -112,7 +112,7 @@ static inline AllocatedPath SafePathFromFS(PathTraitsFS::const_pointer dir) #ifdef WIN32 static AllocatedPath GetStandardDir(int folder_id) { - std::array<char, MAX_PATH> dir; + std::array<PathTraitsFS::value_type, MAX_PATH> dir; auto ret = SHGetFolderPath(nullptr, folder_id | CSIDL_FLAG_DONT_VERIFY, nullptr, SHGFP_TYPE_CURRENT, dir.data()); if (FAILED(ret)) @@ -287,7 +287,7 @@ AllocatedPath GetSystemConfigDir() AllocatedPath GetAppBaseDir() { - std::array<char, MAX_PATH> app; + std::array<PathTraitsFS::value_type, MAX_PATH> app; auto ret = GetModuleFileName(nullptr, app.data(), app.size()); // Check for error diff --git a/src/fs/StandardDirectory.hxx b/src/fs/StandardDirectory.hxx index e3fba375a..d453d109d 100644 --- a/src/fs/StandardDirectory.hxx +++ b/src/fs/StandardDirectory.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/Traits.cxx b/src/fs/Traits.cxx index 166b31f4e..e434a779c 100644 --- a/src/fs/Traits.cxx +++ b/src/fs/Traits.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -74,7 +74,7 @@ GetParentPathImpl(typename Traits::const_pointer p) typename Traits::const_pointer sep = Traits::FindLastSeparator(p); if (sep == nullptr) - return typename Traits::string("."); + return typename Traits::string(Traits::CURRENT_DIRECTORY); if (sep == p) return typename Traits::string(p, p + 1); #ifdef WIN32 @@ -98,7 +98,7 @@ RelativePathImpl(typename Traits::const_pointer base, return nullptr; other += base_length; - if (other != 0) { + if (*other != 0) { if (!Traits::IsSeparator(*other)) /* mismatch */ return nullptr; diff --git a/src/fs/Traits.hxx b/src/fs/Traits.hxx index 1af8f8672..b92330f60 100644 --- a/src/fs/Traits.hxx +++ b/src/fs/Traits.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -22,22 +22,34 @@ #include "check.h" #include "Compiler.h" +#include "util/StringAPI.hxx" #ifdef WIN32 #include "util/CharUtil.hxx" +#include <tchar.h> #endif #include <string> -#include <string.h> #include <assert.h> +#ifdef WIN32 +#define PATH_LITERAL(s) _T(s) +#else +#define PATH_LITERAL(s) (s) +#endif + /** * This class describes the nature of a native filesystem path. */ struct PathTraitsFS { +#ifdef WIN32 + typedef std::wstring string; +#else typedef std::string string; - typedef char value_type; +#endif + typedef string::traits_type char_traits; + typedef char_traits::char_type value_type; typedef value_type *pointer; typedef const value_type *const_pointer; @@ -47,6 +59,8 @@ struct PathTraitsFS { static constexpr value_type SEPARATOR = '/'; #endif + static constexpr const_pointer CURRENT_DIRECTORY = PATH_LITERAL("."); + static constexpr bool IsSeparator(value_type ch) { return #ifdef WIN32 @@ -68,7 +82,7 @@ struct PathTraitsFS { --pos; return IsSeparator(*pos) ? pos : nullptr; #else - return strrchr(p, SEPARATOR); + return StringFindLast(p, SEPARATOR); #endif } @@ -95,7 +109,12 @@ struct PathTraitsFS { gcc_pure gcc_nonnull_all static size_t GetLength(const_pointer p) { - return strlen(p); + return StringLength(p); + } + + gcc_pure gcc_nonnull_all + static const_pointer Find(const_pointer p, value_type ch) { + return StringFind(p, ch); } /** @@ -143,12 +162,15 @@ struct PathTraitsFS { */ struct PathTraitsUTF8 { typedef std::string string; - typedef char value_type; + typedef string::traits_type char_traits; + typedef char_traits::char_type value_type; typedef value_type *pointer; typedef const value_type *const_pointer; static constexpr value_type SEPARATOR = '/'; + static constexpr const_pointer CURRENT_DIRECTORY = "."; + static constexpr bool IsSeparator(value_type ch) { return ch == SEPARATOR; } @@ -186,7 +208,12 @@ struct PathTraitsUTF8 { gcc_pure gcc_nonnull_all static size_t GetLength(const_pointer p) { - return strlen(p); + return StringLength(p); + } + + gcc_pure gcc_nonnull_all + static const_pointer Find(const_pointer p, value_type ch) { + return StringFind(p, ch); } /** diff --git a/src/fs/io/AutoGunzipReader.cxx b/src/fs/io/AutoGunzipReader.cxx index 2552f7b99..b6d30dfd7 100644 --- a/src/fs/io/AutoGunzipReader.cxx +++ b/src/fs/io/AutoGunzipReader.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/io/AutoGunzipReader.hxx b/src/fs/io/AutoGunzipReader.hxx index 9f031e0f5..29a794aed 100644 --- a/src/fs/io/AutoGunzipReader.hxx +++ b/src/fs/io/AutoGunzipReader.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/io/BufferedOutputStream.cxx b/src/fs/io/BufferedOutputStream.cxx index 088a3e279..2268eb50c 100644 --- a/src/fs/io/BufferedOutputStream.cxx +++ b/src/fs/io/BufferedOutputStream.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -41,17 +41,22 @@ bool BufferedOutputStream::Write(const void *data, size_t size) { if (gcc_unlikely(last_error.IsDefined())) + /* the stream has already failed */ return false; + /* try to append to the current buffer */ if (AppendToBuffer(data, size)) return true; + /* not enough room in the buffer - flush it */ if (!Flush()) return false; + /* see if there's now enough room */ if (AppendToBuffer(data, size)) return true; + /* too large for the buffer: direct write */ return os.Write(data, size, last_error); } diff --git a/src/fs/io/BufferedOutputStream.hxx b/src/fs/io/BufferedOutputStream.hxx index f2de758a2..63a3f4aee 100644 --- a/src/fs/io/BufferedOutputStream.hxx +++ b/src/fs/io/BufferedOutputStream.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -30,6 +30,14 @@ class OutputStream; class Error; +/** + * An #OutputStream wrapper that buffers its output to reduce the + * number of OutputStream::Write() calls. + * + * It simplifies error handling by managing an #Error attribute. + * Invoke any number of writes, and check for errors in the end using + * Check(). + */ class BufferedOutputStream { OutputStream &os; @@ -47,11 +55,18 @@ public: gcc_printf(2,3) bool Format(const char *fmt, ...); + /** + * Returns false if an error has occurred. + */ gcc_pure bool Check() const { return !last_error.IsDefined(); } + /** + * Returns false if an error has occurred. In that case, a + * copy of the #Error is returned. + */ bool Check(Error &error) const { if (last_error.IsDefined()) { error.Set(last_error); @@ -60,6 +75,9 @@ public: return true; } + /** + * Write buffer contents to the #OutputStream. + */ bool Flush(); bool Flush(Error &error); diff --git a/src/fs/io/BufferedReader.cxx b/src/fs/io/BufferedReader.cxx index ba2f17dcf..9a296d815 100644 --- a/src/fs/io/BufferedReader.cxx +++ b/src/fs/io/BufferedReader.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -59,8 +59,10 @@ BufferedReader::ReadLine() { do { char *line = ReadBufferedLine(buffer); - if (line != nullptr) + if (line != nullptr) { + ++line_number; return line; + } } while (Fill(true)); if (last_error.IsDefined() || !eof || buffer.IsEmpty()) @@ -78,5 +80,6 @@ BufferedReader::ReadLine() char *line = buffer.Read().data; buffer.Clear(); + ++line_number; return line; } diff --git a/src/fs/io/BufferedReader.hxx b/src/fs/io/BufferedReader.hxx index 61cc8df83..a0c42d23c 100644 --- a/src/fs/io/BufferedReader.hxx +++ b/src/fs/io/BufferedReader.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -41,9 +41,12 @@ class BufferedReader { bool eof; + unsigned line_number; + public: BufferedReader(Reader &_reader) - :reader(_reader), buffer(4096), eof(false) {} + :reader(_reader), buffer(4096), eof(false), + line_number(0) {} gcc_pure bool Check() const { @@ -70,6 +73,10 @@ public: } char *ReadLine(); + + unsigned GetLineNumber() const { + return line_number; + } }; #endif diff --git a/src/fs/io/FileOutputStream.cxx b/src/fs/io/FileOutputStream.cxx index 0eff8b5f0..a4ef8f6b4 100644 --- a/src/fs/io/FileOutputStream.cxx +++ b/src/fs/io/FileOutputStream.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,36 +20,61 @@ #include "config.h" #include "FileOutputStream.hxx" #include "fs/FileSystem.hxx" -#include "system/fd_util.h" #include "util/Error.hxx" +FileOutputStream * +FileOutputStream::Create(Path path, Error &error) +{ + FileOutputStream *f = new FileOutputStream(path, error); + if (!f->IsDefined()) { + delete f; + f = nullptr; + } + + return f; +} + #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)) + :BaseFileOutputStream(_path) { - if (handle == INVALID_HANDLE_VALUE) - error.FormatLastError("Failed to create %s", path.c_str()); + SetHandle(CreateFile(_path.c_str(), GENERIC_WRITE, 0, nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, + nullptr)); + if (!IsDefined()) + error.FormatLastError("Failed to create %s", + GetPath().ToUTF8().c_str()); +} + +uint64_t +BaseFileOutputStream::Tell() const +{ + LONG high = 0; + DWORD low = SetFilePointer(handle, 0, &high, FILE_CURRENT); + if (low == 0xffffffff) + return 0; + + return uint64_t(high) << 32 | uint64_t(low); } bool -FileOutputStream::Write(const void *data, size_t size, Error &error) +BaseFileOutputStream::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()); + error.FormatLastError("Failed to write to %s", + path.ToUTF8().c_str()); return false; } if (size_t(nbytes) != size) { error.FormatLastError(ERROR_DISK_FULL, - "Failed to write to %s", path.c_str()); + "Failed to write to %s", + path.ToUTF8().c_str()); return false; } @@ -61,8 +86,7 @@ FileOutputStream::Commit(gcc_unused Error &error) { assert(IsDefined()); - CloseHandle(handle); - handle = INVALID_HANDLE_VALUE; + Close(); return true; } @@ -71,9 +95,8 @@ FileOutputStream::Cancel() { assert(IsDefined()); - CloseHandle(handle); - handle = INVALID_HANDLE_VALUE; - RemoveFile(path); + Close(); + RemoveFile(GetPath()); } #else @@ -82,28 +105,66 @@ FileOutputStream::Cancel() #include <unistd.h> #include <errno.h> +#ifdef HAVE_LINKAT +#ifndef O_TMPFILE +/* supported since Linux 3.11 */ +#define __O_TMPFILE 020000000 +#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) +#include <stdio.h> +#endif + +/** + * Open a file using Linux's O_TMPFILE for writing the given file. + */ +static bool +OpenTempFile(FileDescriptor &fd, Path path) +{ + const auto directory = path.GetDirectoryName(); + if (directory.IsNull()) + return false; + + return fd.Open(directory.c_str(), O_TMPFILE|O_WRONLY, 0666); +} + +#endif /* HAVE_LINKAT */ + FileOutputStream::FileOutputStream(Path _path, Error &error) - :path(_path), - fd(open_cloexec(path.c_str(), - O_WRONLY|O_CREAT|O_TRUNC, - 0666)) + :BaseFileOutputStream(_path) +{ +#ifdef HAVE_LINKAT + /* try Linux's O_TMPFILE first */ + is_tmpfile = OpenTempFile(SetFD(), GetPath()); + if (!is_tmpfile) { +#endif + /* fall back to plain POSIX */ + if (!SetFD().Open(GetPath().c_str(), + O_WRONLY|O_CREAT|O_TRUNC, + 0666)) + error.FormatErrno("Failed to create %s", + GetPath().c_str()); +#ifdef HAVE_LINKAT + } +#endif +} + +uint64_t +BaseFileOutputStream::Tell() const { - if (fd < 0) - error.FormatErrno("Failed to create %s", path.c_str()); + return fd.Tell(); } bool -FileOutputStream::Write(const void *data, size_t size, Error &error) +BaseFileOutputStream::Write(const void *data, size_t size, Error &error) { assert(IsDefined()); - ssize_t nbytes = write(fd, data, size); + ssize_t nbytes = fd.Write(data, size); if (nbytes < 0) { - error.FormatErrno("Failed to write to %s", path.c_str()); + error.FormatErrno("Failed to write to %s", GetPath().c_str()); return false; } else if ((size_t)nbytes < size) { error.FormatErrno(ENOSPC, - "Failed to write to %s", path.c_str()); + "Failed to write to %s", GetPath().c_str()); return false; } @@ -115,10 +176,27 @@ FileOutputStream::Commit(Error &error) { assert(IsDefined()); - bool success = close(fd) == 0; - fd = -1; +#if HAVE_LINKAT + if (is_tmpfile) { + RemoveFile(GetPath()); + + /* hard-link the temporary file to the final path */ + char fd_path[64]; + snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", + GetFD().Get()); + if (linkat(AT_FDCWD, fd_path, AT_FDCWD, GetPath().c_str(), + AT_SYMLINK_FOLLOW) < 0) { + error.FormatErrno("Failed to commit %s", + GetPath().c_str()); + Close(); + return false; + } + } +#endif + + bool success = Close(); if (!success) - error.FormatErrno("Failed to commit %s", path.c_str()); + error.FormatErrno("Failed to commit %s", GetPath().c_str()); return success; } @@ -128,10 +206,53 @@ FileOutputStream::Cancel() { assert(IsDefined()); - close(fd); - fd = -1; + Close(); + +#ifdef HAVE_LINKAT + if (!is_tmpfile) +#endif + RemoveFile(GetPath()); +} + +#endif + +AppendFileOutputStream::AppendFileOutputStream(Path _path, Error &error) + :BaseFileOutputStream(_path) +{ +#ifdef WIN32 + SetHandle(CreateFile(GetPath().c_str(), GENERIC_WRITE, 0, nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH, + nullptr)); + if (!IsDefined()) + error.FormatLastError("Failed to append to %s", + GetPath().ToUTF8().c_str()); - RemoveFile(path); + if (!SeekEOF()) { + error.FormatLastError("Failed seek end-of-file of %s", + GetPath().ToUTF8().c_str()); + Close(); + } +#else + if (!SetFD().Open(GetPath().c_str(), + O_WRONLY|O_APPEND)) + error.FormatErrno("Failed to append to %s", + GetPath().c_str()); +#endif } +bool +AppendFileOutputStream::Commit(gcc_unused Error &error) +{ + assert(IsDefined()); + +#ifdef WIN32 + return Close(); +#else + bool success = Close(); + if (!success) + error.FormatErrno("Failed to commit %s", GetPath().c_str()); + + return success; #endif +} diff --git a/src/fs/io/FileOutputStream.hxx b/src/fs/io/FileOutputStream.hxx index 5b6309957..b182fd03f 100644 --- a/src/fs/io/FileOutputStream.hxx +++ b/src/fs/io/FileOutputStream.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,10 @@ #include "fs/AllocatedPath.hxx" #include "Compiler.h" +#ifndef WIN32 +#include "system/FileDescriptor.hxx" +#endif + #include <assert.h> #ifdef WIN32 @@ -33,37 +37,124 @@ class Path; -class FileOutputStream final : public OutputStream { - AllocatedPath path; +class BaseFileOutputStream : public OutputStream { + const AllocatedPath path; #ifdef WIN32 HANDLE handle; #else - int fd; + FileDescriptor fd; #endif -public: - FileOutputStream(Path _path, Error &error); +protected: +#ifdef WIN32 + template<typename P> + BaseFileOutputStream(P &&_path) + :path(std::forward<P>(_path)), + handle(INVALID_HANDLE_VALUE) {} +#else + template<typename P> + BaseFileOutputStream(P &&_path) + :path(std::forward<P>(_path)), + fd(FileDescriptor::Undefined()) {} +#endif - ~FileOutputStream() { - if (IsDefined()) - Cancel(); + ~BaseFileOutputStream() { + assert(!IsDefined()); } +#ifdef WIN32 + void SetHandle(HANDLE _handle) { + assert(!IsDefined()); + + handle = _handle; + + assert(IsDefined()); + } +#else + FileDescriptor &SetFD() { + assert(!IsDefined()); + + return fd; + } + + const FileDescriptor &GetFD() const { + return fd; + } +#endif + + bool Close() { + assert(IsDefined()); + +#ifdef WIN32 + CloseHandle(handle); + handle = INVALID_HANDLE_VALUE; + return true; +#else + return fd.Close(); +#endif + } + +#ifdef WIN32 + bool SeekEOF() { + return SetFilePointer(handle, 0, nullptr, + FILE_END) != 0xffffffff; + } +#endif +public: bool IsDefined() const { #ifdef WIN32 return handle != INVALID_HANDLE_VALUE; #else - return fd >= 0; + return fd.IsDefined(); #endif } - bool Commit(Error &error); - void Cancel(); + Path GetPath() const { + return path; + } + + gcc_pure + uint64_t Tell() const; /* virtual methods from class OutputStream */ bool Write(const void *data, size_t size, Error &error) override; }; +class FileOutputStream final : public BaseFileOutputStream { +#ifdef HAVE_LINKAT + /** + * Was O_TMPFILE used? If yes, then linkat() must be used to + * create a link to this file. + */ + bool is_tmpfile; +#endif + +public: + FileOutputStream(Path _path, Error &error); + + ~FileOutputStream() { + if (IsDefined()) + Cancel(); + } + + static FileOutputStream *Create(Path path, Error &error); + + bool Commit(Error &error); + void Cancel(); +}; + +class AppendFileOutputStream final : public BaseFileOutputStream { +public: + AppendFileOutputStream(Path _path, Error &error); + + ~AppendFileOutputStream() { + if (IsDefined()) + Close(); + } + + bool Commit(Error &error); +}; + #endif diff --git a/src/fs/io/FileReader.cxx b/src/fs/io/FileReader.cxx index d63cd8ab0..e54f6f3a8 100644 --- a/src/fs/io/FileReader.cxx +++ b/src/fs/io/FileReader.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,7 @@ #include "config.h" #include "FileReader.hxx" -#include "system/fd_util.h" +#include "fs/FileInfo.hxx" #include "util/Error.hxx" #ifdef WIN32 @@ -30,8 +30,18 @@ FileReader::FileReader(Path _path, Error &error) nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)) { - if (handle == INVALID_HANDLE_VALUE) - error.FormatLastError("Failed to open %s", path.c_str()); + if (handle == INVALID_HANDLE_VALUE) { + const auto path_utf8 = path.ToUTF8(); + error.FormatLastError("Failed to open %s", path_utf8.c_str()); + } +} + +bool +FileReader::GetFileInfo(FileInfo &info, Error &error) const +{ + assert(IsDefined()); + + return ::GetFileInfo(path, info, error); } size_t @@ -41,13 +51,28 @@ FileReader::Read(void *data, size_t size, Error &error) DWORD nbytes; if (!ReadFile(handle, data, size, &nbytes, nullptr)) { - error.FormatLastError("Failed to read from %s", path.c_str()); + const auto path_utf8 = path.ToUTF8(); + error.FormatLastError("Failed to read from %s", + path_utf8.c_str()); nbytes = 0; } return nbytes; } +bool +FileReader::Seek(off_t offset, Error &error) +{ + assert(IsDefined()); + + auto result = SetFilePointer(handle, offset, nullptr, FILE_BEGIN); + const bool success = result != INVALID_SET_FILE_POINTER; + if (!success) + error.SetLastError("Failed to seek"); + + return success; +} + void FileReader::Close() { @@ -58,26 +83,33 @@ FileReader::Close() #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)) + :path(_path) { - if (fd < 0) + fd.OpenReadOnly(path.c_str()); + if (!fd.IsDefined()) error.FormatErrno("Failed to open %s", path.c_str()); } +bool +FileReader::GetFileInfo(FileInfo &info, Error &error) const +{ + assert(IsDefined()); + + const bool success = fstat(fd.Get(), &info.st) == 0; + if (!success) + error.FormatErrno("Failed to access %s", + path.ToUTF8().c_str()); + + return success; +} + size_t FileReader::Read(void *data, size_t size, Error &error) { assert(IsDefined()); - ssize_t nbytes = read(fd, data, size); + ssize_t nbytes = fd.Read(data, size); if (nbytes < 0) { error.FormatErrno("Failed to read from %s", path.c_str()); nbytes = 0; @@ -86,13 +118,25 @@ FileReader::Read(void *data, size_t size, Error &error) return nbytes; } +bool +FileReader::Seek(off_t offset, Error &error) +{ + assert(IsDefined()); + + auto result = fd.Seek(offset); + const bool success = result >= 0; + if (!success) + error.SetErrno("Failed to seek"); + + return success; +} + void FileReader::Close() { assert(IsDefined()); - close(fd); - fd = -1; + fd.Close(); } #endif diff --git a/src/fs/io/FileReader.hxx b/src/fs/io/FileReader.hxx index 9f459aee2..642fc5ed1 100644 --- a/src/fs/io/FileReader.hxx +++ b/src/fs/io/FileReader.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,6 +25,10 @@ #include "fs/AllocatedPath.hxx" #include "Compiler.h" +#ifndef WIN32 +#include "system/FileDescriptor.hxx" +#endif + #include <assert.h> #ifdef WIN32 @@ -32,6 +36,7 @@ #endif class Path; +class FileInfo; class FileReader final : public Reader { AllocatedPath path; @@ -39,12 +44,26 @@ class FileReader final : public Reader { #ifdef WIN32 HANDLE handle; #else - int fd; + FileDescriptor fd; #endif public: FileReader(Path _path, Error &error); +#ifdef WIN32 + FileReader(FileReader &&other) + :path(std::move(other.path)), + handle(other.handle) { + other.handle = INVALID_HANDLE_VALUE; + } +#else + FileReader(FileReader &&other) + :path(std::move(other.path)), + fd(other.fd) { + other.fd.SetUndefined(); + } +#endif + ~FileReader() { if (IsDefined()) Close(); @@ -55,12 +74,22 @@ public: #ifdef WIN32 return handle != INVALID_HANDLE_VALUE; #else - return fd >= 0; + return fd.IsDefined(); #endif } +#ifndef WIN32 + FileDescriptor GetFD() const { + return fd; + } +#endif + void Close(); + bool GetFileInfo(FileInfo &info, Error &error) const; + + bool Seek(off_t offset, Error &error); + /* virtual methods from class Reader */ size_t Read(void *data, size_t size, Error &error) override; }; diff --git a/src/fs/io/GunzipReader.cxx b/src/fs/io/GunzipReader.cxx index ad5e41784..78f5b2c69 100644 --- a/src/fs/io/GunzipReader.cxx +++ b/src/fs/io/GunzipReader.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/io/GunzipReader.hxx b/src/fs/io/GunzipReader.hxx index 06c44bad6..381d1af5e 100644 --- a/src/fs/io/GunzipReader.hxx +++ b/src/fs/io/GunzipReader.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/io/GzipOutputStream.cxx b/src/fs/io/GzipOutputStream.cxx index 27ae6b2ad..d2a693b87 100644 --- a/src/fs/io/GzipOutputStream.cxx +++ b/src/fs/io/GzipOutputStream.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/io/GzipOutputStream.hxx b/src/fs/io/GzipOutputStream.hxx index 27ee2dd24..fdab7bca4 100644 --- a/src/fs/io/GzipOutputStream.hxx +++ b/src/fs/io/GzipOutputStream.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/io/OutputStream.hxx b/src/fs/io/OutputStream.hxx index 71311c71f..f7d101180 100644 --- a/src/fs/io/OutputStream.hxx +++ b/src/fs/io/OutputStream.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/io/PeekReader.cxx b/src/fs/io/PeekReader.cxx index 2e8042ab6..ec9520a37 100644 --- a/src/fs/io/PeekReader.cxx +++ b/src/fs/io/PeekReader.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/io/PeekReader.hxx b/src/fs/io/PeekReader.hxx index 32180b0a8..c00ed66be 100644 --- a/src/fs/io/PeekReader.hxx +++ b/src/fs/io/PeekReader.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/io/Reader.hxx b/src/fs/io/Reader.hxx index d41e92dd0..657f96ac2 100644 --- a/src/fs/io/Reader.hxx +++ b/src/fs/io/Reader.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/io/StdioOutputStream.hxx b/src/fs/io/StdioOutputStream.hxx index c1c0a00bd..88dbe6f00 100644 --- a/src/fs/io/StdioOutputStream.hxx +++ b/src/fs/io/StdioOutputStream.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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/io/TextFile.cxx b/src/fs/io/TextFile.cxx index 28d6dabcb..9866da08a 100644 --- a/src/fs/io/TextFile.cxx +++ b/src/fs/io/TextFile.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -28,14 +28,14 @@ TextFile::TextFile(Path path_fs, Error &error) :file_reader(new FileReader(path_fs, error)), -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB gunzip_reader(file_reader->IsDefined() ? new AutoGunzipReader(*file_reader) : nullptr), #endif buffered_reader(file_reader->IsDefined() ? new BufferedReader(* -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB gunzip_reader #else file_reader @@ -48,7 +48,7 @@ TextFile::TextFile(Path path_fs, Error &error) TextFile::~TextFile() { delete buffered_reader; -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB delete gunzip_reader; #endif delete file_reader; diff --git a/src/fs/io/TextFile.hxx b/src/fs/io/TextFile.hxx index 5577363e7..bee9e8c23 100644 --- a/src/fs/io/TextFile.hxx +++ b/src/fs/io/TextFile.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -34,7 +34,7 @@ class BufferedReader; class TextFile { FileReader *const file_reader; -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB AutoGunzipReader *const gunzip_reader; #endif @@ -59,7 +59,6 @@ public: * 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(); |