633 lines
13 KiB
C++
633 lines
13 KiB
C++
/*
|
|
* 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)
|
|
{
|
|
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);
|
|
|
|
/* cancel pending DeferredMonitor that was scheduled to notify
|
|
new leases */
|
|
DeferredMonitor::Cancel();
|
|
|
|
if (SocketMonitor::IsDefined())
|
|
SocketMonitor::Cancel();
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
#ifndef NDEBUG
|
|
in_service = false;
|
|
in_event = 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::RunDeferred()
|
|
{
|
|
assert(GetEventLoop().IsInside());
|
|
|
|
if (context == nullptr) {
|
|
Error error;
|
|
if (!MountInternal(error)) {
|
|
BroadcastMountError(std::move(error));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (mount_finished)
|
|
BroadcastMountSuccess();
|
|
}
|