diff --git a/configure.ac b/configure.ac index d0e5f8e2164b7cfa9a43f8412bd8f2678a6535e9..33a66b2122167b62ac4f48b5eaca067d995f6369 100644 --- a/configure.ac +++ b/configure.ac @@ -1081,6 +1081,59 @@ PKG_CHECK_MODULES([SYSTEMD], [libsystemd], [ ]) AM_CONDITIONAL([HAVE_SYSTEMD], [test "${have_systemd}" = "yes"]) +dnl Check for dbus +AC_ARG_ENABLE([sddbus], + AS_HELP_STRING([--enable-dbus], + [compile D-Bus message bus support via sd-bus (default auto)])) +AC_ARG_VAR([SDBUS_CFLAGS], [C compiler flags for sd-bus]) +AC_ARG_VAR([SDBUS_LIBS], [linker flags flags for sd-bus]) + +have_sdbus="no" +AS_IF([test "${enable_sdbus}" != "no"], [ + AS_IF([test "${have_systemd}" = "yes"], [ + SDBUS_CFLAGS="${SYSTEMD_CFLAGS} -DHAVE_SYSTEMD" + SDBUS_LIBS="${SYSTEMD_LIBS}" + have_sdbus="yes" + ]) + dnl Check for elogind + AS_IF([test "${have_sdbus}" != "yes"], [ + PKG_CHECK_MODULES([ELOGIND], [libelogind], [ + have_elogind="yes" + ], [ + AC_MSG_WARN([${ELOGIND_PKG_ERRORS}.]) + ]) + + AS_IF([test "${have_elogind}" = "yes" ], [ + SDBUS_CFLAGS="${ELOGIND_CFLAGS} -DHAVE_ELOGIND" + SDBUS_LIBS="${ELOGIND_LIBS}" + have_sdbus="yes" + ]) + ]) + + dnl Check for basu + AS_IF([test "${have_sdbus}" != "yes"], [ + PKG_CHECK_MODULES([BASU], [basu], [ + have_basu="yes" + ], [ + AC_MSG_WARN([${BASU_PKG_ERRORS}.]) + ]) + + AS_IF([test "${have_basu}" = "yes" ], [ + SDBUS_CFLAGS="${BASU_CFLAGS} -DHAVE_BASU" + SDBUS_LIBS="${BASU_LIBS}" + have_sdbus="yes" + ]) + ]) + + AS_IF([test "$have_sdbus" = "yes" ], [ + AC_SUBST([SDBUS_CFLAGS]) + AC_SUBST([SDBUS_LIBS]) + ], [ + AC_MSG_WARN([no sd-bus provider found.]) + ]) +]) + + EXTEND_HELP_STRING([Optimization options:]) dnl @@ -4166,6 +4219,19 @@ dnl mDNS using libmicrodns dnl PKG_ENABLE_MODULES_VLC([MICRODNS], [], [microdns >= 0.1.2], [mDNS services discovery], [auto]) +dnl +dnl UDisks services discovery +dnl +AC_ARG_ENABLE([udisks], + AS_HELP_STRING([--enable-udisks], [Local Drives service discovery plugin (default auto)])) +AS_IF([test "${enable_udisks}" != "no"], + [ + AS_IF([test "${have_sdbus}" != "no"], [ + VLC_ADD_PLUGIN([udisks]) ], [ + AC_MSG_WARN([UDisks plugins requires sd-dbus.]) + ]) +]) + EXTEND_HELP_STRING([Misc options:]) diff --git a/modules/services_discovery/Makefile.am b/modules/services_discovery/Makefile.am index bb06e4170fa99e3ada21efe3245c4b2080269ec4..a7d762b603e2af09cd1978f80d074ae40bfd726a 100644 --- a/modules/services_discovery/Makefile.am +++ b/modules/services_discovery/Makefile.am @@ -89,3 +89,10 @@ libbonjour_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(sddir)' -Wl,-framework,Fo if HAVE_DARWIN sd_LTLIBRARIES += libbonjour_plugin.la endif + +libudisks_plugin_la_SOURCES = services_discovery/udisks.c +libudisks_plugin_la_CFLAGS = $(AM_CFLAGS) $(SDBUS_CFLAGS) +libudisks_plugin_la_LIBADD = $(SDBUS_LIBS) +libudisks_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(sddir)' +EXTRA_LTLIBRARIES += libudisks_plugin.la +sd_LTLIBRARIES += $(LTLIBudisks) diff --git a/modules/services_discovery/udisks.c b/modules/services_discovery/udisks.c new file mode 100644 index 0000000000000000000000000000000000000000..2d29a81137e3d59d442b1878b49cbbd8e0291c72 --- /dev/null +++ b/modules/services_discovery/udisks.c @@ -0,0 +1,587 @@ +/***************************************************************************** + * udisks.c: file system services discovery module + ***************************************************************************** + * Copyright © 2022 VLC authors, VideoLAN and VideoLabs + * + * Authors: Juliane de Sartiges <jill@videolabs.io> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <vlc_common.h> +#include <vlc_plugin.h> +#include <vlc_modules.h> +#include <vlc_configuration.h> +#include <vlc_services_discovery.h> +#include <vlc_xml.h> +#include <vlc_stream.h> +#include <vlc_interrupt.h> + +#ifdef HAVE_POLL_H +#include <poll.h> +#endif + +#ifdef HAVE_SYSTEMD +#include <systemd/sd-bus.h> +#elif HAVE_ELOGIND +#include <elogind/sd-bus.h> +#elif HAVE_BASU +#include <basu/sd-bus.h> +#endif + +#define DBUS_INTERFACE_INTROSPECTABLE "org.freedesktop.DBus.Introspectable" + +#define DBUS_INTERFACE_UDISKS2 "org.freedesktop.UDisks2" +#define DBUS_INTERFACE_UDISKS2_BLOCKS DBUS_INTERFACE_UDISKS2".Block" +#define DBUS_INTERFACE_UDISKS2_BLOCKS DBUS_INTERFACE_UDISKS2".Block" +#define DBUS_INTERFACE_UDISKS2_DRIVE DBUS_INTERFACE_UDISKS2".Drive" +#define DBUS_INTERFACE_UDISKS2_FILESYSTEM DBUS_INTERFACE_UDISKS2".Filesystem" +#define DBUS_INTERFACE_UDISKS2_MANAGER DBUS_INTERFACE_UDISKS2".Manager" +#define DBUS_INTERFACE_UDISKS2_PARTITION DBUS_INTERFACE_UDISKS2".Partition" + +#define DBUS_PATH_UDISKS2 "/org/freedesktop/UDisks2" +#define DBUS_PATH_UDISKS2_DRIVES DBUS_PATH_UDISKS2"/drives" +#define DBUS_PATH_UDISKS2_BLOCK_DEV DBUS_PATH_UDISKS2"/block_devices" +#define DBUS_PATH_UDISKS2_MANAGER DBUS_PATH_UDISKS2"/Manager" + +struct fs_properties_changed_param +{ + services_discovery_t *sd; + char *object_path; +}; + +struct device_info +{ + sd_bus_slot* slot; + input_item_t* item; + char *label; + uint64_t size; + bool removable; + struct fs_properties_changed_param* param; +}; + +struct discovery_sys +{ + sd_bus* bus; + sd_bus_slot *interface_added_slot; + sd_bus_slot *interface_removed_slot; + vlc_thread_t thread; + vlc_dictionary_t entries; + int event; +}; + +const char *binary_prefixes[] = { N_("B"), N_("KiB"), N_("MiB"), N_("GiB"), N_("TiB") }; + +static int human(uint64_t *i) +{ + int exp = 0; + while(*i > 1024 || exp > 5) + { + *i = *i >> 10; + exp++; + } + return exp; +} +static const char *print_label(const char *drive_label, bool removable) +{ + if(drive_label == NULL || drive_label[0] == '\0') + { + if(removable) + return _("Removable Drive"); + else + return _("Internal Drive"); + } + return drive_label; +} +static input_item_t *input_item_NewDrive(const char *drive_label, const char *path, uint64_t size, bool removable) +{ + int r = 0; + input_item_t* ret = NULL; + char *mrl = NULL; + char *label = NULL; + + if(asprintf(&mrl, "file://%s", path) == -1) + return NULL; + + int prefix = human(&size); + r = asprintf(&label, "%s (%ld %s)", print_label(drive_label, removable), size, vlc_gettext(binary_prefixes[prefix])); + if(r == -1) + goto end; + ret = input_item_NewDirectory(mrl, label, ITEM_LOCAL); +end: + free(mrl); + free(label); + return ret; +} + +static int fs_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error *err) +{ + VLC_UNUSED(err); + int r; + struct fs_properties_changed_param* param = (struct fs_properties_changed_param*) userdata; + services_discovery_t *sd = (services_discovery_t *) param->sd; + struct discovery_sys *p_sys = (struct discovery_sys*) sd->p_sys; + + const char *interface_name; + r = sd_bus_message_read(m, "s", &interface_name); + if(r < 0) + return r; + if(strcmp(interface_name, DBUS_INTERFACE_UDISKS2_FILESYSTEM) != 0) + return 0; + + size_t mount_path_len; + const char *mount_path = NULL; + + char *property_name = NULL; + + r = sd_bus_message_enter_container(m, 'a', "{sv}"); + if(r < 0) + return r; + for(;;) + { + r = sd_bus_message_enter_container(m, 'e', "sv"); + r = sd_bus_message_read(m, "s", &property_name); + if(r < 0) + return r; + if (r == 0) + return 0; + if(strcmp(property_name, "MountPoints") == 0) + break; + } + r = sd_bus_message_enter_container(m, 'v', "aay"); + if(r < 0) + return r; + r = sd_bus_message_enter_container(m, 'a', "ay"); + if(r < 0) + return r; + const void *path; + r = sd_bus_message_read_array(m, 'y', &path, &mount_path_len); + mount_path = path; + if(r < 0) + return r; + struct device_info* info = vlc_dictionary_value_for_key(&p_sys->entries, param->object_path); + if(!info) + return -1; + if(mount_path_len) + { + info->item = input_item_NewDrive(info->label, mount_path, info->size, info->removable); + services_discovery_AddItem(sd, info->item); + } + else + services_discovery_RemoveItem(sd, info->item); + return 1; +} + +static int is_filesystem(services_discovery_t *sd, const char *block_path) +{ + struct discovery_sys *p_sys = (struct discovery_sys*) sd->p_sys; + sd_bus* bus = p_sys->bus; + sd_bus_error err = SD_BUS_ERROR_NULL; + sd_bus_message *reply = NULL; + char *xml = NULL; + const char *val = NULL; + xml_reader_t *xmlreader = NULL; + stream_t *xmlstream = NULL; + int r; + int ret = 0; + + r = sd_bus_call_method(bus, DBUS_INTERFACE_UDISKS2, block_path, + DBUS_INTERFACE_INTROSPECTABLE, "Introspect", &err, &reply, NULL); + if(r < 0) + { + msg_Err(sd, "%s: %s\n", err.name, err.message); + sd_bus_error_free(&err); + return -1; + } + + r = sd_bus_message_read(reply, "s", &xml); + if(r < 0) + return -1; + xmlstream = vlc_stream_MemoryNew(sd, (uint8_t *) xml, strlen(xml), true); + xmlreader = xml_ReaderCreate(sd, xmlstream); + while((r = xml_ReaderNextNode(xmlreader, &val)) > 0) + { + if(r == XML_READER_NONE) + break; + if(strcmp(val, "interface") != 0) + continue; + xml_ReaderNextAttr(xmlreader, &val); + if(strcmp(val, DBUS_INTERFACE_UDISKS2_FILESYSTEM) != 0) + continue; + ret = 1; + break; + } + xml_ReaderDelete(xmlreader); + vlc_stream_Delete(xmlstream); + sd_bus_message_unref(reply); + return ret; +} + +static int get_info_from_block_device(services_discovery_t *sd, const char *block_path, struct device_info **info_ret) +{ + struct discovery_sys *p_sys = (struct discovery_sys*) sd->p_sys; + sd_bus* bus = p_sys->bus; + sd_bus_error err = SD_BUS_ERROR_NULL; + int r = 0; + + struct device_info *info = NULL; + size_t mount_path_len; + const char *drive_path = NULL; + const char *mount_path = NULL; + + sd_bus_message *drive_reply = NULL; + sd_bus_message *mounts_reply = NULL; + + + if(!is_filesystem(sd, block_path)) + return 0; + info = calloc(1, sizeof(struct device_info)); + if(!info) + { + r = -1; + goto error; + } + + info->param = malloc(sizeof(struct fs_properties_changed_param)); + if(!info->param) + { + r = -1; + goto error; + } + + info->param->sd = sd; + info->param->object_path = strdup(block_path); + + r = sd_bus_match_signal(bus, &info->slot, + NULL, block_path, + "org.freedesktop.DBus.Properties", "PropertiesChanged", + fs_properties_changed, (void *) info->param); + if(r < 0) + goto error; + + r = sd_bus_get_property_trivial(bus, DBUS_INTERFACE_UDISKS2, + block_path, + DBUS_INTERFACE_UDISKS2_BLOCKS, "Size", + &err, 't', &info->size); + if(r < 0) + { + msg_Err(sd, "%s: %s\n", err.name, err.message); + goto error; + } + + r = sd_bus_get_property(bus, DBUS_INTERFACE_UDISKS2, + block_path, DBUS_INTERFACE_UDISKS2_BLOCKS, + "Drive", &err, &drive_reply, "o"); + if(r < 0) + { + msg_Err(sd, "%s: %s\n", err.name, err.message); + goto error; + } + + r = sd_bus_message_read(drive_reply, "o", &drive_path); + if(r < 0) + goto error; + + if(strcmp(drive_path, "/") != 0) + { + r = sd_bus_get_property_trivial(bus, DBUS_INTERFACE_UDISKS2, + drive_path, + DBUS_INTERFACE_UDISKS2_DRIVE, "Removable", + &err, 'b', &info->removable); + if(r < 0) + { + msg_Err(sd, "%s: %s\n", err.name, err.message); + goto error; + } + } + + r = sd_bus_get_property_string(bus, DBUS_INTERFACE_UDISKS2, + block_path, + DBUS_INTERFACE_UDISKS2_BLOCKS, "IdLabel", + &err, &info->label); + if(r < 0) + { + msg_Err(sd, "%s: %s\n", err.name, err.message); + goto error; + } + + r = sd_bus_get_property(bus, DBUS_INTERFACE_UDISKS2, + block_path, DBUS_INTERFACE_UDISKS2_FILESYSTEM, + "MountPoints", &err, &mounts_reply, "aay"); + if(r < 0) + { + msg_Err(sd, "%s: %s\n", err.name, err.message); + goto error; + } + + r = sd_bus_message_enter_container(mounts_reply, 'a', "ay"); + if(r < 0) + goto error; + + const void *path; + r = sd_bus_message_read_array(mounts_reply, 'y', &path, &mount_path_len); + path = mount_path; + if(r < 0) + goto error; + if(r) + info->item = input_item_NewDrive(info->label, mount_path, info->size, info->removable); + sd_bus_message_unref(drive_reply); + sd_bus_error_free(&err); + *info_ret = info; + return 1; +error: + sd_bus_error_free(&err); + if(drive_reply) + sd_bus_message_unref(drive_reply); + if(mounts_reply) + sd_bus_message_unref(mounts_reply); + if(info) + { + if(info->param) + { + free(info->param->object_path); + free(info->param); + } + if(info->item) + input_item_Release(info->item); + free(info); + } + return r; +} + +static void FreeEntries(void *p_value, void *p_obj) +{ + services_discovery_t *sd = (services_discovery_t *)p_obj; + struct device_info *info = (struct device_info *) p_value; + if(!info) + return; + if(info->param) + { + free(info->param->object_path); + free(info->param); + } + if(info->slot) + sd_bus_slot_unref(info->slot); + free(info->label); + if(info->item) + { + services_discovery_RemoveItem(sd, info->item); + input_item_Release(info->item); + } + free(info); +} + +static int interfaces_added_cb(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) +{ + VLC_UNUSED(ret_error); + int r; + services_discovery_t *sd = (services_discovery_t *)userdata; + struct discovery_sys *p_sys = (struct discovery_sys *)sd->p_sys; + const char *path; + r = sd_bus_message_read(m, "o", &path); + if(r < 0) + goto error; + if(strncmp(path, DBUS_PATH_UDISKS2_BLOCK_DEV, strlen(DBUS_PATH_UDISKS2_BLOCK_DEV)) != 0) + return 0; + struct device_info *info = NULL; + r = get_info_from_block_device(sd, path, &info); + if(r < 0) + goto error; + if(!info) + return 0; + vlc_dictionary_insert(&p_sys->entries, path, info); + if(info->item) + services_discovery_AddItem(sd, info->item); + return 1; +error: + return r; +} + +static int interfaces_removed_cb(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) +{ + VLC_UNUSED(ret_error); + int r; + services_discovery_t *sd = (services_discovery_t *)userdata; + struct discovery_sys *p_sys = (struct discovery_sys *)sd->p_sys; + const char *path; + r = sd_bus_message_read(m, "o", &path); + if(r < 0) + goto error; + if(strncmp(path, DBUS_PATH_UDISKS2_BLOCK_DEV, strlen(DBUS_PATH_UDISKS2_BLOCK_DEV)) != 0) + return 0; + struct device_info* info = vlc_dictionary_value_for_key(&p_sys->entries, path); + if(!info) + return 0; + vlc_dictionary_remove_value_for_key(&p_sys->entries, path, FreeEntries, sd); + return 1; +error: + return r; +} + +static void *Run(void *p_obj) +{ + int r; + services_discovery_t *sd = (services_discovery_t *)p_obj; + struct discovery_sys *p_sys = (struct discovery_sys *)sd->p_sys; + sd_bus* bus = p_sys->bus; + + r = sd_bus_match_signal(bus, &p_sys->interface_added_slot, + DBUS_INTERFACE_UDISKS2, DBUS_PATH_UDISKS2, + "org.freedesktop.DBus.ObjectManager", "InterfacesAdded", + interfaces_added_cb, (void *) sd); + if(r < 0) + { + return NULL; + } + + r = sd_bus_match_signal(bus, &p_sys->interface_removed_slot, + DBUS_INTERFACE_UDISKS2, DBUS_PATH_UDISKS2, + "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved", + interfaces_removed_cb, (void *) sd); + if(r < 0) + { + sd_bus_slot_unref(p_sys->interface_added_slot); + return NULL; + } + + int canc = vlc_savecancel(); + + struct pollfd ufd[1]; + ufd[0].fd = sd_bus_get_fd(bus); + ufd[0].events = POLLIN; + + for(;;) + { + vlc_restorecancel(canc); + + while(poll(ufd, 1, -1) < 0); + + canc = vlc_savecancel(); + + sd_bus_message *msg = NULL; + r = sd_bus_process(bus, &msg); + if(r < 0) + { + msg_Err(sd, "Couldn't process new d-bus event : %d", -r); + break; + } + sd_bus_message_unref(msg); + } + vlc_restorecancel(canc); + if(p_sys->interface_added_slot) + sd_bus_slot_unref(p_sys->interface_added_slot); + if(p_sys->interface_removed_slot) + sd_bus_slot_unref(p_sys->interface_removed_slot); + return NULL; +} + +static int Open(vlc_object_t *p_obj) +{ + services_discovery_t *sd = (services_discovery_t *)p_obj; + struct discovery_sys *p_sys = vlc_alloc(1, sizeof(struct discovery_sys)); + if (!p_sys) + return VLC_ENOMEM; + sd->p_sys = p_sys; + + sd->description = _("UDisks2 Discovery"); + vlc_dictionary_init(&p_sys->entries, 0); + + int r; + + /* connect to the session bus */ + r = sd_bus_open_system(&p_sys->bus); + if(r < 0) + goto error; + sd_bus* bus = p_sys->bus; + sd_bus_message *reply = NULL; + sd_bus_error err = SD_BUS_ERROR_NULL; + r = sd_bus_call_method(bus, DBUS_INTERFACE_UDISKS2, DBUS_PATH_UDISKS2_MANAGER, + DBUS_INTERFACE_UDISKS2_MANAGER, "GetBlockDevices", + &err, &reply, "a{sv}", NULL); + if (r < 0) + { + msg_Err(sd, "%s: %s\n", err.name, err.message); + goto error; + } + + r = sd_bus_message_enter_container(reply, 'a', "o"); + if (r < 0) + goto error; + + char *block_path = NULL; + while ((r = sd_bus_message_read(reply, "o", &block_path)) != 0) + { + if(r < 0) + goto error; + struct device_info *info = NULL; + r = get_info_from_block_device(sd, block_path, &info); + if(r < 0) + goto error; + if(!info) + continue; + vlc_dictionary_insert(&p_sys->entries, block_path, info); + if(info->item) + services_discovery_AddItem(sd, info->item); + } + sd_bus_message_unref(reply); + + if (vlc_clone(&p_sys->thread, Run, sd)) + goto error; + sd_bus_error_free(&err); + return VLC_SUCCESS; +error: + if(p_sys->bus) + sd_bus_close(bus); + if(p_sys->event) + close(p_sys->event); + sd_bus_error_free(&err); + free(p_sys); + return VLC_EGENERIC; +} + +static void Close(vlc_object_t *p_obj) +{ + services_discovery_t *sd = (services_discovery_t *)p_obj; + struct discovery_sys *p_sys = (struct discovery_sys *)sd->p_sys; + if(p_sys->interface_added_slot) + sd_bus_slot_unref(p_sys->interface_added_slot); + sd_bus_close(p_sys->bus); + const uint64_t notify = 1; + write(p_sys->event, ¬ify, sizeof(notify)); + vlc_cancel(p_sys->thread); + vlc_join(p_sys->thread, NULL); + close(p_sys->event); + vlc_dictionary_clear(&p_sys->entries, FreeEntries, sd); + free(p_sys); +} + +VLC_SD_PROBE_HELPER("udisks", N_("UDisks2"), SD_CAT_DEVICES) + +/* + * Module descriptor + */ +vlc_module_begin() + set_shortname( "UDisks" ) + set_description( N_( "Local Drives (UDisks)" ) ) + set_subcategory( SUBCAT_PLAYLIST_SD ) + set_capability( "services_discovery", 0 ) + set_callbacks( Open, Close ) + add_shortcut( "udisks" ) + VLC_SD_PROBE_SUBMODULE +vlc_module_end () diff --git a/po/POTFILES.in b/po/POTFILES.in index 0fabfc2ce3a9f5a79b2a032608b1fa322573efb7..e77ab187a6996d3f5a6354dc383caaaf888d51e0 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1226,6 +1226,7 @@ modules/services_discovery/podcast.c modules/services_discovery/pulse.c modules/services_discovery/sap.c modules/services_discovery/udev.c +modules/services_discovery/udisks.c modules/services_discovery/upnp.cpp modules/services_discovery/windrive.c modules/services_discovery/xcb_apps.c