lib/nfs/Manager: add method MakeConnection()

This uses the libnfs function nfs_parse_url_dir() which means MPD
gains support for the libnfs arguments like "version".

Closes https://github.com/MusicPlayerDaemon/MPD/issues/2039
This commit is contained in:
Max Kellermann 2024-05-06 16:46:40 +02:00
parent 9947d3e67f
commit 9384bff6f9
9 changed files with 112 additions and 31 deletions

1
NEWS
View File

@ -26,6 +26,7 @@ ver 0.24 (not yet released)
* storage
- curl: optimize database update
- nfs: require libnfs 4.0 or later
- nfs: support libnfs URL arguments
* input
- alsa: limit ALSA buffer time to 2 seconds
- curl: add "connect_timeout" configuration

View File

@ -102,7 +102,12 @@ nfs
Load music files from a NFS server. It is used when
:code:`music_directory` contains a ``nfs://`` URI according to
RFC2224, for example :samp:`nfs://servername/path`.
RFC2224, for example :samp:`nfs://servername/path`. MPD supports the
libnfs URL arguments as documented in the `libnfs README
<https://github.com/sahlberg/libnfs/blob/master/README>`__. For
example, you can use NFSv4 with the ``version`` argument::
music_directory "nfs://server/music?version=4"
See :ref:`input_nfs` for more information.
@ -307,7 +312,7 @@ used according to RFC2224. Example:
mpc add nfs://servername/path/filename.ogg
This plugin uses :program:`libnfs`, which supports only NFS version 3.
This plugin uses :program:`libnfs`.
Since :program:`MPD` is not allowed to bind to so-called "privileged
ports", the NFS server needs to enable the ``insecure`` setting;
example :file:`/etc/exports`:

View File

@ -174,16 +174,15 @@ events_to_libnfs(unsigned i) noexcept
}
NfsConnection::NfsConnection(EventLoop &_loop,
nfs_context *_context,
std::string_view _server,
std::string_view _export_name)
:socket_event(_loop, BIND_THIS_METHOD(OnSocketReady)),
defer_new_lease(_loop, BIND_THIS_METHOD(RunDeferred)),
mount_timeout_event(_loop, BIND_THIS_METHOD(OnMountTimeout)),
server(_server), export_name(_export_name),
context(nfs_init_context())
context(_context)
{
if (context == nullptr)
throw std::runtime_error{"nfs_init_context() failed"};
}
NfsConnection::~NfsConnection() noexcept
@ -202,7 +201,7 @@ NfsConnection::~NfsConnection() noexcept
void
NfsConnection::AddLease(NfsLease &lease) noexcept
{
assert(GetEventLoop().IsInside());
assert(!GetEventLoop().IsAlive() || GetEventLoop().IsInside());
new_leases.push_back(lease);
@ -212,7 +211,7 @@ NfsConnection::AddLease(NfsLease &lease) noexcept
void
NfsConnection::RemoveLease(NfsLease &lease) noexcept
{
assert(GetEventLoop().IsInside());
assert(!GetEventLoop().IsAlive() || GetEventLoop().IsInside());
lease.unlink();
}

View File

@ -131,6 +131,7 @@ public:
*/
[[gnu::nonnull]]
NfsConnection(EventLoop &_loop,
nfs_context *_context,
std::string_view _server,
std::string_view _export_name);

View File

@ -39,6 +39,14 @@ nfs_get_event_loop() noexcept
return nfs_glue->GetEventLoop();
}
NfsConnection &
nfs_make_connection(const char *url)
{
assert(in_use > 0);
return nfs_glue->MakeConnection(url);
}
NfsConnection &
nfs_get_connection(std::string_view server,
std::string_view export_name)

View File

@ -21,6 +21,13 @@ nfs_finish() noexcept;
EventLoop &
nfs_get_event_loop() noexcept;
/**
* Throws on error.
*/
[[nodiscard]]
NfsConnection &
nfs_make_connection(const char *url);
/**
* Throws on error.
*/

View File

@ -3,13 +3,18 @@
#include "Manager.hxx"
#include "Connection.hxx"
#include "Error.hxx"
#include "lib/fmt/ExceptionFormatter.hxx"
#include "event/Loop.hxx"
#include "util/DeleteDisposer.hxx"
#include "util/Domain.hxx"
#include "util/StringAPI.hxx"
#include "util/ScopeExit.hxx"
#include "Log.hxx"
extern "C" {
#include <nfsc/libnfs.h>
}
static constexpr Domain nfs_domain("nfs");
class NfsManager::ManagedConnection final
@ -20,9 +25,10 @@ class NfsManager::ManagedConnection final
public:
ManagedConnection(NfsManager &_manager, EventLoop &_loop,
struct nfs_context *_context,
std::string_view _server,
std::string_view _export_name)
:NfsConnection(_loop, _server, _export_name),
:NfsConnection(_loop, _context, _server, _export_name),
manager(_manager) {}
protected:
@ -54,6 +60,28 @@ NfsManager::~NfsManager() noexcept
connections.clear_and_dispose(DeleteDisposer());
}
NfsConnection &
NfsManager::MakeConnection(const char *url)
{
struct nfs_context *const context = nfs_init_context();
if (context == nullptr)
throw std::runtime_error{"nfs_init_context() failed"};
auto *pu = nfs_parse_url_dir(context, url);
if (pu == nullptr) {
AtScopeExit(context) { nfs_destroy_context(context); };
throw NfsClientError(context, "nfs_parse_url_dir() failed");
}
AtScopeExit(pu) { nfs_destroy_url(pu); };
auto c = new ManagedConnection(*this, GetEventLoop(),
context,
pu->server, pu->path);
connections.push_front(*c);
return *c;
}
NfsConnection &
NfsManager::GetConnection(std::string_view server, std::string_view export_name)
{
@ -64,7 +92,12 @@ NfsManager::GetConnection(std::string_view server, std::string_view export_name)
c.GetExportName() == export_name)
return c;
struct nfs_context *const context = nfs_init_context();
if (context == nullptr)
throw std::runtime_error{"nfs_init_context() failed"};
auto c = new ManagedConnection(*this, GetEventLoop(),
context,
server, export_name);
connections.push_front(*c);
return *c;

View File

@ -42,6 +42,20 @@ public:
}
/**
* Create a new #NfsConnection, parsing the specified "nfs://"
* URL.
*
* Throws on error.
*/
[[nodiscard]]
NfsConnection &MakeConnection(const char *url);
/**
* Look up an existing #NfsConnection (or create a new one if
* none matching the given parameters exists). Unlike
* MakeConnection(), this does not support options in a query
* string.
*
* Throws on error.
*/
[[nodiscard]]

View File

@ -26,12 +26,16 @@ extern "C" {
#include <nfsc/libnfs-raw-nfs.h>
}
#include <fmt/core.h>
#include <cassert>
#include <string>
#include <sys/stat.h>
#include <fcntl.h>
using std::string_view_literals::operator""sv;
class NfsStorage final
: public Storage, NfsLease {
@ -39,9 +43,16 @@ class NfsStorage final
INITIAL, CONNECTING, READY, DELAY,
};
const std::string base;
/**
* The full configured URL (with all arguemnts). This is used
* to reconnect.
*/
const std::string url;
const std::string server, export_name;
/**
* The base URL for building file URLs (without arguments).
*/
const std::string base;
NfsConnection *connection;
@ -50,18 +61,20 @@ class NfsStorage final
Mutex mutex;
Cond cond;
State state = State::INITIAL;
State state = State::CONNECTING;
std::exception_ptr last_exception;
public:
NfsStorage(EventLoop &_loop, const char *_base,
std::string_view _server, std::string_view _export_name)
:base(_base),
server(_server),
export_name(_export_name),
defer_connect(_loop, BIND_THIS_METHOD(OnDeferredConnect)),
reconnect_timer(_loop, BIND_THIS_METHOD(OnReconnectTimer)) {
nfs_init(_loop);
NfsStorage(const char *_url, NfsConnection &_connection)
:url(_url),
base(fmt::format("nfs://{}{}"sv, _connection.GetServer(), _connection.GetExportName())),
connection(&_connection),
defer_connect(_connection.GetEventLoop(), BIND_THIS_METHOD(OnDeferredConnect)),
reconnect_timer(_connection.GetEventLoop(), BIND_THIS_METHOD(OnReconnectTimer))
{
BlockingCall(GetEventLoop(), [this](){
connection->AddLease(*this);
});
}
~NfsStorage() override {
@ -142,7 +155,7 @@ private:
assert(GetEventLoop().IsInside());
try {
connection = &nfs_get_connection(server, export_name);
connection = &nfs_make_connection(url.c_str());
} catch (...) {
SetState(State::DELAY, std::current_exception());
reconnect_timer.Schedule(std::chrono::minutes(10));
@ -394,20 +407,20 @@ NfsStorage::OpenDirectory(std::string_view uri_utf8)
static std::unique_ptr<Storage>
CreateNfsStorageURI(EventLoop &event_loop, const char *base)
{
const char *p = StringAfterPrefixCaseASCII(base, "nfs://");
if (p == nullptr)
if (!StringStartsWithCaseASCII(base, "nfs://"sv))
return nullptr;
const char *slash = std::strchr(p, '/');
if (slash == nullptr)
throw std::runtime_error("Malformed nfs:// URI");
nfs_init(event_loop);
const std::string_view server{p, slash}, mount{slash};
try {
auto &connection = nfs_make_connection(base);
nfs_set_base(connection.GetServer(), connection.GetExportName());
nfs_set_base(server, mount);
return std::make_unique<NfsStorage>(event_loop, base,
server, mount);
return std::make_unique<NfsStorage>(base, connection);
} catch (...) {
nfs_finish();
throw;
}
}
static constexpr const char *nfs_prefixes[] = { "nfs://", nullptr };