diff options
Diffstat (limited to 'src/storage')
-rw-r--r-- | src/storage/CompositeStorage.cxx | 362 | ||||
-rw-r--r-- | src/storage/CompositeStorage.hxx | 166 | ||||
-rw-r--r-- | src/storage/Configured.cxx | 82 | ||||
-rw-r--r-- | src/storage/Configured.hxx | 45 | ||||
-rw-r--r-- | src/storage/FileInfo.hxx | 62 | ||||
-rw-r--r-- | src/storage/MemoryDirectoryReader.cxx | 48 | ||||
-rw-r--r-- | src/storage/MemoryDirectoryReader.hxx | 67 | ||||
-rw-r--r-- | src/storage/Registry.cxx | 71 | ||||
-rw-r--r-- | src/storage/Registry.hxx | 45 | ||||
-rw-r--r-- | src/storage/StorageInterface.cxx | 37 | ||||
-rw-r--r-- | src/storage/StorageInterface.hxx | 81 | ||||
-rw-r--r-- | src/storage/StoragePlugin.hxx | 36 | ||||
-rw-r--r-- | src/storage/plugins/LocalStorage.cxx | 217 | ||||
-rw-r--r-- | src/storage/plugins/LocalStorage.hxx | 36 | ||||
-rw-r--r-- | src/storage/plugins/NfsStorage.cxx | 423 | ||||
-rw-r--r-- | src/storage/plugins/NfsStorage.hxx | 29 | ||||
-rw-r--r-- | src/storage/plugins/SmbclientStorage.cxx | 212 | ||||
-rw-r--r-- | src/storage/plugins/SmbclientStorage.hxx | 29 |
18 files changed, 2048 insertions, 0 deletions
diff --git a/src/storage/CompositeStorage.cxx b/src/storage/CompositeStorage.cxx new file mode 100644 index 000000000..89a2fc756 --- /dev/null +++ b/src/storage/CompositeStorage.cxx @@ -0,0 +1,362 @@ +/* + * 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 "CompositeStorage.hxx" +#include "FileInfo.hxx" +#include "fs/AllocatedPath.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#include <set> + +#include <string.h> + +static constexpr Domain composite_domain("composite"); + +/** + * Combines the directory entries of another #StorageDirectoryReader + * instance and the virtual directory entries. + */ +class CompositeDirectoryReader final : public StorageDirectoryReader { + StorageDirectoryReader *other; + + std::set<std::string> names; + std::set<std::string>::const_iterator current, next; + +public: + template<typename M> + CompositeDirectoryReader(StorageDirectoryReader *_other, + const M &map) + :other(_other) { + for (const auto &i : map) + names.insert(i.first); + next = names.begin(); + } + + virtual ~CompositeDirectoryReader() { + delete other; + } + + /* virtual methods from class StorageDirectoryReader */ + const char *Read() override; + bool GetInfo(bool follow, FileInfo &info, Error &error) override; +}; + +const char * +CompositeDirectoryReader::Read() +{ + if (other != nullptr) { + const char *name = other->Read(); + if (name != nullptr) { + names.erase(name); + return name; + } + + delete other; + other = nullptr; + } + + if (next == names.end()) + return nullptr; + + current = next++; + return current->c_str(); +} + +bool +CompositeDirectoryReader::GetInfo(bool follow, FileInfo &info, + Error &error) +{ + if (other != nullptr) + return other->GetInfo(follow, info, error); + + assert(current != names.end()); + + info.type = FileInfo::Type::DIRECTORY; + info.mtime = 0; + info.device = 0; + info.inode = 0; + return true; +} + +static std::string +NextSegment(const char *&uri_r) +{ + const char *uri = uri_r; + const char *slash = strchr(uri, '/'); + if (slash == nullptr) { + uri_r += strlen(uri); + return std::string(uri); + } else { + uri_r = slash + 1; + return std::string(uri, slash); + } +} + +CompositeStorage::Directory::~Directory() +{ + delete storage; +} + +const CompositeStorage::Directory * +CompositeStorage::Directory::Find(const char *uri) const +{ + const Directory *directory = this; + while (*uri != 0) { + const std::string name = NextSegment(uri); + auto i = directory->children.find(name); + if (i == directory->children.end()) + return nullptr; + + directory = &i->second; + } + + return directory; +} + +CompositeStorage::Directory & +CompositeStorage::Directory::Make(const char *uri) +{ + Directory *directory = this; + while (*uri != 0) { + const std::string name = NextSegment(uri); +#if defined(__clang__) || GCC_CHECK_VERSION(4,8) + auto i = directory->children.emplace(std::move(name), + Directory()); +#else + auto i = directory->children.insert(std::make_pair(std::move(name), + Directory())); +#endif + directory = &i.first->second; + } + + return *directory; +} + +bool +CompositeStorage::Directory::Unmount() +{ + if (storage == nullptr) + return false; + + delete storage; + storage = nullptr; + return true; +} + +bool +CompositeStorage::Directory::Unmount(const char *uri) +{ + if (*uri == 0) + return Unmount(); + + const std::string name = NextSegment(uri); + + auto i = children.find(name); + if (i == children.end() || !i->second.Unmount(uri)) + return false; + + if (i->second.IsEmpty()) + children.erase(i); + + return true; + +} + +bool +CompositeStorage::Directory::MapToRelativeUTF8(std::string &buffer, + const char *uri) const +{ + if (storage != nullptr) { + const char *result = storage->MapToRelativeUTF8(uri); + if (result != nullptr) { + buffer = result; + return true; + } + } + + for (const auto &i : children) { + if (i.second.MapToRelativeUTF8(buffer, uri)) { + buffer.insert(buffer.begin(), '/'); + buffer.insert(buffer.begin(), + i.first.begin(), i.first.end()); + return true; + } + } + + return false; +} + +CompositeStorage::CompositeStorage() +{ +} + +CompositeStorage::~CompositeStorage() +{ +} + +Storage * +CompositeStorage::GetMount(const char *uri) +{ + const ScopeLock protect(mutex); + + auto result = FindStorage(uri); + if (*result.uri != 0) + /* not a mount point */ + return nullptr; + + return result.directory->storage; +} + +void +CompositeStorage::Mount(const char *uri, Storage *storage) +{ + const ScopeLock protect(mutex); + + Directory &directory = root.Make(uri); + if (directory.storage != nullptr) + delete directory.storage; + directory.storage = storage; +} + +bool +CompositeStorage::Unmount(const char *uri) +{ + const ScopeLock protect(mutex); + + return root.Unmount(uri); +} + +CompositeStorage::FindResult +CompositeStorage::FindStorage(const char *uri) const +{ + FindResult result{&root, uri}; + + const Directory *directory = &root; + while (*uri != 0) { + const std::string name = NextSegment(uri); + + auto i = directory->children.find(name); + if (i == directory->children.end()) + break; + + directory = &i->second; + if (directory->storage != nullptr) + result = FindResult{directory, uri}; + } + + return result; +} + +CompositeStorage::FindResult +CompositeStorage::FindStorage(const char *uri, Error &error) const +{ + auto result = FindStorage(uri); + if (result.directory == nullptr) + error.Set(composite_domain, "No such directory"); + return result; +} + +bool +CompositeStorage::GetInfo(const char *uri, bool follow, FileInfo &info, + Error &error) +{ + const ScopeLock protect(mutex); + + auto f = FindStorage(uri, error); + if (f.directory->storage != nullptr && + f.directory->storage->GetInfo(f.uri, follow, info, error)) + return true; + + const Directory *directory = f.directory->Find(f.uri); + if (directory != nullptr) { + error.Clear(); + info.type = FileInfo::Type::DIRECTORY; + info.mtime = 0; + info.device = 0; + info.inode = 0; + return true; + } + + return false; +} + +StorageDirectoryReader * +CompositeStorage::OpenDirectory(const char *uri, + Error &error) +{ + const ScopeLock protect(mutex); + + auto f = FindStorage(uri, error); + const Directory *directory = f.directory->Find(f.uri); + if (directory == nullptr || directory->children.empty()) { + /* no virtual directories here */ + + if (f.directory->storage == nullptr) + return nullptr; + + return f.directory->storage->OpenDirectory(f.uri, error); + } + + StorageDirectoryReader *other = + f.directory->storage->OpenDirectory(f.uri, IgnoreError()); + return new CompositeDirectoryReader(other, directory->children); +} + +std::string +CompositeStorage::MapUTF8(const char *uri) const +{ + const ScopeLock protect(mutex); + + auto f = FindStorage(uri); + if (f.directory->storage == nullptr) + return std::string(); + + return f.directory->storage->MapUTF8(f.uri); +} + +AllocatedPath +CompositeStorage::MapFS(const char *uri) const +{ + const ScopeLock protect(mutex); + + auto f = FindStorage(uri); + if (f.directory->storage == nullptr) + return AllocatedPath::Null(); + + return f.directory->storage->MapFS(f.uri); +} + +const char * +CompositeStorage::MapToRelativeUTF8(const char *uri) const +{ + const ScopeLock protect(mutex); + + if (root.storage != nullptr) { + const char *result = root.storage->MapToRelativeUTF8(uri); + if (result != nullptr) + return result; + } + + if (!root.MapToRelativeUTF8(relative_buffer, uri)) + return nullptr; + + return relative_buffer.c_str(); +} diff --git a/src/storage/CompositeStorage.hxx b/src/storage/CompositeStorage.hxx new file mode 100644 index 000000000..c3695c79d --- /dev/null +++ b/src/storage/CompositeStorage.hxx @@ -0,0 +1,166 @@ +/* + * 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_COMPOSITE_STORAGE_HXX +#define MPD_COMPOSITE_STORAGE_HXX + +#include "check.h" +#include "StorageInterface.hxx" +#include "thread/Mutex.hxx" +#include "Compiler.h" + +#include <string> +#include <map> + +class Error; +class Storage; + +/** + * A #Storage implementation that combines multiple other #Storage + * instances in one virtual tree. It is used to "mount" new #Storage + * instances into the storage tree. + * + * This class is thread-safe: mounts may be added and removed at any + * time in any thread. + */ +class CompositeStorage final : public Storage { + /** + * A node in the virtual directory tree. + */ + struct Directory { + /** + * The #Storage mounted n this virtual directory. All + * "leaf" Directory instances must have a #Storage. + * Other Directory instances may have one, and child + * mounts will be "mixed" in. + */ + Storage *storage; + + std::map<std::string, Directory> children; + + Directory():storage(nullptr) {} + ~Directory(); + + gcc_pure + bool IsEmpty() const { + return storage == nullptr && children.empty(); + } + + gcc_pure + const Directory *Find(const char *uri) const; + + Directory &Make(const char *uri); + + bool Unmount(); + bool Unmount(const char *uri); + + gcc_pure + bool MapToRelativeUTF8(std::string &buffer, + const char *uri) const; + }; + + struct FindResult { + const Directory *directory; + const char *uri; + }; + + /** + * Protects the virtual #Directory tree. + * + * TODO: use readers-writer lock + */ + mutable Mutex mutex; + + Directory root; + + mutable std::string relative_buffer; + +public: + CompositeStorage(); + virtual ~CompositeStorage(); + + /** + * Get the #Storage at the specified mount point. Returns + * nullptr if the given URI is not a mount point. + * + * The returned pointer is unprotected. No other thread is + * allowed to unmount the given mount point while the return + * value is being used. + */ + gcc_pure gcc_nonnull_all + Storage *GetMount(const char *uri); + + /** + * Call the given function for each mounted storage, including + * the root storage. Passes mount point URI and the a const + * Storage reference to the function. + */ + template<typename T> + void VisitMounts(T t) const { + const ScopeLock protect(mutex); + std::string uri; + VisitMounts(uri, root, t); + } + + void Mount(const char *uri, Storage *storage); + bool Unmount(const char *uri); + + /* virtual methods from class Storage */ + bool GetInfo(const char *uri, bool follow, FileInfo &info, + Error &error) override; + + StorageDirectoryReader *OpenDirectory(const char *uri, + Error &error) override; + + std::string MapUTF8(const char *uri) const override; + + AllocatedPath MapFS(const char *uri) const override; + + const char *MapToRelativeUTF8(const char *uri) const override; + +private: + template<typename T> + void VisitMounts(std::string &uri, const Directory &directory, + T t) const { + const Storage *const storage = directory.storage; + if (storage != nullptr) + t(uri.c_str(), *storage); + + if (!uri.empty()) + uri.push_back('/'); + + const size_t uri_length = uri.length(); + + for (const auto &i : directory.children) { + uri.resize(uri_length); + uri.append(i.first); + + VisitMounts(uri, i.second, t); + } + } + + gcc_pure + FindResult FindStorage(const char *uri) const; + FindResult FindStorage(const char *uri, Error &error) const; + + const char *MapToRelativeUTF8(const Directory &directory, + const char *uri) const; +}; + +#endif diff --git a/src/storage/Configured.cxx b/src/storage/Configured.cxx new file mode 100644 index 000000000..41541673b --- /dev/null +++ b/src/storage/Configured.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 "Configured.hxx" +#include "Registry.hxx" +#include "plugins/LocalStorage.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigError.hxx" +#include "fs/StandardDirectory.hxx" +#include "fs/CheckFile.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" + +#include <assert.h> + +static Storage * +CreateConfiguredStorageUri(EventLoop &event_loop, const char *uri, + Error &error) +{ + Storage *storage = CreateStorageURI(event_loop, uri, error); + if (storage == nullptr && !error.IsDefined()) + error.Format(config_domain, + "Unrecognized storage URI: %s", uri); + return storage; +} + +static AllocatedPath +GetConfiguredMusicDirectory(Error &error) +{ + AllocatedPath path = config_get_path(CONF_MUSIC_DIR, error); + if (path.IsNull() && !error.IsDefined()) + path = GetUserMusicDir(); + + return path; +} + +static Storage * +CreateConfiguredStorageLocal(Error &error) +{ + AllocatedPath path = GetConfiguredMusicDirectory(error); + if (path.IsNull()) + return nullptr; + + path.ChopSeparators(); + CheckDirectoryReadable(path); + return CreateLocalStorage(path); +} + +Storage * +CreateConfiguredStorage(EventLoop &event_loop, Error &error) +{ + assert(!error.IsDefined()); + + auto uri = config_get_string(CONF_MUSIC_DIR, nullptr); + if (uri != nullptr && uri_has_scheme(uri)) + return CreateConfiguredStorageUri(event_loop, uri, error); + + return CreateConfiguredStorageLocal(error); +} + +bool +IsStorageConfigured() +{ + return config_get_string(CONF_MUSIC_DIR, nullptr) != nullptr; +} diff --git a/src/storage/Configured.hxx b/src/storage/Configured.hxx new file mode 100644 index 000000000..828a192c3 --- /dev/null +++ b/src/storage/Configured.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_STORAGE_CONFIG_HXX +#define MPD_STORAGE_CONFIG_HXX + +#include "check.h" +#include "Compiler.h" + +class Error; +class Storage; +class EventLoop; + +/** + * Read storage configuration settings and create a #Storage instance + * from it. Returns nullptr on error or if no storage is configured + * (no #Error set in that case). + */ +Storage * +CreateConfiguredStorage(EventLoop &event_loop, Error &error); + +/** + * Returns true if there is configuration for a #Storage instance. + */ +gcc_const +bool +IsStorageConfigured(); + +#endif diff --git a/src/storage/FileInfo.hxx b/src/storage/FileInfo.hxx new file mode 100644 index 000000000..8dd152c0a --- /dev/null +++ b/src/storage/FileInfo.hxx @@ -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. + */ + +#ifndef MPD_STORAGE_FILE_INFO_HXX +#define MPD_STORAGE_FILE_INFO_HXX + +#include "check.h" + +#include <time.h> +#include <stdint.h> + +struct FileInfo { + enum class Type : uint8_t { + OTHER, + REGULAR, + DIRECTORY, + }; + + Type type; + + /** + * The file size in bytes. Only valid for #Type::REGULAR. + */ + uint64_t size; + + /** + * The modification time. 0 means unknown / not applicable. + */ + time_t mtime; + + /** + * Device id and inode number. 0 means unknown / not + * applicable. + */ + unsigned device, inode; + + bool IsRegular() const { + return type == Type::REGULAR; + } + + bool IsDirectory() const { + return type == Type::DIRECTORY; + } +}; + +#endif diff --git a/src/storage/MemoryDirectoryReader.cxx b/src/storage/MemoryDirectoryReader.cxx new file mode 100644 index 000000000..160836b1a --- /dev/null +++ b/src/storage/MemoryDirectoryReader.cxx @@ -0,0 +1,48 @@ +/* + * 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 "MemoryDirectoryReader.hxx" + +#include <assert.h> + +const char * +MemoryStorageDirectoryReader::Read() +{ + if (first) + first = false; + else + entries.pop_front(); + + if (entries.empty()) + return nullptr; + + return entries.front().name.c_str(); +} + +bool +MemoryStorageDirectoryReader::GetInfo(gcc_unused bool follow, FileInfo &info, + gcc_unused Error &error) +{ + assert(!first); + assert(!entries.empty()); + + info = entries.front().info; + return true; +} diff --git a/src/storage/MemoryDirectoryReader.hxx b/src/storage/MemoryDirectoryReader.hxx new file mode 100644 index 000000000..1345082cb --- /dev/null +++ b/src/storage/MemoryDirectoryReader.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_STORAGE_MEMORY_DIRECTORY_READER_HXX +#define MPD_STORAGE_MEMORY_DIRECTORY_READER_HXX + +#include "check.h" +#include "StorageInterface.hxx" +#include "FileInfo.hxx" + +#include <string> +#include <forward_list> + +/** + * A #StorageDirectoryReader implementation that returns directory + * entries from a memory allocation. + */ +class MemoryStorageDirectoryReader final : public StorageDirectoryReader { +public: + struct Entry { + std::string name; + + FileInfo info; + + template<typename N> + explicit Entry(N &&_name):name(std::forward<N>(_name)) {} + }; + + typedef std::forward_list<Entry> List; + +private: + List entries; + + bool first; + +public: + MemoryStorageDirectoryReader() + :first(true) {} + + MemoryStorageDirectoryReader(MemoryStorageDirectoryReader &&src) + :entries(std::move(src.entries)), first(src.first) {} + + MemoryStorageDirectoryReader(List &&_entries) + :entries(std::move(_entries)), first(true) {} + + /* virtual methods from class StorageDirectoryReader */ + const char *Read() override; + bool GetInfo(bool follow, FileInfo &info, Error &error) override; +}; + +#endif diff --git a/src/storage/Registry.cxx b/src/storage/Registry.cxx new file mode 100644 index 000000000..d8e273fd5 --- /dev/null +++ b/src/storage/Registry.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 "Registry.hxx" +#include "StoragePlugin.hxx" +#include "plugins/LocalStorage.hxx" +#include "plugins/SmbclientStorage.hxx" +#include "plugins/NfsStorage.hxx" +#include "util/Error.hxx" + +#include <assert.h> +#include <string.h> + +const StoragePlugin *const storage_plugins[] = { + &local_storage_plugin, +#ifdef ENABLE_SMBCLIENT + &smbclient_storage_plugin, +#endif +#ifdef ENABLE_NFS + &nfs_storage_plugin, +#endif + nullptr +}; + +const StoragePlugin * +GetStoragePluginByName(const char *name) +{ + for (auto i = storage_plugins; *i != nullptr; ++i) { + const StoragePlugin &plugin = **i; + if (strcmp(plugin.name, name) == 0) + return *i; + } + + return nullptr; +} + +Storage * +CreateStorageURI(EventLoop &event_loop, const char *uri, Error &error) +{ + assert(!error.IsDefined()); + + for (auto i = storage_plugins; *i != nullptr; ++i) { + const StoragePlugin &plugin = **i; + + if (plugin.create_uri == nullptr) + continue; + + Storage *storage = plugin.create_uri(event_loop, uri, error); + if (storage != nullptr || error.IsDefined()) + return storage; + } + + return nullptr; +} diff --git a/src/storage/Registry.hxx b/src/storage/Registry.hxx new file mode 100644 index 000000000..cb3a78f11 --- /dev/null +++ b/src/storage/Registry.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_STORAGE_REGISTRY_HXX +#define MPD_STORAGE_REGISTRY_HXX + +#include "check.h" +#include "Compiler.h" + +struct StoragePlugin; +class Storage; +class Error; +class EventLoop; + +/** + * nullptr terminated list of all storage plugins which were enabled at + * compile time. + */ +extern const StoragePlugin *const storage_plugins[]; + +gcc_nonnull_all gcc_pure +const StoragePlugin * +GetStoragePluginByName(const char *name); + +gcc_nonnull_all gcc_malloc +Storage * +CreateStorageURI(EventLoop &event_loop, const char *uri, Error &error); + +#endif diff --git a/src/storage/StorageInterface.cxx b/src/storage/StorageInterface.cxx new file mode 100644 index 000000000..93c50a8ac --- /dev/null +++ b/src/storage/StorageInterface.cxx @@ -0,0 +1,37 @@ +/* + * 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 "StorageInterface.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/Traits.hxx" + +AllocatedPath +Storage::MapFS(gcc_unused const char *uri_utf8) const +{ + return AllocatedPath::Null(); +} + +AllocatedPath +Storage::MapChildFS(const char *uri_utf8, + const char *child_utf8) const +{ + const auto uri2 = PathTraitsUTF8::Build(uri_utf8, child_utf8); + return MapFS(uri2.c_str()); +} diff --git a/src/storage/StorageInterface.hxx b/src/storage/StorageInterface.hxx new file mode 100644 index 000000000..4484815bc --- /dev/null +++ b/src/storage/StorageInterface.hxx @@ -0,0 +1,81 @@ +/* + * 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_STORAGE_INTERFACE_HXX +#define MPD_STORAGE_INTERFACE_HXX + +#include "check.h" +#include "Compiler.h" + +#include <string> + +struct FileInfo; +class AllocatedPath; +class Error; + +class StorageDirectoryReader { +public: + StorageDirectoryReader() = default; + StorageDirectoryReader(const StorageDirectoryReader &) = delete; + virtual ~StorageDirectoryReader() {} + + virtual const char *Read() = 0; + virtual bool GetInfo(bool follow, FileInfo &info, Error &error) = 0; +}; + +class Storage { +public: + Storage() = default; + Storage(const Storage &) = delete; + virtual ~Storage() {} + + virtual bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info, + Error &error) = 0; + + virtual StorageDirectoryReader *OpenDirectory(const char *uri_utf8, + Error &error) = 0; + + /** + * Map the given relative URI to an absolute URI. + */ + gcc_pure + virtual std::string MapUTF8(const char *uri_utf8) const = 0; + + /** + * Map the given relative URI to a local file path. Returns + * AllocatedPath::Null() on error or if this storage does not + * support local files. + */ + gcc_pure + virtual AllocatedPath MapFS(const char *uri_utf8) const; + + gcc_pure + AllocatedPath MapChildFS(const char *uri_utf8, + const char *child_utf8) const; + + /** + * Check if the given URI points inside this storage. If yes, + * then it returns a relative URI (pointing inside the given + * string); if not, returns nullptr. + */ + gcc_pure + virtual const char *MapToRelativeUTF8(const char *uri_utf8) const = 0; +}; + +#endif diff --git a/src/storage/StoragePlugin.hxx b/src/storage/StoragePlugin.hxx new file mode 100644 index 000000000..15f431105 --- /dev/null +++ b/src/storage/StoragePlugin.hxx @@ -0,0 +1,36 @@ +/* + * 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_STORAGE_PLUGIN_HXX +#define MPD_STORAGE_PLUGIN_HXX + +#include "check.h" + +class Error; +class Storage; +class EventLoop; + +struct StoragePlugin { + const char *name; + + Storage *(*create_uri)(EventLoop &event_loop, const char *uri, + Error &error); +}; + +#endif diff --git a/src/storage/plugins/LocalStorage.cxx b/src/storage/plugins/LocalStorage.cxx new file mode 100644 index 000000000..b965ceea8 --- /dev/null +++ b/src/storage/plugins/LocalStorage.cxx @@ -0,0 +1,217 @@ +/* + * 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 "LocalStorage.hxx" +#include "storage/StoragePlugin.hxx" +#include "storage/StorageInterface.hxx" +#include "storage/FileInfo.hxx" +#include "util/Error.hxx" +#include "fs/FileSystem.hxx" +#include "fs/AllocatedPath.hxx" +#include "fs/DirectoryReader.hxx" + +#include <string> + +class LocalDirectoryReader final : public StorageDirectoryReader { + AllocatedPath base_fs; + + DirectoryReader reader; + + std::string name_utf8; + +public: + LocalDirectoryReader(AllocatedPath &&_base_fs) + :base_fs(std::move(_base_fs)), reader(base_fs) {} + + bool HasFailed() { + return reader.HasFailed(); + } + + /* virtual methods from class StorageDirectoryReader */ + const char *Read() override; + bool GetInfo(bool follow, FileInfo &info, Error &error) override; +}; + +class LocalStorage final : public Storage { + const AllocatedPath base_fs; + const std::string base_utf8; + +public: + explicit LocalStorage(Path _base_fs) + :base_fs(_base_fs), base_utf8(base_fs.ToUTF8()) { + assert(!base_fs.IsNull()); + assert(!base_utf8.empty()); + } + + /* virtual methods from class Storage */ + bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info, + Error &error) override; + + StorageDirectoryReader *OpenDirectory(const char *uri_utf8, + Error &error) override; + + std::string MapUTF8(const char *uri_utf8) const override; + + AllocatedPath MapFS(const char *uri_utf8) const override; + + const char *MapToRelativeUTF8(const char *uri_utf8) const override; + +private: + AllocatedPath MapFS(const char *uri_utf8, Error &error) const; +}; + +static bool +Stat(Path path, bool follow, FileInfo &info, Error &error) +{ + struct stat st; + if (!StatFile(path, st, follow)) { + error.SetErrno(); + + const auto path_utf8 = path.ToUTF8(); + error.FormatPrefix("Failed to stat %s: ", path_utf8.c_str()); + return false; + } + + if (S_ISREG(st.st_mode)) + info.type = FileInfo::Type::REGULAR; + else if (S_ISDIR(st.st_mode)) + info.type = FileInfo::Type::DIRECTORY; + else + info.type = FileInfo::Type::OTHER; + + info.size = st.st_size; + info.mtime = st.st_mtime; + info.device = st.st_dev; + info.inode = st.st_ino; + return true; +} + +std::string +LocalStorage::MapUTF8(const char *uri_utf8) const +{ + assert(uri_utf8 != nullptr); + + if (*uri_utf8 == 0) + return base_utf8; + + return PathTraitsUTF8::Build(base_utf8.c_str(), uri_utf8); +} + +AllocatedPath +LocalStorage::MapFS(const char *uri_utf8, Error &error) const +{ + assert(uri_utf8 != nullptr); + + if (*uri_utf8 == 0) + return base_fs; + + AllocatedPath path_fs = AllocatedPath::FromUTF8(uri_utf8, error); + if (!path_fs.IsNull()) + path_fs = AllocatedPath::Build(base_fs, path_fs); + + return path_fs; +} + +AllocatedPath +LocalStorage::MapFS(const char *uri_utf8) const +{ + return MapFS(uri_utf8, IgnoreError()); +} + +const char * +LocalStorage::MapToRelativeUTF8(const char *uri_utf8) const +{ + return PathTraitsUTF8::Relative(base_utf8.c_str(), uri_utf8); +} + +bool +LocalStorage::GetInfo(const char *uri_utf8, bool follow, FileInfo &info, + Error &error) +{ + AllocatedPath path_fs = MapFS(uri_utf8, error); + if (path_fs.IsNull()) + return false; + + return Stat(path_fs, follow, info, error); +} + +StorageDirectoryReader * +LocalStorage::OpenDirectory(const char *uri_utf8, Error &error) +{ + AllocatedPath path_fs = MapFS(uri_utf8, error); + if (path_fs.IsNull()) + return nullptr; + + LocalDirectoryReader *reader = + new LocalDirectoryReader(std::move(path_fs)); + if (reader->HasFailed()) { + error.FormatErrno("Failed to open '%s'", uri_utf8); + delete reader; + return nullptr; + } + + return reader; +} + +gcc_pure +static bool +SkipNameFS(const char *name_fs) +{ + return name_fs[0] == '.' && + (name_fs[1] == 0 || + (name_fs[1] == '.' && name_fs[2] == 0)); +} + +const char * +LocalDirectoryReader::Read() +{ + while (reader.ReadEntry()) { + const Path name_fs = reader.GetEntry(); + if (SkipNameFS(name_fs.c_str())) + continue; + + name_utf8 = name_fs.ToUTF8(); + if (name_utf8.empty()) + continue; + + return name_utf8.c_str(); + } + + return nullptr; +} + +bool +LocalDirectoryReader::GetInfo(bool follow, FileInfo &info, Error &error) +{ + const AllocatedPath path_fs = + AllocatedPath::Build(base_fs, reader.GetEntry()); + return Stat(path_fs, follow, info, error); +} + +Storage * +CreateLocalStorage(Path base_fs) +{ + return new LocalStorage(base_fs); +} + +const StoragePlugin local_storage_plugin = { + "local", + nullptr, +}; diff --git a/src/storage/plugins/LocalStorage.hxx b/src/storage/plugins/LocalStorage.hxx new file mode 100644 index 000000000..7295d38e7 --- /dev/null +++ b/src/storage/plugins/LocalStorage.hxx @@ -0,0 +1,36 @@ +/* + * 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_STORAGE_LOCAL_HXX +#define MPD_STORAGE_LOCAL_HXX + +#include "check.h" +#include "Compiler.h" + +struct StoragePlugin; +class Storage; +class Path; + +extern const StoragePlugin local_storage_plugin; + +gcc_malloc gcc_nonnull_all +Storage * +CreateLocalStorage(Path base_fs); + +#endif diff --git a/src/storage/plugins/NfsStorage.cxx b/src/storage/plugins/NfsStorage.cxx new file mode 100644 index 000000000..324b40b6f --- /dev/null +++ b/src/storage/plugins/NfsStorage.cxx @@ -0,0 +1,423 @@ +/* + * 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 "NfsStorage.hxx" +#include "storage/StoragePlugin.hxx" +#include "storage/StorageInterface.hxx" +#include "storage/FileInfo.hxx" +#include "storage/MemoryDirectoryReader.hxx" +#include "lib/nfs/Blocking.hxx" +#include "lib/nfs/Domain.hxx" +#include "lib/nfs/Base.hxx" +#include "lib/nfs/Lease.hxx" +#include "lib/nfs/Connection.hxx" +#include "lib/nfs/Glue.hxx" +#include "fs/AllocatedPath.hxx" +#include "util/Error.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "event/Loop.hxx" +#include "event/Call.hxx" +#include "event/DeferredMonitor.hxx" +#include "event/TimeoutMonitor.hxx" + +extern "C" { +#include <nfsc/libnfs.h> +#include <nfsc/libnfs-raw-nfs.h> +} + +#include <string> + +#include <assert.h> +#include <sys/stat.h> +#include <fcntl.h> + +class NfsStorage final + : public Storage, NfsLease, DeferredMonitor, TimeoutMonitor { + + enum class State { + INITIAL, CONNECTING, READY, DELAY, + }; + + const std::string base; + + const std::string server, export_name; + + NfsConnection *connection; + + Mutex mutex; + Cond cond; + State state; + Error last_error; + +public: + NfsStorage(EventLoop &_loop, const char *_base, + std::string &&_server, std::string &&_export_name) + :DeferredMonitor(_loop), TimeoutMonitor(_loop), + base(_base), + server(std::move(_server)), + export_name(std::move(_export_name)), + state(State::INITIAL) { + nfs_init(); + } + + ~NfsStorage() { + BlockingCall(GetEventLoop(), [this](){ Disconnect(); }); + nfs_finish(); + } + + /* virtual methods from class Storage */ + bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info, + Error &error) override; + + StorageDirectoryReader *OpenDirectory(const char *uri_utf8, + Error &error) override; + + std::string MapUTF8(const char *uri_utf8) const override; + + const char *MapToRelativeUTF8(const char *uri_utf8) const override; + + /* virtual methods from NfsLease */ + void OnNfsConnectionReady() final { + assert(state == State::CONNECTING); + + SetState(State::READY); + } + + void OnNfsConnectionFailed(gcc_unused const Error &error) final { + assert(state == State::CONNECTING); + + SetState(State::DELAY, error); + TimeoutMonitor::ScheduleSeconds(60); + } + + void OnNfsConnectionDisconnected(gcc_unused const Error &error) final { + assert(state == State::READY); + + SetState(State::DELAY, error); + TimeoutMonitor::ScheduleSeconds(5); + } + + /* virtual methods from DeferredMonitor */ + void RunDeferred() final { + if (state == State::INITIAL) + Connect(); + } + + /* virtual methods from TimeoutMonitor */ + void OnTimeout() final { + assert(state == State::DELAY); + + Connect(); + } + +private: + EventLoop &GetEventLoop() { + return DeferredMonitor::GetEventLoop(); + } + + void SetState(State _state) { + assert(GetEventLoop().IsInside()); + + const ScopeLock protect(mutex); + state = _state; + cond.broadcast(); + } + + void SetState(State _state, const Error &error) { + assert(GetEventLoop().IsInside()); + + const ScopeLock protect(mutex); + state = _state; + last_error.Clear(); + last_error.Set(error); + cond.broadcast(); + } + + void Connect() { + assert(state != State::READY); + assert(GetEventLoop().IsInside()); + + connection = &nfs_get_connection(server.c_str(), + export_name.c_str()); + connection->AddLease(*this); + + SetState(State::CONNECTING); + } + + void EnsureConnected() { + if (state != State::READY) + Connect(); + } + + bool WaitConnected(Error &error) { + const ScopeLock protect(mutex); + + while (true) { + switch (state) { + case State::INITIAL: + /* schedule connect */ + mutex.unlock(); + DeferredMonitor::Schedule(); + mutex.lock(); + break; + + case State::CONNECTING: + case State::READY: + return true; + + case State::DELAY: + assert(last_error.IsDefined()); + error.Set(last_error); + return false; + } + + cond.wait(mutex); + } + } + + void Disconnect() { + assert(GetEventLoop().IsInside()); + + switch (state) { + case State::INITIAL: + DeferredMonitor::Cancel(); + break; + + case State::CONNECTING: + case State::READY: + connection->RemoveLease(*this); + SetState(State::INITIAL); + break; + + case State::DELAY: + TimeoutMonitor::Cancel(); + SetState(State::INITIAL); + break; + } + } +}; + +static std::string +UriToNfsPath(const char *_uri_utf8, Error &error) +{ + assert(_uri_utf8 != nullptr); + + /* libnfs paths must begin with a slash */ + std::string uri_utf8("/"); + uri_utf8.append(_uri_utf8); + + return AllocatedPath::FromUTF8(uri_utf8.c_str(), error).Steal(); +} + +std::string +NfsStorage::MapUTF8(const char *uri_utf8) const +{ + assert(uri_utf8 != nullptr); + + if (*uri_utf8 == 0) + return base; + + return PathTraitsUTF8::Build(base.c_str(), uri_utf8); +} + +const char * +NfsStorage::MapToRelativeUTF8(const char *uri_utf8) const +{ + return PathTraitsUTF8::Relative(base.c_str(), uri_utf8); +} + +static void +Copy(FileInfo &info, const struct stat &st) +{ + if (S_ISREG(st.st_mode)) + info.type = FileInfo::Type::REGULAR; + else if (S_ISDIR(st.st_mode)) + info.type = FileInfo::Type::DIRECTORY; + else + info.type = FileInfo::Type::OTHER; + + info.size = st.st_size; + info.mtime = st.st_mtime; + info.device = st.st_dev; + info.inode = st.st_ino; +} + +class NfsGetInfoOperation final : public BlockingNfsOperation { + const char *const path; + FileInfo &info; + +public: + NfsGetInfoOperation(NfsConnection &_connection, const char *_path, + FileInfo &_info) + :BlockingNfsOperation(_connection), path(_path), info(_info) {} + +protected: + bool Start(Error &_error) override { + return connection.Stat(path, *this, _error); + } + + void HandleResult(gcc_unused unsigned status, void *data) override { + Copy(info, *(const struct stat *)data); + } +}; + +bool +NfsStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow, + FileInfo &info, Error &error) +{ + const std::string path = UriToNfsPath(uri_utf8, error); + if (path.empty()) + return false; + + if (!WaitConnected(error)) + return false; + + NfsGetInfoOperation operation(*connection, path.c_str(), info); + return operation.Run(error); +} + +gcc_pure +static bool +SkipNameFS(const char *name) +{ + return name[0] == '.' && + (name[1] == 0 || + (name[1] == '.' && name[2] == 0)); +} + +static void +Copy(FileInfo &info, const struct nfsdirent &ent) +{ + switch (ent.type) { + case NF3REG: + info.type = FileInfo::Type::REGULAR; + break; + + case NF3DIR: + info.type = FileInfo::Type::DIRECTORY; + break; + + default: + info.type = FileInfo::Type::OTHER; + break; + } + + info.size = ent.size; + info.mtime = ent.mtime.tv_sec; + info.device = 0; + info.inode = ent.inode; +} + +class NfsListDirectoryOperation final : public BlockingNfsOperation { + const char *const path; + + MemoryStorageDirectoryReader::List entries; + +public: + NfsListDirectoryOperation(NfsConnection &_connection, + const char *_path) + :BlockingNfsOperation(_connection), path(_path) {} + + StorageDirectoryReader *ToReader() { + return new MemoryStorageDirectoryReader(std::move(entries)); + } + +protected: + bool Start(Error &_error) override { + return connection.OpenDirectory(path, *this, _error); + } + + void HandleResult(gcc_unused unsigned status, void *data) override { + struct nfsdir *const dir = (struct nfsdir *)data; + + CollectEntries(dir); + connection.CloseDirectory(dir); + } + +private: + void CollectEntries(struct nfsdir *dir); +}; + +inline void +NfsListDirectoryOperation::CollectEntries(struct nfsdir *dir) +{ + assert(entries.empty()); + + const struct nfsdirent *ent; + while ((ent = connection.ReadDirectory(dir)) != nullptr) { + const Path name_fs = Path::FromFS(ent->name); + if (SkipNameFS(name_fs.c_str())) + continue; + + std::string name_utf8 = name_fs.ToUTF8(); + if (name_utf8.empty()) + /* ignore files whose name cannot be converted + to UTF-8 */ + continue; + + entries.emplace_front(std::move(name_utf8)); + Copy(entries.front().info, *ent); + } +} + +StorageDirectoryReader * +NfsStorage::OpenDirectory(const char *uri_utf8, Error &error) +{ + const std::string path = UriToNfsPath(uri_utf8, error); + if (path.empty()) + return nullptr; + + if (!WaitConnected(error)) + return nullptr; + + NfsListDirectoryOperation operation(*connection, path.c_str()); + if (!operation.Run(error)) + return nullptr; + + return operation.ToReader(); +} + +static Storage * +CreateNfsStorageURI(EventLoop &event_loop, const char *base, + Error &error) +{ + if (memcmp(base, "nfs://", 6) != 0) + return nullptr; + + const char *p = base + 6; + + const char *mount = strchr(p, '/'); + if (mount == nullptr) { + error.Set(nfs_domain, "Malformed nfs:// URI"); + return nullptr; + } + + const std::string server(p, mount); + + nfs_set_base(server.c_str(), mount); + + return new NfsStorage(event_loop, base, server.c_str(), mount); +} + +const StoragePlugin nfs_storage_plugin = { + "nfs", + CreateNfsStorageURI, +}; diff --git a/src/storage/plugins/NfsStorage.hxx b/src/storage/plugins/NfsStorage.hxx new file mode 100644 index 000000000..f7e18effc --- /dev/null +++ b/src/storage/plugins/NfsStorage.hxx @@ -0,0 +1,29 @@ +/* + * 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_STORAGE_NFS_HXX +#define MPD_STORAGE_NFS_HXX + +#include "check.h" + +struct StoragePlugin; + +extern const StoragePlugin nfs_storage_plugin; + +#endif diff --git a/src/storage/plugins/SmbclientStorage.cxx b/src/storage/plugins/SmbclientStorage.cxx new file mode 100644 index 000000000..70a6e16bb --- /dev/null +++ b/src/storage/plugins/SmbclientStorage.cxx @@ -0,0 +1,212 @@ +/* + * 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 "SmbclientStorage.hxx" +#include "storage/StoragePlugin.hxx" +#include "storage/StorageInterface.hxx" +#include "storage/FileInfo.hxx" +#include "lib/smbclient/Init.hxx" +#include "lib/smbclient/Mutex.hxx" +#include "fs/Traits.hxx" +#include "util/Error.hxx" +#include "thread/Mutex.hxx" + +#include <libsmbclient.h> + +class SmbclientDirectoryReader final : public StorageDirectoryReader { + const std::string base; + const unsigned handle; + + const char *name; + +public: + SmbclientDirectoryReader(std::string &&_base, unsigned _handle) + :base(std::move(_base)), handle(_handle) {} + + virtual ~SmbclientDirectoryReader(); + + /* virtual methods from class StorageDirectoryReader */ + const char *Read() override; + bool GetInfo(bool follow, FileInfo &info, Error &error) override; +}; + +class SmbclientStorage final : public Storage { + const std::string base; + + SMBCCTX *const ctx; + +public: + SmbclientStorage(const char *_base, SMBCCTX *_ctx) + :base(_base), ctx(_ctx) {} + + virtual ~SmbclientStorage() { + smbclient_mutex.lock(); + smbc_free_context(ctx, 1); + smbclient_mutex.unlock(); + } + + /* virtual methods from class Storage */ + bool GetInfo(const char *uri_utf8, bool follow, FileInfo &info, + Error &error) override; + + StorageDirectoryReader *OpenDirectory(const char *uri_utf8, + Error &error) override; + + std::string MapUTF8(const char *uri_utf8) const override; + + const char *MapToRelativeUTF8(const char *uri_utf8) const override; +}; + +std::string +SmbclientStorage::MapUTF8(const char *uri_utf8) const +{ + assert(uri_utf8 != nullptr); + + if (*uri_utf8 == 0) + return base; + + return PathTraitsUTF8::Build(base.c_str(), uri_utf8); +} + +const char * +SmbclientStorage::MapToRelativeUTF8(const char *uri_utf8) const +{ + return PathTraitsUTF8::Relative(base.c_str(), uri_utf8); +} + +static bool +GetInfo(const char *path, FileInfo &info, Error &error) +{ + struct stat st; + smbclient_mutex.lock(); + bool success = smbc_stat(path, &st) == 0; + smbclient_mutex.unlock(); + if (!success) { + error.SetErrno(); + return false; + } + + if (S_ISREG(st.st_mode)) + info.type = FileInfo::Type::REGULAR; + else if (S_ISDIR(st.st_mode)) + info.type = FileInfo::Type::DIRECTORY; + else + info.type = FileInfo::Type::OTHER; + + info.size = st.st_size; + info.mtime = st.st_mtime; + info.device = st.st_dev; + info.inode = st.st_ino; + return true; +} + +bool +SmbclientStorage::GetInfo(const char *uri_utf8, gcc_unused bool follow, + FileInfo &info, Error &error) +{ + const std::string mapped = MapUTF8(uri_utf8); + return ::GetInfo(mapped.c_str(), info, error); +} + +StorageDirectoryReader * +SmbclientStorage::OpenDirectory(const char *uri_utf8, Error &error) +{ + std::string mapped = MapUTF8(uri_utf8); + smbclient_mutex.lock(); + int handle = smbc_opendir(mapped.c_str()); + smbclient_mutex.unlock(); + if (handle < 0) { + error.SetErrno(); + return nullptr; + } + + return new SmbclientDirectoryReader(std::move(mapped.c_str()), handle); +} + +gcc_pure +static bool +SkipNameFS(const char *name) +{ + return name[0] == '.' && + (name[1] == 0 || + (name[1] == '.' && name[2] == 0)); +} + +SmbclientDirectoryReader::~SmbclientDirectoryReader() +{ + smbclient_mutex.lock(); + smbc_close(handle); + smbclient_mutex.unlock(); +} + +const char * +SmbclientDirectoryReader::Read() +{ + const ScopeLock protect(smbclient_mutex); + + struct smbc_dirent *e; + while ((e = smbc_readdir(handle)) != nullptr) { + name = e->name; + if (!SkipNameFS(name)) + return name; + } + + return nullptr; +} + +bool +SmbclientDirectoryReader::GetInfo(gcc_unused bool follow, FileInfo &info, + Error &error) +{ + const std::string path = PathTraitsUTF8::Build(base.c_str(), name); + return ::GetInfo(path.c_str(), info, error); +} + +static Storage * +CreateSmbclientStorageURI(gcc_unused EventLoop &event_loop, const char *base, + Error &error) +{ + if (memcmp(base, "smb://", 6) != 0) + return nullptr; + + if (!SmbclientInit(error)) + return nullptr; + + const ScopeLock protect(smbclient_mutex); + SMBCCTX *ctx = smbc_new_context(); + if (ctx == nullptr) { + error.SetErrno("smbc_new_context() failed"); + return nullptr; + } + + SMBCCTX *ctx2 = smbc_init_context(ctx); + if (ctx2 == nullptr) { + error.SetErrno("smbc_init_context() failed"); + smbc_free_context(ctx, 1); + return nullptr; + } + + return new SmbclientStorage(base, ctx2); +} + +const StoragePlugin smbclient_storage_plugin = { + "smbclient", + CreateSmbclientStorageURI, +}; diff --git a/src/storage/plugins/SmbclientStorage.hxx b/src/storage/plugins/SmbclientStorage.hxx new file mode 100644 index 000000000..7c198d920 --- /dev/null +++ b/src/storage/plugins/SmbclientStorage.hxx @@ -0,0 +1,29 @@ +/* + * 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_STORAGE_SMBCLIENT_HXX +#define MPD_STORAGE_SMBCLIENT_HXX + +#include "check.h" + +struct StoragePlugin; + +extern const StoragePlugin smbclient_storage_plugin; + +#endif |