From 157f0919a2966dc210e82fbff6740ab1f77f8a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vasconcellos?= Date: Sun, 29 Sep 2024 23:18:00 -0300 Subject: [PATCH 01/13] Update to version 1.3.1 --- Makefile.am | 4 + NEWS | 8 + buildroot/Config.in | 1 + buildroot/build-static.sh | 13 + buildroot/external.desc | 2 + buildroot/external.mk | 1 + buildroot/readymedia/Config.in | 24 ++ buildroot/readymedia/readymedia.mk | 30 +++ buildroot/readymedia_defconfig | 28 ++ configure.ac | 18 +- event.h | 2 +- kqueue.c | 24 +- metadata.c | 8 + minidlna.c | 104 ++++---- minissdp.c | 10 +- monitor.c | 361 +------------------------ monitor.h | 15 +- monitor_inotify.c | 408 +++++++++++++++++++++++++++++ monitor_kqueue.c | 28 +- process.c | 5 +- select.c | 9 +- tagutils/tagutils-dsf.c | 1 + tivo_utils.c | 2 + tivo_utils.h | 3 +- upnpevents.c | 25 +- upnpglobalvars.h | 7 +- upnphttp.c | 21 +- upnphttp.h | 2 + utils.c | 4 +- 29 files changed, 690 insertions(+), 478 deletions(-) create mode 100644 buildroot/Config.in create mode 100755 buildroot/build-static.sh create mode 100644 buildroot/external.desc create mode 100644 buildroot/external.mk create mode 100644 buildroot/readymedia/Config.in create mode 100644 buildroot/readymedia/readymedia.mk create mode 100644 buildroot/readymedia_defconfig create mode 100644 monitor_inotify.c diff --git a/Makefile.am b/Makefile.am index 74859c1..1e33833 100644 --- a/Makefile.am +++ b/Makefile.am @@ -36,6 +36,10 @@ else minidlnad_SOURCES += select.c endif +if HAVE_INOTIFY +minidlnad_SOURCES += monitor_inotify.c +endif + if HAVE_VORBISFILE vorbislibs = -lvorbis -logg else diff --git a/NEWS b/NEWS index 634a304..8819873 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,13 @@ (P Versions are the Password support versions, we keep the same base "1.x.y" as the upstream version) +1.3.1 - Released 11-Feb-2022 +-------------------------------- +- Fixed a potential crash in SSDP request parsing. +- Fixed a configure script failure on some platforms. +- Protect against DNS rebinding attacks. +- Fix an socket leakage issue on some platforms. +- Minor bug fixes. + 1.3.0.1P - Released 20-Aug-2021 -------------------------------- - Fixes in the 1.3.0 Migration (Thanks Vitor, PR #22) diff --git a/buildroot/Config.in b/buildroot/Config.in new file mode 100644 index 0000000..24513f6 --- /dev/null +++ b/buildroot/Config.in @@ -0,0 +1 @@ +source "$BR2_EXTERNAL_READYMEDIA_PATH/readymedia/Config.in" diff --git a/buildroot/build-static.sh b/buildroot/build-static.sh new file mode 100755 index 0000000..483f7de --- /dev/null +++ b/buildroot/build-static.sh @@ -0,0 +1,13 @@ +#!/bin/bash -e +if [ $# -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi +BUILDROOT_DIR=$1 +BR2_DEFCONFIG=$(realpath readymedia_defconfig) +export BR2_EXTERNAL=$(realpath .) +cd $BUILDROOT_DIR +make O=output-readymedia defconfig BR2_DEFCONFIG=${BR2_DEFCONFIG} +make O=output-readymedia +echo -e "\n\nStatic binary built in $(realpath output/target/usr/sbin/minidlnad)" +ls -lh $(realpath output/target/usr/sbin/minidlnad) diff --git a/buildroot/external.desc b/buildroot/external.desc new file mode 100644 index 0000000..ba16cb1 --- /dev/null +++ b/buildroot/external.desc @@ -0,0 +1,2 @@ +name: READYMEDIA +desc: ReadyMedia external tree diff --git a/buildroot/external.mk b/buildroot/external.mk new file mode 100644 index 0000000..b97f45f --- /dev/null +++ b/buildroot/external.mk @@ -0,0 +1 @@ +include $(sort $(wildcard $(BR2_EXTERNAL_READYMEDIA_PATH)/*/*.mk)) diff --git a/buildroot/readymedia/Config.in b/buildroot/readymedia/Config.in new file mode 100644 index 0000000..b279b3e --- /dev/null +++ b/buildroot/readymedia/Config.in @@ -0,0 +1,24 @@ +config BR2_PACKAGE_READYMEDIA + bool "readymedia" + depends on BR2_USE_WCHAR # flac + depends on BR2_USE_MMU # fork + depends on BR2_TOOLCHAIN_HAS_THREADS + depends on BR2_PACKAGE_FFMPEG_ARCH_SUPPORTS + select BR2_PACKAGE_FFMPEG + select BR2_PACKAGE_FLAC + select BR2_PACKAGE_LIBVORBIS # selects libogg + select BR2_PACKAGE_LIBOGG + select BR2_PACKAGE_LIBID3TAG # selects zlib + select BR2_PACKAGE_LIBEXIF + select BR2_PACKAGE_JPEG + select BR2_PACKAGE_SQLITE + help + MiniDLNA (aka ReadyDLNA) is server software with the aim of + being fully compliant with DLNA/UPnP-AV clients. + + http://minidlna.sourceforge.net/ + +comment "readymedia needs a toolchain w/ threads, wchar" + depends on BR2_USE_MMU + depends on BR2_PACKAGE_FFMPEG_ARCH_SUPPORTS + depends on !BR2_TOOLCHAIN_HAS_THREADS || !BR2_USE_WCHAR diff --git a/buildroot/readymedia/readymedia.mk b/buildroot/readymedia/readymedia.mk new file mode 100644 index 0000000..91d37d0 --- /dev/null +++ b/buildroot/readymedia/readymedia.mk @@ -0,0 +1,30 @@ +################################################################################ +# +# readymedia +# +################################################################################ + +READYMEDIA_VERSION = v1_3_1 +READYMEDIA_SITE = https://git.code.sf.net/p/minidlna/git +READYMEDIA_SITE_METHOD = git +READYMEDIA_LICENSE = GPL-2.0, BSD-3-Clause +READYMEDIA_LICENSE_FILES = COPYING LICENCE.miniupnpd +READYMEDIA_CPE_ID_VENDOR = readymedia_project +READYMEDIA_CPE_ID_PRODUCT = readymedia + +READYMEDIA_DEPENDENCIES = \ + $(TARGET_NLS_DEPENDENCIES) \ + ffmpeg flac libvorbis libogg libid3tag libexif jpeg sqlite \ + host-xutil_makedepend + +READYMEDIA_CONF_OPTS = \ + --enable-static \ + --enable-tivo \ + --enable-lto + +define READYMEDIA_RUN_AUTOGEN + cd $(@D) && PATH=$(BR_PATH) ./autogen.sh +endef +READYMEDIA_PRE_CONFIGURE_HOOKS = READYMEDIA_RUN_AUTOGEN + +$(eval $(autotools-package)) diff --git a/buildroot/readymedia_defconfig b/buildroot/readymedia_defconfig new file mode 100644 index 0000000..47252d4 --- /dev/null +++ b/buildroot/readymedia_defconfig @@ -0,0 +1,28 @@ +BR2_SHARED_STATIC_LIBS=y +BR2_TOOLCHAIN_BUILDROOT_VENDOR="readymedia" +BR2_TOOLCHAIN_BUILDROOT_MUSL=y +BR2_KERNEL_HEADERS_VERSION=y +BR2_DEFAULT_KERNEL_VERSION="2.6.32" +BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_REALLY_OLD=y +BR2_BINUTILS_VERSION_2_37_X=y +BR2_GCC_VERSION_11_X=y +BR2_GCC_ENABLE_LTO=y +BR2_TARGET_GENERIC_HOSTNAME="readymedia" +BR2_INIT_NONE=y +# BR2_TARGET_ENABLE_ROOT_LOGIN is not set +# BR2_TARGET_GENERIC_GETTY is not set +# BR2_TARGET_GENERIC_REMOUNT_ROOTFS_RW is not set +# BR2_PACKAGE_BUSYBOX is not set +BR2_PACKAGE_FFMPEG_ENCODERS="" +BR2_PACKAGE_FFMPEG_DECODERS="aac alac eac3 flac flv h264 mp3 mpeg2video mpeg4 pcm_s16be pcm_s16le vorbis wmalossless wmapro wmav1 wmav2" +BR2_PACKAGE_FFMPEG_MUXERS="" +BR2_PACKAGE_FFMPEG_DEMUXERS="aac ac3 aiff asf avi dts dv eac3 flac flv h261 h263 h264 matroska mjpeg mov m4v mp3 mpegps mpegts mpegtsraw mpegvideo ogg vc1 wav" +BR2_PACKAGE_FFMPEG_PARSERS="aac ac3 dvbsub dvdsub flac h261 h263 h264 mjpeg mlp mpeg4video mpegaudio mpegvideo vc1 vorbis vp3 vp8" +BR2_PACKAGE_FFMPEG_BSFS="" +BR2_PACKAGE_FFMPEG_PROTOCOLS="file" +BR2_PACKAGE_FFMPEG_FILTERS="" +# BR2_PACKAGE_FFMPEG_INDEVS is not set +# BR2_PACKAGE_FFMPEG_OUTDEVS is not set +BR2_PACKAGE_FFMPEG_EXTRACONF="--enable-small --disable-pic --disable-avfilter --disable-libvorbis --disable-network --disable-muxer=ogg" +# BR2_PACKAGE_IFUPDOWN_SCRIPTS is not set +BR2_PACKAGE_READYMEDIA=y diff --git a/configure.ac b/configure.ac index a2b1e5b..5d03757 100644 --- a/configure.ac +++ b/configure.ac @@ -29,6 +29,7 @@ m4_ifdef([AC_USE_SYSTEM_EXTENSIONS], [AC_USE_SYSTEM_EXTENSIONS]) AM_ICONV AM_GNU_GETTEXT([external]) AM_GNU_GETTEXT_VERSION(0.18) +AM_GNU_GETTEXT_REQUIRE_VERSION(0.18) # Checks for programs. AC_PROG_AWK @@ -414,7 +415,10 @@ for dir in "" /usr/local $SEARCH_DIR; do AC_CHECK_LIB([id3tag -lz], [id3_file_open], [LIBID3TAG_LIBS="-lid3tag -lz"], [unset ac_cv_lib_id3tag_id3_file_open; LDFLAGS="$LDFLAGS_SAVE"; continue]) break done -test x"$ac_cv_lib_id3tag__lz___id3_file_open" = x"yes" || AC_MSG_ERROR([Could not find libid3tag]) +if test x"$ac_cv_lib_id3tag__lz___id3_file_open" != x"yes" && + test x"$ac_cv_lib_id3tag__lz_id3_file_open" != x"yes"; then + AC_MSG_ERROR([Could not find libid3tag]) +fi AC_SUBST(LIBID3TAG_LIBS) LDFLAGS_SAVE="$LDFLAGS" @@ -441,7 +445,8 @@ for dir in "" /usr/local $SEARCH_DIR; do break done if test x"$ac_cv_lib_avformat__lavcodec__lavutil__lz___av_open_input_file" != x"yes" && - test x"$ac_cv_lib_avformat__lavcodec__lavutil__lz___avformat_open_input" != x"yes"; then + test x"$ac_cv_lib_avformat__lavcodec__lavutil__lz___avformat_open_input" != x"yes" && + test x"$ac_cv_lib_avformat__lavcodec__lavutil__lz_avformat_open_input" != x"yes"; then AC_MSG_ERROR([Could not find libavformat - part of ffmpeg]) fi AC_SUBST(LIBAVFORMAT_LIBS) @@ -490,7 +495,12 @@ AC_CHECK_HEADERS([arpa/inet.h asm/unistd.h endian.h machine/endian.h fcntl.h lib test x"$ac_cv_header_poll_h" != x"yes" && AC_MSG_ERROR([poll.h not found or not usable]) test x"$ac_cv_header_sys_queue_h" != x"yes" && AC_MSG_ERROR([sys/queue.h not found or not usable]) -AC_CHECK_FUNCS(inotify_init, AC_DEFINE(HAVE_INOTIFY,1,[Whether kernel has inotify support]), [ +AC_CHECK_FUNCS(inotify_init, +[ + AC_DEFINE(HAVE_INOTIFY,1,[Whether kernel has inotify support]) + AM_CONDITIONAL(HAVE_INOTIFY, true) +], +[ AC_MSG_CHECKING([for __NR_inotify_init syscall]) AC_COMPILE_IFELSE( [AC_LANG_PROGRAM( @@ -506,9 +516,11 @@ AC_CHECK_FUNCS(inotify_init, AC_DEFINE(HAVE_INOTIFY,1,[Whether kernel has inotif [ AC_MSG_RESULT([yes]) AC_DEFINE(HAVE_INOTIFY,1,[Whether kernel has inotify support]) + AM_CONDITIONAL(HAVE_INOTIFY, true) ], [ AC_MSG_RESULT([no]) + AM_CONDITIONAL(HAVE_INOTIFY, false) ]) ]) diff --git a/event.h b/event.h index c05c61b..6b30a21 100644 --- a/event.h +++ b/event.h @@ -42,7 +42,7 @@ typedef int event_module_add_t(struct event *); typedef int event_module_del_t(struct event *, int flags); typedef int event_module_init_t(void); typedef void event_module_fini_t(void); -typedef int event_module_process_t(u_long); +typedef int event_module_process_t(struct timeval *); struct event_module { event_module_add_t *add; event_module_del_t *del; diff --git a/kqueue.c b/kqueue.c index 67b2c86..35b3f2b 100644 --- a/kqueue.c +++ b/kqueue.c @@ -178,27 +178,21 @@ kqueue_set(struct event *ev, short filter, u_short flags, u_int fflags) } static int -kqueue_process(u_long timer) +kqueue_process(struct timeval *tv) { struct event *ev; int events, n, i; - struct timespec ts, *tp; + struct timespec ts; n = (int) nchanges; nchanges = 0; - if (timer == 0) { - tp = NULL; - } else { - ts.tv_sec = timer / 1000; - ts.tv_nsec = (timer % 1000) * 1000000; - tp = &ts; - } + TIMEVAL_TO_TIMESPEC(tv, &ts); - DPRINTF(E_DEBUG, L_GENERAL, "kevent timer: %lu, changes: %d\n", - timer, n); + DPRINTF(E_DEBUG, L_GENERAL, "kevent timer: %lu.%06lu, changes: %d\n", + ts.tv_sec, ts.tv_nsec, n); - events = kevent(kq, change_list, n, event_list, MAXEVENTS, tp); + events = kevent(kq, change_list, n, event_list, MAXEVENTS, &ts); if (events == -1) { if (errno == EINTR) @@ -208,12 +202,6 @@ kqueue_process(u_long timer) DPRINTF(E_DEBUG, L_GENERAL, "kevent events: %d\n", events); - if (events == 0) { - if (timer != 0) - return (0); - DPRINTF(E_FATAL, L_GENERAL, "kevent() returned no events. EXITING\n"); - } - for (i = 0; i < events; i++) { if (event_list[i].flags & EV_ERROR) { DPRINTF(E_ERROR, L_GENERAL, diff --git a/metadata.c b/metadata.c index 4781db7..04ed27c 100644 --- a/metadata.c +++ b/metadata.c @@ -862,6 +862,10 @@ GetVideoMetadata(const char *path, const char *name) xasprintf(&m.mime, "video/x-matroska"); else if( strcmp(ctx->iformat->name, "flv") == 0 ) xasprintf(&m.mime, "video/x-flv"); + else if( strcmp(ctx->iformat->name, "rm") == 0 ) + xasprintf(&m.mime, "application/vnd.rn-realmedia"); + else if( strcmp(ctx->iformat->name, "rmvb") == 0 ) + xasprintf(&m.mime, "application/vnd.rn-realmedia-vbr"); if( m.mime ) goto video_no_dlna; @@ -1538,6 +1542,10 @@ GetVideoMetadata(const char *path, const char *name) xasprintf(&m.mime, "video/x-matroska"); else if( strcmp(ctx->iformat->name, "flv") == 0 ) xasprintf(&m.mime, "video/x-flv"); + else if( strcmp(ctx->iformat->name, "rm") == 0 ) + xasprintf(&m.mime, "application/vnd.rn-realmedia"); + else if( strcmp(ctx->iformat->name, "rmvb") == 0 ) + xasprintf(&m.mime, "application/vnd.rn-realmedia-vbr"); else DPRINTF(E_WARN, L_METADATA, "%s: Unhandled format: %s\n", path, ctx->iformat->name); } diff --git a/minidlna.c b/minidlna.c index 8d6628b..2f75a36 100644 --- a/minidlna.c +++ b/minidlna.c @@ -64,7 +64,6 @@ #include #include #include -#include #include #include #include @@ -1099,6 +1098,19 @@ init(int argc, char **argv) return 0; } +#ifdef HAVE_WATCH +void +start_monitor() +{ + + if (!GETFLAG(INOTIFY_MASK)) + return; + + lav_register_all(); + monitor_start(); +} +#endif + /* === main === */ /* process HTTP or SSDP requests */ int @@ -1111,10 +1123,8 @@ main(int argc, char **argv) struct upnphttp * next; struct timeval tv, timeofday, lastnotifytime = {0, 0}; time_t lastupdatetime = 0, lastdbtime = 0; - u_long timeout; /* in milliseconds */ int last_changecnt = 0; pid_t scanner_pid = 0; - pthread_t inotify_thread = 0; struct event ssdpev, httpev, monev; #ifdef TIVO_SUPPORT uint8_t beacon_interval = 5; @@ -1149,23 +1159,11 @@ main(int argc, char **argv) } check_db(db, ret, &scanner_pid); lastdbtime = _get_dbtime(); -#ifdef HAVE_INOTIFY - if( GETFLAG(INOTIFY_MASK) ) - { - if (!sqlite3_threadsafe() || sqlite3_libversion_number() < 3005001) - DPRINTF(E_ERROR, L_GENERAL, "SQLite library is not threadsafe! " - "Inotify will be disabled.\n"); - else if (pthread_create(&inotify_thread, NULL, start_inotify, NULL) != 0) - DPRINTF(E_FATAL, L_GENERAL, "ERROR: pthread_create() failed for start_inotify. EXITING\n"); - } -#endif /* HAVE_INOTIFY */ -#ifdef HAVE_KQUEUE - if (!GETFLAG(SCANNING_MASK)) { - lav_register_all(); - kqueue_monitor_start(); - } -#endif /* HAVE_KQUEUE */ +#ifdef HAVE_WATCH + if (!GETFLAG(SCANNING_MASK)) + start_monitor(); +#endif smonitor = OpenAndConfMonitorSocket(); if (smonitor > 0) @@ -1196,6 +1194,9 @@ main(int argc, char **argv) httpev = (struct event ){ .fd = shttpl, .rdwr = EVENT_READ, .process = ProcessListen }; event_module.add(&httpev); + if (gettimeofday(&timeofday, 0) < 0) + DPRINTF(E_FATAL, L_GENERAL, "gettimeofday(): %s\n", strerror(errno)); + #ifdef TIVO_SUPPORT if (GETFLAG(TIVO_MASK)) { @@ -1220,18 +1221,17 @@ main(int argc, char **argv) tivo_bcast.sin_family = AF_INET; tivo_bcast.sin_addr.s_addr = htonl(getBcastAddress()); tivo_bcast.sin_port = htons(2190); + lastbeacontime = timeofday; } } #endif - reload_ifaces(0); - lastnotifytime.tv_sec = time(NULL) + runtime_vars.notify_interval; + reload_ifaces(0); /* sends SSDP notifies */ + lastnotifytime = timeofday; /* main loop */ while (!quitting) { - if (gettimeofday(&timeofday, 0) < 0) - DPRINTF(E_FATAL, L_GENERAL, "gettimeofday(): %s\n", strerror(errno)); /* Check if we need to send SSDP NOTIFY messages and do it if * needed */ tv = lastnotifytime; @@ -1245,58 +1245,62 @@ main(int argc, char **argv) runtime_vars.port, runtime_vars.notify_interval); } lastnotifytime = timeofday; - timeout = runtime_vars.notify_interval * 1000; + tv.tv_sec = runtime_vars.notify_interval; + tv.tv_usec = 0; } else { timevalsub(&tv, &timeofday); - timeout = tv.tv_sec * 1000 + tv.tv_usec / 1000; } #ifdef TIVO_SUPPORT if (sbeacon >= 0) { - u_long beacontimeout; + struct timeval beacontv; - tv = lastbeacontime; - tv.tv_sec += beacon_interval; - if (timevalcmp(&timeofday, &tv, >=)) + beacontv = lastbeacontime; + beacontv.tv_sec += beacon_interval; + if (timevalcmp(&timeofday, &beacontv, >=)) { sendBeaconMessage(sbeacon, &tivo_bcast, sizeof(struct sockaddr_in), 1); lastbeacontime = timeofday; - beacontimeout = beacon_interval * 1000; - if (timeout > beacon_interval * 1000) - timeout = beacon_interval * 1000; /* Beacons should be sent every 5 seconds or * so for the first minute, then every minute * or so thereafter. */ if (beacon_interval == 5 && (timeofday.tv_sec - startup_time) > 60) beacon_interval = 60; + beacontv.tv_sec = beacon_interval; + beacontv.tv_usec = 0; } else { - timevalsub(&tv, &timeofday); - beacontimeout = tv.tv_sec * 1000 + - tv.tv_usec / 1000; + timevalsub(&beacontv, &timeofday); } - if (timeout > beacontimeout) - timeout = beacontimeout; + if (timevalcmp(&tv, &beacontv, >)) + tv = beacontv; } #endif - if (GETFLAG(SCANNING_MASK) && kill(scanner_pid, 0) != 0) { - CLEARFLAG(SCANNING_MASK); - if (_get_dbtime() != lastdbtime) - updateID++; -#ifdef HAVE_KQUEUE - lav_register_all(); - kqueue_monitor_start(); -#endif /* HAVE_KQUEUE */ + if (GETFLAG(SCANNING_MASK)) { + if (kill(scanner_pid, 0) != 0) { + DPRINTF(E_INFO, L_GENERAL, "Scanner exited\n"); + CLEARFLAG(SCANNING_MASK); + if (_get_dbtime() != lastdbtime) + updateID++; +#ifdef HAVE_WATCH + start_monitor(); +#endif + } else + /* Keep checking for the scanner every sec. */ + tv.tv_sec = 1; } - event_module.process(timeout); + event_module.process(&tv); if (quitting) goto shutdown; + if (gettimeofday(&timeofday, 0) < 0) + DPRINTF(E_FATAL, L_GENERAL, "gettimeofday(): %s\n", strerror(errno)); + upnpevents_gc(); /* increment SystemUpdateID if the content database has changed, @@ -1362,11 +1366,9 @@ main(int argc, char **argv) close(lan_addr[i].snotify); } - if (inotify_thread) - { - pthread_kill(inotify_thread, SIGCHLD); - pthread_join(inotify_thread, NULL); - } +#ifdef HAVE_WATCH + monitor_stop(); +#endif /* kill other child processes */ process_reap_children(); diff --git a/minissdp.c b/minissdp.c index dffae70..7c7c575 100644 --- a/minissdp.c +++ b/minissdp.c @@ -552,27 +552,27 @@ ProcessSSDPRequest(struct event *ev) if (strncasecmp(bufr+i, "SERVER:", 7) == 0) { srv = bufr+i+7; - while (*srv == ' ' || *srv == '\t') + while (*srv && (*srv == ' ' || *srv == '\t')) srv++; } else if (strncasecmp(bufr+i, "LOCATION:", 9) == 0) { loc = bufr+i+9; - while (*loc == ' ' || *loc == '\t') + while (*loc && (*loc == ' ' || *loc == '\t')) loc++; - while (loc[loc_len]!='\r' && loc[loc_len]!='\n') + while (loc[loc_len] && (loc[loc_len]!='\r' && loc[loc_len]!='\n')) loc_len++; } else if (strncasecmp(bufr+i, "NTS:", 4) == 0) { nts = bufr+i+4; - while (*nts == ' ' || *nts == '\t') + while (*nts && (*nts == ' ' || *nts == '\t')) nts++; } else if (strncasecmp(bufr+i, "NT:", 3) == 0) { nt = bufr+i+3; - while(*nt == ' ' || *nt == '\t') + while(*nt && (*nt == ' ' || *nt == '\t')) nt++; } } diff --git a/monitor.c b/monitor.c index 1d58db7..421c0c6 100644 --- a/monitor.c +++ b/monitor.c @@ -30,16 +30,6 @@ #include #include #include -#ifdef HAVE_INOTIFY -#include -#include -#ifdef HAVE_SYS_INOTIFY_H -#include -#else -#include "linux/inotify.h" -#include "linux/inotify-syscalls.h" -#endif -#endif #include "libav.h" #include "upnpglobalvars.h" @@ -52,208 +42,7 @@ #include "playlist.h" #include "log.h" -static time_t next_pl_fill = 0; - -#ifdef HAVE_INOTIFY -#define EVENT_SIZE ( sizeof (struct inotify_event) ) -#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) ) -#define DESIRED_WATCH_LIMIT 65536 - -#define PATH_BUF_SIZE PATH_MAX - -struct watch -{ - int wd; /* watch descriptor */ - char *path; /* watched path */ - struct watch *next; -}; - -static struct watch *watches; -static struct watch *lastwatch = NULL; - -static char * -get_path_from_wd(int wd) -{ - struct watch *w = watches; - - while( w != NULL ) - { - if( w->wd == wd ) - return w->path; - w = w->next; - } - - return NULL; -} - -static unsigned int -next_highest(unsigned int num) -{ - num |= num >> 1; - num |= num >> 2; - num |= num >> 4; - num |= num >> 8; - num |= num >> 16; - return ++num; -} - -static void -raise_watch_limit(unsigned int limit) -{ - FILE *max_watches = fopen("/proc/sys/fs/inotify/max_user_watches", "r+"); - if (!max_watches) - return; - if (!limit) - { - if (fscanf(max_watches, "%10u", &limit) < 1) - limit = 8192; - rewind(max_watches); - } - fprintf(max_watches, "%u", next_highest(limit)); - fclose(max_watches); -} - -int -add_watch(int fd, const char * path) -{ - struct watch *nw; - int wd; - - wd = inotify_add_watch(fd, path, IN_CREATE|IN_CLOSE_WRITE|IN_DELETE|IN_MOVE); - if( wd < 0 && errno == ENOSPC) - { - raise_watch_limit(0); - wd = inotify_add_watch(fd, path, IN_CREATE|IN_CLOSE_WRITE|IN_DELETE|IN_MOVE); - } - if( wd < 0 ) - { - DPRINTF(E_ERROR, L_INOTIFY, "inotify_add_watch(%s) [%s]\n", path, strerror(errno)); - return (errno); - } - - nw = malloc(sizeof(struct watch)); - if( nw == NULL ) - { - DPRINTF(E_ERROR, L_INOTIFY, "malloc() error\n"); - return (ENOMEM); - } - nw->wd = wd; - nw->next = NULL; - nw->path = strdup(path); - - if( watches == NULL ) - { - watches = nw; - } - - if( lastwatch != NULL ) - { - lastwatch->next = nw; - } - lastwatch = nw; - - DPRINTF(E_INFO, L_INOTIFY, "Added watch to %s [%d]\n", path, wd); - return (0); -} - -static int -remove_watch(int fd, const char * path) -{ - struct watch *w; - - for( w = watches; w; w = w->next ) - { - if( strcmp(path, w->path) == 0 ) - return(inotify_rm_watch(fd, w->wd)); - } - - return 1; -} - -static int -inotify_create_watches(int fd) -{ - FILE * max_watches; - unsigned int num_watches = 0, watch_limit; - char **result; - int i, rows = 0; - struct media_dir_s * media_path; - - for( media_path = media_dirs; media_path != NULL; media_path = media_path->next ) - { - DPRINTF(E_DEBUG, L_INOTIFY, "Add watch to %s\n", media_path->path); - add_watch(fd, media_path->path); - num_watches++; - } - sql_get_table(db, "SELECT PATH from DETAILS where MIME is NULL and PATH is not NULL", &result, &rows, NULL); - for( i=1; i <= rows; i++ ) - { - DPRINTF(E_DEBUG, L_INOTIFY, "Add watch to %s\n", result[i]); - add_watch(fd, result[i]); - num_watches++; - } - sqlite3_free_table(result); - - max_watches = fopen("/proc/sys/fs/inotify/max_user_watches", "r"); - if( max_watches ) - { - if( fscanf(max_watches, "%10u", &watch_limit) < 1 ) - watch_limit = 8192; - fclose(max_watches); - if( (watch_limit < DESIRED_WATCH_LIMIT) || (watch_limit < (num_watches*4/3)) ) - { - if (access("/proc/sys/fs/inotify/max_user_watches", W_OK) == 0) - { - if( DESIRED_WATCH_LIMIT >= (num_watches*3/4) ) - { - raise_watch_limit(8191U); - } - else if( next_highest(num_watches) >= (num_watches*3/4) ) - { - raise_watch_limit(num_watches); - } - else - { - raise_watch_limit(next_highest(num_watches)); - } - } - else - { - DPRINTF(E_WARN, L_INOTIFY, "WARNING: Inotify max_user_watches [%u] is low or close to the number of used watches [%u] " - "and I do not have permission to increase this limit. Please do so manually by " - "writing a higher value into /proc/sys/fs/inotify/max_user_watches.\n", watch_limit, num_watches); - } - } - } - else - { - DPRINTF(E_WARN, L_INOTIFY, "WARNING: Could not read inotify max_user_watches! " - "Hopefully it is enough to cover %u current directories plus any new ones added.\n", num_watches); - } - - return rows; -} - -static int -inotify_remove_watches(int fd) -{ - struct watch *w = watches; - struct watch *last_w; - int rm_watches = 0; - - while( w ) - { - last_w = w; - inotify_rm_watch(fd, w->wd); - free(w->path); - rm_watches++; - w = w->next; - free(last_w); - } - - return rm_watches; -} -#endif +time_t next_pl_fill = 0; static int update_password(const char *password_path) @@ -585,7 +374,7 @@ monitor_insert_file(const char *name, const char *path) dir_types = valid_media_types(path); if (!(mtype & dir_types)) return -1; - + /* If it's already in the database and hasn't been modified, skip it. */ if( stat(path, &st) != 0 ) return -1; @@ -753,7 +542,7 @@ monitor_insert_directory(int fd, char *name, const char * path) #ifdef HAVE_WATCH if( fd > 0 ) - add_watch(fd, path); + monitor_add_watch(fd, path); #endif dir_types = valid_media_types(path); @@ -804,12 +593,12 @@ monitor_remove_directory(int fd, const char * path) /* Invalidate the scanner cache so we don't insert files into non-existent containers */ valid_cache = 0; - #ifdef HAVE_INOTIFY +#ifdef HAVE_WATCH if( fd > 0 ) { - remove_watch(fd, path); + monitor_remove_watch(fd, path); } - #endif +#endif sql = sqlite3_mprintf("SELECT ID from DETAILS where (PATH > '%q/' and PATH <= '%q/%c')" " or PATH = '%q'", path, path, 0xFF, path); if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) ) @@ -832,141 +621,3 @@ monitor_remove_directory(int fd, const char * path) return ret; } - -#ifdef HAVE_INOTIFY -void * -start_inotify(void) -{ - struct pollfd pollfds[1]; - char buffer[BUF_LEN]; - char path_buf[PATH_MAX]; - int length, i = 0; - char * esc_name = NULL; - struct stat st; - sigset_t set; - - sigfillset(&set); - sigdelset(&set, SIGCHLD); - pthread_sigmask(SIG_BLOCK, &set, NULL); - - pollfds[0].fd = inotify_init(); - pollfds[0].events = POLLIN; - - if ( pollfds[0].fd < 0 ) - DPRINTF(E_ERROR, L_INOTIFY, "inotify_init() failed!\n"); - - while( GETFLAG(SCANNING_MASK) ) - { - if( quitting ) - goto quitting; - sleep(1); - } - inotify_create_watches(pollfds[0].fd); - if (setpriority(PRIO_PROCESS, 0, 19) == -1) - DPRINTF(E_WARN, L_INOTIFY, "Failed to reduce inotify thread priority\n"); - sqlite3_release_memory(1<<31); - lav_register_all(); - - while( !quitting ) - { - int timeout = -1; - if (next_pl_fill) - { - time_t diff = next_pl_fill - time(NULL); - if (diff < 0) - timeout = 0; - else - timeout = diff * 1000; - } - length = poll(pollfds, 1, timeout); - if( !length ) - { - if( next_pl_fill && (time(NULL) >= next_pl_fill) ) - { - fill_playlists(); - next_pl_fill = 0; - } - continue; - } - else if( length < 0 ) - { - if( (errno == EINTR) || (errno == EAGAIN) ) - continue; - else - DPRINTF(E_ERROR, L_INOTIFY, "read failed!\n"); - } - else - { - length = read(pollfds[0].fd, buffer, BUF_LEN); - buffer[BUF_LEN-1] = '\0'; - } - - i = 0; - while( !quitting && i < length ) - { - struct inotify_event * event = (struct inotify_event *) &buffer[i]; - if( event->len ) - { - snprintf(path_buf, sizeof(path_buf), "%s/%s", get_path_from_wd(event->wd), event->name); - if( *(event->name) == '.' ) - { - // Handle password file changes - if ( strcmp(event->name, PASSWORD_FILE) == 0 ) - if (update_password(path_buf) != 0) - DPRINTF(E_ERROR, L_PASSWORD, "Failed to update password.\n"); - i += EVENT_SIZE + event->len; - continue; - } - esc_name = modifyString(strdup(event->name), "&", "&amp;", 0); - if ( event->mask & IN_ISDIR && (event->mask & (IN_CREATE|IN_MOVED_TO)) ) - { - DPRINTF(E_DEBUG, L_INOTIFY, "The directory %s was %s.\n", - path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "created")); - monitor_insert_directory(pollfds[0].fd, esc_name, path_buf); - } - else if ( (event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO|IN_CREATE)) && - (lstat(path_buf, &st) == 0) ) - { - if( (event->mask & (IN_MOVED_TO|IN_CREATE)) && (S_ISLNK(st.st_mode) || st.st_nlink > 1) ) - { - DPRINTF(E_DEBUG, L_INOTIFY, "The %s link %s was %s.\n", - (S_ISLNK(st.st_mode) ? "symbolic" : "hard"), - path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "created")); - if( stat(path_buf, &st) == 0 && S_ISDIR(st.st_mode) ) - monitor_insert_directory(pollfds[0].fd, esc_name, path_buf); - else - monitor_insert_file(esc_name, path_buf); - } - else if( event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO) && st.st_size > 0 ) - { - if( (event->mask & IN_MOVED_TO) || - (sql_get_int_field(db, "SELECT TIMESTAMP from DETAILS where PATH = '%q'", path_buf) != st.st_mtime) ) - { - DPRINTF(E_DEBUG, L_INOTIFY, "The file %s was %s.\n", - path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "changed")); - monitor_insert_file(esc_name, path_buf); - } - } - } - else if ( event->mask & (IN_DELETE|IN_MOVED_FROM) ) - { - DPRINTF(E_DEBUG, L_INOTIFY, "The %s %s was %s.\n", - (event->mask & IN_ISDIR ? "directory" : "file"), - path_buf, (event->mask & IN_MOVED_FROM ? "moved away" : "deleted")); - if ( event->mask & IN_ISDIR ) - monitor_remove_directory(pollfds[0].fd, path_buf); - else - monitor_remove_file(path_buf); - } - free(esc_name); - } - i += EVENT_SIZE + event->len; - } - } - inotify_remove_watches(pollfds[0].fd); -quitting: - close(pollfds[0].fd); - - return 0; -} -#endif diff --git a/monitor.h b/monitor.h index c5e7b99..a62206f 100644 --- a/monitor.h +++ b/monitor.h @@ -2,17 +2,12 @@ int monitor_insert_file(const char *name, const char *path); int monitor_insert_directory(int fd, char *name, const char * path); int monitor_remove_file(const char * path); int monitor_remove_directory(int fd, const char * path); +int update_password(const char *password_path) #if defined(HAVE_INOTIFY) || defined(HAVE_KQUEUE) #define HAVE_WATCH 1 -int add_watch(int, const char *); -#endif - -#ifdef HAVE_INOTIFY -void * -start_inotify(); -#endif - -#ifdef HAVE_KQUEUE -void kqueue_monitor_start(); +int monitor_add_watch(int, const char *); +int monitor_remove_watch(int, const char *); +void monitor_start(); +void monitor_stop(); #endif diff --git a/monitor_inotify.c b/monitor_inotify.c new file mode 100644 index 0000000..ad8729a --- /dev/null +++ b/monitor_inotify.c @@ -0,0 +1,408 @@ +/* MiniDLNA media server + * Copyright (C) 2008-2010 Justin Maggard + * + * This file is part of MiniDLNA. + * + * MiniDLNA is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * MiniDLNA 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 MiniDLNA. If not, see . + */ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_INOTIFY +#include +#include +#ifdef HAVE_SYS_INOTIFY_H +#include +#else +#include "linux/inotify.h" +#include "linux/inotify-syscalls.h" +#endif +#endif +#include "libav.h" + +#include "upnpglobalvars.h" +#include "monitor.h" +#include "utils.h" +#include "sql.h" +#include "scanner.h" +#include "metadata.h" +#include "albumart.h" +#include "playlist.h" +#include "log.h" + +extern time_t next_pl_fill; + +#define EVENT_SIZE ( sizeof (struct inotify_event) ) +#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) ) +#define DESIRED_WATCH_LIMIT 65536 + +#define PATH_BUF_SIZE PATH_MAX + +struct watch +{ + int wd; /* watch descriptor */ + char *path; /* watched path */ + struct watch *next; +}; + +static struct watch *watches; +static struct watch *lastwatch = NULL; +static pthread_t thread_id; + +static char * +get_path_from_wd(int wd) +{ + struct watch *w = watches; + + while( w != NULL ) + { + if( w->wd == wd ) + return w->path; + w = w->next; + } + + return NULL; +} + +static unsigned int +next_highest(unsigned int num) +{ + num |= num >> 1; + num |= num >> 2; + num |= num >> 4; + num |= num >> 8; + num |= num >> 16; + return ++num; +} + +static void +raise_watch_limit(unsigned int limit) +{ + FILE *max_watches = fopen("/proc/sys/fs/inotify/max_user_watches", "r+"); + if (!max_watches) + return; + if (!limit) + { + if (fscanf(max_watches, "%u", &limit) < 1) + limit = 8192; + rewind(max_watches); + } + fprintf(max_watches, "%u", next_highest(limit)); + fclose(max_watches); +} + +int +monitor_add_watch(int fd, const char * path) +{ + struct watch *nw; + int wd; + + wd = inotify_add_watch(fd, path, IN_CREATE|IN_CLOSE_WRITE|IN_DELETE|IN_MOVE); + if( wd < 0 && errno == ENOSPC) + { + raise_watch_limit(0); + wd = inotify_add_watch(fd, path, IN_CREATE|IN_CLOSE_WRITE|IN_DELETE|IN_MOVE); + } + if( wd < 0 ) + { + DPRINTF(E_ERROR, L_INOTIFY, "inotify_add_watch(%s) [%s]\n", path, strerror(errno)); + return (errno); + } + + nw = malloc(sizeof(struct watch)); + if( nw == NULL ) + { + DPRINTF(E_ERROR, L_INOTIFY, "malloc() error\n"); + return (ENOMEM); + } + nw->wd = wd; + nw->next = NULL; + nw->path = strdup(path); + + if( watches == NULL ) + { + watches = nw; + } + + if( lastwatch != NULL ) + { + lastwatch->next = nw; + } + lastwatch = nw; + + DPRINTF(E_INFO, L_INOTIFY, "Added watch to %s [%d]\n", path, wd); + return (0); +} + +int +monitor_remove_watch(int fd, const char * path) +{ + struct watch *w; + + for( w = watches; w; w = w->next ) + { + if( strcmp(path, w->path) == 0 ) + return(inotify_rm_watch(fd, w->wd)); + } + + return 1; +} + +static int +inotify_create_watches(int fd) +{ + FILE * max_watches; + unsigned int num_watches = 0, watch_limit; + char **result; + int i, rows = 0; + struct media_dir_s * media_path; + + for( media_path = media_dirs; media_path != NULL; media_path = media_path->next ) + { + DPRINTF(E_DEBUG, L_INOTIFY, "Add watch to %s\n", media_path->path); + monitor_add_watch(fd, media_path->path); + num_watches++; + } + sql_get_table(db, "SELECT PATH from DETAILS where MIME is NULL and PATH is not NULL", &result, &rows, NULL); + for( i=1; i <= rows; i++ ) + { + DPRINTF(E_DEBUG, L_INOTIFY, "Add watch to %s\n", result[i]); + monitor_add_watch(fd, result[i]); + num_watches++; + } + sqlite3_free_table(result); + + max_watches = fopen("/proc/sys/fs/inotify/max_user_watches", "r"); + if( max_watches ) + { + if( fscanf(max_watches, "%10u", &watch_limit) < 1 ) + watch_limit = 8192; + fclose(max_watches); + if( (watch_limit < DESIRED_WATCH_LIMIT) || (watch_limit < (num_watches*4/3)) ) + { + if (access("/proc/sys/fs/inotify/max_user_watches", W_OK) == 0) + { + if( DESIRED_WATCH_LIMIT >= (num_watches*3/4) ) + { + raise_watch_limit(8191U); + } + else if( next_highest(num_watches) >= (num_watches*3/4) ) + { + raise_watch_limit(num_watches); + } + else + { + raise_watch_limit(next_highest(num_watches)); + } + } + else + { + DPRINTF(E_WARN, L_INOTIFY, "WARNING: Inotify max_user_watches [%u] is low or close to the number of used watches [%u] " + "and I do not have permission to increase this limit. Please do so manually by " + "writing a higher value into /proc/sys/fs/inotify/max_user_watches.\n", watch_limit, num_watches); + } + } + } + else + { + DPRINTF(E_WARN, L_INOTIFY, "WARNING: Could not read inotify max_user_watches! " + "Hopefully it is enough to cover %u current directories plus any new ones added.\n", num_watches); + } + + return rows; +} + +static int +inotify_remove_watches(int fd) +{ + struct watch *w = watches; + struct watch *last_w; + int rm_watches = 0; + + while( w ) + { + last_w = w; + inotify_rm_watch(fd, w->wd); + free(w->path); + rm_watches++; + w = w->next; + free(last_w); + } + + return rm_watches; +} + +static void * +inotify_thread(void *arg) +{ + struct pollfd pollfds[1]; + char buffer[BUF_LEN]; + char path_buf[PATH_MAX]; + int length, i = 0; + char * esc_name = NULL; + struct stat st; + sigset_t set; + + sigfillset(&set); + sigdelset(&set, SIGCHLD); + pthread_sigmask(SIG_BLOCK, &set, NULL); + + pollfds[0].fd = inotify_init(); + pollfds[0].events = POLLIN; + + if ( pollfds[0].fd < 0 ) + DPRINTF(E_ERROR, L_INOTIFY, "inotify_init() failed!\n"); + + inotify_create_watches(pollfds[0].fd); + if (setpriority(PRIO_PROCESS, 0, 19) == -1) + DPRINTF(E_WARN, L_INOTIFY, "Failed to reduce inotify thread priority\n"); + sqlite3_release_memory(1<<31); + + while( !quitting ) + { + int timeout = -1; + if (next_pl_fill) + { + time_t diff = next_pl_fill - time(NULL); + if (diff < 0) + timeout = 0; + else + timeout = diff * 1000; + } + length = poll(pollfds, 1, timeout); + if( !length ) + { + if( next_pl_fill && (time(NULL) >= next_pl_fill) ) + { + fill_playlists(); + next_pl_fill = 0; + } + continue; + } + else if( length < 0 ) + { + if( (errno == EINTR) || (errno == EAGAIN) ) + continue; + else + DPRINTF(E_ERROR, L_INOTIFY, "read failed!\n"); + } + else + { + length = read(pollfds[0].fd, buffer, BUF_LEN); + buffer[BUF_LEN-1] = '\0'; + } + + i = 0; + while( !quitting && i < length ) + { + struct inotify_event * event = (struct inotify_event *) &buffer[i]; + if( event->len ) + { + snprintf(path_buf, sizeof(path_buf), "%s/%s", get_path_from_wd(event->wd), event->name); + if( *(event->name) == '.' ) + { + // Handle password file changes + if ( strcmp(event->name, PASSWORD_FILE) == 0 ) + if (update_password(path_buf) != 0) + DPRINTF(E_ERROR, L_PASSWORD, "Failed to update password.\n"); + i += EVENT_SIZE + event->len; + continue; + } + esc_name = modifyString(strdup(event->name), "&", "&amp;", 0); + if ( event->mask & IN_ISDIR && (event->mask & (IN_CREATE|IN_MOVED_TO)) ) + { + DPRINTF(E_DEBUG, L_INOTIFY, "The directory %s was %s.\n", + path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "created")); + monitor_insert_directory(pollfds[0].fd, esc_name, path_buf); + } + else if ( (event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO|IN_CREATE)) && + (lstat(path_buf, &st) == 0) ) + { + if( (event->mask & (IN_MOVED_TO|IN_CREATE)) && (S_ISLNK(st.st_mode) || st.st_nlink > 1) ) + { + DPRINTF(E_DEBUG, L_INOTIFY, "The %s link %s was %s.\n", + (S_ISLNK(st.st_mode) ? "symbolic" : "hard"), + path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "created")); + if( stat(path_buf, &st) == 0 && S_ISDIR(st.st_mode) ) + monitor_insert_directory(pollfds[0].fd, esc_name, path_buf); + else + monitor_insert_file(esc_name, path_buf); + } + else if( event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO) && st.st_size > 0 ) + { + if( (event->mask & IN_MOVED_TO) || + (sql_get_int_field(db, "SELECT TIMESTAMP from DETAILS where PATH = '%q'", path_buf) != st.st_mtime) ) + { + DPRINTF(E_DEBUG, L_INOTIFY, "The file %s was %s.\n", + path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "changed")); + monitor_insert_file(esc_name, path_buf); + } + } + } + else if ( event->mask & (IN_DELETE|IN_MOVED_FROM) ) + { + DPRINTF(E_DEBUG, L_INOTIFY, "The %s %s was %s.\n", + (event->mask & IN_ISDIR ? "directory" : "file"), + path_buf, (event->mask & IN_MOVED_FROM ? "moved away" : "deleted")); + if ( event->mask & IN_ISDIR ) + monitor_remove_directory(pollfds[0].fd, path_buf); + else + monitor_remove_file(path_buf); + } + free(esc_name); + } + i += EVENT_SIZE + event->len; + } + } + inotify_remove_watches(pollfds[0].fd); + close(pollfds[0].fd); + + return 0; +} + +void +monitor_start(void) +{ + + if (!sqlite3_threadsafe() || sqlite3_libversion_number() < 3005001) { + DPRINTF(E_ERROR, L_GENERAL, "SQLite library is not threadsafe!" + "Inotify will be disabled.\n"); + return; + } + if (pthread_create(&thread_id, NULL, inotify_thread, NULL) != 0) + DPRINTF(E_FATAL, L_GENERAL, "pthread_create() failed [%s]\n", + strerror(errno)); +} + +void +monitor_stop(void) +{ + + if (thread_id != 0) { + pthread_kill(thread_id, SIGCHLD); + pthread_join(thread_id, NULL); + } +} diff --git a/monitor_kqueue.c b/monitor_kqueue.c index b181f2b..f628737 100644 --- a/monitor_kqueue.c +++ b/monitor_kqueue.c @@ -74,6 +74,7 @@ dir_vnode_process(struct event *ev, u_int fflags) close(ev->fd); free(wt); monitor_remove_directory(0, path); + free(path); return; } else if ((fflags & (NOTE_WRITE | NOTE_LINK)) == (NOTE_WRITE | NOTE_LINK)) { @@ -219,7 +220,7 @@ dir_vnode_process(struct event *ev, u_int fflags) } int -add_watch(int fd __unused, const char *path) +monitor_add_watch(int fd __unused, const char *path) { struct watch *wt; struct event *ev; @@ -256,14 +257,20 @@ add_watch(int fd __unused, const char *path) return (0); } +int +monitor_remove_watch(int fd __unused, const char *path __unused) +{ + + return (0); +} + /* - * XXXGL: this function has too much copypaste of inotify_create_watches(). - * We need to split out inotify stuff from monitor.c into monitor_inotify.c, - * compile the latter on Linux and this file on FreeBSD, and keep monitor.c - * itself platform independent. + * XXXGL: this function has some copypaste with inotify_create_watches(). + * We need to push more code to platform independent start_monitor() + * in minidlna.c. */ void -kqueue_monitor_start() +monitor_start() { struct media_dir_s *media_path; char **result; @@ -272,9 +279,14 @@ kqueue_monitor_start() DPRINTF(E_DEBUG, L_INOTIFY, "kqueue monitoring starting\n"); for (media_path = media_dirs; media_path != NULL; media_path = media_path->next) - add_watch(0, media_path->path); + monitor_add_watch(0, media_path->path); sql_get_table(db, "SELECT PATH from DETAILS where MIME is NULL and PATH is not NULL", &result, &rows, NULL); for (i = 1; i <= rows; i++ ) - add_watch(0, result[i]); + monitor_add_watch(0, result[i]); sqlite3_free_table(result); } + +void +monitor_stop() +{ +} diff --git a/process.c b/process.c index 18fdf3a..9248d5e 100644 --- a/process.c +++ b/process.c @@ -123,7 +123,8 @@ process_handle_child_termination(int signal) else break; } - if(number_of_children) number_of_children--; + if(number_of_children) + number_of_children--; remove_process_info(pid); } } @@ -152,7 +153,7 @@ process_daemonize(void) } /* close all descriptors */ - for (i=getdtablesize();i>=0;--i) close(i); + for (i=getdtablesize();i>=0;--i) close(i); i = open("/dev/null",O_RDWR); /* open stdin */ dup(i); /* stdout */ diff --git a/select.c b/select.c index dce7311..e287760 100644 --- a/select.c +++ b/select.c @@ -142,9 +142,8 @@ select_del(struct event *ev, int flags) } static int -select_process(u_long msec) +select_process(struct timeval *tv) { - struct timeval tv, *tp; struct event *ev; int ready, i; @@ -155,14 +154,10 @@ select_process(u_long msec) max_fd = events[i]->fd; } - tv.tv_sec = (long) (msec / 1000); - tv.tv_usec = (long) ((msec % 1000) * 1000); - tp = &tv; - work_read_fd_set = master_read_fd_set; work_write_fd_set = master_write_fd_set; - ready = select(max_fd + 1, &work_read_fd_set, &work_write_fd_set, NULL, tp); + ready = select(max_fd + 1, &work_read_fd_set, &work_write_fd_set, NULL, tv); if (ready == -1) { if (errno == EINTR) diff --git a/tagutils/tagutils-dsf.c b/tagutils/tagutils-dsf.c index fe76ce6..1441356 100644 --- a/tagutils/tagutils-dsf.c +++ b/tagutils/tagutils-dsf.c @@ -141,6 +141,7 @@ _get_dsftags(char *file, struct song_metadata *psong) if (!pid3tag) { + fclose(fp); free(id3tagbuf); err = errno; errno = err; diff --git a/tivo_utils.c b/tivo_utils.c index 111e9b6..1f39e5f 100644 --- a/tivo_utils.c +++ b/tivo_utils.c @@ -27,6 +27,8 @@ #include #include "tivo_utils.h" +struct sqlite3PrngType sqlite3Prng; + /* This function based on byRequest */ char * decodeString(char *string, int inplace) diff --git a/tivo_utils.h b/tivo_utils.h index d8756cf..07dea7c 100644 --- a/tivo_utils.h +++ b/tivo_utils.h @@ -30,7 +30,8 @@ struct sqlite3PrngType { unsigned char isInit; /* True if initialized */ unsigned char i, j; /* State variables */ unsigned char s[256]; /* State variables */ -} sqlite3Prng; +}; +extern struct sqlite3PrngType sqlite3Prng; char * decodeString(char *string, int inplace); diff --git a/upnpevents.c b/upnpevents.c index f8128cc..0a708a2 100644 --- a/upnpevents.c +++ b/upnpevents.c @@ -239,23 +239,23 @@ upnp_event_create_notify(struct subscriber *sub) obj = calloc(1, sizeof(struct upnp_event_notify)); if(!obj) { - DPRINTF(E_ERROR, L_HTTP, "%s: calloc(): %s\n", "upnp_event_create_notify", strerror(errno)); + DPRINTF(E_ERROR, L_HTTP, "calloc(): %s\n", strerror(errno)); return; } obj->sub = sub; s = socket(PF_INET, SOCK_STREAM, 0); if(s < 0) { - DPRINTF(E_ERROR, L_HTTP, "%s: socket(): %s\n", "upnp_event_create_notify", strerror(errno)); + DPRINTF(E_ERROR, L_HTTP, "socket(): %s\n", strerror(errno)); goto error; } if((flags = fcntl(s, F_GETFL, 0)) < 0) { - DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_GETFL..): %s\n", - "upnp_event_create_notify", strerror(errno)); + DPRINTF(E_ERROR, L_HTTP, "fcntl(..F_GETFL..): %s\n", + strerror(errno)); goto error; } if(fcntl(s, F_SETFL, flags | O_NONBLOCK) < 0) { - DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_SETFL..): %s\n", - "upnp_event_create_notify", strerror(errno)); + DPRINTF(E_ERROR, L_HTTP, "fcntl(..F_SETFL..): %s\n", + strerror(errno)); goto error; } if(sub) @@ -290,16 +290,17 @@ upnp_event_create_notify(struct subscriber *sub) addr.sin_family = AF_INET; inet_aton(obj->addrstr, &addr.sin_addr); addr.sin_port = htons(port); - DPRINTF(E_DEBUG, L_HTTP, "%s: '%s' %hu '%s'\n", "upnp_event_create_notify", - obj->addrstr, port, obj->path); + DPRINTF(E_DEBUG, L_HTTP, "'%s' %hu '%s'\n", + obj->addrstr, port, obj->path); obj->state = EConnecting; obj->ev = (struct event ){ .fd = s, .rdwr = EVENT_WRITE, - .process = upnp_event_process_notify, .data = obj }; - event_module.add(&obj->ev); + .process = upnp_event_process_notify, .data = obj }; + event_module.add(&obj->ev); if(connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { if(errno != EINPROGRESS && errno != EWOULDBLOCK) { - DPRINTF(E_ERROR, L_HTTP, "%s: connect(): %s\n", "upnp_event_create_notify", strerror(errno)); + DPRINTF(E_ERROR, L_HTTP, "connect(): %s\n", strerror(errno)); obj->state = EError; + event_module.del(&obj->ev, 0); } } @@ -313,7 +314,7 @@ upnp_event_create_notify(struct subscriber *sub) static void upnp_event_prepare(struct upnp_event_notify * obj) { - static const char notifymsg[] = + static const char notifymsg[] = "NOTIFY %s HTTP/1.1\r\n" "Host: %s%s\r\n" "Content-Type: text/xml; charset=\"utf-8\"\r\n" diff --git a/upnpglobalvars.h b/upnpglobalvars.h index 5f7b19b..b31cbce 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -57,7 +57,7 @@ #include -#define MINIDLNA_VERSION "1.3.0.1P" +#define MINIDLNA_VERSION "1.3.1P" #ifdef NETGEAR # define SERVER_NAME "ReadyDLNA" @@ -172,7 +172,10 @@ "http-get:*:audio/x-wav:*," \ "http-get:*:audio/x-flac:*," \ "http-get:*:audio/x-dsd:*," \ - "http-get:*:application/ogg:*" + "http-get:*:application/ogg:*" \ + "http-get:*:application/vnd.rn-realmedia:*" \ + "http-get:*:application/vnd.rn-realmedia-vbr:*" \ + "http-get:*:video/webm:*" #define DLNA_FLAG_DLNA_V1_5 0x00100000 #define DLNA_FLAG_HTTP_STALLING 0x00200000 diff --git a/upnphttp.c b/upnphttp.c index 1371bb6..49df14a 100644 --- a/upnphttp.c +++ b/upnphttp.c @@ -273,6 +273,11 @@ ParseHttpHeaders(struct upnphttp * h) p = colon + 1; while(isspace(*p)) p++; + n = 0; + while(p[n] >= ' ') + n++; + h->req_Host = p; + h->req_HostLen = n; for(n = 0; n < n_lan_addr; n++) { for(i = 0; lan_addr[n].str[i]; i++) @@ -623,7 +628,7 @@ SendResp_presentation(struct upnphttp * h) v = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'v*'"); p = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'i*'"); strcatf(&str, - "" SERVER_NAME " " MINIDLNA_VERSION "" + "" SERVER_NAME " " MINIDLNA_VERSION "" "
" "

" SERVER_NAME " status

"); @@ -909,6 +914,18 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) } DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf); + if(h->req_Host && h->req_HostLen > 0) { + const char *ptr = h->req_Host; + DPRINTF(E_MAXDEBUG, L_HTTP, "Host: %.*s\n", h->req_HostLen, h->req_Host); + for(i = 0; i < h->req_HostLen; i++) { + if(*ptr != ':' && *ptr != '.' && (*ptr > '9' || *ptr < '0')) { + DPRINTF(E_ERROR, L_HTTP, "DNS rebinding attack suspected (Host: %.*s)", h->req_HostLen, h->req_Host); + Send404(h);/* 403 */ + return; + } + ptr++; + } + } if(strcmp("POST", HttpCommand) == 0) { h->req_command = EPost; @@ -1662,7 +1679,7 @@ SendResp_resizedimg(struct upnphttp * h, char * object) if( ret != SQLITE_OK ) { Send500(h); - return; + goto resized_error; } if( rows ) { diff --git a/upnphttp.h b/upnphttp.h index e28a943..57eb2bb 100644 --- a/upnphttp.h +++ b/upnphttp.h @@ -89,6 +89,8 @@ struct upnphttp { struct client_cache_s * req_client; const char * req_soapAction; int req_soapActionLen; + const char * req_Host; /* Host: header */ + int req_HostLen; const char * req_Callback; /* For SUBSCRIBE */ int req_CallbackLen; const char * req_NT; diff --git a/utils.c b/utils.c index dc936f9..03ba850 100644 --- a/utils.c +++ b/utils.c @@ -404,7 +404,9 @@ is_video(const char * file) #ifdef TIVO_SUPPORT ends_with(file, ".TiVo") || #endif - ends_with(file, ".mov") || ends_with(file, ".3gp")); + ends_with(file, ".mov") || ends_with(file, ".3gp") || + ends_with(file, ".rm") || ends_with(file, ".rmvb") || + ends_with(file, ".webm")); } int From 6cab3d4dc42ca51dde56f5fe55f08ab127382350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vasconcellos?= Date: Sun, 29 Sep 2024 23:26:08 -0300 Subject: [PATCH 02/13] Update to version 1.3.2 --- NEWS | 7 ++++++- clients.c | 15 ++++++++++++--- configure.ac | 2 +- tagutils/tagutils-flc.c | 1 + tagutils/tagutils-misc.c | 30 ++++++++++++++++++++++++++++-- upnphttp.c | 34 ++++++++++++++++++++++++++++------ 6 files changed, 76 insertions(+), 13 deletions(-) diff --git a/NEWS b/NEWS index 8819873..b964892 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,11 @@ (P Versions are the Password support versions, we keep the same base "1.x.y" as the upstream version) +1.3.2 - Released 30-Aug-2022 +-------------------------------- +- Improved DNS rebinding attack protection. +- Added Samsung Neo QLED series (2021) support. +- Added webm/rm/rmvb support. + 1.3.1 - Released 11-Feb-2022 -------------------------------- - Fixed a potential crash in SSDP request parsing. @@ -19,7 +25,6 @@ - Fix for Image Resize Error (https://sourceforge.net/p/minidlna/patches/189/) - Fix for negative children (https://sourceforge.net/p/minidlna/patches/187/) - 1.3.0P - Released 19-Aug-2021 -------------------------------- - Fixed .nl Translation (Thanks: Juul Wulms, PR #20) diff --git a/clients.c b/clients.c index 6459739..daaa4a4 100644 --- a/clients.c +++ b/clients.c @@ -55,12 +55,12 @@ struct client_type_s client_types[] = EXAVClientInfo }, - { ECling, + { ECling, FLAG_MS_PFS, "Cling", "Cling/", EUserAgent - }, + }, /* User-Agent: DLNADOC/1.50 SEC_HHP_[PC]LPC001/1.0 MS-DeviceCaps/1024 */ /* This is AllShare running on a PC. We don't want to respond with Samsung @@ -89,7 +89,7 @@ struct client_type_s client_types[] = EUserAgent }, - /* Samsung Series [Q] TVs work wit milliseconds for bookmarks */ + /* Samsung Series [Q] TVs work with milliseconds for bookmarks */ /* User-Agent: DLNADOC/1.50 SEC_HHP_[TV] Samsung Q7 Series (49)/1.0 */ { ESamsungSeriesQ, FLAG_SAMSUNG | FLAG_DLNA | FLAG_NO_RESIZE | FLAG_SAMSUNG_DCM10 | FLAG_CAPTION_RES | FLAG_CONVERT_MS, @@ -98,6 +98,15 @@ struct client_type_s client_types[] = EUserAgent }, + /* Samsung Series [QN] TVs work with milliseconds for bookmarks */ + /* User-Agent: DLNADOC/1.50 SEC_HHP_Samsung QN90AA 50 TV/1.0 */ + { ESamsungSeriesQ, + FLAG_SAMSUNG | FLAG_DLNA | FLAG_NO_RESIZE | FLAG_SAMSUNG_DCM10 | FLAG_CAPTION_RES | FLAG_CONVERT_MS, + "Samsung Series [QN]", + "SEC_HHP_Samsung QN", + EUserAgent + }, + /* User-Agent: DLNADOC/1.50 SEC_HHP_[TV]UE40D7000/1.0 */ /* User-Agent: DLNADOC/1.50 SEC_HHP_ Family TV/1.0 */ /* USER-AGENT: DLNADOC/1.50 SEC_HHP_[TV] UE65JU7000/1.0 UPnP/1.0 */ diff --git a/configure.ac b/configure.ac index 5d03757..a404ae1 100644 --- a/configure.ac +++ b/configure.ac @@ -404,7 +404,7 @@ for dir in "" /usr/local $SEARCH_DIR; do AC_CHECK_LIB([exif], [exif_data_new_from_file], [LIBEXIF_LIBS="-lexif"], [unset ac_cv_lib_exif_exif_data_new_from_file; LDFLAGS="$LDFLAGS_SAVE"; continue]) break done -test x"$ac_cv_lib_jpeg_jpeg_set_defaults" = x"yes" || AC_MSG_ERROR([Could not find libexif]) +test x"$ac_cv_lib_exif_exif_data_new_from_file" = x"yes" || AC_MSG_ERROR([Could not find libexif]) AC_SUBST(LIBEXIF_LIBS) LDFLAGS_SAVE="$LDFLAGS" diff --git a/tagutils/tagutils-flc.c b/tagutils/tagutils-flc.c index 926673b..e3deae4 100644 --- a/tagutils/tagutils-flc.c +++ b/tagutils/tagutils-flc.c @@ -65,6 +65,7 @@ _get_flctags(char *filename, struct song_metadata *psong) psong->song_length = (sec * 1000) + ms; psong->bitrate = (((uint64_t)(psong->file_size) * 1000) / (psong->song_length / 8)); psong->samplerate = block->data.stream_info.sample_rate; + psong->samplesize = block->data.stream_info.bits_per_sample; psong->channels = block->data.stream_info.channels; break; diff --git a/tagutils/tagutils-misc.c b/tagutils/tagutils-misc.c index 0075bab..e335bd6 100644 --- a/tagutils/tagutils-misc.c +++ b/tagutils/tagutils-misc.c @@ -203,12 +203,19 @@ vc_scan(struct song_metadata *psong, const char *comment, const size_t length) } strncpy(strbuf, comment, length); strbuf[length] = '\0'; - + + // Xiph.org lists recommended field names for interoperability between programs. + // Beyond these software may use other tag names, and because the files we are + // tasked with reading may come from a variety of sources we include other commonly + // used tags where we can. + // https://xiph.org/vorbis/doc/v-comment.html // ALBUM, ARTIST, PUBLISHER, COPYRIGHT, DISCNUMBER, ISRC, EAN/UPN, LABEL, LABELNO, // LICENSE, OPUS, SOURCEMEDIA, TITLE, TRACKNUMBER, VERSION, ENCODED-BY, ENCODING, // -- following tags are muliples // COMPOSER, ARRANGER, LYRICIST, AUTHOR, CONDUCTOR, PERFORMER, ENSEMBLE, PART // PARTNUMBER, GENRE, DATE, LOCATION, COMMENT + // -- In addition, some software (e.g. Windows Media Player) insists on using YEAR + // -- rather than DATE, so support this where DATE is not available for reasons of usefulness. if(!strncasecmp(strbuf, "ALBUM=", 6)) { if( *(strbuf+6) ) @@ -232,6 +239,16 @@ vc_scan(struct song_metadata *psong, const char *comment, const size_t length) { psong->contributor_sort[ROLE_BAND] = strdup(strbuf + 16); } + else if(!strncasecmp(strbuf, "COMPOSER=", 9)) + { + if( *(strbuf+9) ) + psong->contributor[ROLE_COMPOSER] = strdup(strbuf + 9); + } + else if(!strncasecmp(strbuf, "CONDUCTOR=", 10)) + { + if( *(strbuf+10) ) + psong->contributor[ROLE_CONDUCTOR] = strdup(strbuf + 10); + } else if(!strncasecmp(strbuf, "TITLE=", 6)) { if( *(strbuf+6) ) @@ -241,16 +258,25 @@ vc_scan(struct song_metadata *psong, const char *comment, const size_t length) { psong->track = atoi(strbuf + 12); } + else if(!strncasecmp(strbuf, "TRACKTOTAL=", 11)) + { + psong->total_tracks = atoi(strbuf + 11); + } else if(!strncasecmp(strbuf, "DISCNUMBER=", 11)) { psong->disc = atoi(strbuf + 11); } + else if(!strncasecmp(strbuf, "DISCTOTAL=", 10)) + { + psong->total_discs = atoi(strbuf + 10); + } else if(!strncasecmp(strbuf, "GENRE=", 6)) { if( *(strbuf+6) ) psong->genre = strdup(strbuf + 6); } - else if(!strncasecmp(strbuf, "DATE=", 5)) + else if(!strncasecmp(strbuf, "DATE=", 5) || + (!strncasecmp(strbuf, "YEAR=", 5) && psong->year == 0)) { if(length >= (5 + 10) && isdigit(strbuf[5 + 0]) && isdigit(strbuf[5 + 1]) && ispunct(strbuf[5 + 2]) && diff --git a/upnphttp.c b/upnphttp.c index 49df14a..ca33504 100644 --- a/upnphttp.c +++ b/upnphttp.c @@ -915,15 +915,29 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf); if(h->req_Host && h->req_HostLen > 0) { - const char *ptr = h->req_Host; + const char *port = memchr(h->req_Host, ':', h->req_HostLen); + size_t ip_sz = port ? (port - h->req_Host) : h->req_HostLen; + struct in_addr addr; + char ip_buf[16]; DPRINTF(E_MAXDEBUG, L_HTTP, "Host: %.*s\n", h->req_HostLen, h->req_Host); - for(i = 0; i < h->req_HostLen; i++) { - if(*ptr != ':' && *ptr != '.' && (*ptr > '9' || *ptr < '0')) { - DPRINTF(E_ERROR, L_HTTP, "DNS rebinding attack suspected (Host: %.*s)", h->req_HostLen, h->req_Host); - Send404(h);/* 403 */ + if (port) { + const char *ptr = port + 1; + for (i = ip_sz + 2; i < h->req_HostLen; i++) { + if (*ptr > '9' || *ptr < '0') + break; + ptr++; + } + if (i != h->req_HostLen || atoi(port + 1) > 65535) { + DPRINTF(E_ERROR, L_HTTP, "DNS rebinding attack suspected (Host: %.*s)\n", h->req_HostLen, h->req_Host); + Send400(h); return; } - ptr++; + } + strncpyt(ip_buf, h->req_Host, MIN(ip_sz + 1, sizeof(ip_buf))); + if (ip_sz >= sizeof(ip_buf) || inet_pton(AF_INET, ip_buf, &addr) <= 0 || !addr.s_addr) { + DPRINTF(E_ERROR, L_HTTP, "DNS rebinding attack suspected (Host: %.*s)\n", h->req_HostLen, h->req_Host); + Send400(h); + return; } } if(strcmp("POST", HttpCommand) == 0) @@ -1327,6 +1341,10 @@ send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset) { break; /* Premature end of file */ } + else if( ret == 0 ) + { + break; /* Premature end of file */ + } else { //DPRINTF(E_DEBUG, L_HTTP, "sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset); @@ -1350,6 +1368,10 @@ send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset) { break; /* premature end of file */ } + else if( ret == 0 ) + { + break; /* premature end of file */ + } ret = write(h->ev.fd, buf, ret); if( ret == -1 ) { DPRINTF(E_DEBUG, L_HTTP, "write error :: error no. %d [%s]\n", errno, strerror(errno)); From 429b0718d50a2021711791f666656ae89a2a7554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vasconcellos?= Date: Sun, 29 Sep 2024 23:54:51 -0300 Subject: [PATCH 03/13] Update to version 1.3.3 --- NEWS | 6 ++++++ buildroot/build-static.sh | 4 ++-- buildroot/readymedia/readymedia.mk | 3 ++- monitor.c | 24 +++++++++++++++--------- monitor.h | 1 + monitor_inotify.c | 9 +++++++++ po/nl.po | 12 ++++++------ po/sv.po | 8 ++++---- upnpevents.c | 2 +- upnpglobalvars.h | 2 +- upnphttp.c | 2 +- upnpsoap.c | 13 +++++++++++-- 12 files changed, 59 insertions(+), 27 deletions(-) diff --git a/NEWS b/NEWS index b964892..12fbcc2 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,11 @@ (P Versions are the Password support versions, we keep the same base "1.x.y" as the upstream version) +1.3.3 - Released 1-Jun-2023 +-------------------------------- +- Fixed HTTP chunk length parsing. +- Improved Dutch and Swedish translations. +- Fixed directory symlink deletion handling. + 1.3.2 - Released 30-Aug-2022 -------------------------------- - Improved DNS rebinding attack protection. diff --git a/buildroot/build-static.sh b/buildroot/build-static.sh index 483f7de..6da6226 100755 --- a/buildroot/build-static.sh +++ b/buildroot/build-static.sh @@ -9,5 +9,5 @@ export BR2_EXTERNAL=$(realpath .) cd $BUILDROOT_DIR make O=output-readymedia defconfig BR2_DEFCONFIG=${BR2_DEFCONFIG} make O=output-readymedia -echo -e "\n\nStatic binary built in $(realpath output/target/usr/sbin/minidlnad)" -ls -lh $(realpath output/target/usr/sbin/minidlnad) +echo -e "\n\nStatic binary built in $(realpath output-readymedia/target/usr/sbin/minidlnad)" +ls -lh $(realpath output-readymedia/target/usr/sbin/minidlnad) diff --git a/buildroot/readymedia/readymedia.mk b/buildroot/readymedia/readymedia.mk index 91d37d0..f59ef83 100644 --- a/buildroot/readymedia/readymedia.mk +++ b/buildroot/readymedia/readymedia.mk @@ -4,8 +4,9 @@ # ################################################################################ -READYMEDIA_VERSION = v1_3_1 +READYMEDIA_VERSION = v1_3_3 READYMEDIA_SITE = https://git.code.sf.net/p/minidlna/git +#READYMEDIA_SITE = ssh://localhost/home/jmaggard/source/minidlna READYMEDIA_SITE_METHOD = git READYMEDIA_LICENSE = GPL-2.0, BSD-3-Clause READYMEDIA_LICENSE_FILES = COPYING LICENCE.miniupnpd diff --git a/monitor.c b/monitor.c index 421c0c6..4d94c87 100644 --- a/monitor.c +++ b/monitor.c @@ -584,21 +584,13 @@ monitor_insert_directory(int fd, char *name, const char * path) } int -monitor_remove_directory(int fd, const char * path) +monitor_remove_tree(const char * path) { char * sql; char **result; int64_t detailID = 0; int rows, i, ret = 1; - /* Invalidate the scanner cache so we don't insert files into non-existent containers */ - valid_cache = 0; -#ifdef HAVE_WATCH - if( fd > 0 ) - { - monitor_remove_watch(fd, path); - } -#endif sql = sqlite3_mprintf("SELECT ID from DETAILS where (PATH > '%q/' and PATH <= '%q/%c')" " or PATH = '%q'", path, path, 0xFF, path); if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) ) @@ -621,3 +613,17 @@ monitor_remove_directory(int fd, const char * path) return ret; } + +int +monitor_remove_directory(int fd, const char * path) +{ + /* Invalidate the scanner cache so we don't insert files into non-existent containers */ + valid_cache = 0; +#ifdef HAVE_WATCH + if( fd > 0 ) + { + monitor_remove_watch(fd, path); + } +#endif + return monitor_remove_tree(path); +} diff --git a/monitor.h b/monitor.h index a62206f..bae34bb 100644 --- a/monitor.h +++ b/monitor.h @@ -1,6 +1,7 @@ int monitor_insert_file(const char *name, const char *path); int monitor_insert_directory(int fd, char *name, const char * path); int monitor_remove_file(const char * path); +int monitor_remove_tree(const char * path); int monitor_remove_directory(int fd, const char * path); int update_password(const char *password_path) diff --git a/monitor_inotify.c b/monitor_inotify.c index ad8729a..46d5502 100644 --- a/monitor_inotify.c +++ b/monitor_inotify.c @@ -370,7 +370,16 @@ inotify_thread(void *arg) if ( event->mask & IN_ISDIR ) monitor_remove_directory(pollfds[0].fd, path_buf); else + { monitor_remove_file(path_buf); + /* + * When a symlink to a directory is deleted + * we cannot tell it from a regular file deletion + * to prevent its children from becoming orphans + * we delete the whole tree when it exists + */ + monitor_remove_tree(path_buf); + } } free(esc_name); } diff --git a/po/nl.po b/po/nl.po index 4540110..5cba2da 100644 --- a/po/nl.po +++ b/po/nl.po @@ -46,7 +46,7 @@ msgstr "Muziek" #: scanner.c:535 msgid "All Music" -msgstr "Alle muziek bestanden" +msgstr "Alle muziekbestanden" #: scanner.c:536 msgid "Genre" @@ -74,7 +74,7 @@ msgstr "Video" #: scanner.c:543 msgid "All Video" -msgstr "Alle video bestanden" +msgstr "Alle videobestanden" #: scanner.c:546 msgid "Pictures" @@ -82,11 +82,11 @@ msgstr "Foto's" #: scanner.c:547 msgid "All Pictures" -msgstr "Alle foto bestanden" +msgstr "Alle fotobestanden" #: scanner.c:548 msgid "Date Taken" -msgstr "Opname datum" +msgstr "Opnamedatum" #: scanner.c:549 msgid "Camera" @@ -99,12 +99,12 @@ msgstr "Mappen doorzoeken" #: scanner.c:777 #, c-format msgid "Scanning %s\n" -msgstr "Zoeken %s\n" +msgstr "%s aan het scannen\n" #: scanner.c:873 #, c-format msgid "Scanning %s finished (%llu files)!\n" -msgstr "Zoeken %s gereed (%llu files)!\n" +msgstr "Scannen van %s gereed (%llu bestanden)!\n" #~ msgid "Recently Added" #~ msgstr "Recent toegevoegd" diff --git a/po/sv.po b/po/sv.po index 77a96a6..dfdf204 100644 --- a/po/sv.po +++ b/po/sv.po @@ -46,7 +46,7 @@ msgstr "Musik" #: scanner.c:535 msgid "All Music" -msgstr "All Musik" +msgstr "All musik" #: scanner.c:536 msgid "Genre" @@ -66,7 +66,7 @@ msgstr "Mappar" #: scanner.c:540 msgid "Playlists" -msgstr "Spelningslistor" +msgstr "Spellistor" #: scanner.c:542 msgid "Video" @@ -104,7 +104,7 @@ msgstr "Söker %s\n" #: scanner.c:873 #, c-format msgid "Scanning %s finished (%llu files)!\n" -msgstr "Avsökning %s slutförd (%llu filer)!\n" +msgstr "Genomsökning av %s slutförd (%llu filer)!\n" #~ msgid "Recently Added" -#~ msgstr "nyligen tillagda" +#~ msgstr "Nyss tillagt" diff --git a/upnpevents.c b/upnpevents.c index 0a708a2..295ac92 100644 --- a/upnpevents.c +++ b/upnpevents.c @@ -391,7 +391,7 @@ static void upnp_event_recv(struct upnp_event_notify * obj) DPRINTF(E_DEBUG, L_HTTP, "%s: (%dbytes) %.*s\n", "upnp_event_recv", n, n, obj->buffer); obj->state = EFinished; - event_module.del(&obj->ev, 0); + event_module.del(&obj->ev, EV_FLAG_CLOSING); if(obj->sub) { obj->sub->seq++; diff --git a/upnpglobalvars.h b/upnpglobalvars.h index b31cbce..ed720af 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -57,7 +57,7 @@ #include -#define MINIDLNA_VERSION "1.3.1P" +#define MINIDLNA_VERSION "1.3.3P" #ifdef NETGEAR # define SERVER_NAME "ReadyDLNA" diff --git a/upnphttp.c b/upnphttp.c index ca33504..ba37c52 100644 --- a/upnphttp.c +++ b/upnphttp.c @@ -432,7 +432,7 @@ ParseHttpHeaders(struct upnphttp * h) if (h->req_buflen <= h->req_contentoff) return; while( (line < (h->req_buf + h->req_buflen)) && - (h->req_chunklen = strtol(line, &endptr, 16) > 0) && + ((h->req_chunklen = strtol(line, &endptr, 16)) > 0) && (endptr != line) ) { endptr = strstr(endptr, "\r\n"); diff --git a/upnpsoap.c b/upnpsoap.c index e18c36c..b443619 100644 --- a/upnpsoap.c +++ b/upnpsoap.c @@ -819,7 +819,7 @@ get_child_count(const char *object, struct magic_container_s *magic, const char } else if (magic && magic->objectid && *(magic->objectid)) { ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s' and (password is null or password = '' or password in (%s));", *(magic->objectid), password ? password : "''"); } else { - ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s' and (password is null or password = '' or password in (%s));", object, password ? password : "''"); + ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%q' and (password is null or password = '' or password in (%s));", object, password ? password : "''"); } return (ret > 0) ? ret : 0; @@ -843,6 +843,9 @@ object_exists(const char *object) static int callback(void *args, int argc, char **argv, char **azColName) { + (void)args; + (void)argc; + (void)azColName; struct Response *passed_args = (struct Response *)args; char *id = argv[0], *parent = argv[1], *refID = argv[2], *detailID = argv[3], *class = argv[4], *size = argv[5], *title = argv[6], *duration = argv[7], *bitrate = argv[8], *sampleFrequency = argv[9], *artist = argv[10], *album = argv[11], @@ -1439,6 +1442,7 @@ static void createPasswordContainer(struct Response *passed_args, const char *id static void BrowseContentDirectory(struct upnphttp * h, const char * action) { + (void)action; static const char resp0[] = "" @@ -1521,7 +1525,7 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) args.client = h->req_client ? h->req_client->type->type : 0; args.flags = h->req_client ? h->req_client->type->flags : 0; args.str = &str; - + args.password = h->req_client ? h->req_client->password : NULL; DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n" @@ -2004,6 +2008,7 @@ parse_search_criteria(const char *str, char *sep) static void SearchContentDirectory(struct upnphttp * h, const char * action) { + (void)action; static const char resp0[] = "" @@ -2251,6 +2256,7 @@ static void _kodi_decode(char *str) case '/': if (!str[1]) *str = '\0'; + /* fall through */ default: str++; break; @@ -2270,6 +2276,7 @@ static int duration_sec(const char *str) static void UpdateObject(struct upnphttp * h, const char * action) { + (void)action; static const char resp[] = "" @@ -2354,6 +2361,7 @@ static void UpdateObject(struct upnphttp * h, const char * action) static void SamsungGetFeatureList(struct upnphttp * h, const char * action) { + (void)action; static const char resp[] = "" "" @@ -2403,6 +2411,7 @@ SamsungGetFeatureList(struct upnphttp * h, const char * action) static void SamsungSetBookmark(struct upnphttp * h, const char * action) { + (void)action; static const char resp[] = "" From b860e53cd5870c0808a4122a8dc8a448f0e3d474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vasconcellos?= Date: Sun, 29 Sep 2024 23:58:01 -0300 Subject: [PATCH 04/13] Make sure the database is closed after scanning Debian patch: https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/03-make-sure-the-database-is-closed-after-scanning.patch --- minidlna.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/minidlna.c b/minidlna.c index 2f75a36..57e32eb 100644 --- a/minidlna.c +++ b/minidlna.c @@ -189,7 +189,7 @@ ProcessListen(struct event *ev) } } -/* Handler for the SIGTERM signal (kill) +/* Handler for the SIGTERM signal (kill) * SIGINT is also handled */ static void sigterm(int sig) @@ -427,6 +427,7 @@ check_db(sqlite3 *db, int new_db, pid_t *scanner_pid) else if (*scanner_pid < 0) { start_scanner(); + sqlite3_close(db); } else SETFLAG(SCANNING_MASK); @@ -474,7 +475,7 @@ writepidfile(const char *fname, int pid, uid_t uid) dir, strerror(errno)); } } - + pidfile = fopen(fname, "w"); if (!pidfile) { @@ -485,7 +486,7 @@ writepidfile(const char *fname, int pid, uid_t uid) if (fprintf(pidfile, "%d\n", pid) <= 0) { - DPRINTF(E_ERROR, L_GENERAL, + DPRINTF(E_ERROR, L_GENERAL, "Unable to write to pidfile %s: %s\n", fname, strerror(errno)); ret = -1; } @@ -582,7 +583,7 @@ init(int argc, char **argv) snprintf(uuidvalue+5, UUIDVALUE_MAX_LEN-5, "4d696e69-444c-164e-9d41-%s", mac_str); getfriendlyname(friendly_name, FRIENDLYNAME_MAX_LEN); - + runtime_vars.port = 8200; runtime_vars.notify_interval = 895; /* seconds between SSDP announces */ runtime_vars.max_connections = 50; @@ -634,7 +635,7 @@ init(int argc, char **argv) break; case UPNPSERIAL: strncpyt(serialnumber, ary_options[i].value, SERIALNUMBER_MAX_LEN); - break; + break; case UPNPMODEL_NAME: strncpyt(modelname, ary_options[i].value, MODELNAME_MAX_LEN); break; @@ -1359,7 +1360,7 @@ main(int argc, char **argv) #endif if (smonitor >= 0) close(smonitor); - + for (i = 0; i < n_lan_addr; i++) { SendSSDPGoodbyes(lan_addr[i].snotify); From 62d9a4ab7408a8655ecfc3a141340e9d8bde08b3 Mon Sep 17 00:00:00 2001 From: Florian Will Date: Mon, 9 Mar 2015 13:18:56 +0300 Subject: [PATCH 05/13] Ignore artist when looking up existing container This prevents creation of multiple album containers for compilation albums (i.e. one separate album for each artist) when adding files via inotify, which causes the scanner cache to be invalidated all the time. OTOH, different albums with identical names ("Greatest Hits", "Love Songs") are now listed as just a single album in the "Album" container. This might be solved using some heuristic based on music file location (same directory / different directories). The new behaviour is usually the same as encountered when doing a full rescan, because there's a scanner cache that is used during full rescans and that cache ignores artist names as well. Debian patch: https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/07-fix-multi-artist-album-handling.patch --- scanner.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scanner.c b/scanner.c index 94203e7..8cd0364 100644 --- a/scanner.c +++ b/scanner.c @@ -98,9 +98,8 @@ insert_container(const char *item, const char *rootParent, const char *refID, co "left join DETAILS d on (o.DETAIL_ID = d.ID)" " where o.PARENT_ID = '%s'" " and o.NAME like '%q'" - " and d.ARTIST %s %Q" " and o.CLASS = 'container.%s' limit 1", - rootParent, item, artist?"like":"is", artist, class); + rootParent, item, class); if( result ) { base = strrchr(result, '$'); From f92a7c62d2098c33a2af52afe943049fb13ddc3e Mon Sep 17 00:00:00 2001 From: Alexander Gerasiov Date: Sat, 5 Dec 2020 14:24:52 +0300 Subject: [PATCH 06/13] Fix testupnpdescgen build. Debian patch: https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/08-Fix-testupnpdescgen-build.patch Signed-off-by: Alexander Gerasiov --- Makefile.am | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 1e33833..dcfc625 100644 --- a/Makefile.am +++ b/Makefile.am @@ -70,7 +70,6 @@ testupnpdescgen_LDADD = \ @LIBID3TAG_LIBS@ \ @LIBSQLITE3_LIBS@ \ @LIBAVFORMAT_LIBS@ \ - @LIBAVUTIL_LIBS@ \ @LIBEXIF_LIBS@ \ -lFLAC $(flacogglibs) $(vorbislibs) $(avahilibs) From a18468c941fd0168c0a9b5300ae0e523c2cdfb80 Mon Sep 17 00:00:00 2001 From: Alexander GQ Gerasiov Date: Sun, 31 Jan 2021 18:55:16 +0300 Subject: [PATCH 07/13] Do not close socket on sighup Debian patch: https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/10-do-not-close-socket-on-sighup.patch --- minidlna.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/minidlna.c b/minidlna.c index 57e32eb..e3a97ae 100644 --- a/minidlna.c +++ b/minidlna.c @@ -215,8 +215,6 @@ sighup(int sig) { signal(sig, sighup); DPRINTF(E_WARN, L_GENERAL, "received signal %d, reloading\n", sig); - - reload_ifaces(1); log_reopen(); } From 59733aa2a3d2df55ef786a3b6808db10f8ad4f3f Mon Sep 17 00:00:00 2001 From: "Barak A. Pearlmutter" Date: Sun, 13 Nov 2022 20:05:44 +0000 Subject: [PATCH 08/13] Fix spelling and typos Debian patch: https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/13-spelling-and-typos.patch --- minidlna.c | 2 +- upnphttp.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/minidlna.c b/minidlna.c index e3a97ae..4f1599f 100644 --- a/minidlna.c +++ b/minidlna.c @@ -524,7 +524,7 @@ static void init_nls(void) if (!messages) messages = "unset"; locale_dir = bindtextdomain("minidlna", getenv("TEXTDOMAINDIR")); - DPRINTF(E_DEBUG, L_GENERAL, "Using locale dir '%s' and locale langauge %s/%s\n", locale_dir, messages, ctype); + DPRINTF(E_DEBUG, L_GENERAL, "Using locale dir '%s' and locale language %s/%s\n", locale_dir, messages, ctype); textdomain("minidlna"); #endif } diff --git a/upnphttp.c b/upnphttp.c index ba37c52..e73ca78 100644 --- a/upnphttp.c +++ b/upnphttp.c @@ -556,7 +556,7 @@ Send500(struct upnphttp * h) "

Internal Server Error

Server encountered " "and Internal Error.\r\n"; h->respflags = FLAG_HTML; - BuildResp2_upnphttp(h, 500, "Internal Server Errror", + BuildResp2_upnphttp(h, 500, "Internal Server Error", body500, sizeof(body500) - 1); SendResp_upnphttp(h); CloseSocket_upnphttp(h); From 07463280d24b229725eea32e6a66dba264b7ecf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vasconcellos?= Date: Mon, 30 Sep 2024 00:12:25 -0300 Subject: [PATCH 09/13] Fixes for configure.ac Based on debian patch: https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/14-autoupdate.patch --- configure.ac | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/configure.ac b/configure.ac index a404ae1..4232fc0 100644 --- a/configure.ac +++ b/configure.ac @@ -14,11 +14,11 @@ # License along with MiniDLNA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA. -AC_INIT(MiniDLNA,1.3.0.2,,minidlna) +AC_INIT([MiniDLNA],[1.3.3],[],[minidlna],[http://sourceforge.net/projects/minidlna/]) #LT_INIT AC_CANONICAL_TARGET -AM_INIT_AUTOMAKE([subdir-objects]) +AM_INIT_AUTOMAKE([-Wall subdir-objects]) AC_CONFIG_HEADERS([config.h]) AM_SILENT_RULES([yes]) @@ -28,7 +28,6 @@ m4_ifdef([AC_USE_SYSTEM_EXTENSIONS], [AC_USE_SYSTEM_EXTENSIONS]) AM_ICONV AM_GNU_GETTEXT([external]) -AM_GNU_GETTEXT_VERSION(0.18) AM_GNU_GETTEXT_REQUIRE_VERSION(0.18) # Checks for programs. @@ -76,21 +75,21 @@ AC_SEARCH_LIBS([clock_gettime], [rt], [AC_DEFINE([HAVE_CLOCK_GETTIME], [1], [use # Check for struct ip_mreqn # AC_MSG_CHECKING(for struct ip_mreqn) -AC_TRY_COMPILE([#include ], [ +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[ struct ip_mreqn mreq; mreq.imr_address.s_addr = 0; -], [ +]])],[ # Yes, we have it... AC_MSG_RESULT(yes) AC_DEFINE([HAVE_STRUCT_IP_MREQN],[],[Support for struct ip_mreqn]) -], [ +],[ # We'll just have to try and use struct ip_mreq AC_MSG_RESULT(no) AC_MSG_CHECKING(for struct ip_mreq) - AC_TRY_COMPILE([#include ], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[ struct ip_mreq mreq; mreq.imr_interface.s_addr = 0; - ], [ +]])], [ # Yes, we have it... AC_MSG_RESULT(yes) AC_DEFINE([HAVE_STRUCT_IP_MREQ],[],[Support for struct ip_mreq]) @@ -270,7 +269,7 @@ AC_COMPILE_IFELSE( ] )], [ - AC_DEFINE([USE_DAEMON], [1], + AC_DEFINE([USE_DAEMON], [1], [use the system's builtin daemon()]) AC_MSG_RESULT([yes]) ], @@ -297,7 +296,7 @@ AC_COMPILE_IFELSE( AC_MSG_RESULT([no]) ], [ - AC_DEFINE([SCANDIR_CONST], [1], + AC_DEFINE([SCANDIR_CONST], [1], [scandir needs const char cast]) AC_MSG_RESULT([yes]) @@ -495,7 +494,7 @@ AC_CHECK_HEADERS([arpa/inet.h asm/unistd.h endian.h machine/endian.h fcntl.h lib test x"$ac_cv_header_poll_h" != x"yes" && AC_MSG_ERROR([poll.h not found or not usable]) test x"$ac_cv_header_sys_queue_h" != x"yes" && AC_MSG_ERROR([sys/queue.h not found or not usable]) -AC_CHECK_FUNCS(inotify_init, +AC_CHECK_FUNCS(inotify_init, [ AC_DEFINE(HAVE_INOTIFY,1,[Whether kernel has inotify support]) AM_CONDITIONAL(HAVE_INOTIFY, true) @@ -670,6 +669,7 @@ case "$target_os" in esac -AC_OUTPUT([ po/Makefile.in +AC_CONFIG_FILES([ po/Makefile.in Makefile ]) +AC_OUTPUT From 6845e554705893001814e0dec232914b065d1031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vasconcellos?= Date: Mon, 30 Sep 2024 00:19:38 -0300 Subject: [PATCH 10/13] Add suport for generating thumbnails with libffmpegthumbnailer This patch is from https://sourceforge.net/p/minidlna/patches/_discuss/thread/28a9f79d/9aca/attachment/minidlna-1.3.3-thumb.patch by https://sourceforge.net/u/wilderman/profile/ with changes to derived and cache files (configure and such) removed. Based on debian patch: https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/15-thumbnails.patch --- Makefile.am | 4 +-- albumart.c | 62 +++++++++++++++++++++++++++++++++++++++++++++-- configure.ac | 15 ++++++++++++ minidlna.c | 29 ++++++++++++++++++++++ minidlna.conf | 12 +++++++++ minidlnatypes.h | 4 +++ monitor.c | 8 ++++++ monitor_inotify.c | 25 +++++++++++++++++++ options.c | 6 +++++ options.h | 6 +++++ upnpglobalvars.h | 5 ++++ utils.c | 14 +++++++++++ utils.h | 3 +++ 13 files changed, 189 insertions(+), 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index dcfc625..603ee03 100644 --- a/Makefile.am +++ b/Makefile.am @@ -62,7 +62,7 @@ minidlnad_LDADD = \ @LIBEXIF_LIBS@ \ @LIBINTL@ \ @LIBICONV@ \ - -lFLAC $(flacogglibs) $(vorbislibs) $(avahilibs) + -lFLAC $(flacogglibs) $(vorbislibs) $(avahilibs) @LIBFFMPEGTHUMBNAILER_LIBS@ testupnpdescgen_SOURCES = testupnpdescgen.c upnpdescgen.c testupnpdescgen_LDADD = \ @@ -71,7 +71,7 @@ testupnpdescgen_LDADD = \ @LIBSQLITE3_LIBS@ \ @LIBAVFORMAT_LIBS@ \ @LIBEXIF_LIBS@ \ - -lFLAC $(flacogglibs) $(vorbislibs) $(avahilibs) + -lFLAC $(flacogglibs) $(vorbislibs) $(avahilibs) @LIBFFMPEGTHUMBNAILER_LIBS@ SUFFIXES = .tmpl . diff --git a/albumart.c b/albumart.c index de026ec..28f174b 100644 --- a/albumart.c +++ b/albumart.c @@ -32,6 +32,10 @@ #include +#ifdef THUMBNAIL_CREATION +#include +#endif + #include "upnpglobalvars.h" #include "albumart.h" #include "sql.h" @@ -348,14 +352,68 @@ check_for_album_file(const char *path) return NULL; } +#ifdef THUMBNAIL_CREATION +char * +generate_thumbnail(const char * path) +{ + char *tfile = NULL; + video_thumbnailer *vt = NULL; + char cache_dir[MAXPATHLEN]; + + if( art_cache_exists(path, &tfile) ) + return tfile; + + if ( is_video(path) ) + { + + vt = video_thumbnailer_create(); + if ( !vt ) + { + free(tfile); + return 0; + } + vt->thumbnail_image_type = Jpeg; + vt->thumbnail_image_quality = runtime_vars.thumb_quality; + vt->thumbnail_size = runtime_vars.thumb_width; + vt->seek_percentage = 20; + vt->overlay_film_strip = (GETFLAG(THUMB_FILMSTRIP))?1:0; + + DPRINTF(E_DEBUG, L_METADATA, "generating thumbnail: %s\n", path); + + strncpyt(cache_dir, tfile, sizeof(cache_dir)); + if ( !make_dir(dirname(cache_dir), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) && + !video_thumbnailer_generate_thumbnail_to_file(vt, path, tfile) ) + { + video_thumbnailer_destroy(vt); + return tfile; + } + + video_thumbnailer_destroy(vt); + } + free(tfile); + + return 0; +} +#endif + int64_t find_album_art(const char *path, uint8_t *image_data, int image_size) { char *album_art = NULL; int64_t ret = 0; - if( (image_size && (album_art = check_embedded_art(path, image_data, image_size))) || - (album_art = check_for_album_file(path)) ) + if(image_size) + album_art = check_embedded_art(path, image_data, image_size); + + if(!album_art) + album_art = check_for_album_file(path); + +#ifdef THUMBNAIL_CREATION + if(!album_art && GETFLAG(THUMB_MASK)) + album_art = generate_thumbnail(path); +#endif + + if(album_art) { ret = sql_get_int_field(db, "SELECT ID from ALBUM_ART where PATH = '%q'", album_art); if( !ret ) diff --git a/configure.ac b/configure.ac index 4232fc0..86aba9e 100644 --- a/configure.ac +++ b/configure.ac @@ -642,6 +642,21 @@ AC_ARG_ENABLE(lto, ] ) +AC_ARG_ENABLE(thumbnail, + [ --enable-thumbnail enable video thumbnail generation using libffmpegthumbnailer],[ + if test "$enableval" = "yes"; then + AC_DEFINE([THUMBNAIL_CREATION],[1],[Define to 1 if you want to enable video thumbnail generation]) + PKG_CHECK_MODULES([LIBFFMPEGTHUMBNAILER], libffmpegthumbnailer, , + AC_MSG_ERROR([Unable to find libffmpegthumbnailer])) + AC_SUBST([LIBFFMPEGTHUMBNAILER_CFLAGS]) + AC_SUBST([LIBFFMPEGTHUMBNAILER_LIBS]) + else + AC_MSG_RESULT([no]) + fi + ],[ + AC_MSG_RESULT([no]) + ] +) case "$target_os" in darwin*) diff --git a/minidlna.c b/minidlna.c index 4f1599f..c319f2b 100644 --- a/minidlna.c +++ b/minidlna.c @@ -590,6 +590,11 @@ init(int argc, char **argv) runtime_vars.ifaces[0] = NULL; runtime_vars.password_length = 4; +#ifdef THUMBNAIL_CREATION + runtime_vars.thumb_width = 160; + runtime_vars.thumb_quality = 8; +#endif + /* read options file first since * command line arguments have final say */ if (readoptionsfile(optionsfile) < 0) @@ -818,6 +823,30 @@ init(int argc, char **argv) if (!strtobool(ary_options[i].value)) CLEARFLAG(SUBTITLES_MASK); break; +#ifdef THUMBNAIL_CREATION + case ENABLE_THUMB: + if( (strcmp(ary_options[i].value, "yes") == 0) || atoi(ary_options[i].value) ) + SETFLAG(THUMB_MASK); + break; + case THUMB_WIDTH: + runtime_vars.thumb_width = atoi(ary_options[i].value); + if (runtime_vars.thumb_width < 120) + runtime_vars.thumb_width = 120; + if (runtime_vars.thumb_width > 480) + runtime_vars.thumb_width = 480; + break; + case THUMB_QUALITY: + runtime_vars.thumb_quality = atoi(ary_options[i].value); + if (runtime_vars.thumb_quality < 5) + runtime_vars.thumb_quality = 5; + if (runtime_vars.thumb_quality > 30) + runtime_vars.thumb_quality = 30; + break; + case ENABLE_THUMB_FILMSTRIP: + if( (strcmp(ary_options[i].value, "yes") == 0) || atoi(ary_options[i].value) ) + SETFLAG(THUMB_FILMSTRIP); + break; +#endif default: DPRINTF(E_ERROR, L_GENERAL, "Unknown option in file %s\n", optionsfile); diff --git a/minidlna.conf b/minidlna.conf index 4eb82f0..36756d9 100644 --- a/minidlna.conf +++ b/minidlna.conf @@ -99,3 +99,15 @@ model_number=1 # enable subtitle support by default on unknown clients. # note: the default is yes #enable_subtitles=yes + +# Suport to Movie Thumbnail generation. To use this option, thumbnail generation must be enable at compile time. +#enable_thumbnail=no + +# The width of the thumbnail image. Large images takes more time to generate. To use this option, thumbnail generation must be enable at compile time. +#thumbnail_width=160 + +# Thumbnail Image quality. To use this option, thumbnail generation must be enable at compile time. +#thumbnail_quality=8 + +# Should the thumbnail have a film strip? To use this option, thumbnail generation must be enable at compile time. +#enable_thumbnail_filmstrip=no diff --git a/minidlnatypes.h b/minidlnatypes.h index 9292b9f..920643f 100644 --- a/minidlnatypes.h +++ b/minidlnatypes.h @@ -53,6 +53,10 @@ struct runtime_vars_s { int nonlocal_iface; /* iface to use respond to nonlocal queries */ const char *root_container; /* root ObjectID (instead of "0") */ const char *ifaces[MAX_LAN_ADDR]; /* list of configured network interfaces */ +#ifdef THUMBNAIL_CREATION + int thumb_width; + int thumb_quality; +#endif }; struct string_s { diff --git a/monitor.c b/monitor.c index 4d94c87..e347abb 100644 --- a/monitor.c +++ b/monitor.c @@ -307,6 +307,14 @@ monitor_remove_file(const char * path) sql_exec(db, "DELETE from OBJECTS where DETAIL_ID = %lld", detailID); } snprintf(art_cache, sizeof(art_cache), "%s/art_cache%s", db_path, path); +#ifdef THUMBNAIL_CREATION + /* Remove video thumbnails */ + if ( is_video(path) ) + { + char *vthumb = art_cache; + strcpy(strchr(vthumb, '\0')-4, ".jpg"); + } +#endif remove(art_cache); return 0; diff --git a/monitor_inotify.c b/monitor_inotify.c index 46d5502..0d37fdf 100644 --- a/monitor_inotify.c +++ b/monitor_inotify.c @@ -265,6 +265,10 @@ inotify_thread(void *arg) char * esc_name = NULL; struct stat st; sigset_t set; +#ifdef THUMBNAIL_CREATION + char renpath_buf[PATH_MAX]; + int cookie = 0; +#endif sigfillset(&set); sigdelset(&set, SIGCHLD); @@ -336,6 +340,16 @@ inotify_thread(void *arg) { DPRINTF(E_DEBUG, L_INOTIFY, "The directory %s was %s.\n", path_buf, (event->mask & IN_MOVED_TO ? "moved here" : "created")); +#ifdef THUMBNAIL_CREATION + /* We do not want to regenerate the thumbnails if e rename a directory. + We should keep at least four cookies/olddir since IN_MOVED_FROM/IN_MOVED_TO may + not arrive in sequence, but one should cover most cases */ + if (event->cookie == cookie && event->mask & IN_MOVED_TO) + { + DPRINTF(E_DEBUG, L_INOTIFY, "Directory rename: %s -> %s \n", renpath_buf, path_buf); + rename_artcache_dir(renpath_buf, path_buf); + } +#endif monitor_insert_directory(pollfds[0].fd, esc_name, path_buf); } else if ( (event->mask & (IN_CLOSE_WRITE|IN_MOVED_TO|IN_CREATE)) && @@ -368,7 +382,18 @@ inotify_thread(void *arg) (event->mask & IN_ISDIR ? "directory" : "file"), path_buf, (event->mask & IN_MOVED_FROM ? "moved away" : "deleted")); if ( event->mask & IN_ISDIR ) +#ifdef THUMBNAIL_CREATION + { + if ( event->mask & IN_MOVED_FROM ) + { + strncpy(renpath_buf, path_buf, sizeof(renpath_buf)); + cookie = event->cookie; + } +#endif monitor_remove_directory(pollfds[0].fd, path_buf); +#ifdef THUMBNAIL_CREATION + } +#endif else { monitor_remove_file(path_buf); diff --git a/options.c b/options.c index 66410a6..9656ec2 100644 --- a/options.c +++ b/options.c @@ -68,6 +68,12 @@ static const struct { { WIDE_LINKS, "wide_links" }, { TIVO_DISCOVERY, "tivo_discovery" }, { ENABLE_SUBTITLES, "enable_subtitles" }, +#ifdef THUMBNAIL_CREATION + { ENABLE_THUMB, "enable_thumbnail" }, + { THUMB_WIDTH, "thumbnail_width" }, + { THUMB_QUALITY, "thumbnail_quality" }, + { ENABLE_THUMB_FILMSTRIP, "enable_thumbnail_filmstrip" }, +#endif { PASSWORD_LENGTH, "password_length" } }; diff --git a/options.h b/options.h index a9a82a2..f9ac111 100644 --- a/options.h +++ b/options.h @@ -61,6 +61,12 @@ enum upnpconfigoptions { WIDE_LINKS, /* allow following symlinks outside the defined media_dirs */ TIVO_DISCOVERY, /* TiVo discovery protocol: bonjour or beacon. Defaults to bonjour if supported */ ENABLE_SUBTITLES, /* Enable generic subtitle support for all clients by default */ +#ifdef THUMBNAIL_CREATION + ENABLE_THUMB, /* enable thumbnail generation */ + THUMB_WIDTH, /* thunbnail image with */ + THUMB_QUALITY, /* thumnail image quality */ + ENABLE_THUMB_FILMSTRIP /* film strip overlay */ +#endif PASSWORD_LENGTH /* Password */ }; diff --git a/upnpglobalvars.h b/upnpglobalvars.h index ed720af..26cb972 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -208,6 +208,11 @@ extern uint32_t runtime_flags; #define SUBTITLES_MASK 0x0400 #define FORCE_ALPHASORT_MASK 0x0800 +#ifdef THUMBNAIL_CREATION +#define THUMB_MASK 0x1000 +#define THUMB_FILMSTRIP 0x2000 +#endif + #define SETFLAG(mask) runtime_flags |= mask #define GETFLAG(mask) (runtime_flags & mask) #define CLEARFLAG(mask) runtime_flags &= ~mask diff --git a/utils.c b/utils.c index 03ba850..eb809b8 100644 --- a/utils.c +++ b/utils.c @@ -576,3 +576,17 @@ timevalfix(struct timeval *t1) t1->tv_usec -= 1000000; } } + +#ifdef THUMBNAIL_CREATION +int +rename_artcache_dir(const char * oldpath, const char * newpath) +{ + char old_artcache[PATH_MAX]; + char new_artcache[PATH_MAX]; + + snprintf(old_artcache, sizeof(old_artcache), "%s/art_cache%s", db_path, oldpath); + snprintf(new_artcache, sizeof(old_artcache), "%s/art_cache%s", db_path, newpath); + + return rename(old_artcache, new_artcache); +} +#endif diff --git a/utils.h b/utils.h index 84b9923..641b57b 100644 --- a/utils.h +++ b/utils.h @@ -100,6 +100,9 @@ const char *mime_to_ext(const char * mime); /* Others */ int make_dir(char * path, mode_t mode); unsigned int DJBHash(uint8_t *data, int len); +#ifdef THUMBNAIL_CREATION +int rename_artcache_dir(const char * oldpath, const char * newpath); +#endif /* Timeval manipulations */ void timevaladd(struct timeval *t1, const struct timeval *t2); From bc0848522169a90d9f02e68058793bae4d86021c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert-Andr=C3=A9=20Mauchin?= Date: Sat, 4 May 2024 18:36:58 +0200 Subject: [PATCH 11/13] Add compatibility with FFMPEG 7.0 channel_layout has been replaced with ch_layout Debian patch: https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/16-Add-compatibility-with-FFMPEG-7.0.patch --- libav.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libav.h b/libav.h index b69752c..fae094b 100644 --- a/libav.h +++ b/libav.h @@ -117,6 +117,8 @@ typedef AVMetadataTag AVDictionaryEntry; # endif #endif +#define HAVE_CH_LAYOUT (LIBAVUTIL_VERSION_INT >= ((57<<16)+(28<<8)+100)) + static inline int lav_open(AVFormatContext **ctx, const char *filename) { @@ -174,7 +176,11 @@ lav_get_interlaced(AVStream *s) #define lav_codec_tag(s) s->codecpar->codec_tag #define lav_sample_rate(s) s->codecpar->sample_rate #define lav_bit_rate(s) s->codecpar->bit_rate +#if HAVE_CH_LAYOUT +#define lav_channels(s) s->codecpar->ch_layout.nb_channels +#else #define lav_channels(s) s->codecpar->channels +#endif #define lav_width(s) s->codecpar->width #define lav_height(s) s->codecpar->height #define lav_profile(s) s->codecpar->profile From a514640f87bda1c77aa4862ec2f278fc411e1f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vasconcellos?= Date: Mon, 30 Sep 2024 00:30:44 -0300 Subject: [PATCH 12/13] Couple of minor fixes --- albumart.c | 6 ++++-- monitor.c | 2 +- monitor.h | 2 +- options.h | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/albumart.c b/albumart.c index 28f174b..6014e9e 100644 --- a/albumart.c +++ b/albumart.c @@ -90,7 +90,7 @@ save_resized_album_art(image_s *imsrc, const char *path) cache_file = image_save_to_jpeg_file(imdst, cache_file); image_free(imdst); - + return cache_file; } @@ -374,7 +374,9 @@ generate_thumbnail(const char * path) } vt->thumbnail_image_type = Jpeg; vt->thumbnail_image_quality = runtime_vars.thumb_quality; - vt->thumbnail_size = runtime_vars.thumb_width; + + video_thumbnailer_set_size(vt, runtime_vars.thumb_width, 0); + vt->seek_percentage = 20; vt->overlay_film_strip = (GETFLAG(THUMB_FILMSTRIP))?1:0; diff --git a/monitor.c b/monitor.c index e347abb..c59d73e 100644 --- a/monitor.c +++ b/monitor.c @@ -44,7 +44,7 @@ time_t next_pl_fill = 0; -static int +int update_password(const char *password_path) { typedef struct _node diff --git a/monitor.h b/monitor.h index bae34bb..1c218d0 100644 --- a/monitor.h +++ b/monitor.h @@ -3,7 +3,7 @@ int monitor_insert_directory(int fd, char *name, const char * path); int monitor_remove_file(const char * path); int monitor_remove_tree(const char * path); int monitor_remove_directory(int fd, const char * path); -int update_password(const char *password_path) +int update_password(const char *password_path); #if defined(HAVE_INOTIFY) || defined(HAVE_KQUEUE) #define HAVE_WATCH 1 diff --git a/options.h b/options.h index f9ac111..11d461b 100644 --- a/options.h +++ b/options.h @@ -65,7 +65,7 @@ enum upnpconfigoptions { ENABLE_THUMB, /* enable thumbnail generation */ THUMB_WIDTH, /* thunbnail image with */ THUMB_QUALITY, /* thumnail image quality */ - ENABLE_THUMB_FILMSTRIP /* film strip overlay */ + ENABLE_THUMB_FILMSTRIP, /* film strip overlay */ #endif PASSWORD_LENGTH /* Password */ }; @@ -76,7 +76,7 @@ enum upnpconfigoptions { int readoptionsfile(const char * fname); -/* freeoptions() +/* freeoptions() * frees memory allocated to option values */ void freeoptions(void); From 483cafb0cb301245761539fe8bd6c2484fdf32a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20Vasconcellos?= Date: Mon, 30 Sep 2024 00:48:58 -0300 Subject: [PATCH 13/13] Version 1.3.3.1P --- NEWS | 12 ++++++++++++ upnpglobalvars.h | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 12fbcc2..3a2fe86 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,17 @@ (P Versions are the Password support versions, we keep the same base "1.x.y" as the upstream version) +1.3.3.1P - Released 30-Sept-2024 +-------------------------------- +- Fixes in the 1.3.1/1.3.2/1.3.3 Migration +- Make sure database is closed after scanning (https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/03-make-sure-the-database-is-closed-after-scanning.patch) +- Ignore artist when looking up existing container (https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/07-fix-multi-artist-album-handling.patch) +- Fix testupnpdescgen build (https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/08-Fix-testupnpdescgen-build.patch) +- Do not close socket on sighup (https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/10-do-not-close-socket-on-sighup.patch) +- Fix spelling and typos (https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/13-spelling-and-typos.patch) +- Fixes for configure.ac (https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/14-autoupdate.patch) +- Add suport for generating thumbnails with libffmpegthumbnailer (https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/15-thumbnails.patch) +- Add compatibility with FFMPEG 7.0 (https://sources.debian.org/patches/minidlna/1.3.3%2Bdfsg-1.1/16-Add-compatibility-with-FFMPEG-7.0.patch) + 1.3.3 - Released 1-Jun-2023 -------------------------------- - Fixed HTTP chunk length parsing. diff --git a/upnpglobalvars.h b/upnpglobalvars.h index 26cb972..5ea3d7c 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -57,7 +57,7 @@ #include -#define MINIDLNA_VERSION "1.3.3P" +#define MINIDLNA_VERSION "1.3.3.1P" #ifdef NETGEAR # define SERVER_NAME "ReadyDLNA"