Merge branch 'v0.21.x'

This commit is contained in:
Max Kellermann 2020-07-06 20:45:29 +02:00
commit c3cfb5fe16
27 changed files with 239 additions and 99 deletions

9
NEWS
View File

@ -43,15 +43,24 @@ ver 0.22 (not yet released)
ver 0.21.25 (not yet released)
* protocol:
- fix crash when using "rangeid" while playing
* database
- simple: automatically scan new mounts
* storage
- fix disappearing mounts after mounting twice
- udisks: fix reading ".mpdignore"
* input
- file: detect premature end of file
- smbclient: don't send credentials to MPD clients
* decoder
- opus: apply pre-skip and end trimming
- opus: fix memory leak
- opus: fix crash bug
- vorbis: fix crash bug
* output
- osx: improve sample rate selection
- osx: fix noise while stopping
* neighbor
- upnp: fix crash during shutdown
* Windows/Android:
- fix Boost detection after breaking change in Meson 0.54

View File

@ -60,25 +60,25 @@ The default plugin which gives :program:`MPD` access to local files. It is used
curl
----
A WebDAV client using libcurl. It is used when :code:`music_directory` contains a http:// or https:// URI, for example :samp:`https://the.server/dav/`.
A WebDAV client using libcurl. It is used when :code:`music_directory`
contains a ``http://`` or ``https://`` URI, for example
:samp:`https://the.server/dav/`.
smbclient
---------
Load music files from a SMB/CIFS server. It is used when :code:`music_directory` contains a smb:// URI, for example :samp:`smb://myfileserver/Music`.
Load music files from a SMB/CIFS server. It is used when
:code:`music_directory` contains a ``smb://`` URI, for example
:samp:`smb://myfileserver/Music`.
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`.
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`.
This plugin uses libnfs, which supports only NFS version 3. Since :program:`MPD` is not allowed to bind to "privileged ports", the NFS server needs to enable the "insecure" setting; example :file:`/etc/exports`:
.. code-block:: none
/srv/mp3 192.168.1.55(ro,insecure)
Don't fear: "insecure" does not mean that your NFS server is insecure. A few decades ago, people thought the concept of "privileged ports" would make network services "secure", which was a fallacy. The absence of this obsolete "security" measure means little.
See :ref:`input_nfs` for more information.
udisks
------
@ -186,7 +186,10 @@ curl
Opens remote files or streams over HTTP using libcurl.
Note that unless overridden by the below settings (e.g. by setting them to a blank value), general curl configuration from environment variables such as http_proxy or specified in :file:`~/.curlrc` will be in effect.
Note that unless overridden by the below settings (e.g. by setting
them to a blank value), general curl configuration from environment
variables such as ``http_proxy`` or specified in :file:`~/.curlrc`
will be in effect.
.. list-table::
:widths: 20 80
@ -206,7 +209,9 @@ Note that unless overridden by the below settings (e.g. by setting them to a bla
ffmpeg
------
Access to various network protocols implemented by the FFmpeg library: gopher://, rtp://, rtsp://, rtmp://, rtmpt://, rtmps://
Access to various network protocols implemented by the FFmpeg library:
``gopher://``, ``rtp://``, ``rtsp://``, ``rtmp://``, ``rtmpt://``,
``rtmps://``
file
----
@ -218,30 +223,51 @@ mms
Plays streams with the MMS protocol using `libmms <https://launchpad.net/libmms>`_.
.. _input_nfs:
nfs
---
Allows :program:`MPD` to access files on NFSv3 servers without actually mounting them (i.e. in userspace, without help from the kernel's VFS layer). All URIs with the nfs:// scheme are used according to RFC2224. Example:
Allows :program:`MPD` to access files on NFS servers without actually
mounting them (i.e. with :program:`libnfs` in userspace, without help
from the kernel's VFS layer). All URIs with the ``nfs://`` scheme are
used according to RFC2224. Example:
.. code-block:: none
mpc add nfs://servername/path/filename.ogg
Note that this usually requires enabling the "insecure" flag in the server's /etc/exports file, because :program:`MPD` cannot bind to so-called "privileged" ports. Don't fear: this will not make your file server insecure; the flag was named in a time long ago when privileged ports were thought to be meaningful for security. By today's standards, NFSv3 is not secure at all, and if you believe it is, you're already doomed.
This plugin uses :program:`libnfs`, which supports only NFS version 3.
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`:
.. code-block:: none
/srv/mp3 192.168.1.55(ro,insecure)
Don't fear: this will not make your file server insecure; the flag was
named a time long ago when privileged ports were thought to be
meaningful for security. By today's standards, NFSv3 is not secure at
all, and if you believe it is, you're already doomed.
smbclient
---------
Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba or Microsoft Windows). All URIs with the smb:// scheme are used. Example:
Allows :program:`MPD` to access files on SMB/CIFS servers (e.g. Samba
or Microsoft Windows). All URIs with the ``smb://`` scheme are
used. Example:
.. code-block:: none
mpc add smb://servername/sharename/filename.ogg
mpc add smb://username:password@servername/sharename/filename.ogg
qobuz
-----
Play songs from the commercial streaming service Qobuz. It plays URLs in the form qobuz://track/ID, e.g.:
Play songs from the commercial streaming service Qobuz. It plays URLs
in the form ``qobuz://track/ID``, e.g.:
.. code-block:: none
@ -267,7 +293,9 @@ Play songs from the commercial streaming service Qobuz. It plays URLs in the for
tidal
-----
Play songs from the commercial streaming service `Tidal <http://tidal.com/>`_. It plays URLs in the form tidal://track/ID, e.g.:
Play songs from the commercial streaming service `Tidal
<http://tidal.com/>`_. It plays URLs in the form ``tidal://track/ID``,
e.g.:
.. warning::

View File

@ -29,9 +29,7 @@
*/
class PluginUnavailable : public std::runtime_error {
public:
template<typename M>
explicit PluginUnavailable(M &&msg) noexcept
:std::runtime_error(std::forward<M>(msg)) {}
using std::runtime_error::runtime_error;
};
/**
@ -42,9 +40,7 @@ public:
*/
class PluginUnconfigured : public PluginUnavailable {
public:
template<typename M>
explicit PluginUnconfigured(M &&msg) noexcept
:PluginUnavailable(std::forward<M>(msg)) {}
using PluginUnavailable::PluginUnavailable;
};
#endif

View File

@ -69,17 +69,22 @@ Song::UpdateFile(Storage &storage)
TagBuilder tag_builder;
auto new_audio_format = AudioFormat::Undefined();
const auto path_fs = storage.MapFS(relative_uri.c_str());
if (path_fs.IsNull()) {
const auto absolute_uri =
storage.MapUTF8(relative_uri.c_str());
if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
&new_audio_format))
return false;
} else {
if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
try {
const auto path_fs = storage.MapFS(relative_uri.c_str());
if (path_fs.IsNull()) {
const auto absolute_uri =
storage.MapUTF8(relative_uri.c_str());
if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
&new_audio_format))
return false;
return false;
} else {
if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
&new_audio_format))
return false;
}
} catch (...) {
// TODO: log or propagate I/O errors?
return false;
}
mtime = info.mtime;
@ -139,8 +144,14 @@ DetachedSong::LoadFile(Path path)
return false;
TagBuilder tag_builder;
if (!ScanFileTagsWithGeneric(path, tag_builder))
try {
if (!ScanFileTagsWithGeneric(path, tag_builder))
return false;
} catch (...) {
// TODO: log or propagate I/O errors?
return false;
}
mtime = fi.GetModificationTime();
tag_builder.Commit(tag);
@ -157,8 +168,14 @@ DetachedSong::Update()
return LoadFile(path_fs);
} else if (IsRemote()) {
TagBuilder tag_builder;
if (!tag_stream_scan(uri.c_str(), tag_builder))
try {
if (!tag_stream_scan(uri.c_str(), tag_builder))
return false;
} catch (...) {
// TODO: log or propagate I/O errors?
return false;
}
mtime = std::chrono::system_clock::time_point::min();
tag_builder.Commit(tag);

View File

@ -43,7 +43,7 @@ CheckDecoderPlugin(const DecoderPlugin &plugin,
}
bool
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept
tag_stream_scan(InputStream &is, TagHandler &handler)
{
assert(is.IsReady());
@ -81,7 +81,7 @@ tag_stream_scan(const char *uri, TagHandler &handler)
bool
tag_stream_scan(InputStream &is, TagBuilder &builder,
AudioFormat *audio_format) noexcept
AudioFormat *audio_format)
{
assert(is.IsReady());

View File

@ -29,14 +29,16 @@ class TagBuilder;
* Scan the tags of an #InputStream. Invokes matching decoder
* plugins, but does not invoke the special "APE" and "ID3" scanners.
*
* Throws on I/O error.
*
* @return true if the file was recognized (even if no metadata was
* found)
*/
bool
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept;
tag_stream_scan(InputStream &is, TagHandler &handler);
/**
* Throws on error.
* Throws on I/O error.
*/
bool
tag_stream_scan(const char *uri, TagHandler &handler);
@ -46,15 +48,17 @@ tag_stream_scan(const char *uri, TagHandler &handler);
* plugins, and falls back to generic scanners (APE and ID3) if no
* tags were found (but the file was recognized).
*
* Throws on I/O error.
*
* @return true if the file was recognized (even if no metadata was
* found)
*/
bool
tag_stream_scan(InputStream &is, TagBuilder &builder,
AudioFormat *audio_format=nullptr) noexcept;
AudioFormat *audio_format=nullptr);
/**
* Throws on error.
* Throws on I/O error.
*/
bool
tag_stream_scan(const char *uri, TagBuilder &builder,

View File

@ -195,6 +195,16 @@ handle_mount(Client &client, Request args, Response &r)
return CommandResult::ERROR;
}
if (composite.IsMountPoint(local_uri)) {
r.Error(ACK_ERROR_ARG, "Mount point busy");
return CommandResult::ERROR;
}
if (composite.IsMounted(remote_uri)) {
r.Error(ACK_ERROR_ARG, "This storage is already mounted");
return CommandResult::ERROR;
}
auto &event_loop = instance.io_thread.GetEventLoop();
auto storage = CreateStorageURI(event_loop, remote_uri);
if (storage == nullptr) {
@ -207,8 +217,10 @@ handle_mount(Client &client, Request args, Response &r)
#ifdef ENABLE_DATABASE
if (auto *db = dynamic_cast<SimpleDatabase *>(instance.GetDatabase())) {
bool need_update;
try {
db->Mount(local_uri, remote_uri);
need_update = !db->Mount(local_uri, remote_uri);
} catch (...) {
composite.Unmount(local_uri);
throw;
@ -217,6 +229,12 @@ handle_mount(Client &client, Request args, Response &r)
// TODO: call Instance::OnDatabaseModified()?
// TODO: trigger database update?
instance.EmitIdle(IDLE_DATABASE);
if (need_update) {
UpdateService *update = client.GetInstance().update;
if (update != nullptr)
update->Enqueue(local_uri, false);
}
}
#endif

View File

@ -427,7 +427,7 @@ IsUnsafeChar(char ch)
return !IsSafeChar(ch);
}
void
bool
SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
{
if (cache_path.IsNull())
@ -446,9 +446,11 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri)
compress);
db->Open();
// TODO: update the new database instance?
bool exists = db->FileExists();
Mount(local_uri, std::move(db));
return exists;
}
inline DatabasePtr

View File

@ -103,9 +103,11 @@ public:
/**
* Throws #std::runtime_error on error.
*
* @return false if the mounted database needs to be updated
*/
gcc_nonnull_all
void Mount(const char *local_uri, const char *storage_uri);
bool Mount(const char *local_uri, const char *storage_uri);
gcc_nonnull_all
bool Unmount(const char *uri) noexcept;

View File

@ -322,8 +322,8 @@ UpdateWalk::UpdateDirectory(Directory &directory,
try {
Mutex mutex;
auto is = InputStream::OpenReady(PathTraitsUTF8::Build(storage.MapUTF8(directory.GetPath()),
".mpdignore").c_str(),
auto is = InputStream::OpenReady(storage.MapUTF8(PathTraitsUTF8::Build(directory.GetPath(),
".mpdignore")).c_str(),
mutex);
child_exclude_list.Load(std::move(is));
} catch (...) {

View File

@ -67,18 +67,22 @@ struct DecoderPlugin {
void (*file_decode)(DecoderClient &client, Path path_fs) = nullptr;
/**
* Scan metadata of a file.
* Scan metadata of a file.
*
* Throws on I/O error.
*
* @return false if the operation has failed
* @return false if the file was not recognized
*/
bool (*scan_file)(Path path_fs, TagHandler &handler) noexcept = nullptr;
bool (*scan_file)(Path path_fs, TagHandler &handler) = nullptr;
/**
* Scan metadata of a file.
* Scan metadata of a stream.
*
* Throws on I/O error.
*
* @return false if the operation has failed
* @return false if the stream was not recognized
*/
bool (*scan_stream)(InputStream &is, TagHandler &handler) noexcept = nullptr;
bool (*scan_stream)(InputStream &is, TagHandler &handler) = nullptr;
/**
* @brief Return a "virtual" filename for subtracks in
@ -99,14 +103,14 @@ struct DecoderPlugin {
void (*_file_decode)(DecoderClient &client,
Path path_fs),
bool (*_scan_file)(Path path_fs,
TagHandler &handler) noexcept) noexcept
TagHandler &handler)) noexcept
:name(_name),
file_decode(_file_decode), scan_file(_scan_file) {}
constexpr DecoderPlugin(const char *_name,
void (*_stream_decode)(DecoderClient &client,
InputStream &is),
bool (*_scan_stream)(InputStream &is, TagHandler &handler) noexcept) noexcept
bool (*_scan_stream)(InputStream &is, TagHandler &handler)) noexcept
:name(_name),
stream_decode(_stream_decode),
scan_stream(_scan_stream) {}
@ -114,11 +118,11 @@ struct DecoderPlugin {
constexpr DecoderPlugin(const char *_name,
void (*_stream_decode)(DecoderClient &client,
InputStream &is),
bool (*_scan_stream)(InputStream &is, TagHandler &handler) noexcept,
bool (*_scan_stream)(InputStream &is, TagHandler &handler),
void (*_file_decode)(DecoderClient &client,
Path path_fs),
bool (*_scan_file)(Path path_fs,
TagHandler &handler) noexcept) noexcept
TagHandler &handler)) noexcept
:name(_name),
stream_decode(_stream_decode),
file_decode(_file_decode),
@ -191,7 +195,7 @@ struct DecoderPlugin {
* Read the tag of a file.
*/
template<typename P>
bool ScanFile(P path_fs, TagHandler &handler) const noexcept {
bool ScanFile(P path_fs, TagHandler &handler) const {
return scan_file != nullptr
? scan_file(path_fs, handler)
: false;
@ -200,7 +204,7 @@ struct DecoderPlugin {
/**
* Read the tag of a stream.
*/
bool ScanStream(InputStream &is, TagHandler &handler) const noexcept {
bool ScanStream(InputStream &is, TagHandler &handler) const {
return scan_stream != nullptr
? scan_stream(is, handler)
: false;

View File

@ -241,7 +241,7 @@ audiofile_stream_decode(DecoderClient &client, InputStream &is)
}
static bool
audiofile_scan_stream(InputStream &is, TagHandler &handler) noexcept
audiofile_scan_stream(InputStream &is, TagHandler &handler)
{
if (!is.IsSeekable() || !is.KnownSize())
return false;

View File

@ -448,7 +448,7 @@ dsdiff_stream_decode(DecoderClient &client, InputStream &is)
}
static bool
dsdiff_scan_stream(InputStream &is, TagHandler &handler) noexcept
dsdiff_scan_stream(InputStream &is, TagHandler &handler)
{
DsdiffMetaData metadata;
DsdiffChunkHeader chunk_header;

View File

@ -325,7 +325,7 @@ dsf_stream_decode(DecoderClient &client, InputStream &is)
}
static bool
dsf_scan_stream(InputStream &is, TagHandler &handler) noexcept
dsf_scan_stream(InputStream &is, TagHandler &handler)
{
/* check DSF metadata */
DsfMetaData metadata;

View File

@ -411,7 +411,7 @@ faad_stream_decode(DecoderClient &client, InputStream &is)
}
static bool
faad_scan_stream(InputStream &is, TagHandler &handler) noexcept
faad_scan_stream(InputStream &is, TagHandler &handler)
{
auto result = faad_get_file_time(is);
if (!result.first)

View File

@ -591,8 +591,7 @@ ffmpeg_decode(DecoderClient &client, InputStream &input)
}
static bool
FfmpegScanStream(AVFormatContext &format_context,
TagHandler &handler) noexcept
FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
{
const int find_result =
avformat_find_stream_info(&format_context, nullptr);
@ -625,16 +624,14 @@ FfmpegScanStream(AVFormatContext &format_context,
}
static bool
ffmpeg_scan_stream(InputStream &is, TagHandler &handler) noexcept
try {
ffmpeg_scan_stream(InputStream &is, TagHandler &handler)
{
AvioStream stream(nullptr, is);
if (!stream.Open())
return false;
auto f = FfmpegOpenInput(stream.io, is.GetURI(), nullptr);
return FfmpegScanStream(*f, handler);
} catch (...) {
return false;
}
/**

View File

@ -69,7 +69,7 @@ flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
}
static bool
flac_scan_file(Path path_fs, TagHandler &handler) noexcept
flac_scan_file(Path path_fs, TagHandler &handler)
{
FlacMetadataChain chain;
if (!chain.Read(NarrowPath(path_fs))) {
@ -84,7 +84,7 @@ flac_scan_file(Path path_fs, TagHandler &handler) noexcept
}
static bool
flac_scan_stream(InputStream &is, TagHandler &handler) noexcept
flac_scan_stream(InputStream &is, TagHandler &handler)
{
FlacMetadataChain chain;
if (!chain.Read(is)) {
@ -313,7 +313,7 @@ oggflac_init([[maybe_unused]] const ConfigBlock &block)
}
static bool
oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
oggflac_scan_file(Path path_fs, TagHandler &handler)
{
FlacMetadataChain chain;
if (!chain.ReadOgg(NarrowPath(path_fs))) {
@ -328,7 +328,7 @@ oggflac_scan_file(Path path_fs, TagHandler &handler) noexcept
}
static bool
oggflac_scan_stream(InputStream &is, TagHandler &handler) noexcept
oggflac_scan_stream(InputStream &is, TagHandler &handler)
{
FlacMetadataChain chain;
if (!chain.ReadOgg(is)) {

View File

@ -1013,7 +1013,7 @@ MadDecoder::RunScan(TagHandler &handler) noexcept
}
static bool
mad_decoder_scan_stream(InputStream &is, TagHandler &handler) noexcept
mad_decoder_scan_stream(InputStream &is, TagHandler &handler)
{
MadDecoder data(nullptr, is);
return data.RunScan(handler);

View File

@ -273,7 +273,7 @@ mpcdec_get_file_duration(InputStream &is)
}
static bool
mpcdec_scan_stream(InputStream &is, TagHandler &handler) noexcept
mpcdec_scan_stream(InputStream &is, TagHandler &handler)
{
const auto duration = mpcdec_get_file_duration(is);
if (duration.IsNegative())

View File

@ -423,8 +423,8 @@ VisitOpusDuration(InputStream &is, OggSyncState &sync, OggStreamState &stream,
}
}
bool
mpd_opus_scan_stream(InputStream &is, TagHandler &handler) noexcept
static bool
mpd_opus_scan_stream(InputStream &is, TagHandler &handler)
{
InputStreamReader reader(is);
OggSyncState oy(reader);

View File

@ -268,7 +268,7 @@ static constexpr struct {
};
static bool
sndfile_scan_stream(InputStream &is, TagHandler &handler) noexcept
sndfile_scan_stream(InputStream &is, TagHandler &handler)
{
SF_INFO info;

View File

@ -370,7 +370,7 @@ VisitVorbisDuration(InputStream &is,
}
static bool
vorbis_scan_stream(InputStream &is, TagHandler &handler) noexcept
vorbis_scan_stream(InputStream &is, TagHandler &handler)
{
/* initialize libogg */

View File

@ -612,7 +612,7 @@ wavpack_scan_file(Path path_fs, TagHandler &handler) noexcept
}
static bool
wavpack_scan_stream(InputStream &is, TagHandler &handler) noexcept
wavpack_scan_stream(InputStream &is, TagHandler &handler)
{
WavpackInput isp(nullptr, is);

View File

@ -45,6 +45,11 @@ ToNeighborInfo(const UDisks2::Object &o) noexcept
return {o.GetUri(), o.path};
}
static constexpr char udisks_neighbor_match[] =
"type='signal',sender='" UDISKS2_INTERFACE "',"
"interface='" DBUS_OM_INTERFACE "',"
"path='" UDISKS2_PATH "'";
class UdisksNeighborExplorer final
: public NeighborExplorer {
@ -106,26 +111,37 @@ UdisksNeighborExplorer::DoOpen()
auto &connection = GetConnection();
/* this ugly try/catch cascade is only here because this
method has no RAII for this method - TODO: improve this */
try {
Error error;
dbus_bus_add_match(connection,
"type='signal',sender='" UDISKS2_INTERFACE "',"
"interface='" DBUS_OM_INTERFACE "',"
"path='" UDISKS2_PATH "'",
error);
dbus_bus_add_match(connection, udisks_neighbor_match, error);
error.CheckThrow("DBus AddMatch error");
dbus_connection_add_filter(connection,
HandleMessage, this,
nullptr);
try {
dbus_connection_add_filter(connection,
HandleMessage, this,
nullptr);
auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
UDISKS2_PATH,
DBUS_OM_INTERFACE,
"GetManagedObjects");
list_request.Send(connection, *msg.Get(),
std::bind(&UdisksNeighborExplorer::OnListNotify,
this, std::placeholders::_1));
try {
auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
UDISKS2_PATH,
DBUS_OM_INTERFACE,
"GetManagedObjects");
list_request.Send(connection, *msg.Get(),
std::bind(&UdisksNeighborExplorer::OnListNotify,
this, std::placeholders::_1));
} catch (...) {
dbus_connection_remove_filter(connection,
HandleMessage,
this);
throw;
}
} catch (...) {
dbus_bus_remove_match(connection,
udisks_neighbor_match, nullptr);
throw;
}
} catch (...) {
dbus_glue.Destruct();
throw;
@ -145,8 +161,10 @@ UdisksNeighborExplorer::DoClose() noexcept
list_request.Cancel();
}
// TODO: remove_match
// TODO: remove_filter
auto &connection = GetConnection();
dbus_connection_remove_filter(connection, HandleMessage, this);
dbus_bus_remove_match(connection, udisks_neighbor_match, nullptr);
dbus_glue.Destruct();
}

View File

@ -211,6 +211,7 @@ CompositeStorage::Mount(const char *uri, std::unique_ptr<Storage> storage)
const std::lock_guard<Mutex> protect(mutex);
Directory &directory = root.Make(uri);
assert(!directory.storage);
directory.storage = std::move(storage);
}

View File

@ -100,6 +100,15 @@ public:
gcc_pure gcc_nonnull_all
Storage *GetMount(std::string_view uri) noexcept;
/**
* Is the given URI a mount point, i.e. is something already
* mounted on this path?
*/
gcc_pure gcc_nonnull_all
bool IsMountPoint(const char *uri) noexcept {
return GetMount(uri) != nullptr;
}
/**
* Call the given function for each mounted storage, including
* the root storage. Passes mount point URI and the a const
@ -112,6 +121,15 @@ public:
VisitMounts(uri, root, t);
}
/**
* Is a storage with the given URI already mounted?
*/
gcc_pure gcc_nonnull_all
bool IsMounted(const char *storage_uri) const noexcept {
const std::lock_guard<Mutex> protect(mutex);
return IsMounted(root, storage_uri);
}
void Mount(const char *uri, std::unique_ptr<Storage> storage);
bool Unmount(const char *uri);
@ -146,6 +164,22 @@ private:
}
}
gcc_pure gcc_nonnull_all
static bool IsMounted(const Directory &directory,
const char *storage_uri) noexcept {
if (directory.storage) {
const auto uri = directory.storage->MapUTF8("");
if (uri == storage_uri)
return true;
}
for (const auto &i : directory.children)
if (IsMounted(i.second, storage_uri))
return true;
return false;
}
/**
* Follow the given URI path, and find the outermost directory
* which is a #Storage mount point. If there are no mounts,

View File

@ -106,6 +106,17 @@ storage_state_restore(const char *line, TextFile &file, Instance &instance)
FormatDebug(storage_domain, "Restoring mount %s => %s", uri.c_str(), url.c_str());
auto &composite_storage = *(CompositeStorage *)instance.storage;
if (composite_storage.IsMountPoint(uri.c_str())) {
LogError(storage_domain, "Mount point busy");
return true;
}
if (composite_storage.IsMounted(url.c_str())) {
LogError(storage_domain, "This storage is already mounted");
return true;
}
auto &event_loop = instance.io_thread.GetEventLoop();
auto storage = CreateStorageURI(event_loop, url.c_str());
if (storage == nullptr) {
@ -124,8 +135,7 @@ storage_state_restore(const char *line, TextFile &file, Instance &instance)
}
}
((CompositeStorage*)instance.storage)->Mount(uri.c_str(),
std::move(storage));
composite_storage.Mount(uri.c_str(), std::move(storage));
return true;
}