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 | 89 | ||||
-rw-r--r-- | src/lib/nfs/Blocking.hxx | 90 | ||||
-rw-r--r-- | src/lib/nfs/Callback.hxx | 33 | ||||
-rw-r--r-- | src/lib/nfs/Cancellable.hxx | 168 | ||||
-rw-r--r-- | src/lib/nfs/Connection.cxx | 662 | ||||
-rw-r--r-- | src/lib/nfs/Connection.hxx | 239 | ||||
-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 | 297 | ||||
-rw-r--r-- | src/lib/nfs/FileReader.hxx | 101 | ||||
-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 | 107 | ||||
-rw-r--r-- | src/lib/nfs/Manager.hxx | 116 |
17 files changed, 2205 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..58eaf6af2 --- /dev/null +++ b/src/lib/nfs/Blocking.cxx @@ -0,0 +1,89 @@ +/* + * 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 "Domain.hxx" +#include "event/Call.hxx" +#include "util/Error.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 */ + if (!LockWaitFinished()) { + _error.Set(nfs_domain, 0, "Timeout"); + return false; + } + + /* 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..eb16dfb8c --- /dev/null +++ b/src/lib/nfs/Blocking.hxx @@ -0,0 +1,90 @@ +/* + * 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 { + static constexpr unsigned timeout_ms = 60000; + + Mutex mutex; + Cond cond; + + bool finished; + + Error error; + +protected: + NfsConnection &connection; + +public: + BlockingNfsOperation(NfsConnection &_connection) + :finished(false), connection(_connection) {} + + bool Run(Error &error); + +private: + bool LockWaitFinished() { + const ScopeLock protect(mutex); + while (!finished) + if (!cond.timed_wait(mutex, timeout_ms)) + return false; + + return true; + } + + /** + * 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..151be0528 --- /dev/null +++ b/src/lib/nfs/Cancellable.hxx @@ -0,0 +1,168 @@ +/* + * 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; + } + + template<typename F> + void ForEach(F &&f) { + for (CT &i : list) + f(i); + } +}; + +#endif diff --git a/src/lib/nfs/Connection.cxx b/src/lib/nfs/Connection.cxx new file mode 100644 index 000000000..6e9f77345 --- /dev/null +++ b/src/lib/nfs/Connection.cxx @@ -0,0 +1,662 @@ +/* + * 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 */ + +static constexpr unsigned NFS_MOUNT_TIMEOUT = 60; + +inline bool +NfsConnection::CancellableCallback::Stat(nfs_context *ctx, + const char *path, + Error &error) +{ + assert(connection.GetEventLoop().IsInside()); + + 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) +{ + assert(connection.GetEventLoop().IsInside()); + + 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) +{ + assert(connection.GetEventLoop().IsInside()); + + 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) +{ + assert(connection.GetEventLoop().IsInside()); + + 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) +{ + assert(connection.GetEventLoop().IsInside()); + + 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(connection.GetEventLoop().IsInside()); + assert(!open); + assert(close_fh == nullptr); + assert(fh != nullptr); + + close_fh = fh; + Cancel(); +} + +inline void +NfsConnection::CancellableCallback::PrepareDestroyContext() +{ + assert(IsCancelled()); + + if (close_fh != nullptr) { + connection.InternalClose(close_fh); + close_fh = nullptr; + } +} + +inline void +NfsConnection::CancellableCallback::Callback(int err, void *data) +{ + assert(connection.GetEventLoop().IsInside()); + + 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); + + if (err >= 0) { + 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(GetEventLoop().IsInside()); + 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(GetEventLoop().IsInside()); + 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) +{ + assert(GetEventLoop().IsInside()); + + return nfs_readdir(context, dir); +} + +void +NfsConnection::CloseDirectory(struct nfsdir *dir) +{ + assert(GetEventLoop().IsInside()); + + return nfs_closedir(context, dir); +} + +bool +NfsConnection::Open(const char *path, int flags, NfsCallback &callback, + Error &error) +{ + assert(GetEventLoop().IsInside()); + 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(GetEventLoop().IsInside()); + 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(GetEventLoop().IsInside()); + 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 *) +{ +} + +inline void +NfsConnection::InternalClose(struct nfsfh *fh) +{ + assert(GetEventLoop().IsInside()); + assert(context != nullptr); + assert(fh != nullptr); + + nfs_close_async(context, fh, DummyCallback, nullptr); +} + +void +NfsConnection::Close(struct nfsfh *fh) +{ + assert(GetEventLoop().IsInside()); + + InternalClose(fh); + 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); + +#ifndef NDEBUG + assert(!in_destroy); + in_destroy = true; +#endif + + if (!mount_finished) { + assert(TimeoutMonitor::IsActive()); + TimeoutMonitor::Cancel(); + } + + /* cancel pending DeferredMonitor that was scheduled to notify + new leases */ + DeferredMonitor::Cancel(); + + if (SocketMonitor::IsDefined()) + SocketMonitor::Steal(); + + callbacks.ForEach([](CancellableCallback &c){ + c.PrepareDestroyContext(); + }); + + nfs_destroy_context(context); + context = nullptr; +} + +inline void +NfsConnection::DeferClose(struct nfsfh *fh) +{ + assert(GetEventLoop().IsInside()); + assert(in_event); + assert(in_service); + assert(context != nullptr); + assert(fh != nullptr); + + deferred_close.push_front(fh); +} + +void +NfsConnection::ScheduleSocket() +{ + assert(GetEventLoop().IsInside()); + 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))); +} + +inline int +NfsConnection::Service(unsigned flags) +{ + assert(GetEventLoop().IsInside()); + assert(context != nullptr); + +#ifndef NDEBUG + assert(!in_event); + in_event = true; + + assert(!in_service); + in_service = true; +#endif + + int result = nfs_service(context, events_to_libnfs(flags)); + +#ifndef NDEBUG + assert(context != nullptr); + assert(in_service); + in_service = false; +#endif + + return result; +} + +bool +NfsConnection::OnSocketReady(unsigned flags) +{ + assert(GetEventLoop().IsInside()); + 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(); + + const int result = Service(flags); + + while (!deferred_close.empty()) { + InternalClose(deferred_close.front()); + 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)); + + BroadcastError(std::move(error)); + + DestroyContext(); + closed = true; + } else if (nfs_get_fd(context) < 0) { + /* this happens when rpc_reconnect_requeue() is called + after the connection broke, but autoreconnect 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); + + BroadcastError(std::move(error)); + + DestroyContext(); + closed = true; + } + + assert(context == nullptr || nfs_get_fd(context) >= 0); + +#ifndef NDEBUG + assert(in_event); + in_event = false; +#endif + + if (context != nullptr) + ScheduleSocket(); + + return !closed; +} + +inline void +NfsConnection::MountCallback(int status, gcc_unused nfs_context *nfs, + gcc_unused void *data) +{ + assert(GetEventLoop().IsInside()); + assert(context == nfs); + + mount_finished = true; + + assert(TimeoutMonitor::IsActive() || in_destroy); + TimeoutMonitor::Cancel(); + + 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(GetEventLoop().IsInside()); + 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; + + TimeoutMonitor::ScheduleSeconds(NFS_MOUNT_TIMEOUT); + +#ifndef NDEBUG + in_service = false; + in_event = false; + in_destroy = false; +#endif + + 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::OnTimeout() +{ + assert(GetEventLoop().IsInside()); + assert(!mount_finished); + + mount_finished = true; + DestroyContext(); + + BroadcastMountError(Error(nfs_domain, "Mount timeout")); +} + +void +NfsConnection::RunDeferred() +{ + assert(GetEventLoop().IsInside()); + + 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..3969a7e8f --- /dev/null +++ b/src/lib/nfs/Connection.hxx @@ -0,0 +1,239 @@ +/* + * 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/TimeoutMonitor.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, TimeoutMonitor, 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); + + /** + * Called by NfsConnection::DestroyContext() right + * before nfs_destroy_context(). This object is given + * a chance to prepare for the latter. + */ + void PrepareDestroyContext(); + + 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; + +#ifndef NDEBUG + /** + * 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; + + /** + * True when DestroyContext() is being called. + */ + bool in_destroy; +#endif + + bool mount_finished; + +public: + gcc_nonnull_all + NfsConnection(EventLoop &_loop, + const char *_server, const char *_export_name) + :SocketMonitor(_loop), TimeoutMonitor(_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(); + + /** + * Wrapper for nfs_close_async(). + */ + void InternalClose(struct nfsfh *fh); + + /** + * 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(); + + /** + * Wrapper for nfs_service(). + */ + int Service(unsigned flags); + + /* virtual methods from SocketMonitor */ + virtual bool OnSocketReady(unsigned flags) override; + + /* virtual methods from TimeoutMonitor */ + void OnTimeout() final; + + /* 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..1b80f2c86 --- /dev/null +++ b/src/lib/nfs/FileReader.cxx @@ -0,0 +1,297 @@ +/* + * 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; + } + + /* this cancels State::MOUNT */ + connection->RemoveLease(*this); + + CancelOrClose(); +} + +void +NfsFileReader::CancelOrClose() +{ + assert(state != State::INITIAL && + state != State::DEFER); + + 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); + + state = State::INITIAL; + + Error copy; + copy.Set(error); + OnNfsFileError(std::move(copy)); +} + +void +NfsFileReader::OnNfsConnectionDisconnected(const Error &error) +{ + assert(state > State::MOUNT); + + CancelOrClose(); + + 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) +{ + switch (state) { + case State::INITIAL: + case State::DEFER: + case State::MOUNT: + case State::IDLE: + assert(false); + gcc_unreachable(); + + case State::OPEN: + connection->RemoveLease(*this); + state = State::INITIAL; + break; + + case State::STAT: + connection->RemoveLease(*this); + connection->Close(fh); + state = State::INITIAL; + break; + + case State::READ: + state = State::IDLE; + break; + } + + 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..1495a2832 --- /dev/null +++ b/src/lib/nfs/FileReader.hxx @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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 "Compiler.h" + +#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: + /** + * Cancel the current operation, if any. The NfsLease must be + * unregistered already. + */ + void CancelOrClose(); + + 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..6d50cce18 --- /dev/null +++ b/src/lib/nfs/Manager.cxx @@ -0,0 +1,107 @@ +/* + * 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()); + + /* defer deletion so the caller + (i.e. NfsConnection::OnSocketReady()) can still use this + object */ + manager.ScheduleDelete(*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(GetEventLoop().IsInside()); + + CollectGarbage(); + + 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(GetEventLoop().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, GetEventLoop(), + server, export_name); + connections.insert_commit(*c, hint); + return *c; + } else { + return *result.first; + } +} + +void +NfsManager::CollectGarbage() +{ + assert(GetEventLoop().IsInside()); + + garbage.clear_and_dispose([](ManagedConnection *c){ + delete c; + }); +} + +void +NfsManager::OnIdle() +{ + CollectGarbage(); +} diff --git a/src/lib/nfs/Manager.hxx b/src/lib/nfs/Manager.hxx new file mode 100644 index 000000000..130c81aca --- /dev/null +++ b/src/lib/nfs/Manager.hxx @@ -0,0 +1,116 @@ +/* + * 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 "event/IdleMonitor.hxx" + +#include <boost/intrusive/set.hpp> +#include <boost/intrusive/slist.hpp> + +/** + * A manager for NFS connections. Handles multiple connections to + * multiple NFS servers. + */ +class NfsManager final : IdleMonitor { + struct LookupKey { + const char *server; + const char *export_name; + }; + + class ManagedConnection final + : public NfsConnection, + public boost::intrusive::slist_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>>, + 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; + }; + + /** + * 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; + + typedef boost::intrusive::slist<ManagedConnection> List; + + /** + * A list of "garbage" connection objects. Their destruction + * is postponed because they were thrown into the garbage list + * when callers on the stack were still using them. + */ + List garbage; + +public: + NfsManager(EventLoop &_loop) + :IdleMonitor(_loop) {} + + /** + * Must be run from EventLoop's thread. + */ + ~NfsManager(); + + gcc_pure + NfsConnection &GetConnection(const char *server, + const char *export_name); + +private: + void ScheduleDelete(ManagedConnection &c) { + connections.erase(connections.iterator_to(c)); + garbage.push_front(c); + IdleMonitor::Schedule(); + } + + /** + * Delete all connections on the #garbage list. + */ + void CollectGarbage(); + + /* virtual methods from IdleMonitor */ + void OnIdle() override; +}; + +#endif |