mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-25 11:11:08 +00:00
80dfb7bb3f
strncpy() is assumed to be for strings so the compiler assumes that it will need an extra byte for the string-terminaning NULL. For cases where we know it's actually "binary" data, just copy it with memcpy. https://bugzilla.gnome.org/show_bug.cgi?id=795756
689 lines
21 KiB
C
689 lines
21 KiB
C
/* GStreamer
|
|
* Copyright (C) 2015 Sebastian Dröge <sebastian@centricular.com>
|
|
*
|
|
*
|
|
* 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 <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <errno.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <net/if.h>
|
|
#include <netinet/in.h>
|
|
#include <string.h>
|
|
|
|
#ifdef HAVE_GETIFADDRS_AF_LINK
|
|
#include <ifaddrs.h>
|
|
#include <net/if_dl.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_PTP_HELPER_SETUID
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_PTP_HELPER_CAPABILITIES
|
|
#include <sys/capability.h>
|
|
#endif
|
|
|
|
#include <glib.h>
|
|
#include <gio/gio.h>
|
|
|
|
#include <gst/gst.h>
|
|
#include <gst/net/gstptp_private.h>
|
|
|
|
#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);
|
|
g_socket_set_multicast_loopback (socket_event, FALSE);
|
|
|
|
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);
|
|
g_socket_set_multicast_loopback (socket_general, FALSE);
|
|
|
|
/* 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;
|
|
}
|