Compare commits

...

75 Commits

Author SHA1 Message Date
Max Kellermann
6d89020f80 release v0.19.7 2014-12-17 19:20:54 +01:00
Max Kellermann
9c56c49e73 Merge tag 'v0.18.21' into v0.19.x 2014-12-17 19:19:13 +01:00
Max Kellermann
acb798e544 release v0.18.21 2014-12-17 19:13:47 +01:00
Max Kellermann
c5720a15c7 LogBackend: force-flush stderr on WIN32
setvbuf() does not seem to have an effect on Windows.
2014-12-17 19:12:25 +01:00
Max Kellermann
90709b332a LogInit: make stderr line-buffered
Make sure everything gets logged right away.  No delays because
stdio's buffer is not yet full.
2014-12-17 19:12:01 +01:00
Max Kellermann
81f17d10c8 util/HugeAllocator: enable MEM_COMMIT on Windows
Without MEM_COMMIT, the reserved address space is not accessible, and
MPD crashes.
2014-12-17 19:10:58 +01:00
k44
773de38bd9 playlist/embcue: fix filename suffix detection
The definition of the playlist_plugin struct member of the embcue
plugin was incorrect.
2014-12-16 18:43:05 +01:00
Max Kellermann
a48704925d storage/nfs: add timeout 2014-12-15 00:45:13 +01:00
Max Kellermann
fa4beeee75 decoder/ffmpeg: detect and fix negative time stamps
Works around assertion failure due to something that appears to be a
(minor) FFmpeg bug.
2014-12-15 00:40:46 +01:00
Max Kellermann
d8351772d3 configure.ac: prepare for 0.18.21 2014-12-15 00:39:52 +01:00
Max Kellermann
68d1abdb85 storage/nfs: clear last_error in SetState()
Fixes bogus assertion failure.
2014-12-15 00:39:30 +01:00
Max Kellermann
7e8474a85a lib/nfs/Connection: unregister socket with SocketMonitor::Steal()
SocketMonitor::Cancel() does not actually unregister the socket; it
only disables the event.
2014-12-15 00:31:12 +01:00
Max Kellermann
82da364b8b lib/nfs/Connection: implement mount timeout 2014-12-15 00:05:53 +01:00
Max Kellermann
7fa91ec175 lib/nfs/Connection: add debug flag "in_destroy" 2014-12-15 00:03:30 +01:00
Max Kellermann
1d3a09d377 lib/nfs/Connection: add assertion 2014-12-14 22:51:37 +01:00
Max Kellermann
02563a35f0 lib/nfs/Connection: fix reconnect after mount failure
When mounting had not yet finished, SocketMonitor::IsDefined() was
always false, due to the workaround at the beginning of the function
that calls SocketMonitor::Steal().  This commit drops the IsDefined()
check because it was never necessary and breaks reconnect.
2014-12-14 22:49:16 +01:00
Max Kellermann
d653f35bb7 lib/nfs/Connection: fix typo in code comment 2014-12-14 22:49:09 +01:00
Max Kellermann
a543627abd lib/nfs/Connection: fix memory leak (and assertion failure)
nfs_destroy_context() will invoke all pending callbacks with
err==-EINTR.  In CancellableCallback::Callback(), this will invoke
NfsConnection::DeferClose(), which however is only designed to be
called from nfs_service().  In non-debug mode, this will leak memory
because nfs_close_async() is never called.

Workaround: before nfs_destroy_context(), invoke nfs_close_async() on
all pending file handles.
2014-12-14 16:02:47 +01:00
Max Kellermann
80f2ba7fca lib/nfs/Connection: move code to Service() 2014-12-14 15:45:10 +01:00
Max Kellermann
32bca64920 lib/nfs/Connection: add assertions 2014-12-14 15:40:29 +01:00
Max Kellermann
7fa1a84ec3 lib/nfs/Connection: move code to method InternalClose() 2014-12-14 15:38:09 +01:00
Max Kellermann
ab4bb26a0a lib/nfs/Connection: make in_service and in_event debug-only flags 2014-12-14 15:20:40 +01:00
Max Kellermann
4b8d258cff lib/nfs/Connection: fix crash while canceling a failing Open()
The method NfsConnection::CancellableCallback::Callback() will always
invoke NfsConnection::Close() on the file handle, even if the void
pointer is not a nfsfh.  This can happen if the Open() was not
successful, e.g. when the file does not exist.
2014-12-14 15:16:01 +01:00
Max Kellermann
3c29aa6271 event/Loop: read the "again" flag while holding mutex 2014-12-14 14:47:36 +01:00
Max Kellermann
51464b4317 lib/nfs/Connection: add assertions 2014-12-14 14:24:49 +01:00
Max Kellermann
2fec463542 util/HugeAllocator: disable MEM_LARGE_PAGES on Windows
MEM_LARGE_PAGES does not appear to work.  Instead, MEM_RESERVE appears
to be necessary.  Until I figure this out, this large pages are
disabled.
2014-12-12 13:20:58 +01:00
Max Kellermann
1affc641c4 input/Init: eliminate double colon from log message 2014-12-12 13:20:37 +01:00
Max Kellermann
0cfd4fff62 playlist/Print: don't skip non-existent songs in "listplaylist"
Skipping those songs silently will confuse the client, because
commands specifying the song index within a playlist
(e.g. playlistdelete) will be out of sync.

This copies spl_print()'s behavior to playlist_file_print().
2014-12-09 13:36:48 +01:00
Max Kellermann
8904127c10 configure.ac: prepare for 0.19.7 2014-12-09 13:09:03 +01:00
Max Kellermann
c46f48abec release v0.19.6 2014-12-08 15:12:41 +01:00
Max Kellermann
4acbf7b90d android/build.py: update FFmpeg to 2.5 2014-12-08 15:05:49 +01:00
Max Kellermann
cbc1a58e93 Merge tag 'v0.18.20' into v0.19.x 2014-12-08 15:03:09 +01:00
Max Kellermann
1b5f33a435 release v0.18.20 2014-12-08 14:57:17 +01:00
Max Kellermann
41b4a63f2b decoder/ffmpeg: support FFmpeg 2.5
Version 2.5 fixed an API oddity, however it broke API compatibility,
at least with C++.  Disable the workaround when a libavformat version
is detected that is recent enough.
2014-12-08 14:25:34 +01:00
Max Kellermann
d8fc2db910 thread/Id: drop "::" prefix before pthread function names
The "::" to explicitly refer to the global namespace appeared like a
good idea in C++, but it breaks with C libraries that implement
standard functions using macros (e.g. musl).
2014-12-08 14:17:17 +01:00
Max Kellermann
dc11dea7cc configure.ac: prepare for 0.18.20 2014-12-08 14:13:20 +01:00
Nix
811af02f56 Output: start with a null mixer.
There are code paths (mostly error cases) in which it is possible to
initialize an AudioOutput and then kill it without ever calling
audio_output_new().  In such a case, its destructor will attempt to
free a mixer that was never initialized, leading to an attempt to
take out a lock on a mutex that was similarly never initialized,
which hangs forever.

Fix by always initializing the mixer appropriately.
2014-12-01 22:14:09 +01:00
Max Kellermann
8780e23ed3 android/build.py: update ffmpeg 2.4.3, curl 7.39 2014-11-28 21:08:27 +01:00
Max Kellermann
be492ed108 android: update libFLAC to 1.3.1
Due to security vulnerabilities.
2014-11-28 21:08:27 +01:00
Max Kellermann
24da14f4f7 .gitignore: ignore /lib/ 2014-11-28 21:08:18 +01:00
Max Kellermann
03d2fb450f configure.ac: prepare for 0.19.6 2014-11-28 20:13:57 +01:00
Max Kellermann
67cba251c8 release v0.19.5 2014-11-26 20:11:42 +01:00
Max Kellermann
0bc511715b Makefile.am: distribute Android sources 2014-11-26 20:11:42 +01:00
Max Kellermann
27ce80544f Merge tag 'v0.18.19' into v0.19.x 2014-11-26 20:02:57 +01:00
Max Kellermann
04f627c2af release v0.18.19 2014-11-26 19:58:48 +01:00
Max Kellermann
e72eef421b lib/nfs/FileReader: clean up on disconnect
Avoids crash because Close() invokes a call on a destructed
NfsConnection.
2014-11-25 14:02:15 +01:00
Max Kellermann
016063c810 lib/nfs/FileReader: move code to CancelOrClose() 2014-11-25 14:00:32 +01:00
Max Kellermann
38f19981b2 lib/nfs/FileReader: reset state in OnNfsConnectionFailed()
Avoid calling NfsConnection::RemoveLease(), because the lease has been
removed already.
2014-11-25 13:51:09 +01:00
Max Kellermann
40dd968f13 lib/nfs/FileReader: update "state" in OnNfsError()
Clean up the "state" to indicate that there is no longer any
asynchronous operation.  Fixes another NFS-related crash due to
cleanup of a non-existing asynchronous operation.
2014-11-25 13:39:42 +01:00
Max Kellermann
3cef348f30 lib/nfs/Manager: defer NfsConnection destruction
Avoids a crash that occurs when NfsConnection::OnSocketReady()
dereferences itself before returning.
2014-11-25 13:31:18 +01:00
Max Kellermann
b293b16007 lib/nfs/Connection: broadcast error before closing connection
During the NfsLease::OnNfsConnectionFailed() call, the old (defunct)
nfs_context may be used to close file handles.  Such code does not yet
exist, but will be added soon to fix other bugs.
2014-11-25 13:27:06 +01:00
Max Kellermann
f5f43db2da lib/nfs/Connection: cancel DeferredMonitor on disconnect
Fixes potential second mount attempt after the old connection to the
NFS server was shut down.
2014-11-25 13:22:25 +01:00
Max Kellermann
029555d192 lib/nfs/FileReader: include Compiler.h for "final" fallback 2014-11-25 13:18:22 +01:00
Max Kellermann
fa4d202e71 decoder/mp4v2: remove because of incompatible license
libmp4v2 is licensed under MPL 1.1, which is incompatible with GPLv2.
Unfortunately, this means that we must remove the plugin.

More information can be found in the Debian bug report:

 http://bugs.debian.org/767504
2014-11-25 13:10:52 +01:00
Max Kellermann
a8ebfd7a92 event/DeferredMonitor: include cleanup 2014-11-25 10:44:06 +01:00
Max Kellermann
b19e5720cc test/run_input: make variables more local 2014-11-25 07:51:33 +01:00
Max Kellermann
a254f5a3a8 archive/zzip: fix inverted error handler
Set the Error when zzip_seek()==-1 and not on success.  Fixes a crash
after seeking.
2014-11-24 22:08:50 +01:00
Max Kellermann
143c735f96 configure.ac: prepare for 0.18.19 2014-11-24 22:08:50 +01:00
Max Kellermann
951bad46e0 decoder/{dsdiff,dsf,opus}: fix deadlock while seeking 2014-11-24 08:54:30 +01:00
Max Kellermann
716225cd2f doc/protocol: mention that "count" can have multiple filters 2014-11-24 08:09:58 +01:00
Max Kellermann
bbc618b8f9 configure.ac: prepare for 0.19.5 2014-11-24 08:09:44 +01:00
Max Kellermann
11ead56d6d android: release v0.19.4
Android releases were missing since 0.19.1.
2014-11-24 08:00:47 +01:00
Max Kellermann
e972ae4afa android: switch to gcc 4.9 / llvm 3.5 (NDK r10c) 2014-11-24 08:00:45 +01:00
Max Kellermann
0709065f50 Java/File: fix include guard 2014-11-24 07:51:25 +01:00
Max Kellermann
d6bc5c35a7 release v0.19.4 2014-11-18 21:40:52 +01:00
Max Kellermann
dc03f003ac Merge tag 'v0.18.18' into v0.19.x 2014-11-18 21:38:44 +01:00
Max Kellermann
7aa2104596 release v0.18.18 2014-11-18 21:34:03 +01:00
Max Kellermann
460cfba6ff QueueCommands: workaround for buggy clients that send "add /" 2014-11-18 21:31:54 +01:00
Max Kellermann
c8b93d6573 Client: assume uid==0 is local socket
A negative uid value means it's not a "local socket" (PF_LOCAL).
uid==0 means user "root" connected.
2014-11-18 20:56:27 +01:00
Max Kellermann
3f5f96ac91 event/ServerSocket: fix get_remote_uid() error value
Must return -1 on error, not 0.  0 is root.
2014-11-18 20:53:59 +01:00
Max Kellermann
7c6b991de7 decoder/opus: add MIME types audio/ogg and application/ogg 2014-11-12 15:16:34 +01:00
Max Kellermann
82460aa49f configure.ac: prepare for 0.19.4 2014-11-12 15:16:07 +01:00
Florent Le Coz
7e7b403043 Construct a Null AllocatedPath if the filename conversion into UTF8 failed 2014-11-11 17:15:19 +01:00
Max Kellermann
c64ad78c7b decoder/ffmpeg: support opus 2014-11-10 18:00:30 +01:00
Max Kellermann
4a043a915f configure.ac: prepare for 0.18.1 2014-11-10 17:59:06 +01:00
43 changed files with 454 additions and 488 deletions

2
.gitignore vendored

@@ -77,6 +77,8 @@ tags
/test/test_vorbis_encoder
/test/DumpDatabase
/lib/
/*.tar.gz
/*.tar.bz2
/*.tar.xz

@@ -119,9 +119,6 @@ For AdLib playback.
despotify - https://github.com/SimonKagstrom/despotify
For Spotify playback.
MP4v2 - https://code.google.com/p/mp4v2/
For MP4 playback. You will need FAAD2.
Optional Miscellaneous Dependencies
-----------------------------------

@@ -828,7 +828,6 @@ libdecoder_a_CPPFLAGS = $(AM_CPPFLAGS) \
$(WAVPACK_CFLAGS) \
$(MAD_CFLAGS) \
$(MPG123_CFLAGS) \
$(MP4V2_CFLAGS) \
$(OPUS_CFLAGS) \
$(FFMPEG_CFLAGS) \
$(MPCDEC_CFLAGS) \
@@ -848,7 +847,6 @@ DECODER_LIBS = \
$(WAVPACK_LIBS) \
$(MAD_LIBS) \
$(MPG123_LIBS) \
$(MP4V2_LIBS) \
$(OPUS_LIBS) \
$(FFMPEG_LIBS2) \
$(MPCDEC_LIBS) \
@@ -963,12 +961,6 @@ noinst_LIBRARIES += libmodplug_decoder_plugin.a
DECODER_LIBS += libmodplug_decoder_plugin.a $(MODPLUG_LIBS)
endif
if HAVE_MP4V2
libdecoder_a_SOURCES += \
src/decoder/plugins/Mp4v2DecoderPlugin.cxx \
src/decoder/plugins/Mp4v2DecoderPlugin.hxx
endif
if ENABLE_SIDPLAY
libdecoder_a_SOURCES += \
src/decoder/plugins/SidplayDecoderPlugin.cxx \
@@ -2187,4 +2179,11 @@ EXTRA_DIST = $(doc_DATA) autogen.sh \
$(wildcard scripts/*.sh) \
$(man_MANS) $(DOCBOOK_FILES) doc/mpdconf.example doc/doxygen.conf \
systemd/mpd.socket \
android/AndroidManifest.xml \
android/build.py \
android/custom_rules.xml \
android/res/values/strings.xml \
android/src/Bridge.java \
android/src/Loader.java \
android/src/Main.java \
src/win32/mpd_win32_rc.rc.in src/win32/mpd.ico

62
NEWS

@@ -1,3 +1,44 @@
ver 0.19.7 (2014/12/17)
* input
- nfs: fix crash while canceling a failing file open operation
- nfs: fix memory leak on connection failure
- nfs: fix reconnect after mount failure
- nfs: implement mount timeout (60 seconds)
* storage
- nfs: implement I/O timeout (60 seconds)
* playlist
- embcue: fix filename suffix detection
- don't skip non-existent songs in "listplaylist"
* decoder
- ffmpeg: fix time stamp underflow
* fix memory allocator bug on Windows
ver 0.19.6 (2014/12/08)
* decoder
- ffmpeg: support FFmpeg 2.5
* fix build failure with musl
* android
- update libFLAC to 1.3.1
- update FFmpeg to 2.5
ver 0.19.5 (2014/11/26)
* input
- nfs: fix crash on connection failure
* archive
- zzip: fix crash after seeking
* decoder
- dsdiff, dsf, opus: fix deadlock while seeking
- mp4v2: remove because of incompatible license
ver 0.19.4 (2014/11/18)
* protocol
- workaround for buggy clients that send "add /"
* decoder
- ffmpeg: support opus
- opus: add MIME types audio/ogg and application/ogg
* fix crash on failed filename charset conversion
* fix local socket detection from uid=0 (root)
ver 0.19.3 (2014/11/11)
* protocol
- fix "(null)" result string to "list" when AlbumArtist is disabled
@@ -120,6 +161,27 @@ ver 0.19 (2014/10/10)
* install systemd unit for socket activation
* Android port
ver 0.18.21 (2014/12/17)
* playlist
- embcue: fix filename suffix detection
* decoder
- ffmpeg: fix time stamp underflow
ver 0.18.20 (2014/12/08)
* decoder
- ffmpeg: support FFmpeg 2.5
* fix build failure with musl
ver 0.18.19 (2014/11/26)
* archive
- zzip: fix crash after seeking
ver 0.18.18 (2014/11/18)
* decoder
- ffmpeg: support opus
* fix crash on failed filename charset conversion
* fix local socket detection from uid=0 (root)
ver 0.18.17 (2014/11/02)
* playlist
- don't allow empty playlist name

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.musicpd"
android:installLocation="auto"
android:versionCode="7"
android:versionName="0.19.1">
android:versionCode="10"
android:versionName="0.19.6">
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17"/>

@@ -40,8 +40,8 @@ build_arch = 'linux-x86_64'
os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(root_path, 'lib/pkgconfig')
# select the NDK compiler
gcc_version = '4.8'
llvm_version = '3.3'
gcc_version = '4.9'
llvm_version = '3.5'
# select the NDK target
ndk_arch = 'arm'
@@ -314,8 +314,8 @@ thirdparty_libs = [
),
AutotoolsProject(
'https://svn.xiph.org/releases/flac/flac-1.3.0.tar.xz',
'13b5c214cee8373464d3d65dee362cdd',
'http://downloads.xiph.org/releases/flac/flac-1.3.1.tar.xz',
'b9922c9a0378c88d3e901b234f852698',
'lib/libFLAC.a',
[
'--disable-shared', '--enable-static',
@@ -341,8 +341,8 @@ thirdparty_libs = [
),
FfmpegProject(
'http://www.ffmpeg.org/releases/ffmpeg-2.2.3.tar.bz2',
'dbb5b6b69bd010916f17df0ae596e0b1',
'http://ffmpeg.org/releases/ffmpeg-2.5.tar.bz2',
'4346fe710cc6bdd981f6534d2420d1ab',
'lib/libavcodec.a',
[
'--disable-shared', '--enable-static',
@@ -366,8 +366,8 @@ thirdparty_libs = [
),
AutotoolsProject(
'http://curl.haxx.se/download/curl-7.37.0.tar.lzma',
'54bfd1eb5214f604186d6f5ac61c7781',
'http://curl.haxx.se/download/curl-7.39.0.tar.lzma',
'e9aa6dec29920eba8ef706ea5823bad7',
'lib/libcurl.a',
[
'--disable-shared', '--enable-static',

@@ -1,10 +1,10 @@
AC_PREREQ(2.60)
AC_INIT(mpd, 0.19.3, musicpd-dev-team@lists.sourceforge.net)
AC_INIT(mpd, 0.19.7, musicpd-dev-team@lists.sourceforge.net)
VERSION_MAJOR=0
VERSION_MINOR=19
VERSION_REVISION=3
VERSION_REVISION=7
VERSION_EXTRA=0
AC_CONFIG_SRCDIR([src/Main.cxx])
@@ -496,11 +496,6 @@ AC_ARG_ENABLE(modplug,
[enable modplug decoder plugin]),,
enable_modplug=auto)
AC_ARG_ENABLE(mp4v2,
AS_HELP_STRING([--enable-mp4v2],
[enable libmp4v2 decoder plugin]),,
enable_mp4v2=auto)
AC_ARG_ENABLE(mpc,
AS_HELP_STRING([--enable-mpc],
[disable musepack (MPC) support (default: auto)]),,
@@ -1264,24 +1259,6 @@ if test x$enable_modplug = xyes; then
fi
AM_CONDITIONAL(HAVE_MODPLUG, test x$enable_modplug = xyes)
dnl -------------------------------- libmp4v2 ---------------------------------
if test x$enable_aac = xyes; then
MPD_AUTO_LIB(mp4v2, MP4V2, mp4v2, MP4Create, [-lmp4v2], [],
[mp4v2], [libmp4v2 not found])
if test x$enable_mp4v2 = xyes; then
AC_DEFINE(HAVE_MP4V2, 1, [Define to use libmp4v2 for MP4 decoding])
fi
else
if test x$enable_mp4v2 = xyes; then
AC_MSG_ERROR([MP4V2 requires AAC!])
fi
enable_mp4v2=no
fi
AM_CONDITIONAL(HAVE_MP4V2, test x$enable_mp4v2 = xyes)
dnl -------------------------------- libopus ----------------------------------
MPD_AUTO_PKG(opus, OPUS, [opus ogg],
[opus decoder plugin], [libopus not found])
@@ -1868,7 +1845,6 @@ printf '\n\t'
results(sndfile, [libsndfile])
results(mikmod, [MikMod])
results(modplug, [MODPLUG])
results(mp4v2, [MP4V2])
results(mad, [MAD])
results(mpg123, [MPG123])
results(mpc, [Musepack])

@@ -1533,6 +1533,7 @@ OK
<command>count</command>
<arg choice="req"><replaceable>TAG</replaceable></arg>
<arg choice="req"><replaceable>NEEDLE</replaceable></arg>
<arg choice="opt"><replaceable>...</replaceable></arg>
<arg choice="opt">group</arg>
<arg choice="opt"><replaceable>GROUPTYPE</replaceable></arg>
</cmdsynopsis>

@@ -194,6 +194,12 @@ FileLog(const Domain &domain, const char *message)
domain.GetName(),
chomp_length(message), message);
#ifdef WIN32
/* force-flush the log file, because setvbuf() does not seem
to have an effect on WIN32 */
fflush(stderr);
#endif
#ifdef HAVE_GLIB
g_free(converted);
#endif

@@ -111,6 +111,9 @@ log_early_init(bool verbose)
#ifdef ANDROID
(void)verbose;
#else
/* force stderr to be line-buffered */
setvbuf(stderr, nullptr, _IOLBF, 0);
if (verbose)
SetLogThreshold(LogLevel::DEBUG);
#endif

@@ -168,12 +168,13 @@ bool
ZzipInputStream::Seek(offset_type new_offset, Error &error)
{
zzip_off_t ofs = zzip_seek(file, new_offset, SEEK_SET);
if (ofs != -1) {
if (ofs < 0) {
error.Set(zzip_domain, "zzip_seek() has failed");
offset = ofs;
return true;
return false;
}
return false;
offset = ofs;
return true;
}
/* exported structures */

@@ -127,7 +127,7 @@ public:
* a local (UNIX domain) socket?
*/
bool IsLocal() const {
return uid > 0;
return uid >= 0;
}
unsigned GetPermission() const {

@@ -41,7 +41,7 @@ Client::AllowFile(Path path_fs, Error &error) const
instance */
return true;
if (uid <= 0) {
if (uid < 0) {
/* unauthenticated client */
error.Set(ack_domain, ACK_ERROR_PERMISSION, "Access denied");
return false;

@@ -61,7 +61,16 @@ translate_uri(Client &client, const char *uri)
CommandResult
handle_add(Client &client, gcc_unused unsigned argc, char *argv[])
{
const char *const uri = translate_uri(client, argv[1]);
const char *uri = argv[1];
if (memcmp(uri, "/", 2) == 0)
/* this URI is malformed, but some clients are buggy
and use "add /" to add the whole database, which
was never intended to work, but once did; in order
to retain backwards compatibility, work around this
here */
uri = "";
uri = translate_uri(client, uri);
if (uri == nullptr)
return CommandResult::ERROR;

@@ -37,7 +37,6 @@
#include "plugins/MadDecoderPlugin.hxx"
#include "plugins/SndfileDecoderPlugin.hxx"
#include "plugins/Mpg123DecoderPlugin.hxx"
#include "plugins/Mp4v2DecoderPlugin.hxx"
#include "plugins/WildmidiDecoderPlugin.hxx"
#include "plugins/MikmodDecoderPlugin.hxx"
#include "plugins/ModplugDecoderPlugin.hxx"
@@ -55,9 +54,6 @@ const struct DecoderPlugin *const decoder_plugins[] = {
#ifdef HAVE_MPG123
&mpg123_decoder_plugin,
#endif
#ifdef HAVE_MP4V2
&mp4v2_decoder_plugin,
#endif
#ifdef ENABLE_VORBIS_DECODER
&vorbis_decoder_plugin,
#endif

@@ -53,7 +53,7 @@ dsdlib_skip_to(Decoder *decoder, InputStream &is,
offset_type offset)
{
if (is.IsSeekable())
return is.Seek(offset, IgnoreError());
return is.LockSeek(offset, IgnoreError());
if (is.GetOffset() > offset)
return false;
@@ -72,7 +72,7 @@ dsdlib_skip(Decoder *decoder, InputStream &is,
return true;
if (is.IsSeekable())
return is.Seek(is.GetOffset() + delta, IgnoreError());
return is.LockSeek(is.GetOffset() + delta, IgnoreError());
if (delta > 1024 * 1024)
/* don't skip more than one megabyte; it would be too

@@ -313,10 +313,13 @@ ffmpeg_send_packet(Decoder &decoder, InputStream &is,
AVFrame *frame,
uint8_t **buffer, int *buffer_size)
{
if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE)
decoder_timestamp(decoder,
time_from_ffmpeg(packet->pts - start_time_fallback(*stream),
stream->time_base));
if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) {
auto start = start_time_fallback(*stream);
if (packet->pts >= start)
decoder_timestamp(decoder,
time_from_ffmpeg(packet->pts - start,
stream->time_base));
}
AVPacket packet2 = *packet;
@@ -423,10 +426,15 @@ ffmpeg_probe(Decoder *decoder, InputStream &is)
avpd.filename = is.GetURI();
#ifdef AVPROBE_SCORE_MIME
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(56, 5, 1)
/* this attribute was added in libav/ffmpeg version 11, but
unfortunately it's "uint8_t" instead of "char", and it's
not "const" - wtf? */
avpd.mime_type = (uint8_t *)const_cast<char *>(is.GetMimeType());
#else
/* API problem fixed in FFmpeg 2.5 */
avpd.mime_type = is.GetMimeType();
#endif
#endif
return av_probe_input_format(&avpd, true);

@@ -1,330 +0,0 @@
/*
* 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" /* must be first for large file support */
#include "Mp4v2DecoderPlugin.hxx"
#include "../DecoderAPI.hxx"
#include "CheckAudioFormat.hxx"
#include "tag/TagHandler.hxx"
#include "fs/Path.hxx"
#include "util/Error.hxx"
#include "util/Domain.hxx"
#include "Log.hxx"
#include <mp4v2/mp4v2.h>
#include <neaacdec.h>
#include <cstdio>
#include <cstdlib>
static constexpr Domain mp4v2_decoder_domain("mp4v2");
static MP4TrackId
mp4_get_aac_track(MP4FileHandle handle, NeAACDecHandle decoder,
AudioFormat &audio_format, Error &error)
{
unsigned long sample_rate;
const MP4TrackId tracks = MP4GetNumberOfTracks(handle);
for (MP4TrackId id = 1; id <= tracks; id++) {
const char* track_type = MP4GetTrackType(handle, id);
if (track_type == 0)
continue;
const auto obj_type = MP4GetTrackEsdsObjectTypeId(handle, id);
if (obj_type == MP4_INVALID_AUDIO_TYPE)
continue;
if (obj_type == MP4_MPEG4_AUDIO_TYPE) {
const auto mpeg_type = MP4GetTrackAudioMpeg4Type(handle, id);
if (!MP4_IS_MPEG4_AAC_AUDIO_TYPE(mpeg_type))
continue;
} else if (!MP4_IS_AAC_AUDIO_TYPE(obj_type))
continue;
if (decoder == nullptr)
/* found audio track, no decoder */
return id;
unsigned char *buff = nullptr;
unsigned buff_size = 0;
if (!MP4GetTrackESConfiguration(handle, id, &buff, &buff_size))
continue;
uint8_t channels;
int32_t nbytes = NeAACDecInit(decoder, buff, buff_size,
&sample_rate, &channels);
free(buff);
if (nbytes < 0)
/* invalid stream */
continue;
if (!audio_format_init_checked(audio_format, sample_rate,
SampleFormat::S16,
channels,
error))
continue;
return id;
}
error.Set(mp4v2_decoder_domain, "no valid aac track found");
return MP4_INVALID_TRACK_ID;
}
static NeAACDecHandle
mp4_faad_new(MP4FileHandle handle, AudioFormat &audio_format, Error &error)
{
const NeAACDecHandle decoder = NeAACDecOpen();
const NeAACDecConfigurationPtr config =
NeAACDecGetCurrentConfiguration(decoder);
config->outputFormat = FAAD_FMT_16BIT;
config->downMatrix = 1;
config->dontUpSampleImplicitSBR = 0;
NeAACDecSetConfiguration(decoder, config);
const auto track = mp4_get_aac_track(handle, decoder, audio_format, error);
if (track == MP4_INVALID_TRACK_ID) {
NeAACDecClose(decoder);
return nullptr;
}
return decoder;
}
static void
mp4_file_decode(Decoder &mpd_decoder, Path path_fs)
{
const MP4FileHandle handle = MP4Read(path_fs.c_str());
if (handle == MP4_INVALID_FILE_HANDLE) {
FormatError(mp4v2_decoder_domain,
"unable to open file");
return;
}
AudioFormat audio_format;
Error error;
const NeAACDecHandle decoder = mp4_faad_new(handle, audio_format, error);
if (decoder == nullptr) {
LogError(error);
MP4Close(handle);
return;
}
const MP4TrackId track = mp4_get_aac_track(handle, nullptr, audio_format, error);
/* initialize the MPD core */
const MP4Timestamp scale = MP4GetTrackTimeScale(handle, track);
const SongTime duration = SongTime::FromScale<uint64_t>(MP4GetTrackDuration(handle, track),
scale);
const MP4SampleId num_samples = MP4GetTrackNumberOfSamples(handle, track);
decoder_initialized(mpd_decoder, audio_format, true, duration);
/* the decoder loop */
DecoderCommand cmd = DecoderCommand::NONE;
for (MP4SampleId sample = 1;
sample < num_samples && cmd != DecoderCommand::STOP;
sample++) {
unsigned char *data = nullptr;
unsigned int data_length = 0;
if (cmd == DecoderCommand::SEEK) {
const MP4Timestamp offset =
decoder_seek_time(mpd_decoder).ToScale(scale);
sample = MP4GetSampleIdFromTime(handle, track, offset,
false);
decoder_command_finished(mpd_decoder);
}
/* read */
if (MP4ReadSample(handle, track, sample, &data, &data_length) == 0) {
FormatError(mp4v2_decoder_domain, "unable to read sample");
break;
}
/* decode it */
NeAACDecFrameInfo frame_info;
const void *const decoded = NeAACDecDecode(decoder, &frame_info, data, data_length);
if (frame_info.error > 0) {
FormatWarning(mp4v2_decoder_domain,
"error decoding AAC stream: %s",
NeAACDecGetErrorMessage(frame_info.error));
break;
}
if (frame_info.channels != audio_format.channels) {
FormatDefault(mp4v2_decoder_domain,
"channel count changed from %u to %u",
audio_format.channels, frame_info.channels);
break;
}
if (frame_info.samplerate != audio_format.sample_rate) {
FormatDefault(mp4v2_decoder_domain,
"sample rate changed from %u to %lu",
audio_format.sample_rate,
(unsigned long)frame_info.samplerate);
break;
}
/* update bit rate and position */
unsigned bit_rate = 0;
if (frame_info.samples > 0) {
bit_rate = frame_info.bytesconsumed * 8.0 *
frame_info.channels * audio_format.sample_rate /
frame_info.samples / 1000 + 0.5;
}
/* send PCM samples to MPD */
cmd = decoder_data(mpd_decoder, nullptr, decoded,
(size_t)frame_info.samples * 2,
bit_rate);
free(data);
}
/* cleanup */
NeAACDecClose(decoder);
MP4Close(handle);
}
static inline void
mp4_safe_invoke_tag(const struct tag_handler *handler, void *handler_ctx,
TagType tag, const char *value)
{
if (value != nullptr)
tag_handler_invoke_tag(handler, handler_ctx, tag, value);
}
static bool
mp4_scan_file(Path path_fs,
const struct tag_handler *handler, void *handler_ctx)
{
const MP4FileHandle handle = MP4Read(path_fs.c_str());
if (handle == MP4_INVALID_FILE_HANDLE)
return false;
AudioFormat tmp_audio_format;
Error error;
const MP4TrackId id = mp4_get_aac_track(handle, nullptr, tmp_audio_format, error);
if (id == MP4_INVALID_TRACK_ID) {
LogError(error);
MP4Close(handle);
return false;
}
const MP4Timestamp scale = MP4GetTrackTimeScale(handle, id);
const SongTime dur =
SongTime::FromScale<uint64_t>(MP4GetTrackDuration(handle, id),
scale);
tag_handler_invoke_duration(handler, handler_ctx, dur);
const MP4Tags* tags = MP4TagsAlloc();
MP4TagsFetch(tags, handle);
static constexpr struct {
const char *MP4Tags::*p;
TagType tag_type;
} mp4v2_tags[] = {
{ &MP4Tags::name, TAG_NAME },
{ &MP4Tags::artist, TAG_ARTIST },
{ &MP4Tags::albumArtist, TAG_ALBUM_ARTIST },
{ &MP4Tags::album, TAG_ALBUM },
{ &MP4Tags::composer, TAG_COMPOSER },
{ &MP4Tags::comments, TAG_COMMENT },
{ &MP4Tags::genre, TAG_GENRE },
{ &MP4Tags::releaseDate, TAG_DATE },
{ &MP4Tags::sortArtist, TAG_ARTIST_SORT },
{ &MP4Tags::sortAlbumArtist, TAG_ALBUM_ARTIST_SORT },
};
for (const auto &i : mp4v2_tags)
mp4_safe_invoke_tag(handler, handler_ctx,
i.tag_type, tags->*i.p);
char buff[8]; /* tmp buffer for index to string. */
if (tags->track != nullptr) {
sprintf(buff, "%d", tags->track->index);
tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, buff);
}
if (tags->disk != nullptr) {
sprintf(buff, "%d", tags->disk->index);
tag_handler_invoke_tag(handler, handler_ctx, TAG_DISC, buff);
}
MP4TagsFree(tags);
MP4Close(handle);
return true;
}
static const char *const mp4_suffixes[] = {
"mp4",
"m4a",
/* "m4p", encrypted */
/* "m4b", audio book */
/* "m4r", ring tones */
/* "m4v", video */
nullptr
};
static const char *const mp4_mime_types[] = {
"application/mp4",
"application/m4a",
"audio/mp4",
"audio/m4a",
/* "audio/m4p", */
/* "audio/m4b", */
/* "audio/m4r", */
/* "audio/m4v", */
nullptr
};
const struct DecoderPlugin mp4v2_decoder_plugin = {
"mp4v2",
nullptr,
nullptr,
nullptr,
mp4_file_decode,
mp4_scan_file,
nullptr,
nullptr,
mp4_suffixes,
mp4_mime_types
};

@@ -1,25 +0,0 @@
/*
* 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_DECODER_MP4V2_HXX
#define MPD_DECODER_MP4V2_HXX
extern const struct DecoderPlugin mp4v2_decoder_plugin;
#endif

@@ -214,7 +214,7 @@ LoadEOSPacket(InputStream &is, Decoder *decoder, int serialno,
ogg_stream_clear(&os);
/* restore the previous file position */
is.Seek(old_offset, IgnoreError());
is.LockSeek(old_offset, IgnoreError());
return result;
}
@@ -510,6 +510,13 @@ static const char *const opus_suffixes[] = {
};
static const char *const opus_mime_types[] = {
/* the official MIME type (RFC 5334) */
"audio/ogg",
/* deprecated (RFC 5334) */
"application/ogg",
/* deprecated; from an early draft */
"audio/opus",
nullptr
};

@@ -21,9 +21,6 @@
#define MPD_SOCKET_DEFERRED_MONITOR_HXX
#include "check.h"
#include "Compiler.h"
#include <atomic>
class EventLoop;

@@ -179,9 +179,10 @@ EventLoop::Run()
mutex.lock();
HandleDeferred();
busy = false;
const bool _again = again;
mutex.unlock();
if (again)
if (_again)
/* re-evaluate timers because one of the
IdleMonitors may have added a new
timeout */

@@ -130,7 +130,7 @@ get_remote_uid(int fd)
socklen_t len = sizeof (cred);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) < 0)
return 0;
return -1;
return cred.uid;
#else

@@ -46,7 +46,11 @@ AllocatedPath
AllocatedPath::FromUTF8(const char *path_utf8)
{
#ifdef HAVE_GLIB
return AllocatedPath(Donate(), ::PathFromUTF8(path_utf8));
char *path = ::PathFromUTF8(path_utf8);
if (path == nullptr)
return AllocatedPath::Null();
return AllocatedPath(Donate(), path);
#else
return FromFS(path_utf8);
#endif

@@ -67,7 +67,7 @@ input_stream_global_init(Error &error)
case InputPlugin::InitResult::UNAVAILABLE:
if (error.IsDefined()) {
FormatError(error,
"Input plugin '%s' is unavailable: ",
"Input plugin '%s' is unavailable",
plugin->name);
error.Clear();
}

@@ -43,6 +43,8 @@
#include <alsa/asoundlib.h>
#include <atomic>
#include <assert.h>
#include <string.h>

@@ -28,7 +28,7 @@
*/
#ifndef JAVA_FILE_HXX
#define JAVA_FILE_HPP
#define JAVA_FILE_HXX
#include "Object.hxx"

@@ -20,7 +20,9 @@
#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)
@@ -31,7 +33,10 @@ BlockingNfsOperation::Run(Error &_error)
[this](){ connection.AddLease(*this); });
/* wait for completion */
LockWaitFinished();
if (!LockWaitFinished()) {
_error.Set(nfs_domain, 0, "Timeout");
return false;
}
/* check for error */
if (error.IsDefined()) {

@@ -35,6 +35,8 @@ class NfsConnection;
* thread, and method Run() waits for completion.
*/
class BlockingNfsOperation : protected NfsCallback, NfsLease {
static constexpr unsigned timeout_ms = 60000;
Mutex mutex;
Cond cond;
@@ -52,10 +54,13 @@ public:
bool Run(Error &error);
private:
void LockWaitFinished() {
bool LockWaitFinished() {
const ScopeLock protect(mutex);
while (!finished)
cond.wait(mutex);
if (!cond.timed_wait(mutex, timeout_ms))
return false;
return true;
}
/**

@@ -157,6 +157,12 @@ public:
return *i;
}
template<typename F>
void ForEach(F &&f) {
for (CT &i : list)
f(i);
}
};
#endif

@@ -34,11 +34,15 @@ extern "C" {
#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",
@@ -54,6 +58,8 @@ 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",
@@ -69,6 +75,8 @@ 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) {
@@ -85,6 +93,8 @@ 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",
@@ -100,6 +110,8 @@ 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",
@@ -113,6 +125,7 @@ NfsConnection::CancellableCallback::Read(nfs_context *ctx, struct nfsfh *fh,
inline void
NfsConnection::CancellableCallback::CancelAndScheduleClose(struct nfsfh *fh)
{
assert(connection.GetEventLoop().IsInside());
assert(!open);
assert(close_fh == nullptr);
assert(fh != nullptr);
@@ -121,9 +134,22 @@ NfsConnection::CancellableCallback::CancelAndScheduleClose(struct nfsfh *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);
@@ -143,8 +169,10 @@ NfsConnection::CancellableCallback::Callback(int err, void *data)
allocated file handle immediately */
assert(close_fh == nullptr);
struct nfsfh *fh = (struct nfsfh *)data;
connection.Close(fh);
if (err >= 0) {
struct nfsfh *fh = (struct nfsfh *)data;
connection.Close(fh);
}
} else if (close_fh != nullptr)
connection.DeferClose(close_fh);
@@ -209,6 +237,7 @@ NfsConnection::RemoveLease(NfsLease &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);
@@ -225,6 +254,7 @@ bool
NfsConnection::OpenDirectory(const char *path, NfsCallback &callback,
Error &error)
{
assert(GetEventLoop().IsInside());
assert(!callbacks.Contains(callback));
auto &c = callbacks.Add(callback, *this, true);
@@ -240,12 +270,16 @@ NfsConnection::OpenDirectory(const char *path, NfsCallback &callback,
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);
}
@@ -253,6 +287,7 @@ 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);
@@ -268,6 +303,7 @@ NfsConnection::Open(const char *path, int flags, NfsCallback &callback,
bool
NfsConnection::Stat(struct nfsfh *fh, NfsCallback &callback, Error &error)
{
assert(GetEventLoop().IsInside());
assert(!callbacks.Contains(callback));
auto &c = callbacks.Add(callback, *this, false);
@@ -284,6 +320,7 @@ 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);
@@ -307,10 +344,22 @@ 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)
{
nfs_close_async(context, fh, DummyCallback, nullptr);
assert(GetEventLoop().IsInside());
InternalClose(fh);
ScheduleSocket();
}
@@ -327,8 +376,26 @@ 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::Cancel();
SocketMonitor::Steal();
callbacks.ForEach([](CancellableCallback &c){
c.PrepareDestroyContext();
});
nfs_destroy_context(context);
context = nullptr;
@@ -337,8 +404,11 @@ NfsConnection::DestroyContext()
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);
}
@@ -346,6 +416,7 @@ NfsConnection::DeferClose(struct nfsfh *fh)
void
NfsConnection::ScheduleSocket()
{
assert(GetEventLoop().IsInside());
assert(context != nullptr);
if (!SocketMonitor::IsDefined()) {
@@ -360,9 +431,35 @@ NfsConnection::ScheduleSocket()
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;
@@ -374,21 +471,10 @@ NfsConnection::OnSocketReady(unsigned flags)
re-register it each time */
SocketMonitor::Steal();
assert(!in_event);
in_event = true;
assert(!in_service);
in_service = true;
int result = nfs_service(context, events_to_libnfs(flags));
assert(context != nullptr);
assert(in_service);
in_service = false;
const int result = Service(flags);
while (!deferred_close.empty()) {
nfs_close_async(context, deferred_close.front(),
DummyCallback, nullptr);
InternalClose(deferred_close.front());
deferred_close.pop_front();
}
@@ -405,13 +491,13 @@ NfsConnection::OnSocketReady(unsigned flags)
error.Format(nfs_domain, "NFS connection has failed: %s",
nfs_get_error(context));
BroadcastError(std::move(error));
DestroyContext();
closed = true;
BroadcastError(std::move(error));
} else if (SocketMonitor::IsDefined() && nfs_get_fd(context) < 0) {
} else if (nfs_get_fd(context) < 0) {
/* this happens when rpc_reconnect_requeue() is called
after the connection broke, but autoreconnet was
after the connection broke, but autoreconnect was
disabled - nfs_service() returns 0 */
Error error;
const char *msg = nfs_get_error(context);
@@ -421,14 +507,18 @@ NfsConnection::OnSocketReady(unsigned flags)
error.Format(nfs_domain,
"NFS socket disappeared: %s", msg);
BroadcastError(std::move(error));
DestroyContext();
closed = true;
BroadcastError(std::move(error));
}
assert(context == nullptr || nfs_get_fd(context) >= 0);
#ifndef NDEBUG
assert(in_event);
in_event = false;
#endif
if (context != nullptr)
ScheduleSocket();
@@ -440,10 +530,14 @@ 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",
@@ -464,6 +558,7 @@ NfsConnection::MountCallback(int status, nfs_context *nfs, void *data,
inline bool
NfsConnection::MountInternal(Error &error)
{
assert(GetEventLoop().IsInside());
assert(context == nullptr);
context = nfs_init_context();
@@ -474,8 +569,14 @@ NfsConnection::MountInternal(Error &error)
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) {
@@ -531,9 +632,23 @@ NfsConnection::BroadcastError(Error &&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)) {

@@ -23,6 +23,7 @@
#include "Lease.hxx"
#include "Cancellable.hxx"
#include "event/SocketMonitor.hxx"
#include "event/TimeoutMonitor.hxx"
#include "event/DeferredMonitor.hxx"
#include "util/Error.hxx"
@@ -40,7 +41,7 @@ class NfsCallback;
/**
* An asynchronous connection to a NFS server.
*/
class NfsConnection : SocketMonitor, DeferredMonitor {
class NfsConnection : SocketMonitor, TimeoutMonitor, DeferredMonitor {
class CancellableCallback : public CancellablePointer<NfsCallback> {
NfsConnection &connection;
@@ -84,6 +85,13 @@ class NfsConnection : SocketMonitor, DeferredMonitor {
*/
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);
@@ -111,6 +119,7 @@ class NfsConnection : SocketMonitor, DeferredMonitor {
Error postponed_mount_error;
#ifndef NDEBUG
/**
* True when nfs_service() is being called.
*/
@@ -122,13 +131,20 @@ class NfsConnection : SocketMonitor, DeferredMonitor {
*/
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), DeferredMonitor(_loop),
:SocketMonitor(_loop), TimeoutMonitor(_loop),
DeferredMonitor(_loop),
server(_server), export_name(_export_name),
context(nullptr) {}
@@ -184,6 +200,11 @@ protected:
private:
void DestroyContext();
/**
* Wrapper for nfs_close_async().
*/
void InternalClose(struct nfsfh *fh);
/**
* Invoke nfs_close_async() after nfs_service() returns.
*/
@@ -200,9 +221,17 @@ private:
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;
};

@@ -56,8 +56,18 @@ NfsFileReader::Close()
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 */
@@ -164,6 +174,8 @@ NfsFileReader::OnNfsConnectionFailed(const Error &error)
{
assert(state == State::MOUNT);
state = State::INITIAL;
Error copy;
copy.Set(error);
OnNfsFileError(std::move(copy));
@@ -174,7 +186,7 @@ NfsFileReader::OnNfsConnectionDisconnected(const Error &error)
{
assert(state > State::MOUNT);
state = State::INITIAL;
CancelOrClose();
Error copy;
copy.Set(error);
@@ -246,6 +258,30 @@ NfsFileReader::OnNfsCallback(unsigned status, void *data)
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));
}

@@ -24,6 +24,7 @@
#include "Lease.hxx"
#include "Callback.hxx"
#include "event/DeferredMonitor.hxx"
#include "Compiler.h"
#include <string>
@@ -75,6 +76,12 @@ protected:
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);

@@ -29,8 +29,10 @@ NfsManager::ManagedConnection::OnNfsConnectionError(Error &&error)
{
FormatError(error, "NFS error on %s:%s", GetServer(), GetExportName());
manager.connections.erase(manager.connections.iterator_to(*this));
delete this;
/* defer deletion so the caller
(i.e. NfsConnection::OnSocketReady()) can still use this
object */
manager.ScheduleDelete(*this);
}
inline bool
@@ -59,7 +61,9 @@ NfsManager::Compare::operator()(const ManagedConnection &a,
NfsManager::~NfsManager()
{
assert(loop.IsInside());
assert(GetEventLoop().IsInside());
CollectGarbage();
connections.clear_and_dispose([](ManagedConnection *c){
delete c;
@@ -71,13 +75,13 @@ NfsManager::GetConnection(const char *server, const char *export_name)
{
assert(server != nullptr);
assert(export_name != nullptr);
assert(loop.IsInside());
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, loop,
auto c = new ManagedConnection(*this, GetEventLoop(),
server, export_name);
connections.insert_commit(*c, hint);
return *c;
@@ -85,3 +89,19 @@ NfsManager::GetConnection(const char *server, const char *export_name)
return *result.first;
}
}
void
NfsManager::CollectGarbage()
{
assert(GetEventLoop().IsInside());
garbage.clear_and_dispose([](ManagedConnection *c){
delete c;
});
}
void
NfsManager::OnIdle()
{
CollectGarbage();
}

@@ -23,14 +23,16 @@
#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 {
class NfsManager final : IdleMonitor {
struct LookupKey {
const char *server;
const char *export_name;
@@ -38,6 +40,7 @@ class NfsManager {
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;
@@ -63,8 +66,6 @@ class NfsManager {
const LookupKey b) const;
};
EventLoop &loop;
/**
* Maps server and export_name to #ManagedConnection.
*/
@@ -74,9 +75,18 @@ class NfsManager {
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)
:loop(_loop) {}
:IdleMonitor(_loop) {}
/**
* Must be run from EventLoop's thread.
@@ -86,6 +96,21 @@ public:
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

@@ -48,6 +48,7 @@
AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin)
:plugin(_plugin),
mixer(nullptr),
enabled(true), really_enabled(false),
open(false),
pause(false),

@@ -43,12 +43,13 @@ playlist_provider_print(Client &client, const char *uri,
DetachedSong *song;
while ((song = e.NextSong()) != nullptr) {
if (playlist_check_translate_song(*song, base_uri.c_str(),
loader)) {
if (detail)
song_print_info(client, *song);
else
song_print_uri(client, *song);
}
loader) &&
detail)
song_print_info(client, *song);
else
/* fallback if no detail was requested or no
detail was available */
song_print_uri(client, *song);
delete song;
}

@@ -178,7 +178,7 @@ const struct playlist_plugin embcue_playlist_plugin = {
embcue_playlist_open_uri,
nullptr,
nullptr,
embcue_playlist_suffixes,
nullptr,
nullptr,
};

@@ -146,6 +146,7 @@ private:
const ScopeLock protect(mutex);
state = _state;
last_error.Clear();
last_error.Set(error);
cond.broadcast();
}

@@ -75,7 +75,7 @@ public:
#ifdef WIN32
return ::GetCurrentThreadId();
#else
return ::pthread_self();
return pthread_self();
#endif
}
@@ -84,7 +84,7 @@ public:
#ifdef WIN32
return id == other.id;
#else
return ::pthread_equal(id, other.id);
return pthread_equal(id, other.id);
#endif
}

@@ -70,7 +70,10 @@ gcc_malloc
static inline void *
HugeAllocate(size_t size)
{
return VirtualAlloc(nullptr, size, MEM_LARGE_PAGES, PAGE_READWRITE);
// TODO: use MEM_LARGE_PAGES
return VirtualAlloc(nullptr, size,
MEM_COMMIT|MEM_RESERVE,
PAGE_READWRITE);
}
static inline void

@@ -54,11 +54,6 @@ tag_save(FILE *file, const Tag &tag)
static int
dump_input_stream(InputStream *is)
{
Error error;
char buffer[4096];
size_t num_read;
ssize_t num_written;
is->Lock();
/* print meta data */
@@ -76,7 +71,9 @@ dump_input_stream(InputStream *is)
delete tag;
}
num_read = is->Read(buffer, sizeof(buffer), error);
Error error;
char buffer[4096];
size_t num_read = is->Read(buffer, sizeof(buffer), error);
if (num_read == 0) {
if (error.IsDefined())
LogError(error);
@@ -84,11 +81,12 @@ dump_input_stream(InputStream *is)
break;
}
num_written = write(1, buffer, num_read);
ssize_t num_written = write(1, buffer, num_read);
if (num_written <= 0)
break;
}
Error error;
if (!is->Check(error)) {
LogError(error);
is->Unlock();
@@ -102,10 +100,6 @@ dump_input_stream(InputStream *is)
int main(int argc, char **argv)
{
Error error;
InputStream *is;
int ret;
if (argc != 2) {
fprintf(stderr, "Usage: run_input URI\n");
return EXIT_FAILURE;
@@ -129,6 +123,7 @@ int main(int argc, char **argv)
archive_plugin_init_all();
#endif
Error error;
if (!input_stream_global_init(error)) {
LogError(error);
return 2;
@@ -139,7 +134,8 @@ int main(int argc, char **argv)
Mutex mutex;
Cond cond;
is = InputStream::OpenReady(argv[1], mutex, cond, error);
InputStream *is = InputStream::OpenReady(argv[1], mutex, cond, error);
int ret;
if (is != NULL) {
ret = dump_input_stream(is);
delete is;