diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib/nfs/Base.cxx | 61 | ||||
-rw-r--r-- | src/lib/nfs/Base.hxx | 46 | ||||
-rw-r--r-- | src/lib/nfs/Blocking.cxx | 84 | ||||
-rw-r--r-- | src/lib/nfs/Blocking.hxx | 85 | ||||
-rw-r--r-- | src/lib/nfs/Callback.hxx | 33 | ||||
-rw-r--r-- | src/lib/nfs/Cancellable.hxx | 162 | ||||
-rw-r--r-- | src/lib/nfs/Connection.cxx | 547 | ||||
-rw-r--r-- | src/lib/nfs/Connection.hxx | 210 | ||||
-rw-r--r-- | src/lib/nfs/Domain.cxx | 24 | ||||
-rw-r--r-- | src/lib/nfs/Domain.hxx | 27 | ||||
-rw-r--r-- | src/lib/nfs/FileReader.cxx | 261 | ||||
-rw-r--r-- | src/lib/nfs/FileReader.hxx | 94 | ||||
-rw-r--r-- | src/lib/nfs/Glue.cxx | 59 | ||||
-rw-r--r-- | src/lib/nfs/Glue.hxx | 38 | ||||
-rw-r--r-- | src/lib/nfs/Lease.hxx | 48 | ||||
-rw-r--r-- | src/lib/nfs/Manager.cxx | 87 | ||||
-rw-r--r-- | src/lib/nfs/Manager.hxx | 91 |
17 files changed, 1957 insertions, 0 deletions
diff --git a/src/lib/nfs/Base.cxx b/src/lib/nfs/Base.cxx new file mode 100644 index 000000000..3004cd11b --- /dev/null +++ b/src/lib/nfs/Base.cxx @@ -0,0 +1,61 @@ +/* + * 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 "Base.hxx" + +#include <assert.h> +#include <string.h> + +static char nfs_base_server[64]; +static char nfs_base_export_name[256]; +static size_t nfs_base_export_name_length; + +void +nfs_set_base(const char *server, const char *export_name) +{ + assert(server != nullptr); + assert(export_name != nullptr); + + const size_t server_length = strlen(server); + const size_t export_name_length = strlen(export_name); + + if (server_length >= sizeof(nfs_base_server) || + export_name_length > sizeof(nfs_base_export_name)) + return; + + memcpy(nfs_base_server, server, server_length + 1); + memcpy(nfs_base_export_name, export_name, export_name_length); + nfs_base_export_name_length = export_name_length; +} + +const char * +nfs_check_base(const char *server, const char *path) +{ + assert(server != nullptr); + assert(path != nullptr); + + return strcmp(nfs_base_server, server) == 0 && + memcmp(nfs_base_export_name, path, + nfs_base_export_name_length) == 0 && + (path[nfs_base_export_name_length] == 0 || + path[nfs_base_export_name_length] == '/') + ? path + nfs_base_export_name_length + : nullptr; +} diff --git a/src/lib/nfs/Base.hxx b/src/lib/nfs/Base.hxx new file mode 100644 index 000000000..3a92a86d3 --- /dev/null +++ b/src/lib/nfs/Base.hxx @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NFS_BASE_HXX +#define MPD_NFS_BASE_HXX + +#include "check.h" +#include "Compiler.h" + +/** + * Set the "base" NFS server and export name. This will be the + * default export that will be mounted if a file within this export is + * being opened, instead of guessing the mount point. + * + * This is a kludge that is not truly thread-safe. + */ +void +nfs_set_base(const char *server, const char *export_name); + +/** + * Check if the given server and path are inside the "base" + * server/export_name. If yes, then a pointer to the portion of + * "path" after the export_name is returned; otherwise, nullptr is + * returned. + */ +gcc_pure +const char * +nfs_check_base(const char *server, const char *path); + +#endif diff --git a/src/lib/nfs/Blocking.cxx b/src/lib/nfs/Blocking.cxx new file mode 100644 index 000000000..5f769c408 --- /dev/null +++ b/src/lib/nfs/Blocking.cxx @@ -0,0 +1,84 @@ +/* + * 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 "Blocking.hxx" +#include "Connection.hxx" +#include "event/Call.hxx" + +bool +BlockingNfsOperation::Run(Error &_error) +{ + /* subscribe to the connection, which will invoke either + OnNfsConnectionReady() or OnNfsConnectionFailed() */ + BlockingCall(connection.GetEventLoop(), + [this](){ connection.AddLease(*this); }); + + /* wait for completion */ + LockWaitFinished(); + + /* check for error */ + if (error.IsDefined()) { + _error = std::move(error); + return false; + } + + return true; +} + +void +BlockingNfsOperation::OnNfsConnectionReady() +{ + if (!Start(error)) { + connection.RemoveLease(*this); + LockSetFinished(); + } +} + +void +BlockingNfsOperation::OnNfsConnectionFailed(const Error &_error) +{ + error.Set(_error); + LockSetFinished(); +} + +void +BlockingNfsOperation::OnNfsConnectionDisconnected(const Error &_error) +{ + error.Set(_error); + LockSetFinished(); +} + +void +BlockingNfsOperation::OnNfsCallback(unsigned status, void *data) +{ + connection.RemoveLease(*this); + + HandleResult(status, data); + LockSetFinished(); +} + +void +BlockingNfsOperation::OnNfsError(Error &&_error) +{ + connection.RemoveLease(*this); + + error = std::move(_error); + LockSetFinished(); +} diff --git a/src/lib/nfs/Blocking.hxx b/src/lib/nfs/Blocking.hxx new file mode 100644 index 000000000..f8354822d --- /dev/null +++ b/src/lib/nfs/Blocking.hxx @@ -0,0 +1,85 @@ +/* + * 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_BLOCKING_NFS_CALLBACK_HXX +#define MPD_BLOCKING_NFS_CALLBACK_HXX + +#include "check.h" +#include "Callback.hxx" +#include "Lease.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "util/Error.hxx" + +class NfsConnection; + +/** + * Utility class to implement a blocking NFS call using the libnfs + * async API. The actual method call is deferred to the #EventLoop + * thread, and method Run() waits for completion. + */ +class BlockingNfsOperation : protected NfsCallback, NfsLease { + Mutex mutex; + Cond cond; + + bool finished; + + Error error; + +protected: + NfsConnection &connection; + +public: + BlockingNfsOperation(NfsConnection &_connection) + :finished(false), connection(_connection) {} + + bool Run(Error &error); + +private: + void LockWaitFinished() { + const ScopeLock protect(mutex); + while (!finished) + cond.wait(mutex); + } + + /** + * Mark the operation as "finished" and wake up the waiting + * thread. + */ + void LockSetFinished() { + const ScopeLock protect(mutex); + finished = true; + cond.signal(); + } + + /* virtual methods from NfsLease */ + void OnNfsConnectionReady() final; + void OnNfsConnectionFailed(const Error &error) final; + void OnNfsConnectionDisconnected(const Error &error) final; + + /* virtual methods from NfsCallback */ + void OnNfsCallback(unsigned status, void *data) final; + void OnNfsError(Error &&error) final; + +protected: + virtual bool Start(Error &error) = 0; + virtual void HandleResult(unsigned status, void *data) = 0; +}; + +#endif diff --git a/src/lib/nfs/Callback.hxx b/src/lib/nfs/Callback.hxx new file mode 100644 index 000000000..ae82ecc3c --- /dev/null +++ b/src/lib/nfs/Callback.hxx @@ -0,0 +1,33 @@ +/* + * 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_NFS_CALLBACK_HXX +#define MPD_NFS_CALLBACK_HXX + +#include "check.h" + +class Error; + +class NfsCallback { +public: + virtual void OnNfsCallback(unsigned status, void *data) = 0; + virtual void OnNfsError(Error &&error) = 0; +}; + +#endif diff --git a/src/lib/nfs/Cancellable.hxx b/src/lib/nfs/Cancellable.hxx new file mode 100644 index 000000000..be4527ac3 --- /dev/null +++ b/src/lib/nfs/Cancellable.hxx @@ -0,0 +1,162 @@ +/* + * 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_NFS_CANCELLABLE_HXX +#define MPD_NFS_CANCELLABLE_HXX + +#include "Compiler.h" + +#include <boost/intrusive/list.hpp> + +#include <algorithm> + +#include <assert.h> + +template<typename T> +class CancellablePointer + : public boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> { +public: + typedef T *pointer_type; + typedef T &reference_type; + typedef const T &const_reference_type; + +private: + pointer_type p; + +public: + explicit CancellablePointer(reference_type _p):p(&_p) {} + + CancellablePointer(const CancellablePointer &) = delete; + + constexpr bool IsCancelled() const { + return p == nullptr; + } + + void Cancel() { + assert(!IsCancelled()); + + p = nullptr; + } + + reference_type Get() { + assert(p != nullptr); + + return *p; + } + + constexpr bool Is(const_reference_type other) const { + return p == &other; + } +}; + +template<typename T, typename CT=CancellablePointer<T>> +class CancellableList { +public: + typedef typename CT::reference_type reference_type; + typedef typename CT::const_reference_type const_reference_type; + +private: + typedef boost::intrusive::list<CT, + boost::intrusive::constant_time_size<false>> List; + typedef typename List::iterator iterator; + typedef typename List::const_iterator const_iterator; + List list; + + class MatchPointer { + const_reference_type p; + + public: + explicit constexpr MatchPointer(const_reference_type _p) + :p(_p) {} + + constexpr bool operator()(const CT &a) const { + return a.Is(p); + } + }; + + gcc_pure + iterator Find(reference_type p) { + return std::find_if(list.begin(), list.end(), MatchPointer(p)); + } + + gcc_pure + const_iterator Find(const_reference_type p) const { + return std::find_if(list.begin(), list.end(), MatchPointer(p)); + } + + gcc_pure + iterator Find(CT &c) { + return list.iterator_to(c); + } + + gcc_pure + const_iterator Find(const CT &c) const { + return list.iterator_to(c); + } + +public: +#ifndef NDEBUG + gcc_pure + bool IsEmpty() const { + for (const auto &c : list) + if (!c.IsCancelled()) + return false; + + return true; + } +#endif + + gcc_pure + bool Contains(const_reference_type p) const { + return Find(p) != list.end(); + } + + template<typename... Args> + CT &Add(reference_type p, Args&&... args) { + assert(Find(p) == list.end()); + + CT *c = new CT(p, std::forward<Args>(args)...); + list.push_back(*c); + return *c; + } + + void Remove(CT &ct) { + auto i = Find(ct); + assert(i != list.end()); + + list.erase(i); + delete &ct; + } + + void Cancel(reference_type p) { + auto i = Find(p); + assert(i != list.end()); + + i->Cancel(); + } + + CT &Get(reference_type p) { + auto i = Find(p); + assert(i != list.end()); + + return *i; + } +}; + +#endif diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx new file mode 100644 index 000000000..c2c7ceb2b --- /dev/null +++ b/src/lib/nfs/Connection.cxx @@ -0,0 +1,547 @@ +/* + * 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 "Connection.hxx" +#include "Lease.hxx" +#include "Domain.hxx" +#include "Callback.hxx" +#include "event/Loop.hxx" +#include "system/fd_util.h" +#include "util/Error.hxx" + +extern "C" { +#include <nfsc/libnfs.h> +} + +#include <utility> + +#include <poll.h> /* for POLLIN, POLLOUT */ + +inline bool +NfsConnection::CancellableCallback::Stat(nfs_context *ctx, + const char *path, + Error &error) +{ + int result = nfs_stat_async(ctx, path, Callback, this); + if (result < 0) { + error.Format(nfs_domain, "nfs_stat_async() failed: %s", + nfs_get_error(ctx)); + return false; + } + + return true; +} + +inline bool +NfsConnection::CancellableCallback::OpenDirectory(nfs_context *ctx, + const char *path, + Error &error) +{ + int result = nfs_opendir_async(ctx, path, Callback, this); + if (result < 0) { + error.Format(nfs_domain, "nfs_opendir_async() failed: %s", + nfs_get_error(ctx)); + return false; + } + + return true; +} + +inline bool +NfsConnection::CancellableCallback::Open(nfs_context *ctx, + const char *path, int flags, + Error &error) +{ + int result = nfs_open_async(ctx, path, flags, + Callback, this); + if (result < 0) { + error.Format(nfs_domain, "nfs_open_async() failed: %s", + nfs_get_error(ctx)); + return false; + } + + return true; +} + +inline bool +NfsConnection::CancellableCallback::Stat(nfs_context *ctx, + struct nfsfh *fh, + Error &error) +{ + int result = nfs_fstat_async(ctx, fh, Callback, this); + if (result < 0) { + error.Format(nfs_domain, "nfs_fstat_async() failed: %s", + nfs_get_error(ctx)); + return false; + } + + return true; +} + +inline bool +NfsConnection::CancellableCallback::Read(nfs_context *ctx, struct nfsfh *fh, + uint64_t offset, size_t size, + Error &error) +{ + int result = nfs_pread_async(ctx, fh, offset, size, Callback, this); + if (result < 0) { + error.Format(nfs_domain, "nfs_pread_async() failed: %s", + nfs_get_error(ctx)); + return false; + } + + return true; +} + +inline void +NfsConnection::CancellableCallback::CancelAndScheduleClose(struct nfsfh *fh) +{ + assert(!open); + assert(close_fh == nullptr); + assert(fh != nullptr); + + close_fh = fh; + Cancel(); +} + +inline void +NfsConnection::CancellableCallback::Callback(int err, void *data) +{ + if (!IsCancelled()) { + assert(close_fh == nullptr); + + NfsCallback &cb = Get(); + + connection.callbacks.Remove(*this); + + if (err >= 0) + cb.OnNfsCallback((unsigned)err, data); + else + cb.OnNfsError(Error(nfs_domain, err, + (const char *)data)); + } else { + if (open) { + /* a nfs_open_async() call was cancelled - to + avoid a memory leak, close the newly + allocated file handle immediately */ + assert(close_fh == nullptr); + + struct nfsfh *fh = (struct nfsfh *)data; + connection.Close(fh); + } else if (close_fh != nullptr) + connection.DeferClose(close_fh); + + connection.callbacks.Remove(*this); + } +} + +void +NfsConnection::CancellableCallback::Callback(int err, + gcc_unused struct nfs_context *nfs, + void *data, void *private_data) +{ + CancellableCallback &c = *(CancellableCallback *)private_data; + c.Callback(err, data); +} + +static constexpr unsigned +libnfs_to_events(int i) +{ + return ((i & POLLIN) ? SocketMonitor::READ : 0) | + ((i & POLLOUT) ? SocketMonitor::WRITE : 0); +} + +static constexpr int +events_to_libnfs(unsigned i) +{ + return ((i & SocketMonitor::READ) ? POLLIN : 0) | + ((i & SocketMonitor::WRITE) ? POLLOUT : 0); +} + +NfsConnection::~NfsConnection() +{ + assert(GetEventLoop().IsInside()); + assert(new_leases.empty()); + assert(active_leases.empty()); + assert(callbacks.IsEmpty()); + assert(deferred_close.empty()); + + if (context != nullptr) + DestroyContext(); +} + +void +NfsConnection::AddLease(NfsLease &lease) +{ + assert(GetEventLoop().IsInside()); + + new_leases.push_back(&lease); + + DeferredMonitor::Schedule(); +} + +void +NfsConnection::RemoveLease(NfsLease &lease) +{ + assert(GetEventLoop().IsInside()); + + new_leases.remove(&lease); + active_leases.remove(&lease); +} + +bool +NfsConnection::Stat(const char *path, NfsCallback &callback, Error &error) +{ + assert(!callbacks.Contains(callback)); + + auto &c = callbacks.Add(callback, *this, false); + if (!c.Stat(context, path, error)) { + callbacks.Remove(c); + return false; + } + + ScheduleSocket(); + return true; +} + +bool +NfsConnection::OpenDirectory(const char *path, NfsCallback &callback, + Error &error) +{ + assert(!callbacks.Contains(callback)); + + auto &c = callbacks.Add(callback, *this, true); + if (!c.OpenDirectory(context, path, error)) { + callbacks.Remove(c); + return false; + } + + ScheduleSocket(); + return true; +} + +const struct nfsdirent * +NfsConnection::ReadDirectory(struct nfsdir *dir) +{ + return nfs_readdir(context, dir); +} + +void +NfsConnection::CloseDirectory(struct nfsdir *dir) +{ + return nfs_closedir(context, dir); +} + +bool +NfsConnection::Open(const char *path, int flags, NfsCallback &callback, + Error &error) +{ + assert(!callbacks.Contains(callback)); + + auto &c = callbacks.Add(callback, *this, true); + if (!c.Open(context, path, flags, error)) { + callbacks.Remove(c); + return false; + } + + ScheduleSocket(); + return true; +} + +bool +NfsConnection::Stat(struct nfsfh *fh, NfsCallback &callback, Error &error) +{ + assert(!callbacks.Contains(callback)); + + auto &c = callbacks.Add(callback, *this, false); + if (!c.Stat(context, fh, error)) { + callbacks.Remove(c); + return false; + } + + ScheduleSocket(); + return true; +} + +bool +NfsConnection::Read(struct nfsfh *fh, uint64_t offset, size_t size, + NfsCallback &callback, Error &error) +{ + assert(!callbacks.Contains(callback)); + + auto &c = callbacks.Add(callback, *this, false); + if (!c.Read(context, fh, offset, size, error)) { + callbacks.Remove(c); + return false; + } + + ScheduleSocket(); + return true; +} + +void +NfsConnection::Cancel(NfsCallback &callback) +{ + callbacks.Cancel(callback); +} + +static void +DummyCallback(int, struct nfs_context *, void *, void *) +{ +} + +void +NfsConnection::Close(struct nfsfh *fh) +{ + nfs_close_async(context, fh, DummyCallback, nullptr); + ScheduleSocket(); +} + +void +NfsConnection::CancelAndClose(struct nfsfh *fh, NfsCallback &callback) +{ + CancellableCallback &cancel = callbacks.Get(callback); + cancel.CancelAndScheduleClose(fh); +} + +void +NfsConnection::DestroyContext() +{ + assert(GetEventLoop().IsInside()); + assert(context != nullptr); + + if (SocketMonitor::IsDefined()) + SocketMonitor::Cancel(); + + nfs_destroy_context(context); + context = nullptr; +} + +inline void +NfsConnection::DeferClose(struct nfsfh *fh) +{ + assert(in_event); + assert(in_service); + + deferred_close.push_front(fh); +} + +void +NfsConnection::ScheduleSocket() +{ + assert(context != nullptr); + + if (!SocketMonitor::IsDefined()) { + int _fd = nfs_get_fd(context); + if (_fd < 0) + return; + + fd_set_cloexec(_fd, true); + SocketMonitor::Open(_fd); + } + + SocketMonitor::Schedule(libnfs_to_events(nfs_which_events(context))); +} + +bool +NfsConnection::OnSocketReady(unsigned flags) +{ + assert(deferred_close.empty()); + + bool closed = false; + + const bool was_mounted = mount_finished; + if (!mount_finished) + /* until the mount is finished, the NFS client may use + various sockets, therefore we unregister and + re-register it each time */ + SocketMonitor::Steal(); + + assert(!in_event); + in_event = true; + + assert(!in_service); + in_service = true; + + int result = nfs_service(context, events_to_libnfs(flags)); + + assert(context != nullptr); + assert(in_service); + in_service = false; + + while (!deferred_close.empty()) { + nfs_close_async(context, deferred_close.front(), + DummyCallback, nullptr); + deferred_close.pop_front(); + } + + if (!was_mounted && mount_finished) { + if (postponed_mount_error.IsDefined()) { + DestroyContext(); + closed = true; + BroadcastMountError(std::move(postponed_mount_error)); + } else if (result == 0) + BroadcastMountSuccess(); + } else if (result < 0) { + /* the connection has failed */ + Error error; + error.Format(nfs_domain, "NFS connection has failed: %s", + nfs_get_error(context)); + + DestroyContext(); + closed = true; + + BroadcastError(std::move(error)); + } else if (SocketMonitor::IsDefined() && nfs_get_fd(context) < 0) { + /* this happens when rpc_reconnect_requeue() is called + after the connection broke, but autoreconnet was + disabled - nfs_service() returns 0 */ + Error error; + const char *msg = nfs_get_error(context); + if (msg == nullptr) + error.Set(nfs_domain, "NFS socket disappeared"); + else + error.Format(nfs_domain, + "NFS socket disappeared: %s", msg); + + DestroyContext(); + closed = true; + + BroadcastError(std::move(error)); + } + + assert(in_event); + in_event = false; + + if (context != nullptr) + ScheduleSocket(); + + return !closed; +} + +inline void +NfsConnection::MountCallback(int status, gcc_unused nfs_context *nfs, + gcc_unused void *data) +{ + assert(context == nfs); + + mount_finished = true; + + if (status < 0) { + postponed_mount_error.Format(nfs_domain, status, + "nfs_mount_async() failed: %s", + nfs_get_error(context)); + return; + } +} + +void +NfsConnection::MountCallback(int status, nfs_context *nfs, void *data, + void *private_data) +{ + NfsConnection *c = (NfsConnection *)private_data; + + c->MountCallback(status, nfs, data); +} + +inline bool +NfsConnection::MountInternal(Error &error) +{ + assert(context == nullptr); + + context = nfs_init_context(); + if (context == nullptr) { + error.Set(nfs_domain, "nfs_init_context() failed"); + return false; + } + + postponed_mount_error.Clear(); + mount_finished = false; + in_service = false; + in_event = false; + + if (nfs_mount_async(context, server.c_str(), export_name.c_str(), + MountCallback, this) != 0) { + error.Format(nfs_domain, + "nfs_mount_async() failed: %s", + nfs_get_error(context)); + nfs_destroy_context(context); + context = nullptr; + return false; + } + + ScheduleSocket(); + return true; +} + +void +NfsConnection::BroadcastMountSuccess() +{ + assert(GetEventLoop().IsInside()); + + while (!new_leases.empty()) { + auto i = new_leases.begin(); + active_leases.splice(active_leases.end(), new_leases, i); + (*i)->OnNfsConnectionReady(); + } +} + +void +NfsConnection::BroadcastMountError(Error &&error) +{ + assert(GetEventLoop().IsInside()); + + while (!new_leases.empty()) { + auto l = new_leases.front(); + new_leases.pop_front(); + l->OnNfsConnectionFailed(error); + } + + OnNfsConnectionError(std::move(error)); +} + +void +NfsConnection::BroadcastError(Error &&error) +{ + assert(GetEventLoop().IsInside()); + + while (!active_leases.empty()) { + auto l = active_leases.front(); + active_leases.pop_front(); + l->OnNfsConnectionDisconnected(error); + } + + BroadcastMountError(std::move(error)); +} + +void +NfsConnection::RunDeferred() +{ + if (context == nullptr) { + Error error; + if (!MountInternal(error)) { + BroadcastMountError(std::move(error)); + return; + } + } + + if (mount_finished) + BroadcastMountSuccess(); +} diff --git a/src/lib/nfs/Connection.hxx b/src/lib/nfs/Connection.hxx new file mode 100644 index 000000000..aebafd4d3 --- /dev/null +++ b/src/lib/nfs/Connection.hxx @@ -0,0 +1,210 @@ +/* + * 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_NFS_CONNECTION_HXX +#define MPD_NFS_CONNECTION_HXX + +#include "Lease.hxx" +#include "Cancellable.hxx" +#include "event/SocketMonitor.hxx" +#include "event/DeferredMonitor.hxx" +#include "util/Error.hxx" + +#include <boost/intrusive/list.hpp> + +#include <string> +#include <list> +#include <forward_list> + +struct nfs_context; +struct nfsdir; +struct nfsdirent; +class NfsCallback; + +/** + * An asynchronous connection to a NFS server. + */ +class NfsConnection : SocketMonitor, DeferredMonitor { + class CancellableCallback : public CancellablePointer<NfsCallback> { + NfsConnection &connection; + + /** + * Is this a nfs_open_async() operation? If yes, then + * we need to call nfs_close_async() on the new file + * handle as soon as the callback is invoked + * successfully. + */ + const bool open; + + /** + * The file handle scheduled to be closed as soon as + * the operation finishes. + */ + struct nfsfh *close_fh; + + public: + explicit CancellableCallback(NfsCallback &_callback, + NfsConnection &_connection, + bool _open) + :CancellablePointer<NfsCallback>(_callback), + connection(_connection), + open(_open), close_fh(nullptr) {} + + bool Stat(nfs_context *context, const char *path, + Error &error); + bool OpenDirectory(nfs_context *context, const char *path, + Error &error); + bool Open(nfs_context *context, const char *path, int flags, + Error &error); + bool Stat(nfs_context *context, struct nfsfh *fh, + Error &error); + bool Read(nfs_context *context, struct nfsfh *fh, + uint64_t offset, size_t size, + Error &error); + + /** + * Cancel the operation and schedule a call to + * nfs_close_async() with the given file handle. + */ + void CancelAndScheduleClose(struct nfsfh *fh); + + private: + static void Callback(int err, struct nfs_context *nfs, + void *data, void *private_data); + void Callback(int err, void *data); + }; + + std::string server, export_name; + + nfs_context *context; + + typedef std::list<NfsLease *> LeaseList; + LeaseList new_leases, active_leases; + + typedef CancellableList<NfsCallback, CancellableCallback> CallbackList; + CallbackList callbacks; + + /** + * A list of NFS file handles (struct nfsfh *) which shall be + * closed as soon as nfs_service() returns. If we close the + * file handle while in nfs_service(), libnfs may crash, and + * deferring this call to after nfs_service() avoids this + * problem. + */ + std::forward_list<struct nfsfh *> deferred_close; + + Error postponed_mount_error; + + /** + * True when nfs_service() is being called. + */ + bool in_service; + + /** + * True when OnSocketReady() is being called. During that, + * event updates are omitted. + */ + bool in_event; + + bool mount_finished; + +public: + gcc_nonnull_all + NfsConnection(EventLoop &_loop, + const char *_server, const char *_export_name) + :SocketMonitor(_loop), DeferredMonitor(_loop), + server(_server), export_name(_export_name), + context(nullptr) {} + + /** + * Must be run from EventLoop's thread. + */ + ~NfsConnection(); + + gcc_pure + const char *GetServer() const { + return server.c_str(); + } + + gcc_pure + const char *GetExportName() const { + return export_name.c_str(); + } + + EventLoop &GetEventLoop() { + return SocketMonitor::GetEventLoop(); + } + + /** + * Ensure that the connection is established. The connection + * is kept up while at least one #NfsLease is registered. + * + * This method is thread-safe. However, #NfsLease's methods + * will be invoked from within the #EventLoop's thread. + */ + void AddLease(NfsLease &lease); + void RemoveLease(NfsLease &lease); + + bool Stat(const char *path, NfsCallback &callback, Error &error); + + bool OpenDirectory(const char *path, NfsCallback &callback, + Error &error); + const struct nfsdirent *ReadDirectory(struct nfsdir *dir); + void CloseDirectory(struct nfsdir *dir); + + bool Open(const char *path, int flags, NfsCallback &callback, + Error &error); + bool Stat(struct nfsfh *fh, NfsCallback &callback, Error &error); + bool Read(struct nfsfh *fh, uint64_t offset, size_t size, + NfsCallback &callback, Error &error); + void Cancel(NfsCallback &callback); + + void Close(struct nfsfh *fh); + void CancelAndClose(struct nfsfh *fh, NfsCallback &callback); + +protected: + virtual void OnNfsConnectionError(Error &&error) = 0; + +private: + void DestroyContext(); + + /** + * Invoke nfs_close_async() after nfs_service() returns. + */ + void DeferClose(struct nfsfh *fh); + + bool MountInternal(Error &error); + void BroadcastMountSuccess(); + void BroadcastMountError(Error &&error); + void BroadcastError(Error &&error); + + static void MountCallback(int status, nfs_context *nfs, void *data, + void *private_data); + void MountCallback(int status, nfs_context *nfs, void *data); + + void ScheduleSocket(); + + /* virtual methods from SocketMonitor */ + virtual bool OnSocketReady(unsigned flags) override; + + /* virtual methods from DeferredMonitor */ + virtual void RunDeferred() override; +}; + +#endif diff --git a/src/lib/nfs/Domain.cxx b/src/lib/nfs/Domain.cxx new file mode 100644 index 000000000..fefe0dbf3 --- /dev/null +++ b/src/lib/nfs/Domain.cxx @@ -0,0 +1,24 @@ +/* + * 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 "Domain.hxx" +#include "util/Domain.hxx" + +const Domain nfs_domain("nfs"); diff --git a/src/lib/nfs/Domain.hxx b/src/lib/nfs/Domain.hxx new file mode 100644 index 000000000..6730b92e1 --- /dev/null +++ b/src/lib/nfs/Domain.hxx @@ -0,0 +1,27 @@ +/* + * 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_NFS_DOMAIN_HXX +#define MPD_NFS_DOMAIN_HXX + +class Domain; + +extern const Domain nfs_domain; + +#endif diff --git a/src/lib/nfs/FileReader.cxx b/src/lib/nfs/FileReader.cxx new file mode 100644 index 000000000..4837e1f0e --- /dev/null +++ b/src/lib/nfs/FileReader.cxx @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "FileReader.hxx" +#include "Glue.hxx" +#include "Base.hxx" +#include "Connection.hxx" +#include "Domain.hxx" +#include "event/Call.hxx" +#include "IOThread.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" + +#include <utility> + +#include <assert.h> +#include <string.h> +#include <fcntl.h> + +NfsFileReader::NfsFileReader() + :DeferredMonitor(io_thread_get()), state(State::INITIAL) +{ +} + +NfsFileReader::~NfsFileReader() +{ + assert(state == State::INITIAL); +} + +void +NfsFileReader::Close() +{ + if (state == State::INITIAL) + return; + + if (state == State::DEFER) { + state = State::INITIAL; + DeferredMonitor::Cancel(); + return; + } + + connection->RemoveLease(*this); + + if (state == State::IDLE) + /* no async operation in progress: can close + immediately */ + connection->Close(fh); + else if (state > State::OPEN) + /* one async operation in progress: cancel it and + defer the nfs_close_async() call */ + connection->CancelAndClose(fh, *this); + else if (state > State::MOUNT) + /* we don't have a file handle yet - just cancel the + async operation */ + connection->Cancel(*this); + + state = State::INITIAL; +} + +void +NfsFileReader::DeferClose() +{ + BlockingCall(io_thread_get(), [this](){ Close(); }); +} + +bool +NfsFileReader::Open(const char *uri, Error &error) +{ + assert(state == State::INITIAL); + + if (!StringStartsWith(uri, "nfs://")) { + error.Set(nfs_domain, "Malformed nfs:// URI"); + return false; + } + + uri += 6; + + const char *slash = strchr(uri, '/'); + if (slash == nullptr) { + error.Set(nfs_domain, "Malformed nfs:// URI"); + return false; + } + + server = std::string(uri, slash); + + uri = slash; + + const char *new_path = nfs_check_base(server.c_str(), uri); + if (new_path != nullptr) { + export_name = std::string(uri, new_path); + if (*new_path == 0) + new_path = "/"; + path = new_path; + } else { + slash = strrchr(uri + 1, '/'); + if (slash == nullptr || slash[1] == 0) { + error.Set(nfs_domain, "Malformed nfs:// URI"); + return false; + } + + export_name = std::string(uri, slash); + path = slash; + } + + state = State::DEFER; + DeferredMonitor::Schedule(); + return true; +} + +bool +NfsFileReader::Read(uint64_t offset, size_t size, Error &error) +{ + assert(state == State::IDLE); + + if (!connection->Read(fh, offset, size, *this, error)) + return false; + + state = State::READ; + return true; +} + +void +NfsFileReader::CancelRead() +{ + if (state == State::READ) { + connection->Cancel(*this); + state = State::IDLE; + } +} + +void +NfsFileReader::OnNfsConnectionReady() +{ + assert(state == State::MOUNT); + + Error error; + if (!connection->Open(path, O_RDONLY, *this, error)) { + OnNfsFileError(std::move(error)); + return; + } + + state = State::OPEN; +} + +void +NfsFileReader::OnNfsConnectionFailed(const Error &error) +{ + assert(state == State::MOUNT); + + Error copy; + copy.Set(error); + OnNfsFileError(std::move(copy)); +} + +void +NfsFileReader::OnNfsConnectionDisconnected(const Error &error) +{ + assert(state > State::MOUNT); + + state = State::INITIAL; + + Error copy; + copy.Set(error); + OnNfsFileError(std::move(copy)); +} + +inline void +NfsFileReader::OpenCallback(nfsfh *_fh) +{ + assert(state == State::OPEN); + assert(connection != nullptr); + assert(_fh != nullptr); + + fh = _fh; + + Error error; + if (!connection->Stat(fh, *this, error)) { + OnNfsFileError(std::move(error)); + return; + } + + state = State::STAT; +} + +inline void +NfsFileReader::StatCallback(const struct stat *st) +{ + assert(state == State::STAT); + assert(connection != nullptr); + assert(fh != nullptr); + assert(st != nullptr); + + if (!S_ISREG(st->st_mode)) { + OnNfsFileError(Error(nfs_domain, "Not a regular file")); + return; + } + + state = State::IDLE; + + OnNfsFileOpen(st->st_size); +} + +void +NfsFileReader::OnNfsCallback(unsigned status, void *data) +{ + switch (state) { + case State::INITIAL: + case State::DEFER: + case State::MOUNT: + case State::IDLE: + assert(false); + gcc_unreachable(); + + case State::OPEN: + OpenCallback((struct nfsfh *)data); + break; + + case State::STAT: + StatCallback((const struct stat *)data); + break; + + case State::READ: + state = State::IDLE; + OnNfsFileRead(data, status); + break; + } +} + +void +NfsFileReader::OnNfsError(Error &&error) +{ + OnNfsFileError(std::move(error)); +} + +void +NfsFileReader::RunDeferred() +{ + assert(state == State::DEFER); + + state = State::MOUNT; + + connection = &nfs_get_connection(server.c_str(), export_name.c_str()); + connection->AddLease(*this); +} diff --git a/src/lib/nfs/FileReader.hxx b/src/lib/nfs/FileReader.hxx new file mode 100644 index 000000000..7f43e0ecf --- /dev/null +++ b/src/lib/nfs/FileReader.hxx @@ -0,0 +1,94 @@ +/* + * 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_NFS_FILE_READER_HXX +#define MPD_NFS_FILE_READER_HXX + +#include "check.h" +#include "Lease.hxx" +#include "Callback.hxx" +#include "event/DeferredMonitor.hxx" + +#include <string> + +#include <stdint.h> +#include <stddef.h> +#include <sys/stat.h> + +struct nfsfh; +class NfsConnection; + +class NfsFileReader : NfsLease, NfsCallback, DeferredMonitor { + enum class State { + INITIAL, + DEFER, + MOUNT, + OPEN, + STAT, + READ, + IDLE, + }; + + State state; + + std::string server, export_name; + const char *path; + + NfsConnection *connection; + + nfsfh *fh; + +public: + NfsFileReader(); + ~NfsFileReader(); + + void Close(); + void DeferClose(); + + bool Open(const char *uri, Error &error); + bool Read(uint64_t offset, size_t size, Error &error); + void CancelRead(); + + bool IsIdle() const { + return state == State::IDLE; + } + +protected: + virtual void OnNfsFileOpen(uint64_t size) = 0; + virtual void OnNfsFileRead(const void *data, size_t size) = 0; + virtual void OnNfsFileError(Error &&error) = 0; + +private: + void OpenCallback(nfsfh *_fh); + void StatCallback(const struct stat *st); + + /* virtual methods from NfsLease */ + void OnNfsConnectionReady() final; + void OnNfsConnectionFailed(const Error &error) final; + void OnNfsConnectionDisconnected(const Error &error) final; + + /* virtual methods from NfsCallback */ + void OnNfsCallback(unsigned status, void *data) final; + void OnNfsError(Error &&error) final; + + /* virtual methods from DeferredMonitor */ + void RunDeferred() final; +}; + +#endif diff --git a/src/lib/nfs/Glue.cxx b/src/lib/nfs/Glue.cxx new file mode 100644 index 000000000..6e1e0f99b --- /dev/null +++ b/src/lib/nfs/Glue.cxx @@ -0,0 +1,59 @@ +/* + * 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 "Glue.hxx" +#include "Manager.hxx" +#include "IOThread.hxx" +#include "event/Call.hxx" +#include "util/Manual.hxx" + +#include <assert.h> + +static Manual<NfsManager> nfs_glue; +static unsigned in_use; + +void +nfs_init() +{ + if (in_use++ > 0) + return; + + nfs_glue.Construct(io_thread_get()); +} + +void +nfs_finish() +{ + assert(in_use > 0); + + if (--in_use > 0) + return; + + BlockingCall(io_thread_get(), [](){ nfs_glue.Destruct(); }); +} + +NfsConnection & +nfs_get_connection(const char *server, const char *export_name) +{ + assert(in_use > 0); + assert(io_thread_inside()); + + return nfs_glue->GetConnection(server, export_name); +} diff --git a/src/lib/nfs/Glue.hxx b/src/lib/nfs/Glue.hxx new file mode 100644 index 000000000..6da8957cb --- /dev/null +++ b/src/lib/nfs/Glue.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_NFS_GLUE_HXX +#define MPD_NFS_GLUE_HXX + +#include "check.h" +#include "Compiler.h" + +class NfsConnection; + +void +nfs_init(); + +void +nfs_finish(); + +gcc_pure +NfsConnection & +nfs_get_connection(const char *server, const char *export_name); + +#endif diff --git a/src/lib/nfs/Lease.hxx b/src/lib/nfs/Lease.hxx new file mode 100644 index 000000000..6f88acf53 --- /dev/null +++ b/src/lib/nfs/Lease.hxx @@ -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. + */ + +#ifndef MPD_NFS_LEASE_HXX +#define MPD_NFS_LEASE_HXX + +#include "check.h" + +class Error; + +class NfsLease { +public: + /** + * The #NfsConnection has successfully mounted the server's + * export and is ready for regular operation. + */ + virtual void OnNfsConnectionReady() = 0; + + /** + * The #NfsConnection has failed to mount the server's export. + * This is being called instead of OnNfsConnectionReady(). + */ + virtual void OnNfsConnectionFailed(const Error &error) = 0; + + /** + * The #NfsConnection has failed after OnNfsConnectionReady() + * had been called already. + */ + virtual void OnNfsConnectionDisconnected(const Error &error) = 0; +}; + +#endif diff --git a/src/lib/nfs/Manager.cxx b/src/lib/nfs/Manager.cxx new file mode 100644 index 000000000..c5aecf48d --- /dev/null +++ b/src/lib/nfs/Manager.cxx @@ -0,0 +1,87 @@ +/* + * 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 "Manager.hxx" +#include "event/Loop.hxx" +#include "Log.hxx" + +#include <string.h> + +void +NfsManager::ManagedConnection::OnNfsConnectionError(Error &&error) +{ + FormatError(error, "NFS error on %s:%s", GetServer(), GetExportName()); + + manager.connections.erase(manager.connections.iterator_to(*this)); + delete this; +} + +inline bool +NfsManager::Compare::operator()(const LookupKey a, + const ManagedConnection &b) const +{ + int result = strcmp(a.server, b.GetServer()); + if (result != 0) + return result < 0; + + result = strcmp(a.export_name, b.GetExportName()); + return result < 0; +} + +inline bool +NfsManager::Compare::operator()(const ManagedConnection &a, + const LookupKey b) const +{ + int result = strcmp(a.GetServer(), b.server); + if (result != 0) + return result < 0; + + result = strcmp(a.GetExportName(), b.export_name); + return result < 0; +} + +NfsManager::~NfsManager() +{ + assert(loop.IsInside()); + + connections.clear_and_dispose([](ManagedConnection *c){ + delete c; + }); +} + +NfsConnection & +NfsManager::GetConnection(const char *server, const char *export_name) +{ + assert(server != nullptr); + assert(export_name != nullptr); + assert(loop.IsInside()); + + Map::insert_commit_data hint; + auto result = connections.insert_check(LookupKey{server, export_name}, + Compare(), hint); + if (result.second) { + auto c = new ManagedConnection(*this, loop, + server, export_name); + connections.insert_commit(*c, hint); + return *c; + } else { + return *result.first; + } +} diff --git a/src/lib/nfs/Manager.hxx b/src/lib/nfs/Manager.hxx new file mode 100644 index 000000000..612b01f9c --- /dev/null +++ b/src/lib/nfs/Manager.hxx @@ -0,0 +1,91 @@ +/* + * 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_NFS_MANAGER_HXX +#define MPD_NFS_MANAGER_HXX + +#include "check.h" +#include "Connection.hxx" +#include "Compiler.h" + +#include <boost/intrusive/set.hpp> + +/** + * A manager for NFS connections. Handles multiple connections to + * multiple NFS servers. + */ +class NfsManager { + struct LookupKey { + const char *server; + const char *export_name; + }; + + class ManagedConnection final + : public NfsConnection, + public boost::intrusive::set_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> { + NfsManager &manager; + + public: + ManagedConnection(NfsManager &_manager, EventLoop &_loop, + const char *_server, + const char *_export_name) + :NfsConnection(_loop, _server, _export_name), + manager(_manager) {} + + protected: + /* virtual methods from NfsConnection */ + void OnNfsConnectionError(Error &&error) override; + }; + + struct Compare { + gcc_pure + bool operator()(const LookupKey a, + const ManagedConnection &b) const; + + gcc_pure + bool operator()(const ManagedConnection &a, + const LookupKey b) const; + }; + + EventLoop &loop; + + /** + * Maps server and export_name to #ManagedConnection. + */ + typedef boost::intrusive::set<ManagedConnection, + boost::intrusive::compare<Compare>, + boost::intrusive::constant_time_size<false>> Map; + + Map connections; + +public: + NfsManager(EventLoop &_loop) + :loop(_loop) {} + + /** + * Must be run from EventLoop's thread. + */ + ~NfsManager(); + + gcc_pure + NfsConnection &GetConnection(const char *server, + const char *export_name); +}; + +#endif |