diff --git a/gst-env.py b/gst-env.py index 2a714c28e9..2d4ae32a43 100755 --- a/gst-env.py +++ b/gst-env.py @@ -289,7 +289,7 @@ def get_subprocess_env(options, gst_version): env["GST_PLUGIN_SCANNER"] = os.path.normpath( "%s/subprojects/gstreamer/libs/gst/helpers/gst-plugin-scanner" % options.builddir) env["GST_PTP_HELPER"] = os.path.normpath( - "%s/subprojects/gstreamer/libs/gst/helpers/gst-ptp-helper" % options.builddir) + "%s/subprojects/gstreamer/libs/gst/helpers/ptp/gst-ptp-helper" % options.builddir) if os.name == 'nt': lib_path_envvar = 'PATH' diff --git a/subprojects/gstreamer/libs/gst/helpers/gst-ptp-helper.c b/subprojects/gstreamer/libs/gst/helpers/gst-ptp-helper.c deleted file mode 100644 index 4e8ab3624f..0000000000 --- a/subprojects/gstreamer/libs/gst/helpers/gst-ptp-helper.c +++ /dev/null @@ -1,687 +0,0 @@ -/* GStreamer - * Copyright (C) 2015 Sebastian Dröge - * - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -/* Helper process that runs setuid root or with appropriate privileges to - * listen on ports < 1024, do multicast operations and get MAC addresses of - * interfaces. Privileges are dropped after these operations are done. - * - * It listens on the PTP multicast group on port 319 and 320 and forwards - * everything received there to stdout, while forwarding everything received - * on stdout to those sockets. - * Additionally it provides the MAC address of a network interface via stdout - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_GETIFADDRS_AF_LINK -#include -#include -#endif - -#ifdef HAVE_PTP_HELPER_SETUID -#include -#include -#endif - -#ifdef HAVE_PTP_HELPER_CAPABILITIES -#include -#endif - -#include -#include - -#include -#include - -#define PTP_MULTICAST_GROUP "224.0.1.129" -#define PTP_EVENT_PORT 319 -#define PTP_GENERAL_PORT 320 - -static gchar **ifaces = NULL; -static gboolean verbose = FALSE; -static guint64 clock_id = (guint64) - 1; -static guint8 clock_id_array[8]; - -static GOptionEntry opt_entries[] = { - {"interface", 'i', 0, G_OPTION_ARG_STRING_ARRAY, &ifaces, - "Interface to listen on", NULL}, - {"clock-id", 'c', 0, G_OPTION_ARG_INT64, &clock_id, - "PTP clock id", NULL}, - {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, - "Be verbose", NULL}, - {NULL} -}; - -static GSocketAddress *event_saddr, *general_saddr; -static GSocket *socket_event, *socket_general; -static GIOChannel *stdin_channel, *stdout_channel; - -static gboolean -have_socket_data_cb (GSocket * socket, GIOCondition condition, - gpointer user_data) -{ - gchar buffer[8192]; - gssize read; - gsize written; - GError *err = NULL; - GIOStatus status; - StdIOHeader header = { 0, }; - - read = g_socket_receive (socket, buffer, sizeof (buffer), NULL, &err); - if (read == -1) - g_error ("Failed to read from socket: %s", err->message); - g_clear_error (&err); - - if (verbose) - g_message ("Received %" G_GSSIZE_FORMAT " bytes from %s socket", read, - (socket == socket_event ? "event" : "general")); - - header.size = read; - header.type = (socket == socket_event) ? TYPE_EVENT : TYPE_GENERAL; - - status = - g_io_channel_write_chars (stdout_channel, (gchar *) & header, - sizeof (header), &written, &err); - if (status == G_IO_STATUS_ERROR) { - g_error ("Failed to write to stdout: %s", err->message); - g_clear_error (&err); - } else if (status == G_IO_STATUS_EOF) { - g_message ("EOF on stdout"); - exit (0); - } else if (status != G_IO_STATUS_NORMAL) { - g_error ("Unexpected stdout write status: %d", status); - } else if (written != sizeof (header)) { - g_error ("Unexpected write size: %" G_GSIZE_FORMAT, written); - } - - status = - g_io_channel_write_chars (stdout_channel, buffer, read, &written, &err); - if (status == G_IO_STATUS_ERROR) { - g_error ("Failed to write to stdout: %s", err->message); - g_clear_error (&err); - } else if (status == G_IO_STATUS_EOF) { - g_message ("EOF on stdout"); - exit (0); - } else if (status != G_IO_STATUS_NORMAL) { - g_error ("Unexpected stdout write status: %d", status); - } else if (written != read) { - g_error ("Unexpected write size: %" G_GSIZE_FORMAT, written); - } - - return G_SOURCE_CONTINUE; -} - -static gboolean -have_stdin_data_cb (GIOChannel * channel, GIOCondition condition, - gpointer user_data) -{ - GIOStatus status; - StdIOHeader header = { 0, }; - gchar buffer[8192]; - GError *err = NULL; - gsize read; - gssize written; - - if ((condition & G_IO_STATUS_EOF)) { - g_message ("EOF on stdin"); - exit (0); - } - - status = - g_io_channel_read_chars (channel, (gchar *) & header, sizeof (header), - &read, &err); - if (status == G_IO_STATUS_ERROR) { - g_error ("Failed to read from stdin: %s", err->message); - g_clear_error (&err); - } else if (status == G_IO_STATUS_EOF) { - g_message ("EOF on stdin"); - exit (0); - } else if (status != G_IO_STATUS_NORMAL) { - g_error ("Unexpected stdin read status: %d", status); - } else if (read != sizeof (header)) { - g_error ("Unexpected read size: %" G_GSIZE_FORMAT, read); - } else if (header.size > 8192) { - g_error ("Unexpected size: %u", header.size); - } - - status = g_io_channel_read_chars (channel, buffer, header.size, &read, &err); - if (status == G_IO_STATUS_ERROR) { - g_error ("Failed to read from stdin: %s", err->message); - g_clear_error (&err); - } else if (status == G_IO_STATUS_EOF) { - g_message ("EOF on stdin"); - exit (0); - } else if (status != G_IO_STATUS_NORMAL) { - g_error ("Unexpected stdin read status: %d", status); - } else if (read != header.size) { - g_error ("Unexpected read size: %" G_GSIZE_FORMAT, read); - } - - switch (header.type) { - case TYPE_EVENT: - case TYPE_GENERAL: - written = - g_socket_send_to (header.type == - TYPE_EVENT ? socket_event : socket_general, - (header.type == TYPE_EVENT ? event_saddr : general_saddr), buffer, - header.size, NULL, &err); - if (written == -1) - g_error ("Failed to write to socket: %s", err->message); - else if (written != header.size) - g_error ("Unexpected write size: %" G_GSSIZE_FORMAT, written); - g_clear_error (&err); - if (verbose) - g_message ("Sent %" G_GSSIZE_FORMAT " bytes to %s socket", read, - (header.type == TYPE_EVENT ? "event" : "general")); - break; - default: - break; - } - - return G_SOURCE_CONTINUE; -} - -static void -setup_sockets (void) -{ - GInetAddress *bind_addr, *mcast_addr; - GSocketAddress *bind_saddr; - GSource *socket_event_source, *socket_general_source; - gchar **probed_ifaces = NULL; - GError *err = NULL; - - /* Create sockets */ - socket_event = - g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, - G_SOCKET_PROTOCOL_UDP, &err); - if (!socket_event) - g_error ("Couldn't create event socket: %s", err->message); - g_clear_error (&err); - - socket_general = - g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, - G_SOCKET_PROTOCOL_UDP, &err); - if (!socket_general) - g_error ("Couldn't create general socket: %s", err->message); - g_clear_error (&err); - - /* Bind sockets */ - bind_addr = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4); - bind_saddr = g_inet_socket_address_new (bind_addr, PTP_EVENT_PORT); - if (!g_socket_bind (socket_event, bind_saddr, TRUE, &err)) - g_error ("Couldn't bind event socket: %s", err->message); - g_object_unref (bind_saddr); - bind_saddr = g_inet_socket_address_new (bind_addr, PTP_GENERAL_PORT); - if (!g_socket_bind (socket_general, bind_saddr, TRUE, &err)) - g_error ("Couldn't bind general socket: %s", err->message); - g_object_unref (bind_saddr); - g_object_unref (bind_addr); - - /* Probe all non-loopback interfaces */ - if (!ifaces) { -#if defined(HAVE_SIOCGIFCONF_SIOCGIFFLAGS_SIOCGIFHWADDR) - struct ifreq ifr; - struct ifconf ifc; - gchar buf[8192]; - - ifc.ifc_len = sizeof (buf); - ifc.ifc_buf = buf; - if (ioctl (g_socket_get_fd (socket_event), SIOCGIFCONF, &ifc) != -1) { - guint i, idx = 0; - - probed_ifaces = g_new0 (gchar *, ifc.ifc_len + 1); - - for (i = 0; i < ifc.ifc_len / sizeof (struct ifreq); i++) { - strncpy (ifr.ifr_name, ifc.ifc_req[i].ifr_name, IFNAMSIZ); - if (ioctl (g_socket_get_fd (socket_event), SIOCGIFFLAGS, &ifr) == 0) { - if ((ifr.ifr_flags & IFF_LOOPBACK)) - continue; - probed_ifaces[idx] = g_strndup (ifc.ifc_req[i].ifr_name, IFNAMSIZ); - idx++; - } else { - g_warning ("can't get flags of interface '%s'", - ifc.ifc_req[i].ifr_name); - probed_ifaces[idx] = g_strndup (ifc.ifc_req[i].ifr_name, IFNAMSIZ); - idx++; - } - if (idx != 0) - ifaces = probed_ifaces; - } - } -#elif defined(HAVE_GETIFADDRS_AF_LINK) - struct ifaddrs *ifaddr, *ifa; - - if (getifaddrs (&ifaddr) != -1) { - GPtrArray *arr; - - arr = g_ptr_array_new (); - - for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { - if ((ifa->ifa_flags & IFF_LOOPBACK)) - continue; - - if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_LINK) - continue; - - g_ptr_array_add (arr, g_strdup (ifa->ifa_name)); - } - freeifaddrs (ifaddr); - - g_ptr_array_add (arr, NULL); - ifaces = probed_ifaces = (gchar **) g_ptr_array_free (arr, FALSE); - } -#else -#warning "Implement something to list all network interfaces" -#endif - } - - /* Get a clock id from the MAC address if none was given */ - if (clock_id == (guint64) - 1) { - gboolean success = FALSE; - -#if defined(HAVE_SIOCGIFCONF_SIOCGIFFLAGS_SIOCGIFHWADDR) - struct ifreq ifr; - - if (ifaces) { - gchar **ptr = ifaces; - - while (*ptr) { - memcpy (ifr.ifr_name, *ptr, IFNAMSIZ); - if (ioctl (g_socket_get_fd (socket_event), SIOCGIFHWADDR, &ifr) == 0) { - clock_id_array[0] = ifr.ifr_hwaddr.sa_data[0]; - clock_id_array[1] = ifr.ifr_hwaddr.sa_data[1]; - clock_id_array[2] = ifr.ifr_hwaddr.sa_data[2]; - clock_id_array[3] = 0xff; - clock_id_array[4] = 0xfe; - clock_id_array[5] = ifr.ifr_hwaddr.sa_data[3]; - clock_id_array[6] = ifr.ifr_hwaddr.sa_data[4]; - clock_id_array[7] = ifr.ifr_hwaddr.sa_data[5]; - success = TRUE; - break; - } - } - - ptr++; - } else { - struct ifconf ifc; - gchar buf[8192]; - - ifc.ifc_len = sizeof (buf); - ifc.ifc_buf = buf; - if (ioctl (g_socket_get_fd (socket_event), SIOCGIFCONF, &ifc) != -1) { - guint i; - - for (i = 0; i < ifc.ifc_len / sizeof (struct ifreq); i++) { - strncpy (ifr.ifr_name, ifc.ifc_req[i].ifr_name, IFNAMSIZ); - if (ioctl (g_socket_get_fd (socket_event), SIOCGIFFLAGS, &ifr) == 0) { - if ((ifr.ifr_flags & IFF_LOOPBACK)) - continue; - - if (ioctl (g_socket_get_fd (socket_event), SIOCGIFHWADDR, - &ifr) == 0) { - clock_id_array[0] = ifr.ifr_hwaddr.sa_data[0]; - clock_id_array[1] = ifr.ifr_hwaddr.sa_data[1]; - clock_id_array[2] = ifr.ifr_hwaddr.sa_data[2]; - clock_id_array[3] = 0xff; - clock_id_array[4] = 0xfe; - clock_id_array[5] = ifr.ifr_hwaddr.sa_data[3]; - clock_id_array[6] = ifr.ifr_hwaddr.sa_data[4]; - clock_id_array[7] = ifr.ifr_hwaddr.sa_data[5]; - success = TRUE; - break; - } - } else { - g_warning ("can't get flags of interface '%s'", - ifc.ifc_req[i].ifr_name); - } - } - } - } -#elif defined(HAVE_GETIFADDRS_AF_LINK) - struct ifaddrs *ifaddr, *ifa; - - if (getifaddrs (&ifaddr) != -1) { - for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) { - struct sockaddr_dl *sdl = (struct sockaddr_dl *) ifa->ifa_addr; - guint8 mac_addr[6]; - - if ((ifa->ifa_flags & IFF_LOOPBACK)) - continue; - - if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_LINK) - continue; - - if (ifaces) { - gchar **p = ifaces; - gboolean found = FALSE; - - while (*p) { - if (strcmp (*p, ifa->ifa_name) == 0) { - found = TRUE; - break; - } - p++; - } - - if (!found) - continue; - } - - if (sdl->sdl_alen != 6) - continue; - - memcpy (mac_addr, LLADDR (sdl), sdl->sdl_alen); - - clock_id_array[0] = mac_addr[0]; - clock_id_array[1] = mac_addr[1]; - clock_id_array[2] = mac_addr[2]; - clock_id_array[3] = 0xff; - clock_id_array[4] = 0xfe; - clock_id_array[5] = mac_addr[3]; - clock_id_array[6] = mac_addr[4]; - clock_id_array[7] = mac_addr[5]; - success = TRUE; - break; - } - - freeifaddrs (ifaddr); - } -#else -#warning "Implement something to get MAC addresses of network interfaces" -#endif - - if (!success) { - g_warning ("can't get any MAC address, using random clock id"); - clock_id = (((guint64) g_random_int ()) << 32) | (g_random_int ()); - GST_WRITE_UINT64_BE (clock_id_array, clock_id); - clock_id_array[3] = 0xff; - clock_id_array[4] = 0xfe; - } - } else { - GST_WRITE_UINT64_BE (clock_id_array, clock_id); - } - - /* Join multicast groups */ - mcast_addr = g_inet_address_new_from_string (PTP_MULTICAST_GROUP); - if (ifaces) { - gchar **ptr = ifaces; - gboolean success = FALSE; - - while (*ptr) { - gint c = 0; - if (!g_socket_join_multicast_group (socket_event, mcast_addr, FALSE, *ptr, - &err) - && !g_error_matches (err, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE)) - g_warning ("Couldn't join multicast group on interface '%s': %s", *ptr, - err->message); - else - c++; - g_clear_error (&err); - - if (!g_socket_join_multicast_group (socket_general, mcast_addr, FALSE, - *ptr, &err) - && !g_error_matches (err, G_IO_ERROR, G_IO_ERROR_ADDRESS_IN_USE)) - g_warning ("Couldn't join multicast group on interface '%s': %s", *ptr, - err->message); - else - c++; - g_clear_error (&err); - - if (c == 2) - success = TRUE; - ptr++; - } - - if (!success) { - /* Join multicast group without any interface */ - if (!g_socket_join_multicast_group (socket_event, mcast_addr, FALSE, NULL, - &err)) - g_error ("Couldn't join multicast group: %s", err->message); - g_clear_error (&err); - if (!g_socket_join_multicast_group (socket_general, mcast_addr, FALSE, - NULL, &err)) - g_error ("Couldn't join multicast group: %s", err->message); - g_clear_error (&err); - } - } else { - /* Join multicast group without any interface */ - if (!g_socket_join_multicast_group (socket_event, mcast_addr, FALSE, NULL, - &err)) - g_error ("Couldn't join multicast group: %s", err->message); - g_clear_error (&err); - if (!g_socket_join_multicast_group (socket_general, mcast_addr, FALSE, NULL, - &err)) - g_error ("Couldn't join multicast group: %s", err->message); - g_clear_error (&err); - } - - event_saddr = g_inet_socket_address_new (mcast_addr, PTP_EVENT_PORT); - general_saddr = g_inet_socket_address_new (mcast_addr, PTP_GENERAL_PORT); - - /* Create socket sources */ - socket_event_source = - g_socket_create_source (socket_event, G_IO_IN | G_IO_PRI, NULL); - g_source_set_priority (socket_event_source, G_PRIORITY_HIGH); - g_source_set_callback (socket_event_source, (GSourceFunc) have_socket_data_cb, - NULL, NULL); - g_source_attach (socket_event_source, NULL); - socket_general_source = - g_socket_create_source (socket_general, G_IO_IN | G_IO_PRI, NULL); - g_source_set_priority (socket_general_source, G_PRIORITY_DEFAULT); - g_source_set_callback (socket_general_source, - (GSourceFunc) have_socket_data_cb, NULL, NULL); - g_source_attach (socket_general_source, NULL); - - g_strfreev (probed_ifaces); -} - -static void -drop_privileges (void) -{ -#ifdef HAVE_PTP_HELPER_SETUID - /* Switch to the given user/group */ -#ifdef HAVE_PTP_HELPER_SETUID_GROUP - { - struct group *grp; - - grp = getgrnam (HAVE_PTP_HELPER_SETUID_GROUP); - if (!grp) - g_error ("Failed to get group information '%s': %s", - HAVE_PTP_HELPER_SETUID_GROUP, g_strerror (errno)); - - if (setgid (grp->gr_gid) != 0) - g_error ("Failed to change to group '%s': %s", - HAVE_PTP_HELPER_SETUID_GROUP, g_strerror (errno)); - } -#endif - -#ifdef HAVE_PTP_HELPER_SETUID_USER - { - struct passwd *pwd; - - pwd = getpwnam (HAVE_PTP_HELPER_SETUID_USER); - if (!pwd) - g_error ("Failed to get user information '%s': %s", - HAVE_PTP_HELPER_SETUID_USER, g_strerror (errno)); - -#ifndef HAVE_PTP_HELPER_SETUID_GROUP - if (setgid (pwd->pw_gid) != 0) - g_error ("Failed to change to user group '%s': %s", - HAVE_PTP_HELPER_SETUID_USER, g_strerror (errno)); -#endif - - if (setuid (pwd->pw_uid) != 0) - g_error ("Failed to change to user '%s': %s", HAVE_PTP_HELPER_SETUID_USER, - g_strerror (errno)); - } -#endif -#endif -#ifdef HAVE_PTP_HELPER_CAPABILITIES - /* Drop all capabilities */ - { - cap_t caps; - - caps = cap_get_proc (); - if (caps == 0) - g_error ("Failed to get process caps: %s", g_strerror (errno)); - if (cap_clear (caps) != 0) - g_error ("Failed to clear caps: %s", g_strerror (errno)); - if (cap_set_proc (caps) != 0) - g_error ("Failed to set process caps: %s", g_strerror (errno)); - } -#endif -} - -static void -setup_stdio_channels (void) -{ - GSource *stdin_source; - - /* Create stdin source */ - stdin_channel = g_io_channel_unix_new (STDIN_FILENO); - if (g_io_channel_set_encoding (stdin_channel, NULL, - NULL) == G_IO_STATUS_ERROR) - g_error ("Failed to set stdin to binary encoding"); - g_io_channel_set_buffered (stdin_channel, FALSE); - stdin_source = - g_io_create_watch (stdin_channel, G_IO_IN | G_IO_PRI | G_IO_HUP); - g_source_set_priority (stdin_source, G_PRIORITY_DEFAULT); - g_source_set_callback (stdin_source, (GSourceFunc) have_stdin_data_cb, NULL, - NULL); - g_source_attach (stdin_source, NULL); - - /* Create stdout channel */ - stdout_channel = g_io_channel_unix_new (STDOUT_FILENO); - if (g_io_channel_set_encoding (stdout_channel, NULL, - NULL) == G_IO_STATUS_ERROR) - g_error ("Failed to set stdout to binary encoding"); - g_io_channel_set_buffered (stdout_channel, FALSE); -} - -static void -write_clock_id (void) -{ - GError *err = NULL; - GIOStatus status; - StdIOHeader header = { 0, }; - gsize written; - - /* Write clock id to stdout */ - - header.type = TYPE_CLOCK_ID; - header.size = 8; - status = - g_io_channel_write_chars (stdout_channel, (gchar *) & header, - sizeof (header), &written, &err); - if (status == G_IO_STATUS_ERROR) { - g_error ("Failed to write to stdout: %s", err->message); - g_clear_error (&err); - } else if (status == G_IO_STATUS_EOF) { - g_message ("EOF on stdout"); - exit (0); - } else if (status != G_IO_STATUS_NORMAL) { - g_error ("Unexpected stdout write status: %d", status); - } else if (written != sizeof (header)) { - g_error ("Unexpected write size: %" G_GSIZE_FORMAT, written); - } - - status = - g_io_channel_write_chars (stdout_channel, - (const gchar *) clock_id_array, sizeof (clock_id_array), &written, &err); - if (status == G_IO_STATUS_ERROR) { - g_error ("Failed to write to stdout: %s", err->message); - g_clear_error (&err); - } else if (status == G_IO_STATUS_EOF) { - g_message ("EOF on stdout"); - exit (0); - } else if (status != G_IO_STATUS_NORMAL) { - g_error ("Unexpected stdout write status: %d", status); - } else if (written != sizeof (clock_id_array)) { - g_error ("Unexpected write size: %" G_GSIZE_FORMAT, written); - } -} - -#ifdef __APPLE__ -static gint -dummy_poll (GPollFD * fds, guint nfds, gint timeout) -{ - return g_poll (fds, nfds, timeout); -} -#endif - -gint -main (gint argc, gchar ** argv) -{ - GOptionContext *opt_ctx; - GMainLoop *loop; - GError *err = NULL; - - /* FIXME: Work around some side effects of the changes from - * https://bugzilla.gnome.org/show_bug.cgi?id=741054 - * - * The modified poll function somehow calls setugid(), which - * then abort()s the application. Make sure that we use g_poll() - * here! - */ -#ifdef __APPLE__ - { - GMainContext *context = g_main_context_default (); - g_main_context_set_poll_func (context, dummy_poll); - } -#endif - -#ifdef HAVE_PTP_HELPER_SETUID - if (setuid (0) < 0) - g_error ("not running with superuser privileges"); -#endif - - opt_ctx = g_option_context_new ("- GStreamer PTP helper process"); - g_option_context_add_main_entries (opt_ctx, opt_entries, NULL); - if (!g_option_context_parse (opt_ctx, &argc, &argv, &err)) - g_error ("Error parsing options: %s", err->message); - g_clear_error (&err); - g_option_context_free (opt_ctx); - - setup_sockets (); - drop_privileges (); - setup_stdio_channels (); - write_clock_id (); - - /* Get running */ - loop = g_main_loop_new (NULL, FALSE); - g_main_loop_run (loop); - - /* We never exit cleanly, so don't do cleanup */ - g_assert_not_reached (); - - return 0; -} diff --git a/subprojects/gstreamer/libs/gst/helpers/meson.build b/subprojects/gstreamer/libs/gst/helpers/meson.build index 543064ca13..7346e4de0a 100644 --- a/subprojects/gstreamer/libs/gst/helpers/meson.build +++ b/subprojects/gstreamer/libs/gst/helpers/meson.build @@ -1,3 +1,5 @@ +subdir('ptp') + exe = executable('gst-plugin-scanner', 'gst-plugin-scanner.c', c_args : gst_c_args, @@ -24,107 +26,6 @@ if bashcomp_found ) endif -# Check PTP support -have_ptp = false -if host_system == 'android' - message('PTP not supported on Android because of permissions.') -elif host_system == 'windows' - message('PTP not supported on Windows, not ported yet.') -elif host_system == 'ios' - message('PTP not supported on iOS because of permissions.') -elif ['linux', 'darwin', 'netbsd', 'freebsd', 'openbsd', 'kfreebsd', 'dragonfly', 'sunos', 'gnu', 'gnu/kfreebsd'].contains(host_system) - message('PTP supported on ' + host_system + '.') - have_ptp = true -else - message('PTP not supported on ' + host_system + ', not ported yet.') -endif - -if have_ptp - cdata.set('HAVE_PTP', 1, description : 'PTP support available') - - if cc.compiles('''#include - #include - int some_func (void) { - struct ifreq ifr; - struct ifconf ifc; - ioctl(0, SIOCGIFCONF, &ifc); - ioctl(0, SIOCGIFFLAGS, &ifr); - ioctl(0, SIOCGIFHWADDR, &ifr); - return ifr.ifr_hwaddr.sa_data[0]; - }''', - name : 'SIOCGIFCONF, SIOCGIFFLAGS and SIOCGIFHWADDR available') - cdata.set('HAVE_SIOCGIFCONF_SIOCGIFFLAGS_SIOCGIFHWADDR', 1, - description : 'SIOCGIFCONF, SIOCGIFFLAGS and SIOCGIFHWADDR is available') - endif - - if cc.compiles('''#include - #include - #include - int some_func (void) { - struct ifaddrs *ifaddr; - getifaddrs(&ifaddr); - return (ifaddr->ifa_flags & IFF_LOOPBACK) && ifaddr->ifa_addr->sa_family != AF_LINK; - }''', name : 'getifaddrs() and AF_LINK available') - cdata.set('HAVE_GETIFADDRS_AF_LINK', 1, - description : 'getifaddrs() and AF_LINK is available') - endif - - setcap_prog = find_program('setcap', '/usr/sbin/setcap', '/sbin/setcap', required : false) - cap_dep = dependency('libcap', required: false) - - # user/group to change to in gst-ptp-helper - ptp_helper_setuid_user = get_option('ptp-helper-setuid-user') - if ptp_helper_setuid_user != '' - cdata.set_quoted('HAVE_PTP_HELPER_SETUID_USER', ptp_helper_setuid_user, - description : 'PTP helper setuid user') - endif - ptp_helper_setuid_group = get_option('ptp-helper-setuid-group') - if ptp_helper_setuid_group != '' - cdata.set_quoted('HAVE_PTP_HELPER_SETUID_GROUP', ptp_helper_setuid_group, - description : 'PTP helper setuid group') - endif - - # how to install gst-ptp-helper - with_ptp_helper_permissions = get_option('ptp-helper-permissions') - if with_ptp_helper_permissions == 'auto' - if setcap_prog.found() and cap_dep.found() - with_ptp_helper_permissions = 'capabilities' - else - with_ptp_helper_permissions = 'setuid-root' - endif - endif - message('How to install gst-ptp-helper: ' + with_ptp_helper_permissions) - - if with_ptp_helper_permissions == 'none' - # nothing to do - elif with_ptp_helper_permissions == 'setuid-root' - cdata.set('HAVE_PTP_HELPER_SETUID', 1, - description : 'Use setuid-root for permissions in PTP helper') - elif with_ptp_helper_permissions == 'capabilities' - if not setcap_prog.found() - error('capabilities-based ptp-helper-permissions requested, but could not find setcap tool.') - elif not cap_dep.found() - error('capabilities-based ptp-helper-permissions requested, but could not find libcap.') - endif - cdata.set('HAVE_PTP_HELPER_CAPABILITIES', 1, - description : 'Use capabilities for permissions in PTP helper') - else - error('Unexpected ptp helper permissions value: ' + with_ptp_helper_permissions) - endif - - exe = executable('gst-ptp-helper', 'gst-ptp-helper.c', - c_args : gst_c_args, - include_directories : [configinc, libsinc], - dependencies : [gst_dep, gio_dep, mathlib, cap_dep], - install_dir : helpers_install_dir, - install : true) - - meson.add_install_script('ptp_helper_post_install.sh', - helpers_install_dir, with_ptp_helper_permissions, - setcap_prog.found() ? setcap_prog.full_path() : '') - meson.add_devenv({'GST_PTP_HELPER': exe.full_path()}) -endif - install_data(['gst_gdb.py', 'glib_gobject_helper.py'], install_dir : join_paths(get_option('datadir'), 'gstreamer-@0@'.format(apiversion), 'gdb'), install_tag : 'devel') diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/args.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/args.rs new file mode 100644 index 0000000000..75868b9989 --- /dev/null +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/args.rs @@ -0,0 +1,66 @@ +// GStreamer +// +// Copyright (C) 2015-2023 Sebastian Dröge +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +use std::env; + +use crate::{ + bail, + error::{Context, Error}, +}; + +/// Parsed command-line arguments. +#[derive(Debug)] +pub struct Args { + pub interfaces: Vec, + pub verbose: bool, + pub clock_id: u64, +} + +/// Parse the command-line arguments. +pub fn parse_args() -> Result { + let mut interfaces = Vec::new(); + let mut verbose = false; + let mut clock_id = 0; + + let mut args = env::args(); + // Skip executable name + let _ = args.next(); + + while let Some(arg) = args.next() { + match arg.as_str() { + "-v" | "--verbose" => { + verbose = true; + } + "-i" | "--interface" => { + let iface = args.next().context("No interface following -i")?; + interfaces.push(iface); + } + "-c" | "--clock-id" => { + let clock_id_arg = args.next().context("No clock-id following -c")?; + clock_id = clock_id_arg.parse::().context("Invalid clock ID")?; + } + arg => { + bail!("Unknown command-line argument {}", arg); + } + } + } + + let args = Args { + interfaces, + verbose, + clock_id, + }; + + if verbose { + eprintln!("Running with arguments {:#?}", args); + } + + Ok(args) +} diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/conf_lib.rs.in b/subprojects/gstreamer/libs/gst/helpers/ptp/conf_lib.rs.in new file mode 100644 index 0000000000..039b153c4b --- /dev/null +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/conf_lib.rs.in @@ -0,0 +1,2 @@ +pub const PTP_HELPER_SETUID_USER: Option<&str> = @PTP_HELPER_SETUID_USER@; +pub const PTP_HELPER_SETUID_GROUP: Option<&str> = @PTP_HELPER_SETUID_GROUP@; diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/error.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/error.rs new file mode 100644 index 0000000000..8a8218f2cf --- /dev/null +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/error.rs @@ -0,0 +1,219 @@ +// GStreamer +// +// Copyright (C) 2015-2023 Sebastian Dröge +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +use std::{ + error::Error as StdError, + fmt::{Debug, Display}, +}; + +/// Custom error type for error display reasons. +pub struct Error(Box); + +impl Error { + #[doc(hidden)] + pub fn new(message: String, source: Option>) -> Self { + Error(Box::new(ErrorInner { message, source })) + } +} + +struct ErrorInner { + message: String, + source: Option>, +} + +impl Debug for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut e = self; + let mut first = true; + + // Print the actual error message of this error and then iterate over the whole + // error chain and print each cause on the chain indented. + 'next_error: loop { + if first { + writeln!(f, "{}", e.0.message)?; + first = false; + } else { + for line in e.0.message.lines() { + writeln!(f, " {}", line)?; + } + } + + let mut source = match e.0.source { + Some(ref source) => &**source, + None => break 'next_error, + }; + + if let Some(source) = source.downcast_ref::() { + e = source; + writeln!(f, "\nCaused by:\n")?; + continue 'next_error; + } + + loop { + writeln!(f, "\nCaused by:\n")?; + let source_str = source.to_string(); + for line in source_str.lines() { + writeln!(f, " {}", line)?; + } + + source = match source.source() { + None => break 'next_error, + Some(source) => source, + }; + + if let Some(source) = source.downcast_ref::() { + e = source; + writeln!(f, "\nCaused by:\n")?; + continue 'next_error; + } + } + } + + Ok(()) + } +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ::fmt(self, f) + } +} + +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match self.0.source { + None => None, + Some(ref source) => Some(&**source), + } + } +} + +impl<'a> From<&'a str> for Error { + fn from(message: &'a str) -> Self { + Error(Box::new(ErrorInner { + message: String::from(message), + source: None, + })) + } +} + +impl From for Error { + fn from(message: String) -> Self { + Error(Box::new(ErrorInner { + message, + source: None, + })) + } +} + +#[macro_export] +/// Create a new `Error` from the given message and possibly source error. +macro_rules! format_err { + (source: $source:expr, $msg:literal $(,)?) => { + $crate::error::Error::new( + String::from($msg), + Some($source.into()), + ) + }; + (source: $source:expr, $err:expr $(,)?) => { + $crate::error::Error::new( + format!($err), + Some($source.into()), + ) + }; + (source: $source:expr, $fmt:expr, $($arg:tt)*) => { + $crate::error::Error::new( + format!($fmt, $($arg)*), + Some($source.into()), + ) + }; + + ($msg:literal $(,)?) => { + $crate::error::Error::new( + String::from($msg), + None, + ) + }; + ($err:expr $(,)?) => { + $crate::error::Error::new( + format!($err), + None, + ) + }; + ($fmt:expr, $($arg:tt)*) => { + $crate::error::Error::new( + format!($fmt, $($arg)*), + None, + ) + }; +} + +#[macro_export] +/// Return new `Error` from the given message and possibly source error. +macro_rules! bail { + ($($arg:tt)+) => { + return Err($crate::format_err!($($arg)+)); + }; +} + +/// Trait for adding a context message to any `Result` or `Option` +/// and turning it into a `Result`. +pub trait Context { + /// Add a static context. + /// + /// This should only be called if `context` requires no allocations or otherwise + /// exists already. + fn context(self, context: C) -> Result + where + C: Display; + + /// Add a lazily created context. + fn with_context(self, func: F) -> Result + where + C: Display, + F: FnOnce() -> C; +} + +impl Context for Result +where + E: StdError + 'static, +{ + fn context(self, context: C) -> Result + where + C: Display, + { + self.map_err(|err| Error::new(context.to_string(), Some(Box::new(err)))) + } + + fn with_context(self, func: F) -> Result + where + C: Display, + F: FnOnce() -> C, + { + self.map_err(|err| Error::new(func().to_string(), Some(Box::new(err)))) + } +} + +impl Context for Option { + fn context(self, context: C) -> Result + where + C: Display, + { + self.ok_or_else(|| Error::new(context.to_string(), None)) + } + + fn with_context(self, func: F) -> Result + where + C: Display, + F: FnOnce() -> C, + { + self.ok_or_else(|| Error::new(func().to_string(), None)) + } +} diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs new file mode 100644 index 0000000000..5379813465 --- /dev/null +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/ffi.rs @@ -0,0 +1,786 @@ +// GStreamer +// +// Copyright (C) 2015-2023 Sebastian Dröge +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +#[cfg(unix)] +#[allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] +pub mod unix { + use std::os::{raw::*, unix::io::RawFd}; + + #[cfg(not(any( + target_os = "linux", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "macos", + target_os = "solaris", + target_os = "illumos", + )))] + compile_error!("Unsupported Operating System"); + + // The API definitions below are taken from the libc crate version 0.2.139, the corresponding C + // headers, manpages and related documentation. + // + // XXX: Once meson has cargo subproject support all of the below can be replaced with the libc crate. + pub const STDIN_FILENO: RawFd = 0; + pub const STDOUT_FILENO: RawFd = 1; + pub const O_RDONLY: c_int = 0; + + pub const POLLIN: c_short = 0x1; + pub const POLLERR: c_short = 0x8; + pub const POLLHUP: c_short = 0x10; + pub const POLLNVAL: c_short = 0x20; + + pub const IPPROTO_IP: c_int = 0; + #[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "macos", + ))] + pub const IP_ADD_MEMBERSHIP: c_int = 12; + #[cfg(target_os = "linux")] + pub const IP_ADD_MEMBERSHIP: c_int = 35; + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + pub const IP_ADD_MEMBERSHIP: c_int = 19; + + #[cfg(any( + target_os = "solaris", + target_os = "illumos", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "macos", + ))] + pub const SOL_SOCKET: c_int = 0xffff; + #[cfg(all( + target_os = "linux", + any( + target_arch = "sparc", + target_arch = "sparc64", + target_arch = "mips", + target_arch = "mips64", + ) + ))] + pub const SOL_SOCKET: c_int = 0xffff; + #[cfg(all( + target_os = "linux", + not(any( + target_arch = "sparc", + target_arch = "sparc64", + target_arch = "mips", + target_arch = "mips64", + )), + ))] + pub const SOL_SOCKET: c_int = 1; + + #[cfg(any( + target_os = "solaris", + target_os = "illumos", + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "macos", + ))] + pub const SO_REUSEADDR: c_int = 0x4; + #[cfg(all( + target_os = "linux", + any( + target_arch = "sparc", + target_arch = "sparc64", + target_arch = "mips", + target_arch = "mips64", + ), + ))] + pub const SO_REUSEADDR: c_int = 0x4; + #[cfg(all( + target_os = "linux", + not(any( + target_arch = "sparc", + target_arch = "sparc64", + target_arch = "mips", + target_arch = "mips64", + )), + ))] + pub const SO_REUSEADDR: c_int = 2; + + #[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "macos", + ))] + pub const SO_REUSEPORT: c_int = 0x200; + #[cfg(all( + target_os = "linux", + any( + target_arch = "sparc", + target_arch = "sparc64", + target_arch = "mips", + target_arch = "mips64", + ), + ))] + pub const SO_REUSEPORT: c_int = 0x200; + #[cfg(all( + target_os = "linux", + not(any( + target_arch = "sparc", + target_arch = "sparc64", + target_arch = "mips", + target_arch = "mips64" + )), + ))] + pub const SO_REUSEPORT: c_int = 15; + + pub const AF_INET: c_int = 2; + #[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "macos", + ))] + pub const AF_LINK: c_int = 18; + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + pub const AF_LINK: c_int = 25; + #[cfg(target_os = "linux")] + pub const AF_PACKET: c_int = 17; + + pub const IFF_UP: c_int = 0x1; + pub const IFF_LOOPBACK: c_int = 0x8; + + #[cfg(target_os = "linux")] + pub const IFF_MULTICAST: c_int = 0x1000; + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + pub const IFF_MULTICAST: ::c_int = 0x0800; + #[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "macos", + ))] + pub const IFF_MULTICAST: c_int = 0x08000; + + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + pub const IF_NAMESIZE: usize = 32; + #[cfg(not(any(target_os = "linux", target_os = "solaris", target_os = "illumos")))] + pub const IF_NAMESIZE: usize = 16; + + extern "C" { + #[cfg_attr( + all(target_os = "macos", target_arch = "x86"), + link_name = "open$UNIX2003" + )] + pub fn open(path: *const u8, oflag: c_int, ...) -> i32; + #[cfg_attr( + all(target_os = "macos", target_arch = "x86"), + link_name = "read$UNIX2003" + )] + pub fn read(fd: RawFd, buf: *mut u8, count: usize) -> isize; + #[cfg_attr( + all(target_os = "macos", target_arch = "x86"), + link_name = "write$UNIX2003" + )] + pub fn write(fd: RawFd, buf: *const u8, count: usize) -> isize; + + #[cfg_attr( + all(target_os = "macos", target_arch = "x86"), + link_name = "close$UNIX2003" + )] + pub fn close(fd: c_int) -> c_int; + + #[cfg_attr( + all(target_os = "macos", target_arch = "x86"), + link_name = "poll$UNIX2003" + )] + pub fn poll(fds: *mut pollfd, nfds: nfds_t, timeout: c_int) -> c_int; + + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + pub fn if_nametoindex(name: *const c_char) -> c_uint; + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + pub fn setsockopt( + socket: c_int, + level: c_int, + name: c_int, + value: *const c_void, + option_len: u32, + ) -> c_int; + + pub fn getifaddrs(ifap: *mut *mut ifaddrs) -> c_int; + pub fn freeifaddrs(ifa: *mut ifaddrs); + } + + #[cfg(any(target_os = "linux", target_os = "solaris", target_os = "illumos"))] + pub type nfds_t = c_ulong; + #[cfg(not(any(target_os = "linux", target_os = "solaris", target_os = "illumos")))] + pub type nfds_t = c_uint; + + #[repr(C)] + #[derive(Clone, Copy)] + pub struct pollfd { + pub fd: c_int, + pub events: c_short, + pub revents: c_short, + } + + pub type in_addr_t = u32; + + #[repr(C)] + #[derive(Copy, Clone)] + pub struct in_addr { + pub s_addr: in_addr_t, + } + + // Solaris does not have support for this so we fall back to use the std API + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + #[repr(C)] + #[derive(Copy, Clone)] + pub struct ip_mreqn { + pub imr_multiaddr: in_addr, + pub imr_address: in_addr, + pub imr_ifindex: c_int, + } + + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + #[repr(C)] + pub struct ifaddrs { + pub ifa_next: *mut ifaddrs, + pub ifa_name: *mut c_char, + pub ifa_flags: c_ulong, + pub ifa_addr: *mut sockaddr, + pub ifa_netmask: *mut sockaddr, + pub ifa_dstaddr: *mut sockaddr, + pub ifa_data: *mut c_void, + } + + #[cfg(target_os = "linux")] + #[repr(C)] + pub struct ifaddrs { + pub ifa_next: *mut ifaddrs, + pub ifa_name: *mut c_char, + pub ifa_flags: c_uint, + pub ifa_addr: *mut sockaddr, + pub ifa_netmask: *mut sockaddr, + pub ifa_ifu: *mut sockaddr, + pub ifa_data: *mut c_void, + } + + #[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "macos", + ))] + #[repr(C)] + pub struct ifaddrs { + pub ifa_next: *mut ifaddrs, + pub ifa_name: *mut c_char, + pub ifa_flags: c_uint, + pub ifa_addr: *mut sockaddr, + pub ifa_netmask: *mut sockaddr, + pub ifa_dstaddr: *mut sockaddr, + pub ifa_data: *mut c_void, + #[cfg(target_os = "netbsd")] + pub ifa_addrflags: c_uint, + } + + #[cfg(any(target_os = "linux", target_os = "solaris", target_os = "illumos"))] + #[repr(C)] + pub struct sockaddr { + pub sa_family: u16, + pub sa_data: [u8; 14], + } + + #[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "macos", + ))] + #[repr(C)] + pub struct sockaddr { + pub sa_len: u8, + pub sa_family: u8, + pub sa_data: [u8; 14], + } + + #[cfg(any(target_os = "linux", target_os = "solaris", target_os = "illumos"))] + #[repr(C)] + pub struct sockaddr_in { + pub sin_family: u16, + pub sin_port: u16, + pub sin_addr: in_addr, + pub sin_zero: [u8; 8], + } + #[cfg(any( + target_os = "freebsd", + target_os = "openbsd", + target_os = "netbsd", + target_os = "dragonfly", + target_os = "macos", + ))] + #[repr(C)] + pub struct sockaddr_in { + pub sin_len: u8, + pub sin_family: u8, + pub sin_port: u16, + pub sin_addr: in_addr, + pub sin_zero: [u8; 8], + } + + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + #[repr(C)] + pub struct sockaddr_dl { + pub sdl_family: u16, + pub sdl_index: u16, + pub sdl_type: u8, + pub sdl_nlen: u8, + pub sdl_alen: u8, + pub sdl_slen: u8, + pub sdl_data: [u8; 244], + } + + #[cfg(any(target_os = "netbsd", target_os = "macos"))] + #[repr(C)] + pub struct sockaddr_dl { + pub sdl_len: u8, + pub sdl_family: u8, + pub sdl_index: u16, + pub sdl_type: u8, + pub sdl_nlen: u8, + pub sdl_alen: u8, + pub sdl_slen: u8, + pub sdl_data: [u8; 12], + } + + #[cfg(target_os = "openbsd")] + #[repr(C)] + pub struct sockaddr_dl { + pub sdl_len: u8, + pub sdl_family: u8, + pub sdl_index: u16, + pub sdl_type: u8, + pub sdl_nlen: u8, + pub sdl_alen: u8, + pub sdl_slen: u8, + pub sdl_data: [u8; 24], + } + + #[cfg(target_os = "freebsd")] + #[repr(C)] + pub struct sockaddr_dl { + pub sdl_len: u8, + pub sdl_family: u8, + pub sdl_index: u16, + pub sdl_type: u8, + pub sdl_nlen: u8, + pub sdl_alen: u8, + pub sdl_slen: u8, + pub sdl_data: [u8; 46], + } + + #[cfg(target_os = "dragonfly")] + #[repr(C)] + pub struct sockaddr_dl { + pub sdl_len: u8, + pub sdl_family: u8, + pub sdl_index: u16, + pub sdl_type: u8, + pub sdl_nlen: u8, + pub sdl_alen: u8, + pub sdl_slen: u8, + pub sdl_data: [u8; 12], + pub sdl_rcf: u16, + pub sdl_route: [u8; 16], + } + + #[cfg(target_os = "linux")] + #[repr(C)] + pub struct sockaddr_ll { + pub sll_family: u16, + pub sll_protocol: u16, + pub sll_ifindex: u32, + pub sll_hatype: u16, + pub sll_pkttype: u8, + pub sll_halen: u8, + pub sll_addr: [u8; 8], + } + + #[cfg(target_os = "linux")] + pub mod linux { + pub use super::*; + + #[cfg(target_arch = "x86")] + pub const SYS_getrandom: c_ulong = 355; + #[cfg(all(target_arch = "x86_64", target_pointer_width = "32"))] + pub const SYS_getrandom: c_ulong = 0x40000000 + 318; + #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] + pub const SYS_getrandom: c_ulong = 318; + #[cfg(target_arch = "arm")] + pub const SYS_getrandom: c_ulong = 384; + #[cfg(target_arch = "aarch64")] + pub const SYS_getrandom: c_ulong = 278; + #[cfg(target_arch = "mips")] + pub const SYS_getrandom: c_ulong = 4000 + 353; + #[cfg(target_arch = "mips64")] + pub const SYS_getrandom: c_ulong = 5000 + 313; + #[cfg(any( + target_arch = "riscv32", + target_arch = "riscv64", + target_arch = "loongarch64" + ))] + pub const SYS_getrandom: c_ulong = 278; + #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))] + pub const SYS_getrandom: c_ulong = 359; + #[cfg(any(target_arch = "sparc", target_arch = "sparc64"))] + pub const SYS_getrandom: c_ulong = 347; + #[cfg(target_arch = "s390x")] + pub const SYS_getrandom: c_ulong = 349; + #[cfg(target_arch = "m68k")] + pub const SYS_getrandom: c_ulong = 352; + #[cfg(not(any( + target_arch = "x86", + target_arch = "x86_64", + target_arch = "arm", + target_arch = "aarch64", + target_arch = "mips", + target_arch = "mips64", + target_arch = "riscv32", + target_arch = "riscv64", + target_arch = "loongarch64", + target_arch = "powerpc", + target_arch = "powerpc64", + target_arch = "sparc", + target_arch = "sparc64", + target_arch = "s390x", + target_arch = "m68k", + )))] + pub const SYS_getrandom: c_ulong = 0; + + extern "C" { + pub fn syscall(num: c_ulong, ...) -> c_long; + } + } + + #[cfg(ptp_helper_permissions = "setcap")] + pub mod setcaps { + use super::*; + + pub type cap_t = *mut c_void; + + #[link(name = "cap")] + extern "C" { + pub fn cap_clear(c: cap_t) -> c_int; + pub fn cap_get_proc() -> cap_t; + pub fn cap_set_proc(c: cap_t) -> c_int; + pub fn cap_free(c: cap_t) -> c_int; + } + } + + #[cfg(ptp_helper_permissions = "setuid-root")] + pub mod setuid_root { + use super::*; + + pub type uid_t = u32; + pub type gid_t = u32; + + #[repr(C)] + pub struct passwd { + pub pw_name: *mut c_char, + pub pw_passwd: *mut c_char, + pub pw_uid: uid_t, + pub pw_gid: gid_t, + // More fields following here + truncated: c_void, + } + + #[repr(C)] + pub struct group { + pub gr_name: *mut c_char, + pub gr_passwd: *mut c_char, + pub gr_gid: gid_t, + // More fields following here + truncated: c_void, + } + + extern "C" { + #[cfg_attr(target_os = "netbsd", link_name = "__getpwnam50")] + pub fn getpwnam(name: *const c_char) -> *mut passwd; + pub fn getgrnam(name: *const c_char) -> *mut group; + pub fn setgid(gid: gid_t) -> c_int; + pub fn getgid() -> gid_t; + pub fn setuid(gid: gid_t) -> c_int; + } + } +} + +#[cfg(windows)] +#[allow(non_camel_case_types, non_upper_case_globals, non_snake_case)] +pub mod windows { + use std::os::{ + raw::*, + windows::raw::{HANDLE, SOCKET}, + }; + + // The API definitions below are taken from the windows-sys crate version 0.45.0, the + // corresponding C headers, MSDN and related documentation. + // + // XXX: Once meson has cargo subproject support all of the below can be replaced with the windows-sys crate. + pub const STD_INPUT_HANDLE: i32 = -10; + pub const STD_OUTPUT_HANDLE: i32 = -11; + + pub const FILE_TYPE_CHAR: u32 = 0x0002; + pub const FILE_TYPE_PIPE: u32 = 0x0003; + + #[link(name = "kernel32")] + extern "system" { + pub fn GetStdHandle(nstdhandle: i32) -> HANDLE; + + pub fn ReadFile( + hfile: HANDLE, + lpbuffer: *mut u8, + nnumberofbytestoread: u32, + lpnumberofbytesread: *mut u32, + lpoverlapped: *mut c_void, + ) -> i32; + + pub fn WriteFile( + hfile: HANDLE, + lpbuffer: *const u8, + nnumberofbytestowrite: u32, + lpnumberofbyteswritten: *mut u32, + lpoverlapped: *mut c_void, + ) -> i32; + + pub fn WaitForMultipleObjects( + ncount: u32, + lphandles: *const HANDLE, + bwaitall: i32, + dwmilliseconds: u32, + ) -> u32; + + pub fn SetConsoleMode(hconsolehandle: HANDLE, dwmode: u32) -> i32; + pub fn FlushConsoleInputBuffer(hconsolehandle: HANDLE) -> i32; + + pub fn CreateEventA( + lpeventattributes: *const c_void, + bmanualreset: i32, + binitialstate: i32, + lpname: *const u8, + ) -> HANDLE; + pub fn SetEvent(hevent: HANDLE) -> i32; + pub fn ResetEvent(hevent: HANDLE) -> i32; + pub fn CloseHandle(hobject: HANDLE) -> i32; + + pub fn GetFileType(hfile: HANDLE) -> u32; + + pub fn GetProcessHeap() -> isize; + pub fn HeapAlloc(hheap: isize, dwflags: u32, dwbytes: usize) -> *mut c_void; + pub fn HeapFree(hheap: isize, dwflags: u32, lpmem: *mut c_void) -> i32; + pub fn HeapReAlloc( + hheap: isize, + dwflags: u32, + lpmem: *mut c_void, + dwbytes: usize, + ) -> *mut c_void; + } + + pub const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002; + + #[link(name = "bcrypt")] + extern "system" { + pub fn BCryptGenRandom( + hAlgorithm: *mut c_void, + pBuffer: *mut u8, + cbBuffer: u32, + dwFlags: u32, + ) -> u32; + } + + pub const FD_READ: u32 = 1; + pub const FD_READ_BIT: usize = 0; + + #[repr(C)] + #[derive(Copy, Clone)] + pub struct WSANETWORKEVENTS { + pub lnetworkevents: u32, + pub ierrorcode: [i32; 10], + } + + pub const IPPROTO_IP: u32 = 0u32; + pub const IP_ADD_MEMBERSHIP: u32 = 12u32; + + pub const SOL_SOCKET: u32 = 65535; + pub const SO_REUSEADDR: u32 = 4; + + #[repr(C)] + #[derive(Copy, Clone)] + pub struct IN_ADDR_0_0 { + pub s_b1: u8, + pub s_b2: u8, + pub s_b3: u8, + pub s_b4: u8, + } + + #[repr(C)] + #[derive(Copy, Clone)] + pub struct IN_ADDR_0_1 { + pub s_w1: u16, + pub s_w2: u16, + } + + #[repr(C)] + #[derive(Copy, Clone)] + pub union IN_ADDR_0 { + pub S_un_b: IN_ADDR_0_0, + pub S_un_w: IN_ADDR_0_1, + pub S_addr: u32, + } + + #[repr(C)] + #[derive(Copy, Clone)] + pub struct IN_ADDR { + pub S_un: IN_ADDR_0, + } + + #[repr(C)] + #[derive(Copy, Clone)] + pub struct IP_MREQ { + pub imr_multiaddr: IN_ADDR, + pub imr_address: IN_ADDR, + } + + #[link(name = "ws2_32")] + extern "system" { + pub fn WSAEventSelect(s: SOCKET, heventobject: HANDLE, lnetworkevents: u32) -> i32; + pub fn WSAEnumNetworkEvents( + s: SOCKET, + heventobject: HANDLE, + lpnetworkevents: *mut WSANETWORKEVENTS, + ) -> i32; + pub fn WSACreateEvent() -> HANDLE; + pub fn WSACloseEvent(hevent: HANDLE) -> i32; + pub fn WSAGetLastError() -> i32; + + pub fn setsockopt( + socket: SOCKET, + level: i32, + name: i32, + value: *const c_void, + option_len: i32, + ) -> i32; + } + + pub const AF_INET: u32 = 2; + + pub const GAA_FLAG_SKIP_ANYCAST: u32 = 0x0002; + pub const GAA_FLAG_SKIP_MULTICAST: u32 = 0x0004; + pub const GAA_FLAG_SKIP_DNS_SERVER: u32 = 0x0008; + + pub const ADAPTER_FLAG_RECEIVE_ONLY: u32 = 0x08; + pub const ADAPTER_FLAG_NO_MULTICAST: u32 = 0x10; + pub const ADAPTER_FLAG_IPV4_ENABLED: u32 = 0x80; + + pub const IF_TYPE_SOFTWARE_LOOPBACK: u32 = 24; + + pub const IF_OPER_STATUS_UP: u32 = 1; + + pub const ERROR_NOT_ENOUGH_MEMORY: u32 = 8u32; + + #[repr(C)] + #[derive(Copy, Clone)] + pub struct IP_ADAPTER_ADDRESSES_LH_0_0 { + pub length: u32, + pub ifindex: u32, + } + + #[repr(C)] + #[derive(Copy, Clone)] + pub union IP_ADAPTER_ADDRESSES_LH_0 { + pub alignment: u64, + pub anonymous: IP_ADAPTER_ADDRESSES_LH_0_0, + } + + #[repr(C)] + #[derive(Copy, Clone)] + pub struct IP_ADAPTER_UNICAST_ADDRESS_LH_0_0 { + pub length: u32, + pub ifindex: u32, + } + + #[repr(C)] + #[derive(Copy, Clone)] + pub union IP_ADAPTER_UNICAST_ADDRESS_LH_0 { + pub alignment: u64, + pub anonymous: IP_ADAPTER_UNICAST_ADDRESS_LH_0_0, + } + + #[repr(C)] + pub struct SOCKADDR { + pub sa_family: u16, + pub sin_port: u16, + pub in_addr: IN_ADDR, + } + + #[repr(C)] + pub struct SOCKET_ADDRESS { + pub lpsocketaddr: *mut SOCKADDR, + pub isocketaddrlength: i32, + } + + #[repr(C)] + pub struct IP_ADAPTER_UNICAST_ADDRESS_LH { + pub anonymous: IP_ADAPTER_UNICAST_ADDRESS_LH_0, + pub next: *mut IP_ADAPTER_UNICAST_ADDRESS_LH, + pub address: SOCKET_ADDRESS, + // More fields following here + truncated: c_void, + } + + #[repr(C)] + pub struct IP_ADAPTER_ADDRESSES_LH { + pub anonymous: IP_ADAPTER_ADDRESSES_LH_0, + pub next: *mut IP_ADAPTER_ADDRESSES_LH, + pub adaptername: *const u8, + pub firstunicastaddress: *mut IP_ADAPTER_UNICAST_ADDRESS_LH, + pub firstanycastaddress: *mut c_void, + pub firstmulticastaddress: *mut c_void, + pub firstdnsserveraddress: *mut c_void, + pub dnssuffix: *const u16, + pub description: *const u16, + pub friendlyname: *const u16, + pub physicaladdress: [u8; 8], + pub physicaladdresslength: u32, + pub flags: u32, + pub mtu: u32, + pub iftype: u32, + pub operstatus: u32, + // More fields following here + truncated: c_void, + } + + #[link(name = "iphlpapi")] + extern "system" { + pub fn GetAdaptersAddresses( + family: u32, + flags: u32, + reserved: *mut c_void, + adapteraddresses: *mut IP_ADAPTER_ADDRESSES_LH, + sizepointer: *mut u32, + ) -> u32; + } +} diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/io.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/io.rs new file mode 100644 index 0000000000..7b0e064d51 --- /dev/null +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/io.rs @@ -0,0 +1,769 @@ +// GStreamer +// +// Copyright (C) 2015-2023 Sebastian Dröge +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +#[cfg(unix)] +mod imp { + use std::{ + io::{self, Read, Write}, + net::UdpSocket, + os::unix::io::{AsRawFd, RawFd}, + }; + + use crate::{bail, error::Error, ffi::unix::*}; + + /// Inputs and outputs, and allowing to poll the inputs for available data. + /// + /// This carries the event/general UDP socket and stdin/stdout. + pub struct Poll { + event_socket: UdpSocket, + general_socket: UdpSocket, + stdin: Stdin, + stdout: Stdout, + } + + /// Result of polling the inputs of the `Poll`. + /// + /// Any input that has data available for reading will be set to `true`, potentially multiple + /// at once. + /// + /// Note that reading from the sockets is non-blocking but reading from stdin is blocking so + /// special care has to be taken to only read as much as is available. + pub struct PollResult { + pub event_socket: bool, + pub general_socket: bool, + pub stdin: bool, + } + + impl Poll { + /// Name of the input based on the `struct pollfd` index. + fn fd_name(idx: usize) -> &'static str { + match idx { + 0 => "event socket", + 1 => "general socket", + 2 => "stdin", + _ => unreachable!(), + } + } + + /// Create a new `Poll` instance from the two sockets. + pub fn new(event_socket: UdpSocket, general_socket: UdpSocket) -> Result { + let stdin = Stdin::acquire(); + let stdout = Stdout::acquire(); + + Ok(Self { + event_socket, + general_socket, + stdin, + stdout, + }) + } + + /// Mutable reference to the event socket. + pub fn event_socket(&mut self) -> &mut UdpSocket { + &mut self.event_socket + } + + /// Mutable reference to the general socket. + pub fn general_socket(&mut self) -> &mut UdpSocket { + &mut self.general_socket + } + + /// Mutable reference to stdin for reading. + pub fn stdin(&mut self) -> &mut Stdin { + &mut self.stdin + } + + /// Mutable reference to stdout for writing. + pub fn stdout(&mut self) -> &mut Stdout { + &mut self.stdout + } + + /// Poll the event socket, general socket and stdin for available data to read. + /// + /// This blocks until at least one input has data available. + pub fn poll(&mut self) -> Result { + let mut pollfd = [ + pollfd { + fd: self.event_socket.as_raw_fd(), + events: POLLIN, + revents: 0, + }, + pollfd { + fd: self.general_socket.as_raw_fd(), + events: POLLIN, + revents: 0, + }, + pollfd { + fd: self.stdin.0, + events: POLLIN, + revents: 0, + }, + ]; + + // SAFETY: Polls the given pollfds above and requires a valid number to be passed. + // A negative timeout means that it will wait until at least one of the pollfds is + // ready. + // + // Will return -1 on error, otherwise the number of ready pollfds. This can never be + // zero as a non-empty set of pollfds is passed. + // + // On EINTR polling should be retried. + unsafe { + loop { + let res = poll(pollfd[..].as_mut_ptr(), pollfd.len() as _, -1); + if res == -1 { + let err = std::io::Error::last_os_error(); + if err.kind() == std::io::ErrorKind::Interrupted { + continue; + } + bail!(source: err, "Failed polling"); + } + assert_ne!(res, 0); + break; + } + } + + // Check for errors or hangup first + for (idx, pfd) in pollfd.iter().enumerate() { + if pfd.revents & (POLLERR | POLLNVAL) != 0 { + bail!("Poll error on {}", Self::fd_name(idx)); + } + + if pfd.revents & POLLHUP != 0 { + bail!("Hang up during polling on {}", Self::fd_name(idx)); + } + } + + Ok(PollResult { + event_socket: pollfd[0].revents & POLLIN != 0, + general_socket: pollfd[1].revents & POLLIN != 0, + stdin: pollfd[2].revents & POLLIN != 0, + }) + } + } + + /// Raw, unbuffered handle to `stdin`. + /// + /// This implements the `Read` trait for reading. + pub struct Stdin(RawFd); + + impl Stdin { + fn acquire() -> Self { + Stdin(STDIN_FILENO) + } + } + + impl Read for Stdin { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + // SAFETY: read() requires a valid fd and a mutable buffer with the given size. + // The fd is valid by construction as is the buffer. + // + // read() will return the number of bytes read or a negative value on errors. + let res = unsafe { read(self.0, buf.as_mut_ptr(), buf.len()) }; + + if res < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(res as usize) + } + } + } + + /// Raw, unbuffered handle to `stdout`. + /// + /// This implements the `Write` trait for writing. + pub struct Stdout(RawFd); + + impl Stdout { + fn acquire() -> Self { + Stdout(STDOUT_FILENO) + } + } + + impl Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + // SAFETY: write() requires a valid fd and a mutable buffer with the given size. + // The fd is valid by construction as is the buffer. + // + // write() will return the number of bytes written or a negative value on errors. + let res = unsafe { write(self.0, buf.as_ptr(), buf.len()) }; + + if res == -1 { + Err(std::io::Error::last_os_error()) + } else { + Ok(res as usize) + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } +} + +#[cfg(windows)] +mod imp { + use std::{ + cmp, + io::{self, Read, Write}, + mem, + net::UdpSocket, + os::windows::{io::AsRawSocket, raw::HANDLE}, + ptr, + sync::{Arc, Condvar, Mutex}, + thread, + }; + + use crate::{ + bail, + error::{Context, Error}, + ffi::windows::*, + }; + + /// Inputs and outputs, and allowing to poll the inputs for available data. + /// + /// This carries the event/general UDP socket and stdin/stdout. + pub struct Poll { + event_socket: UdpSocket, + event_socket_event: EventHandle, + general_socket: UdpSocket, + general_socket_event: EventHandle, + stdin: Stdin, + stdout: Stdout, + } + + /// Helper struct for a WSA event. + struct EventHandle(HANDLE); + + impl EventHandle { + fn new() -> io::Result { + // SAFETY: WSACreateEvent() returns 0 on error or otherwise a valid WSA event + // that has to be closed again later. + unsafe { + let event = WSACreateEvent(); + if event.is_null() || event as isize == -1 { + Err(io::Error::from_raw_os_error(WSAGetLastError())) + } else { + Ok(EventHandle(event)) + } + } + } + } + + impl Drop for EventHandle { + fn drop(&mut self) { + // SAFETY: The event is valid by construction and dropped at most once, so can be + // safely closed here.. + // + // The return value is intentionally ignored as nothing else can be done + // on errors anyway. + unsafe { + let _ = WSACloseEvent(self.0); + } + } + } + + /// Result of polling the inputs of the `Poll`. + /// + /// Any input that has data available for reading will be set to `true`, potentially multiple + /// at once. + /// + /// Note that reading from the sockets is non-blocking but reading from stdin is blocking so + /// special care has to be taken to only read as much as is available. + pub struct PollResult { + pub event_socket: bool, + pub general_socket: bool, + pub stdin: bool, + } + + impl Poll { + /// Create a new `Poll` instance from the two sockets. + pub fn new(event_socket: UdpSocket, general_socket: UdpSocket) -> Result { + let stdin = Stdin::acquire().context("Failure acquiring stdin handle")?; + let stdout = Stdout::acquire().context("Failed acquiring stdout handle")?; + + // Create event objects for the readability of the sockets. + let event_socket_event = EventHandle::new().context("Failed creating WSA event")?; + let general_socket_event = EventHandle::new().context("Failed creating WSA event")?; + + // SAFETY: WSAEventSelect() requires a valid socket and WSA event, which are both + // passed here, and the bitflag of events that should be selected for. + // + // On error a non-zero value is returned. + unsafe { + if WSAEventSelect(event_socket.as_raw_socket(), event_socket_event.0, FD_READ) != 0 + { + bail!( + source: io::Error::from_raw_os_error(WSAGetLastError()), + "Failed selecting for read events on event socket" + ); + } + + if WSAEventSelect( + general_socket.as_raw_socket(), + general_socket_event.0, + FD_READ, + ) != 0 + { + bail!( + source: io::Error::from_raw_os_error(WSAGetLastError()), + "Failed selecting for read events on general socket" + ); + } + } + + Ok(Self { + event_socket, + event_socket_event, + general_socket, + general_socket_event, + stdin, + stdout, + }) + } + + /// Mutable reference to the event socket. + pub fn event_socket(&mut self) -> &mut UdpSocket { + &mut self.event_socket + } + + /// Mutable reference to the general socket. + pub fn general_socket(&mut self) -> &mut UdpSocket { + &mut self.general_socket + } + + /// Mutable reference to stdin for reading. + pub fn stdin(&mut self) -> &mut Stdin { + &mut self.stdin + } + + /// Mutable reference to stdout for writing. + pub fn stdout(&mut self) -> &mut Stdout { + &mut self.stdout + } + + /// Poll the event socket, general socket and stdin for available data to read. + /// + /// This blocks until at least one input has data available. + pub fn poll(&mut self) -> Result { + let handles = [ + self.event_socket_event.0, + self.general_socket_event.0, + // If stdin is a pipe then we use the signalling event, otherwise stdin itself. + if let Some(ref thread_state) = self.stdin.thread_state { + thread_state.event + } else { + self.stdin.handle + }, + ]; + + // If stdin is a pipe and currently no data is pending on it then signal + // the reading thread to try reading one byte and blocking for that long. + if let Some(ref mut thread_state) = self.stdin.thread_state { + let mut guard = thread_state.buffer.lock().unwrap(); + if !guard.buffer_filled && !guard.fill_buffer { + guard.fill_buffer = true; + // SAFETY: The thread's event is valid by construction until the thread + // is stopped, and can be reset at any time. + unsafe { + ResetEvent(thread_state.event); + } + thread_state.buffer_cond.notify_one(); + } + } + + // SAFETY: Wait for the socket/stdin objects to become ready. This requires a valid + // array of valid handles and the corresponding length, whether it should wait for all + // handles (no), and a timeout (infinity). + // + // On error u32::MAX is returned, otherwise an index into the array of handles is + // returned for the handle that became ready. + let res = unsafe { + let res = + WaitForMultipleObjects(handles.len() as _, handles[..].as_ptr(), 0, u32::MAX); + if res == u32::MAX { + bail!( + source: io::Error::from_raw_os_error(WSAGetLastError()), + "Failed waiting for events" + ); + } + + assert!( + (0..=2).contains(&res), + "Unexpected WaitForMultipleObjects() return value {}", + res, + ); + + res + }; + + // For the sockets, enumerate the events that woke up the waiting, collect any errors + // and reset the event objects. + if (0..=1).contains(&res) { + let (socket, event) = if res == 0 { + (&self.event_socket, &self.event_socket_event) + } else { + (&self.general_socket, &self.general_socket_event) + }; + + // SAFETY: Requires a valid socket and event, which is given by construction here. + // The passed in memory for the network events will be filled if no error happens, + // and the function returns a non-zero value if an error has happened. + let networkevents = unsafe { + let mut networkevents = mem::MaybeUninit::uninit(); + if WSAEnumNetworkEvents( + socket.as_raw_socket(), + event.0, + networkevents.as_mut_ptr(), + ) != 0 + { + bail!( + source: io::Error::from_raw_os_error(WSAGetLastError()), + "Failed enumerating network events on {} socket", + if res == 0 { "event" } else { "general" }, + ); + } + + networkevents.assume_init() + }; + + if networkevents.ierrorcode[FD_READ_BIT] != 0 { + bail!( + source: io::Error::from_raw_os_error(networkevents.ierrorcode[FD_READ_BIT]), + "Error on {} socket while waiting for events", + if res == 0 { "event" } else { "general" }, + ); + } + assert!(networkevents.lnetworkevents & FD_READ != 0); + } + + Ok(PollResult { + event_socket: res == 0, + general_socket: res == 1, + stdin: res == 2, + }) + } + } + + /// Raw, unbuffered handle to `stdin`. + /// + /// This implements the `Read` trait for reading. + pub struct Stdin { + handle: HANDLE, + thread_state: Option>, + join_handle: Option>, + } + + struct StdinThreadState { + buffer: Mutex, + buffer_cond: Condvar, + event: HANDLE, + handle: HANDLE, + } + + unsafe impl Send for StdinThreadState {} + unsafe impl Sync for StdinThreadState {} + + struct StdinBuffer { + buffer: [u8; 1], + error: Option, + buffer_filled: bool, + fill_buffer: bool, + shutdown: bool, + } + + impl Drop for Stdin { + fn drop(&mut self) { + // If stdin was a pipe and a thread was started to check for read-readiness + // then stop this thread now and release its resources. + if let Some(ref thread_state) = self.thread_state { + let mut guard = thread_state.buffer.lock().unwrap(); + guard.shutdown = true; + thread_state.buffer_cond.notify_one(); + drop(guard); + let _ = self.join_handle.take().unwrap().join(); + + // SAFETY: The thread is stopped now so the event is not used by anything else + // anymore and can safely be closed now. + // + // The return value is explicitly ignored because nothing can be done on error + // anyway. + unsafe { + let _ = CloseHandle(thread_state.event); + } + } + } + } + + impl Stdin { + fn acquire() -> Result { + // SAFETY: GetStdHandle returns a borrowed handle, or 0 if none is set or -1 if an + // error has happened. + let handle = unsafe { + let handle = GetStdHandle(STD_INPUT_HANDLE); + if handle.is_null() { + bail!("No stdin handle set"); + } else if handle as isize == -1 { + bail!(source: io::Error::last_os_error(), "Can't get stdin handle"); + } + + handle + }; + // SAFETY: GetFileType() is safe to call on any valid handle. + let type_ = unsafe { GetFileType(handle) }; + + if type_ == FILE_TYPE_CHAR { + // Set the console to raw mode and flush any pending input. + // + // SAFETY: Calling this on non-console handles will cause an error but otherwise + // have no negative effects. We can safely change the console mode here as nothing + // else is accessing the console. + unsafe { + let _ = SetConsoleMode(handle, 0); + let _ = FlushConsoleInputBuffer(handle); + } + + Ok(Stdin { + handle, + thread_state: None, + join_handle: None, + }) + } else if type_ == FILE_TYPE_PIPE { + // XXX: Because g_spawn() creates the overridden pipes with _pipe(), they're + // 1. Full duplex, so WaitForMultipleObjects() always considers them ready as you can write + // 2. Not overlapped so only synchronous IO can be used + // To work around this we're creating a thread here that just reads synchronously + // from stdin to signal ready-ness. + + // SAFETY: Creating an event handle with all-zero parameters is valid and on error + // a NULL handle will be returned. Otherwise a valid event handle is returned that + // needs to be closed again later, which happens as part of the StdinThreadState + // Drop implementation. + let event = unsafe { + let event = CreateEventA(ptr::null(), 0, 0, ptr::null()); + if event.is_null() { + bail!( + source: io::Error::last_os_error(), + "Failed creating event handle" + ); + } + + event + }; + let thread_state = Arc::new(StdinThreadState { + buffer: Mutex::new(StdinBuffer { + buffer: [0], + error: None, + buffer_filled: false, + fill_buffer: true, + shutdown: false, + }), + buffer_cond: Condvar::new(), + event, + handle, + }); + + let join_handle = thread::spawn({ + let thread_state = thread_state.clone(); + move || Self::stdin_readiness_thread(&thread_state) + }); + + Ok(Stdin { + handle, + thread_state: Some(thread_state), + join_handle: Some(join_handle), + }) + } else { + bail!("unhandled stdin handle type {:x}", type_); + } + } + + /// Thread function to signal readiness of stdin. + /// + /// This thread tries to read a single byte and buffers it, then signals an event + /// object and waits for reading to finish and a new call to `poll()` to + /// start trying to read a single byte again. + fn stdin_readiness_thread(thread_state: &StdinThreadState) { + loop { + let mut buffer = [0u8]; + // SAFETY: Reads one byte from the handle synchronously. Nothing else is currently + // reading from the handle as this thread is waiting below on the condition + // variable as long as a single byte was read already, and only wakes up again if a + // full packet was read from stdin and the other thread is waiting on the event + // handle again. + let res = unsafe { + let mut bytes_read = mem::MaybeUninit::uninit(); + let res = ReadFile( + thread_state.handle, + buffer[..].as_mut_ptr(), + buffer.len() as u32, + bytes_read.as_mut_ptr(), + ptr::null_mut(), + ); + if res == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(bytes_read.assume_init()) + } + }; + + let mut guard = thread_state.buffer.lock().unwrap(); + assert!(!guard.buffer_filled); + assert!(guard.fill_buffer); + if guard.shutdown { + break; + } + guard.buffer_filled = true; + guard.fill_buffer = false; + match res { + Err(err) => { + guard.error = Some(err); + } + Ok(bytes_read) => { + guard.buffer[0] = buffer[0]; + assert_eq!(bytes_read, 1); + } + } + + // SAFETY: Signalling an event is valid from any thread at any time and the event + // handle is valid by construction. + unsafe { + SetEvent(thread_state.event); + } + while !guard.shutdown && !guard.fill_buffer { + guard = thread_state.buffer_cond.wait(guard).unwrap(); + } + if guard.shutdown { + break; + } + } + } + } + + impl Read for Stdin { + fn read(&mut self, mut buf: &mut [u8]) -> io::Result { + if buf.is_empty() { + return Ok(0); + } + + // If a read byte is pending from the readiness signalling thread then + // read that first here before reading any remaining data. + let mut already_read = 0; + if let Some(ref mut thread_state) = self.thread_state { + let mut guard = thread_state.buffer.lock().unwrap(); + assert!(!guard.fill_buffer); + if guard.buffer_filled { + guard.buffer_filled = false; + if let Some(err) = guard.error.take() { + return Err(err); + } + buf[0] = guard.buffer[0]; + if buf.len() == 1 { + return Ok(1); + } + buf = &mut buf[1..]; + already_read = 1; + } + } + + // SAFETY: Reads the given number of bytes into the buffer from the stdin handle. + // The other thread is not currently reading as checked above, and would only be + // triggered to read again by this thread once poll() is called. + unsafe { + let mut lpnumberofbytesread = mem::MaybeUninit::uninit(); + let res = ReadFile( + self.handle, + buf.as_mut_ptr(), + cmp::min(buf.len() as u32, u32::MAX) as u32, + lpnumberofbytesread.as_mut_ptr(), + ptr::null_mut(), + ); + + if res == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(lpnumberofbytesread.assume_init() as usize + already_read) + } + } + } + } + + /// Raw, unbuffered handle to `stdout`. + /// + /// This implements the `Write` trait for writing. + pub struct Stdout(HANDLE); + + impl Stdout { + fn acquire() -> Result { + // SAFETY: GetStdHandle returns a borrowed handle, or 0 if none is set or -1 if an + // error has happened. + let handle = unsafe { + let handle = GetStdHandle(STD_OUTPUT_HANDLE); + if handle.is_null() { + bail!("No stdout handle set"); + } else if handle as isize == -1 { + bail!( + source: io::Error::last_os_error(), + "Can't get stdout handle" + ); + } + + handle + }; + // SAFETY: GetFileType() is safe to call on any valid handle. + let type_ = unsafe { GetFileType(handle) }; + + if type_ == FILE_TYPE_CHAR { + // Set the console to raw mode. + // + // SAFETY: Calling this on non-console handles will cause an error but otherwise + // have no negative effects. We can safely change the console mode here as nothing + // else is accessing the console. + unsafe { + let _ = SetConsoleMode(handle, 0); + } + } else if type_ != FILE_TYPE_PIPE { + bail!("Unsupported stdout handle type {:x}", type_); + } + + Ok(Stdout(handle)) + } + } + + impl Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + // SAFETY: Writes the given number of bytes to stdout or at most u32::MAX. On error + // zero is returned, otherwise the number of bytes written is set accordingly and + // returned. + unsafe { + let mut lpnumberofbyteswritten = mem::MaybeUninit::uninit(); + let res = WriteFile( + self.0, + buf.as_ptr(), + cmp::min(buf.len() as u32, u32::MAX) as u32, + lpnumberofbyteswritten.as_mut_ptr(), + ptr::null_mut(), + ); + + if res == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(lpnumberofbyteswritten.assume_init() as usize) + } + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } + } +} + +pub use self::imp::{Poll, PollResult, Stdin, Stdout}; diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/main.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/main.rs new file mode 100644 index 0000000000..dc3173085e --- /dev/null +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/main.rs @@ -0,0 +1,232 @@ +// GStreamer +// +// Copyright (C) 2015-2023 Sebastian Dröge +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +//! Helper process that runs setuid root or with appropriate privileges to +//! listen on ports < 1024, do multicast operations and get MAC addresses of +//! interfaces. Privileges are dropped after these operations are done. +//! +//! It listens on the PTP multicast group on port 319 and 320 and forwards +//! everything received there to stdout, while forwarding everything received +//! on stdin to those sockets. +//! Additionally it provides the MAC address of a network interface via stdout + +use std::{ + io::{Read, Write}, + net::{Ipv4Addr, SocketAddr, UdpSocket}, +}; + +mod args; +mod error; +mod ffi; +mod io; +mod net; +mod privileges; +mod rand; + +use error::{Context, Error}; +use rand::rand; + +/// PTP Multicast group. +const PTP_MULTICAST_ADDR: Ipv4Addr = Ipv4Addr::new(224, 0, 1, 129); +/// PTP Event message port. +const PTP_EVENT_PORT: u16 = 319; +/// PTP General message port. +const PTP_GENERAL_PORT: u16 = 320; + +/// Create a new `UdpSocket` for the given port and configure it for PTP. +fn create_socket(port: u16) -> Result { + let socket = UdpSocket::bind(SocketAddr::from((Ipv4Addr::UNSPECIFIED, port))) + .with_context(|| format!("Failed to bind socket to port {}", port))?; + + socket.set_ttl(1).context("Failed setting TTL on socket")?; + socket + .set_multicast_ttl_v4(1) + .context("Failed to set multicast TTL on socket")?; + + net::set_reuse(&socket); + + Ok(socket) +} + +/// Join the multicast groups for PTP on the configured interfaces. +fn join_multicast( + args: &args::Args, + event_socket: &UdpSocket, + general_socket: &UdpSocket, +) -> Result<[u8; 8], Error> { + let mut ifaces = net::query_interfaces().context("Failed to query network interfaces")?; + if ifaces.is_empty() { + bail!("No suitable network interfaces for PTP found"); + } + + if !args.interfaces.is_empty() { + ifaces.retain(|iface| { + for filter_iface in &args.interfaces { + if &iface.name == filter_iface { + return true; + } + if let Some(ref other_name) = iface.other_name { + if other_name == filter_iface { + return true; + } + } + if let Ok(addr) = filter_iface.parse::() { + if addr == iface.ip_addr { + return true; + } + } + } + + false + }); + + if ifaces.is_empty() { + bail!("None of the selected network interfaces found"); + } + if ifaces.len() != args.interfaces.len() { + bail!("Not all selected network interfaces found"); + } + } + + for socket in [&event_socket, &general_socket].iter() { + for iface in &ifaces { + net::join_multicast_v4(socket, &PTP_MULTICAST_ADDR, iface) + .context("Failed to join multicast group")?; + } + } + + let clock_id = if args.clock_id == 0 { + ifaces + .iter() + .find_map(|iface| iface.hw_addr) + .map(|hw_addr| { + [ + hw_addr[0], hw_addr[1], hw_addr[2], 0xff, 0xfe, hw_addr[3], hw_addr[4], + hw_addr[5], + ] + }) + .unwrap_or_else(rand) + } else { + args.clock_id.to_be_bytes() + }; + + Ok(clock_id) +} + +fn main() -> Result<(), Error> { + let args = args::parse_args().context("Failed parsing commandline parameters")?; + + let event_socket = create_socket(PTP_EVENT_PORT).context("Failed creating event socket")?; + let general_socket = + create_socket(PTP_GENERAL_PORT).context("Failed creating general socket")?; + + privileges::drop().context("Failed dropping privileges")?; + + let clock_id = join_multicast(&args, &event_socket, &general_socket) + .context("Failed joining multicast groups")?; + + let mut poll = io::Poll::new(event_socket, general_socket).context("Failed creating poller")?; + + // Write clock ID first + { + let mut clock_id_data = [0u8; 4 + 8]; + clock_id_data[0..2].copy_from_slice(&8u16.to_le_bytes()); + clock_id_data[2] = 2; + clock_id_data[3] = 0; + clock_id_data[4..].copy_from_slice(&clock_id); + + poll.stdout() + .write_all(&clock_id_data) + .context("Failed writing to stdout")?; + } + + // Now read-write from stdin/stdout and the sockets + // + // We assume that stdout never blocks and stdin receives a complete valid packet whenever it is + // ready and never blocks in the middle of a packet. + let mut socket_buffer = [0u8; 1500]; + let mut stdinout_buffer = [0u8; 1504]; + + loop { + let poll_res = poll.poll().context("Failed polling")?; + + // If any of the sockets are ready, continue reading packets from them until no more + // packets are left and directly forward them to stdout. + 'next_socket: for idx in [poll_res.event_socket, poll_res.general_socket] + .iter() + .enumerate() + .filter_map(|(idx, r)| if *r { Some(idx) } else { None }) + { + let res = match idx { + 0 => poll.event_socket().recv(&mut socket_buffer), + 1 => poll.general_socket().recv(&mut socket_buffer), + _ => unreachable!(), + }; + + match res { + Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => { + continue 'next_socket; + } + Err(err) => { + bail!( + source: err, + "Failed reading from {} socket", + if idx == 0 { "event" } else { "general" } + ); + } + Ok(read) => { + stdinout_buffer[0..2].copy_from_slice(&(read as u16).to_ne_bytes()); + stdinout_buffer[2] = idx as u8; + stdinout_buffer[3] = 0; + stdinout_buffer[4..][..read].copy_from_slice(&socket_buffer[..read]); + + poll.stdout() + .write_all(&stdinout_buffer[..(read + 4)]) + .context("Failed writing to stdout")?; + } + } + } + + // After handling the sockets check if a packet is available on stdin, read it and forward + // it to the corresponding socket. + if poll_res.stdin { + poll.stdin() + .read_exact(&mut stdinout_buffer[0..4]) + .context("Failed reading packet header from stdin")?; + + let size = u16::from_ne_bytes([stdinout_buffer[0], stdinout_buffer[1]]); + if size as usize > stdinout_buffer.len() { + bail!("Invalid packet size on stdin {}", size); + } + let type_ = stdinout_buffer[2]; + + poll.stdin() + .read_exact(&mut stdinout_buffer[0..size as usize]) + .context("Failed reading packet body from stdin")?; + + let buf = &stdinout_buffer[0..size as usize]; + match type_ { + 0 => poll + .event_socket() + .send_to(buf, (PTP_MULTICAST_ADDR, PTP_EVENT_PORT)), + 1 => poll + .general_socket() + .send_to(buf, (PTP_MULTICAST_ADDR, PTP_GENERAL_PORT)), + _ => unreachable!(), + } + .with_context(|| { + format!( + "Failed sending to {} socket", + if type_ == 0 { "event" } else { "general" } + ) + })?; + } + } +} diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/meson.build b/subprojects/gstreamer/libs/gst/helpers/ptp/meson.build new file mode 100644 index 0000000000..401b3e1fe3 --- /dev/null +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/meson.build @@ -0,0 +1,91 @@ +# Check PTP support +have_rust = add_languages('rust', native : false, required : false) +have_ptp = true +if not have_rust + have_ptp = false + message('PTP not supported without Rust compiler') +endif + +if not ['linux', 'freebsd', 'openbsd', 'netbsd', 'dragonfly', 'darwin', 'sunos', 'solaris', 'illumos', 'windows'].contains(host_system) + have_ptp = false + message('PTP not supported on this OS') +endif + +if have_ptp + rustc = meson.get_compiler('rust') + cdata.set('HAVE_PTP', 1, description : 'PTP support available') + + ptp_helper_conf_data = configuration_data() + rust_args = [] + + setcap_prog = find_program('setcap', '/usr/sbin/setcap', '/sbin/setcap', required : false) + cap_dep = dependency('libcap', required: false) + + # user/group to change to in gst-ptp-helper + ptp_helper_setuid_user = get_option('ptp-helper-setuid-user') + if ptp_helper_setuid_user != '' + ptp_helper_conf_data.set('PTP_HELPER_SETUID_USER', 'Some("@0@")'.format(ptp_helper_setuid_user)) + else + ptp_helper_conf_data.set('PTP_HELPER_SETUID_USER', 'None') + endif + ptp_helper_setuid_group = get_option('ptp-helper-setuid-group') + if ptp_helper_setuid_group != '' + ptp_helper_conf_data.set('PTP_HELPER_SETUID_GROUP', 'Some("@0@")'.format(ptp_helper_setuid_group)) + else + ptp_helper_conf_data.set('PTP_HELPER_SETUID_GROUP', 'None') + endif + + # how to install gst-ptp-helper + with_ptp_helper_permissions = get_option('ptp-helper-permissions') + if with_ptp_helper_permissions == 'auto' + if setcap_prog.found() and cap_dep.found() + with_ptp_helper_permissions = 'capabilities' + elif host_system == 'windows' + with_ptp_helper_permissions = 'none' + else + with_ptp_helper_permissions = 'setuid-root' + endif + endif + message('How to install gst-ptp-helper: ' + with_ptp_helper_permissions) + + if with_ptp_helper_permissions == 'none' + rust_args += ['--cfg', 'ptp_helper_permissions="none"'] + # nothing to do + elif with_ptp_helper_permissions == 'setuid-root' + rust_args += ['--cfg', 'ptp_helper_permissions="setuid-root"'] + elif with_ptp_helper_permissions == 'capabilities' + if not setcap_prog.found() + error('capabilities-based ptp-helper-permissions requested, but could not find setcap tool.') + elif not cap_dep.found() + error('capabilities-based ptp-helper-permissions requested, but could not find libcap.') + endif + rust_args += ['--cfg', 'ptp_helper_permissions="setcap"'] + else + error('Unexpected ptp helper permissions value: ' + with_ptp_helper_permissions) + endif + + conf_lib_rs = configure_file(input : 'conf_lib.rs.in', + output : 'conf_lib.rs', + configuration: ptp_helper_conf_data) + + conf = static_library('gst_ptp_helper_conf', conf_lib_rs, + override_options : ['rust_std=2018'], + rust_args : ['-Cpanic=abort'], + rust_crate_type : 'rlib') + + exe = executable('gst-ptp-helper', 'main.rs', + override_options : ['rust_std=2018'], + rust_args : ['-Cpanic=abort', rust_args], + dependencies : [cap_dep], + link_with : conf, + install_dir : helpers_install_dir, + install : true) + + if host_system != 'windows' + meson.add_install_script('ptp_helper_post_install.sh', + helpers_install_dir, with_ptp_helper_permissions, + setcap_prog.found() ? setcap_prog.full_path() : '') + endif + + meson.add_devenv({'GST_PTP_HELPER': exe.full_path()}) +endif diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/net.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/net.rs new file mode 100644 index 0000000000..e3b0bbddeb --- /dev/null +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/net.rs @@ -0,0 +1,667 @@ +// GStreamer +// +// Copyright (C) 2015-2023 Sebastian Dröge +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +use std::net::Ipv4Addr; + +use crate::{bail, error::Error}; + +#[derive(Debug)] +/// Network interface information. +pub struct InterfaceInfo { + /// Name of the interface + pub name: String, + /// Other name of the interface, if any + pub other_name: Option, + /// Interface index + pub index: usize, + /// Unicast IPv4 address of the interface + pub ip_addr: Ipv4Addr, + /// Physical MAC address of the interface, if any + pub hw_addr: Option<[u8; 6]>, +} + +#[cfg(unix)] +mod imp { + use super::*; + + use std::{ffi::CStr, io, marker, mem, net::UdpSocket, os::unix::io::AsRawFd, ptr}; + + use crate::{error::Context, ffi::unix::*}; + + /// Returns information for all non-loopback, multicast-capable network interfaces. + pub fn query_interfaces() -> Result, Error> { + struct Ifaddrs(*mut ifaddrs); + + impl Ifaddrs { + fn new() -> io::Result { + loop { + // SAFETY: Requires passing valid storage for the returned ifaddrs pointer and + // returns -1 on errors. It might return NULL if there are no network interfaces. + // + // On error it might return EINTR in which case we should simply try again. + unsafe { + let mut ifaddrs = ptr::null_mut(); + if getifaddrs(&mut ifaddrs) == -1 { + let err = io::Error::last_os_error(); + if err.kind() == std::io::ErrorKind::Interrupted { + continue; + } + + return Err(err); + } else { + return Ok(Self(ifaddrs)); + } + } + } + } + + fn iter(&self) -> IfaddrsIter { + IfaddrsIter { + ptr: ptr::NonNull::new(self.0), + phantom: marker::PhantomData, + } + } + } + + impl Drop for Ifaddrs { + fn drop(&mut self) { + // SAFETY: The pointer is a valid ifaddrs pointer by construction and dropped only + // once, so freeing it here is OK. It might be NULL so check for that first. + unsafe { + if !self.0.is_null() { + freeifaddrs(self.0); + } + } + } + } + + struct IfaddrsIter<'a> { + ptr: Option>, + phantom: marker::PhantomData<&'a Ifaddrs>, + } + + impl<'a> Iterator for IfaddrsIter<'a> { + type Item = &'a ifaddrs; + + fn next(&mut self) -> Option { + match self.ptr { + None => None, + Some(ptr) => { + // SAFETY: The pointer is a valid ifaddrs pointer by construction so + // creating a reference to it is OK. + let addr = unsafe { &*ptr.as_ptr() }; + self.ptr = ptr::NonNull::new(addr.ifa_next); + Some(addr) + } + } + } + } + + let ifaddrs = Ifaddrs::new().context("Failed getting interface addresses")?; + + let mut if_infos = Vec::::new(); + + for ifaddr in ifaddrs.iter() { + // SAFETY: ifa_name points to a NUL-terminated interface name string that is valid as + // long as its struct is + let name = unsafe { CStr::from_ptr(ifaddr.ifa_name) }.to_str().unwrap(); + + // Skip loopback interfaces, interfaces that are not up and interfaces that can't do + // multicast. These are all unusable for PTP purposes. + let flags = ifaddr.ifa_flags; + if (flags & IFF_LOOPBACK as u32 != 0) + || (flags & IFF_UP as u32 == 0) + || (flags & IFF_MULTICAST as u32 == 0) + { + continue; + } + + // If the interface has no address then skip it. Only interfaces with IPv4 addresses + // are usable for PTP purposes. + if ifaddr.ifa_addr.is_null() { + continue; + } + + // Get the interface index from its name. If it has none then we can't use it to join + // the PTP multicast group reliable for this interface. + // + // SAFETY: Must be called with a valid, NUL-terminated string which is provided by + // ifa_name and will return the interface index or zero on error. + // + // On error it can return EINTR in which case we should try again. + let index = loop { + let index = unsafe { if_nametoindex(ifaddr.ifa_name) } as usize; + if index == 0 { + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::Interrupted { + continue; + } + } + break index; + }; + if index == 0 { + continue; + } + + // Interfaces are listed multiple times here, once per address. We collect all IPv4 and + // MAC addresses for interfaces below. + + // SAFETY: ifa_addr is a valid sockaddr pointer and was checked to be not NULL further + // above. + let sa_family = unsafe { (*ifaddr.ifa_addr).sa_family }; + + // If this interface has an IPv4 address then retrieve and store it here. + if sa_family == AF_INET as _ { + // SAFETY: If the address family is AF_INET then it is actually a valid sockaddr_in + // pointer and can be used as such. + let addr = unsafe { &*(ifaddr.ifa_addr as *const sockaddr_in) }; + let ip_addr = Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes()); + + if let Some(if_info) = if_infos.iter_mut().find(|info| info.name == name) { + if if_info.ip_addr.is_broadcast() { + if_info.ip_addr = ip_addr; + } + } else { + if_infos.push(InterfaceInfo { + name: String::from(name), + other_name: None, + index, + ip_addr, + hw_addr: None, + }); + } + } + + #[cfg(target_os = "linux")] + { + if sa_family == AF_PACKET as _ { + // SAFETY: If the address family is AF_PACKET then it is actually a valid sockaddr_ll + // pointer and can be used as such. + let addr = unsafe { &*(ifaddr.ifa_addr as *const sockaddr_ll) }; + if addr.sll_halen == 6 { + let mut hw_addr = [0u8; 6]; + hw_addr.copy_from_slice(&addr.sll_addr[0..6]); + if let Some(if_info) = if_infos.iter_mut().find(|info| info.name == name) { + if if_info.hw_addr.is_none() { + if_info.hw_addr = Some(hw_addr); + } + } else { + if_infos.push(InterfaceInfo { + name: String::from(name), + other_name: None, + index, + ip_addr: Ipv4Addr::BROADCAST, + hw_addr: Some(hw_addr), + }); + } + } + } + } + #[cfg(not(target_os = "linux"))] + { + use std::slice; + + if sa_family == AF_LINK as _ { + // SAFETY: If the address family is AF_LINK then it is actually a valid sockaddr_dl + // pointer and can be used as such. + let addr = unsafe { &*(ifaddr.ifa_addr as *const sockaddr_dl) }; + if addr.sdl_nlen <= IF_NAMESIZE as u8 && addr.sdl_alen == 6 { + let mut hw_addr = [0u8; 6]; + // SAFETY: There can be more than the given number of bytes stored and + // this happens regularly on macOS at least. It is required that the + // interface name is at most IF_NAMESIZE (checked just above). + unsafe { + let sdl_addr_ptr = addr.sdl_data.as_ptr() as *const u8; + let sdl_addr = + slice::from_raw_parts(sdl_addr_ptr.add(addr.sdl_nlen as usize), 6); + hw_addr.copy_from_slice(sdl_addr); + } + + if let Some(if_info) = if_infos.iter_mut().find(|info| info.name == name) { + if if_info.hw_addr.is_none() { + if_info.hw_addr = Some(hw_addr); + } + } else { + if_infos.push(InterfaceInfo { + name: String::from(name), + other_name: None, + index, + ip_addr: Ipv4Addr::BROADCAST, + hw_addr: Some(hw_addr), + }); + } + } + } + } + } + + if_infos.retain(|iface| !iface.ip_addr.is_broadcast()); + + Ok(if_infos) + } + + // Join multicast address for a given interface. + pub fn join_multicast_v4( + socket: &UdpSocket, + addr: &Ipv4Addr, + iface: &InterfaceInfo, + ) -> Result<(), Error> { + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + { + let mreqn = ip_mreqn { + imr_multiaddr: in_addr { + s_addr: u32::from_ne_bytes(addr.octets()), + }, + imr_address: in_addr { + s_addr: u32::from_ne_bytes(Ipv4Addr::UNSPECIFIED.octets()), + }, + imr_ifindex: iface.index as _, + }; + + // SAFETY: Requires a valid ip_mreq or ip_mreqn struct to be passed together + // with its size for checking which of the two it is. On errors a negative + // integer is returned. + unsafe { + if setsockopt( + socket.as_raw_fd(), + IPPROTO_IP, + IP_ADD_MEMBERSHIP, + &mreqn as *const _ as *const _, + mem::size_of_val(&mreqn) as _, + ) < 0 + { + bail!( + source: io::Error::last_os_error(), + "Failed joining multicast group for interface {}", + iface.name, + ); + } + } + + Ok(()) + } + #[cfg(any(target_os = "solaris", target_os = "illumos"))] + { + use crate::error::Context; + + socket + .join_multicast_v4(addr, &iface.ip_addr) + .with_context(|| { + format!( + "Failed joining multicast group for interface {} at address {}", + iface.name, iface.ip_addr + ) + })?; + + Ok(()) + } + } + + /// Allow multiple sockets to bind to the same address / port. + /// + /// This is best-effort and might not actually do anything. + pub fn set_reuse(socket: &UdpSocket) { + // SAFETY: SO_REUSEADDR takes an i32 value that can be 0/false or 1/true and + // enables the given feature on the socket. + // + // We explicitly ignore errors here. If it works, good, if it doesn't then not much + // lost other than the ability to run multiple processes at once. + unsafe { + let v = 1i32; + let _ = setsockopt( + socket.as_raw_fd(), + SOL_SOCKET, + SO_REUSEADDR, + &v as *const _ as *const _, + mem::size_of_val(&v) as u32, + ); + } + + #[cfg(not(any(target_os = "solaris", target_os = "illumos")))] + { + // SAFETY: SO_REUSEPORT takes an i32 value that can be 0/false or 1/true and + // enables the given feature on the socket. + // + // We explicitly ignore errors here. If it works, good, if it doesn't then not much + // lost other than the ability to run multiple processes at once. + unsafe { + let v = 1i32; + let _ = setsockopt( + socket.as_raw_fd(), + SOL_SOCKET, + SO_REUSEPORT, + &v as *const _ as *const _, + mem::size_of_val(&v) as u32, + ); + } + } + } +} + +#[cfg(windows)] +mod imp { + use super::*; + + use std::{ + ffi::{CStr, OsString}, + io, marker, mem, + net::UdpSocket, + os::{ + raw::*, + windows::{ffi::OsStringExt, io::AsRawSocket}, + }, + ptr, slice, + }; + + use crate::{error::Context, ffi::windows::*}; + + /// Returns information for all non-loopback, multicast-capable network interfaces. + pub fn query_interfaces() -> Result, Error> { + struct AdapterAddresses { + addresses: ptr::NonNull, + heap: isize, + } + + impl AdapterAddresses { + fn new() -> io::Result { + // SAFETY: Gets the process's default heap and is safe to be called at any time. + let heap = unsafe { GetProcessHeap() }; + + // SAFETY: GetAdaptersAddresses() requires allocated memory to be passed in. + // In the beginning 16kB are allocated via HeapAlloc() from the default process's + // heap (see above), then passed to GetAdaptersAddresses(). + // + // If this returns ERROR_NOT_ENOUGH_MEMORY then this was not enough memory and the + // required amount is returned as out parameter. In that case we loop up to 10 + // times, reallocate memory via HeapReAlloc() and try again. + // + // On other errors the memory is freed before returning via HeapFree(), or when 10 + // iterations were reached. + unsafe { + let mut alloc_len = 16_384; + let mut tries = 0; + let mut addresses: *mut IP_ADAPTER_ADDRESSES_LH = ptr::null_mut(); + + loop { + if tries > 10 { + HeapFree(heap, 0, addresses as *mut _); + return Err(io::Error::from(io::ErrorKind::OutOfMemory)); + } + + if addresses.is_null() { + addresses = HeapAlloc(heap, 0, alloc_len as usize) as *mut _; + } else { + addresses = + HeapReAlloc(heap, 0, addresses as *mut _, alloc_len as usize) + as *mut _; + } + if addresses.is_null() { + return Err(io::Error::from(io::ErrorKind::OutOfMemory)); + } + + let res = GetAdaptersAddresses( + AF_INET, + GAA_FLAG_SKIP_ANYCAST + | GAA_FLAG_SKIP_MULTICAST + | GAA_FLAG_SKIP_DNS_SERVER, + ptr::null_mut(), + addresses, + &mut alloc_len, + ); + + if res == 0 { + return Ok(AdapterAddresses { + heap, + addresses: ptr::NonNull::new_unchecked(addresses), + }); + } else if res == ERROR_NOT_ENOUGH_MEMORY { + tries += 1; + continue; + } else { + HeapFree(heap, 0, addresses as *mut _); + return Err(io::Error::from_raw_os_error(res as i32)); + } + } + } + } + + fn iter(&self) -> AdapterAddressesIter { + AdapterAddressesIter { + ptr: Some(self.addresses), + phantom: marker::PhantomData, + } + } + } + + impl Drop for AdapterAddresses { + fn drop(&mut self) { + // SAFETY: The pointer is a valid IP_ADAPTER_ADDRESSES_LH pointer by construction + // and dropped only once, so freeing it here is OK. It might be NULL so check for + // that first. + // + // Heap is the process's heap as set in the constructor above. + unsafe { + HeapFree(self.heap, 0, self.addresses.as_ptr() as *mut _); + } + } + } + + struct AdapterAddressesIter<'a> { + ptr: Option>, + phantom: marker::PhantomData<&'a AdapterAddresses>, + } + + impl<'a> Iterator for AdapterAddressesIter<'a> { + type Item = &'a IP_ADAPTER_ADDRESSES_LH; + + fn next(&mut self) -> Option { + match self.ptr { + None => None, + Some(ptr) => { + // SAFETY: The pointer is a valid IP_ADAPTER_ADDRESSES_LH pointer by + // construction so creating a reference to it is OK. + let addr = unsafe { &*ptr.as_ptr() }; + self.ptr = ptr::NonNull::new(addr.next); + Some(addr) + } + } + } + } + + struct UnicastAddressesIter<'a> { + ptr: Option>, + phantom: marker::PhantomData<&'a IP_ADAPTER_ADDRESSES_LH>, + } + + impl<'a> UnicastAddressesIter<'a> { + fn new(addresses: &'a IP_ADAPTER_ADDRESSES_LH) -> Self { + Self { + ptr: ptr::NonNull::new(addresses.firstunicastaddress), + phantom: marker::PhantomData, + } + } + } + + impl<'a> Iterator for UnicastAddressesIter<'a> { + type Item = &'a IP_ADAPTER_UNICAST_ADDRESS_LH; + + fn next(&mut self) -> Option { + match self.ptr { + None => None, + Some(ptr) => { + let addr = unsafe { &*ptr.as_ptr() }; + self.ptr = ptr::NonNull::new(addr.next); + Some(addr) + } + } + } + } + + let addresses = AdapterAddresses::new().context("Failed getting adapter addresses")?; + let mut if_infos = Vec::::new(); + for address in addresses.iter() { + // SAFETY: adaptername points to a NUL-terminated ASCII name string that is valid + // as long as its struct is + let adaptername = unsafe { CStr::from_ptr(address.adaptername as *const c_char) } + .to_str() + .unwrap(); + + // Skip adapters that are receive-only, can't do multicast or don't have IPv4 support + // as they're not usable in a PTP context. + if address.flags & ADAPTER_FLAG_RECEIVE_ONLY != 0 + || address.flags & ADAPTER_FLAG_NO_MULTICAST != 0 + || address.flags & ADAPTER_FLAG_IPV4_ENABLED == 0 + { + continue; + } + + // Skip adapters that are loopback or not up. + if address.iftype == IF_TYPE_SOFTWARE_LOOPBACK + || address.operstatus != IF_OPER_STATUS_UP + { + continue; + } + + // SAFETY: Both fields of the union are always valid + let index = unsafe { address.anonymous.anonymous.ifindex } as usize; + // Skip adapters that have no valid interface index as they can't be used to join the + // PTP multicast group reliably for this interface only. + if index == 0 { + continue; + } + + // SAFETY: friendlyname is a NUL-terminated UCS2/wide string or NULL. + let friendlyname = unsafe { + if !address.friendlyname.is_null() { + let len = { + let mut len = 0; + while *address.friendlyname.add(len) != 0 { + len += 1; + } + len + }; + + let f = slice::from_raw_parts(address.friendlyname, len); + let f = OsString::from_wide(f); + Some(String::from(f.to_str().unwrap())) + } else { + None + } + }; + + let mut hw_addr = None; + if address.physicaladdresslength == 6 { + let mut addr = [0u8; 6]; + addr.copy_from_slice(&address.physicaladdress[..6]); + hw_addr = Some(addr); + } + + let ip_addr = UnicastAddressesIter::new(address).find_map(|addr| { + if addr.address.lpsocketaddr.is_null() { + return None; + } + + // SAFETY: lpsocketaddr is a valid, non-NULL socket address and its family + // field can be read to distinguish IPv4 and other socket addresses + if unsafe { (*addr.address.lpsocketaddr).sa_family } != AF_INET as u16 { + return None; + } + + Some(Ipv4Addr::from( + // SAFETY: lpsocketaddr is a valid, non-NULL IPv4 socket address as checked + // above and can be dereferenced as such. + unsafe { + (*addr.address.lpsocketaddr) + .in_addr + .S_un + .S_addr + .to_ne_bytes() + }, + )) + }); + + if let Some(ip_addr) = ip_addr { + if_infos.push(InterfaceInfo { + name: String::from(adaptername), + other_name: friendlyname, + index, + ip_addr, + hw_addr, + }); + } + } + + Ok(if_infos) + } + + // Join multicast address for a given interface. + pub fn join_multicast_v4( + socket: &UdpSocket, + addr: &Ipv4Addr, + iface: &InterfaceInfo, + ) -> Result<(), Error> { + let mreq = IP_MREQ { + imr_multiaddr: IN_ADDR { + S_un: IN_ADDR_0 { + S_addr: u32::from_ne_bytes(addr.octets()), + }, + }, + imr_address: IN_ADDR { + S_un: IN_ADDR_0 { + S_addr: u32::from_ne_bytes(Ipv4Addr::new(0, 0, 0, iface.index as u8).octets()), + }, + }, + }; + + // SAFETY: Requires a valid ip_mreq struct to be passed together with its size for checking + // validity. On errors a negative integer is returned. + unsafe { + if setsockopt( + socket.as_raw_socket(), + IPPROTO_IP as i32, + IP_ADD_MEMBERSHIP as i32, + &mreq as *const _ as *const _, + mem::size_of_val(&mreq) as _, + ) < 0 + { + bail!( + source: io::Error::from_raw_os_error(WSAGetLastError()), + "Failed joining multicast group for interface {}", + iface.name, + ); + } + } + + Ok(()) + } + + /// Allow multiple sockets to bind to the same address / port. + /// + /// This is best-effort and might not actually do anything. + pub fn set_reuse(socket: &UdpSocket) { + // SAFETY: SO_REUSEADDR takes an i32 value that can be 0/false or 1/true and + // enables the given feature on the socket. + // + // We explicitly ignore errors here. If it works, good, if it doesn't then not much + // lost other than the ability to run multiple processes at once. + unsafe { + let v = 1i32; + let _ = setsockopt( + socket.as_raw_socket(), + SOL_SOCKET as i32, + SO_REUSEADDR as i32, + &v as *const _ as *const _, + mem::size_of_val(&v) as _, + ); + } + } +} + +pub use imp::*; diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/privileges.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/privileges.rs new file mode 100644 index 0000000000..66e5c954e1 --- /dev/null +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/privileges.rs @@ -0,0 +1,169 @@ +// GStreamer +// +// Copyright (C) 2015-2023 Sebastian Dröge +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +use crate::error::Error; + +/// Drop all additional permissions / capabilities the current process might have as they're not +/// needed anymore. +/// +/// This does nothing if no such mechanism is implemented / selected for the target platform. +pub fn drop() -> Result<(), Error> { + #[cfg(ptp_helper_permissions = "setcap")] + { + // Drop all current capabilities of the process. + + use std::io; + + use crate::{bail, ffi::unix::setcaps::*}; + + struct Cap(cap_t); + impl Drop for Cap { + fn drop(&mut self) { + // SAFETY: The capabilities are valid by construction and are only dropped + // once here. + unsafe { + let _ = cap_free(self.0); + } + } + } + + // SAFETY: There are 3 steps here + // 1. Get the current capabilities of the process. This + // returns NULL on error or otherwise newly allocated capabilities that have to be + // freed again in the end. For that purpose we wrap them in the Cap struct. + // + // 2. Clearing all current capabilities. This requires a valid capabilities pointer, + // which we have at this point by construction. + // + // 3. Setting the current process's capabilities, which is only affecting the current + // thread unfortunately. At this point, no other threads were started yet so this is + // not a problem. Also the capabilities pointer is still valid by construction. + // + // On every return path, the capabilities are going to be freed. + unsafe { + let c = cap_get_proc(); + if c.is_null() { + bail!( + source: io::Error::last_os_error(), + "Failed to get current process capabilities" + ); + } + + let c = Cap(c); + if cap_clear(c.0) != 0 { + bail!( + source: io::Error::last_os_error(), + "Failed to clear capabilities" + ); + } + if cap_set_proc(c.0) != 0 { + bail!( + source: io::Error::last_os_error(), + "Failed to set current process capabilities" + ); + } + } + } + #[cfg(ptp_helper_permissions = "setuid-root")] + { + // Drop the process's UID/GID from root to the configured user/group or the user "nobody". + + use std::{ffi::CString, io}; + + use crate::{bail, error::Context, ffi::unix::setuid_root::*}; + + fn get_uid_gid_for_user(name: &str) -> io::Result<(uid_t, gid_t)> { + let name_cstr = CString::new(name).unwrap(); + + loop { + // SAFETY: getpwnam() requires a NUL-terminated user name string and + // returns either the user information in static storage, or NULL on error. + // In case of EINTR, getting the user information can be retried. + // + // The user information is stored in static storage so might change if something + // else calls related functions. As this is the only thread up to this point and we + // just extract two integers from it there is no such possibility. + unsafe { + let pw = getpwnam(name_cstr.as_ptr()); + if pw.is_null() { + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::Interrupted { + continue; + } + return Err(err); + } + return Ok(((*pw).pw_uid, (*pw).pw_gid)); + } + } + } + + fn get_gid_for_group(name: &str) -> io::Result { + let name_cstr = CString::new(name).unwrap(); + loop { + // SAFETY: getgrnam() requires a NUL-terminated group name string and + // returns either the group information in static storage, or NULL on error. + // In case of EINTR, getting the group information can be retried. + // + // The user information is stored in static storage so might change if something + // else calls related functions. As this is the only thread up to this point and we + // just extract two integers from it there is no such possibility. + unsafe { + let grp = getgrnam(name_cstr.as_ptr()); + if grp.is_null() { + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::Interrupted { + continue; + } + return Err(err); + } + + return Ok((*grp).gr_gid); + } + } + } + + let username = gst_ptp_helper_conf::PTP_HELPER_SETUID_USER.unwrap_or("nobody"); + + let (uid, gid) = get_uid_gid_for_user(username) + .with_context(|| format!("Failed to get user information for {}", username))?; + let gid = if let Some(group) = gst_ptp_helper_conf::PTP_HELPER_SETUID_GROUP { + get_gid_for_group(group) + .with_context(|| format!("Failed to get group information for {}", group))? + } else { + gid + }; + + // SAFETY: This function can be called at any time and never fails. + let old_gid = unsafe { getgid() }; + + // SAFETY: Changes the effective group id of the process and return zero on success. + unsafe { + if setgid(gid) != 0 { + bail!( + source: io::Error::last_os_error(), + "Failed to set group id {} for process", + gid, + ); + } + } + + // SAFETY: Changes the effective user id of the process and return zero on success. + // On errors, try resetting to the old gid just to be sure. + unsafe { + if setuid(uid) != 0 { + let err = io::Error::last_os_error(); + let _ = setgid(old_gid); + bail!(source: err, "Failed to set user id {} for process", uid); + } + } + } + + Ok(()) +} diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp_helper_post_install.sh b/subprojects/gstreamer/libs/gst/helpers/ptp/ptp_helper_post_install.sh similarity index 100% rename from subprojects/gstreamer/libs/gst/helpers/ptp_helper_post_install.sh rename to subprojects/gstreamer/libs/gst/helpers/ptp/ptp_helper_post_install.sh diff --git a/subprojects/gstreamer/libs/gst/helpers/ptp/rand.rs b/subprojects/gstreamer/libs/gst/helpers/ptp/rand.rs new file mode 100644 index 0000000000..48cda11ec5 --- /dev/null +++ b/subprojects/gstreamer/libs/gst/helpers/ptp/rand.rs @@ -0,0 +1,150 @@ +// GStreamer +// +// Copyright (C) 2015-2023 Sebastian Dröge +// +// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0. +// If a copy of the MPL was not distributed with this file, You can obtain one at +// . +// +// SPDX-License-Identifier: MPL-2.0 + +/// Returns a random'ish 64 bit value. +pub fn rand() -> [u8; 8] { + #[cfg(unix)] + { + // Try getrandom syscall or otherwise first on Linux + #[cfg(target_os = "linux")] + { + use std::io::Read; + + use crate::ffi::unix::linux::*; + + // Depends on us knowing the syscall number + if SYS_getrandom != 0 { + struct GetRandom; + + impl Read for GetRandom { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + // SAFETY: `getrandom` syscall fills up to the requested amount of bytes of + // the provided memory and returns the number of bytes or a negative value + // on errors. + unsafe { + let res = syscall(SYS_getrandom, buf.as_mut_ptr(), buf.len(), 0u32); + if res < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(res as usize) + } + } + } + } + + let mut r = [0u8; 8]; + if GetRandom.read_exact(&mut r).is_ok() { + return r; + } + } + } + + // Otherwise try /dev/urandom + { + use crate::ffi::unix::*; + use std::{io::Read, os::raw::c_int}; + + struct Fd(c_int); + + impl Drop for Fd { + fn drop(&mut self) { + // SAFETY: The fd is valid by construction below and closed by this at + // most once. + unsafe { + // Return value is intentionally ignored as there's nothing that + // can be done on errors anyway. + let _ = close(self.0); + } + } + } + + impl Read for Fd { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + // SAFETY: read() requires a valid fd and a mutable buffer with the given size. + // The fd is valid by construction as is the buffer. + // + // read() will return the number of bytes read or a negative value on errors. + let res = unsafe { read(self.0, buf.as_mut_ptr(), buf.len()) }; + if res < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(res as usize) + } + } + } + + let fd = loop { + // SAFETY: open() requires a NUL-terminated file path and will + // return an integer in any case. A negative value is an invalid fd + // and signals an error. On EINTR, opening can be retried. + let fd = unsafe { open(b"/dev/urandom\0".as_ptr(), O_RDONLY) }; + if fd < 0 { + let err = std::io::Error::last_os_error(); + if err.kind() == std::io::ErrorKind::Interrupted { + continue; + } + + break None; + } + + break Some(Fd(fd)); + }; + + if let Some(mut fd) = fd { + let mut r = [0u8; 8]; + + if fd.read_exact(&mut r).is_ok() { + return r; + } + } + } + } + #[cfg(windows)] + { + // Try BCryptGenRandom(), which is available since Windows Vista + // + // SAFETY: BCryptGenRandom() fills the provided memory with the requested number of bytes + // and returns 0 on success. In that case, all memory was written and is initialized now. + unsafe { + use std::{mem, ptr}; + + use crate::ffi::windows::*; + + let mut r = mem::MaybeUninit::<[u8; 8]>::uninit(); + let res = BCryptGenRandom( + ptr::null_mut(), + r.as_mut_ptr() as *mut u8, + 8, + BCRYPT_USE_SYSTEM_PREFERRED_RNG, + ); + if res == 0 { + return r.assume_init(); + } + } + } + + // As fallback use a combination of the process ID and the current system time + let now = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .unwrap() + .as_nanos() + .to_be_bytes(); + let pid = std::process::id().to_be_bytes(); + [ + now[0] ^ now[15] ^ pid[0], + now[1] ^ now[14] ^ pid[1], + now[2] ^ now[13] ^ pid[2], + now[3] ^ now[12] ^ pid[3], + now[4] ^ now[11] ^ pid[0], + now[5] ^ now[10] ^ pid[1], + now[6] ^ now[9] ^ pid[2], + now[7] ^ now[8] ^ pid[3], + ] +}