decoder/Plugin: allow scan_{file,stream}() to throw

Bug #915 is about an I/O exception thrown where none was allowed,
leading to crash via std::terminate().  However, instead of catching
and logging the error inside the decoder plugin, it should be able to
propagate the I/O error to the MPD core, so MPD can avoid trying other
decoder plugins.

Closes https://github.com/MusicPlayerDaemon/MPD/issues/915
This commit is contained in:
Max Kellermann 2020-07-06 13:36:22 +02:00
parent daefc61aa4
commit bfdf13dca3
19 changed files with 90 additions and 58 deletions

2
NEWS
View File

@ -7,6 +7,8 @@ ver 0.21.25 (not yet released)
* decoder * decoder
- opus: apply pre-skip and end trimming - opus: apply pre-skip and end trimming
- opus: fix memory leak - opus: fix memory leak
- opus: fix crash bug
- vorbis: fix crash bug
* output * output
- osx: improve sample rate selection - osx: improve sample rate selection
- osx: fix noise while stopping - osx: fix noise while stopping

View File

@ -79,17 +79,22 @@ Song::UpdateFile(Storage &storage) noexcept
TagBuilder tag_builder; TagBuilder tag_builder;
auto new_audio_format = AudioFormat::Undefined(); auto new_audio_format = AudioFormat::Undefined();
const auto path_fs = storage.MapFS(relative_uri.c_str()); try {
if (path_fs.IsNull()) { const auto path_fs = storage.MapFS(relative_uri.c_str());
const auto absolute_uri = if (path_fs.IsNull()) {
storage.MapUTF8(relative_uri.c_str()); const auto absolute_uri =
if (!tag_stream_scan(absolute_uri.c_str(), tag_builder, storage.MapUTF8(relative_uri.c_str());
&new_audio_format)) if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
return false;
} else {
if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
&new_audio_format)) &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; mtime = info.mtime;
@ -153,8 +158,14 @@ DetachedSong::LoadFile(Path path) noexcept
return false; return false;
TagBuilder tag_builder; 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; return false;
}
mtime = fi.GetModificationTime(); mtime = fi.GetModificationTime();
tag_builder.Commit(tag); tag_builder.Commit(tag);
@ -173,8 +184,14 @@ DetachedSong::Update() noexcept
return LoadFile(path_fs); return LoadFile(path_fs);
} else if (IsRemote()) { } else if (IsRemote()) {
TagBuilder tag_builder; 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; return false;
}
mtime = std::chrono::system_clock::time_point::min(); mtime = std::chrono::system_clock::time_point::min();
tag_builder.Commit(tag); tag_builder.Commit(tag);

View File

@ -47,11 +47,11 @@ public:
handler(_handler), handler(_handler),
is(nullptr) {} is(nullptr) {}
bool ScanFile(const DecoderPlugin &plugin) noexcept { bool ScanFile(const DecoderPlugin &plugin) {
return plugin.ScanFile(path_fs, handler); return plugin.ScanFile(path_fs, handler);
} }
bool ScanStream(const DecoderPlugin &plugin) noexcept { bool ScanStream(const DecoderPlugin &plugin) {
if (plugin.scan_stream == nullptr) if (plugin.scan_stream == nullptr)
return false; return false;
@ -73,14 +73,14 @@ public:
return plugin.ScanStream(*is, handler); return plugin.ScanStream(*is, handler);
} }
bool Scan(const DecoderPlugin &plugin) noexcept { bool Scan(const DecoderPlugin &plugin) {
return plugin.SupportsSuffix(suffix) && return plugin.SupportsSuffix(suffix) &&
(ScanFile(plugin) || ScanStream(plugin)); (ScanFile(plugin) || ScanStream(plugin));
} }
}; };
bool bool
ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler) noexcept ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler)
{ {
assert(!path_fs.IsNull()); assert(!path_fs.IsNull());
@ -100,7 +100,7 @@ ScanFileTagsNoGeneric(Path path_fs, TagHandler &handler) noexcept
bool bool
ScanFileTagsWithGeneric(Path path, TagBuilder &builder, ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
AudioFormat *audio_format) noexcept AudioFormat *audio_format)
{ {
FullTagHandler h(builder, audio_format); FullTagHandler h(builder, audio_format);

View File

@ -30,22 +30,26 @@ class TagBuilder;
* but does not fall back to generic scanners (APE and ID3) if no tags * but does not fall back to generic scanners (APE and ID3) if no tags
* were found (but the file was recognized). * were found (but the file was recognized).
* *
* Throws on I/O error.
*
* @return true if the file was recognized (even if no metadata was * @return true if the file was recognized (even if no metadata was
* found) * found)
*/ */
bool bool
ScanFileTagsNoGeneric(Path path, TagHandler &handler) noexcept; ScanFileTagsNoGeneric(Path path, TagHandler &handler);
/** /**
* Scan the tags of a song file. Invokes matching decoder plugins, * Scan the tags of a song file. Invokes matching decoder plugins,
* and falls back to generic scanners (APE and ID3) if no tags were * and falls back to generic scanners (APE and ID3) if no tags were
* found (but the file was recognized). * found (but the file was recognized).
* *
* Throws on I/O error.
*
* @return true if the file was recognized (even if no metadata was * @return true if the file was recognized (even if no metadata was
* found) * found)
*/ */
bool bool
ScanFileTagsWithGeneric(Path path, TagBuilder &builder, ScanFileTagsWithGeneric(Path path, TagBuilder &builder,
AudioFormat *audio_format=nullptr) noexcept; AudioFormat *audio_format=nullptr);
#endif #endif

View File

@ -45,7 +45,7 @@ CheckDecoderPlugin(const DecoderPlugin &plugin,
} }
bool bool
tag_stream_scan(InputStream &is, TagHandler &handler) noexcept tag_stream_scan(InputStream &is, TagHandler &handler)
{ {
assert(is.IsReady()); assert(is.IsReady());
@ -73,19 +73,17 @@ tag_stream_scan(InputStream &is, TagHandler &handler) noexcept
} }
bool bool
tag_stream_scan(const char *uri, TagHandler &handler) noexcept tag_stream_scan(const char *uri, TagHandler &handler)
try { {
Mutex mutex; Mutex mutex;
auto is = InputStream::OpenReady(uri, mutex); auto is = InputStream::OpenReady(uri, mutex);
return tag_stream_scan(*is, handler); return tag_stream_scan(*is, handler);
} catch (const std::exception &e) {
return false;
} }
bool bool
tag_stream_scan(InputStream &is, TagBuilder &builder, tag_stream_scan(InputStream &is, TagBuilder &builder,
AudioFormat *audio_format) noexcept AudioFormat *audio_format)
{ {
assert(is.IsReady()); assert(is.IsReady());
@ -102,12 +100,10 @@ tag_stream_scan(InputStream &is, TagBuilder &builder,
bool bool
tag_stream_scan(const char *uri, TagBuilder &builder, tag_stream_scan(const char *uri, TagBuilder &builder,
AudioFormat *audio_format) noexcept AudioFormat *audio_format)
try { {
Mutex mutex; Mutex mutex;
auto is = InputStream::OpenReady(uri, mutex); auto is = InputStream::OpenReady(uri, mutex);
return tag_stream_scan(*is, builder, audio_format); return tag_stream_scan(*is, builder, audio_format);
} catch (const std::exception &e) {
return false;
} }

View File

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

View File

@ -67,18 +67,22 @@ struct DecoderPlugin {
void (*file_decode)(DecoderClient &client, Path path_fs); void (*file_decode)(DecoderClient &client, Path path_fs);
/** /**
* 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; bool (*scan_file)(Path path_fs, TagHandler &handler);
/** /**
* 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; bool (*scan_stream)(InputStream &is, TagHandler &handler);
/** /**
* @brief Return a "virtual" filename for subtracks in * @brief Return a "virtual" filename for subtracks in
@ -135,7 +139,7 @@ struct DecoderPlugin {
* Read the tag of a file. * Read the tag of a file.
*/ */
template<typename P> template<typename P>
bool ScanFile(P path_fs, TagHandler &handler) const noexcept { bool ScanFile(P path_fs, TagHandler &handler) const {
return scan_file != nullptr return scan_file != nullptr
? scan_file(path_fs, handler) ? scan_file(path_fs, handler)
: false; : false;
@ -144,7 +148,7 @@ struct DecoderPlugin {
/** /**
* Read the tag of a stream. * 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 return scan_stream != nullptr
? scan_stream(is, handler) ? scan_stream(is, handler)
: false; : false;

View File

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

View File

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

View File

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

View File

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

View File

@ -646,8 +646,7 @@ ffmpeg_decode(DecoderClient &client, InputStream &input)
} }
static bool static bool
FfmpegScanStream(AVFormatContext &format_context, FfmpegScanStream(AVFormatContext &format_context, TagHandler &handler)
TagHandler &handler) noexcept
{ {
const int find_result = const int find_result =
avformat_find_stream_info(&format_context, nullptr); avformat_find_stream_info(&format_context, nullptr);
@ -680,7 +679,7 @@ FfmpegScanStream(AVFormatContext &format_context,
} }
static bool static bool
ffmpeg_scan_stream(InputStream &is, TagHandler &handler) noexcept ffmpeg_scan_stream(InputStream &is, TagHandler &handler)
{ {
AvioStream stream(nullptr, is); AvioStream stream(nullptr, is);
if (!stream.Open()) if (!stream.Open())

View File

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

View File

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

View File

@ -275,7 +275,7 @@ mpcdec_get_file_duration(InputStream &is)
} }
static bool 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); const auto duration = mpcdec_get_file_duration(is);
if (duration.IsNegative()) if (duration.IsNegative())

View File

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

View File

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

View File

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

View File

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